summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-11-19 16:42:22 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2022-11-21 11:55:23 -0500
commitcaa9293e2e0d0b186a24962ad72b954271934913 (patch)
tree7ea771ca0d1db7dbadadfb7428ab58a4f89d6bf6 /lib
parent46e6693cb3db445f18aa25d5e4ca613504bd12b3 (diff)
downloadsqlalchemy-caa9293e2e0d0b186a24962ad72b954271934913.tar.gz
add common base class for all SQL col expression objects
Added a new type :class:`.SQLColumnExpression` which may be indicated in user code to represent any SQL column oriented expression, including both those based on :class:`.ColumnElement` as well as on ORM :class:`.QueryableAttribute`. This type is a real class, not an alias, so can also be used as the foundation for other objects. Fixes: #8847 Change-Id: I3161bdff1c9f447793fce87864e1774a90cd4146
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/__init__.py1
-rw-r--r--lib/sqlalchemy/orm/__init__.py1
-rw-r--r--lib/sqlalchemy/orm/attributes.py3
-rw-r--r--lib/sqlalchemy/orm/base.py43
-rw-r--r--lib/sqlalchemy/orm/interfaces.py5
-rw-r--r--lib/sqlalchemy/orm/properties.py2
-rw-r--r--lib/sqlalchemy/sql/__init__.py1
-rw-r--r--lib/sqlalchemy/sql/elements.py22
-rw-r--r--lib/sqlalchemy/sql/expression.py1
9 files changed, 71 insertions, 8 deletions
diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py
index 55ce29310..ccc3a446d 100644
--- a/lib/sqlalchemy/__init__.py
+++ b/lib/sqlalchemy/__init__.py
@@ -179,6 +179,7 @@ from .sql.expression import Select as Select
from .sql.expression import select as select
from .sql.expression import Selectable as Selectable
from .sql.expression import SelectBase as SelectBase
+from .sql.expression import SQLColumnExpression as SQLColumnExpression
from .sql.expression import StatementLambdaElement as StatementLambdaElement
from .sql.expression import Subquery as Subquery
from .sql.expression import table as table
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 5e2161515..96acce2ff 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -49,6 +49,7 @@ from .base import Mapped as Mapped
from .base import NotExtension as NotExtension
from .base import ORMDescriptor as ORMDescriptor
from .base import PassiveFlag as PassiveFlag
+from .base import SQLORMExpression as SQLORMExpression
from .base import WriteOnlyMapped as WriteOnlyMapped
from .context import FromStatement as FromStatement
from .context import QueryContext as QueryContext
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 2c77111c1..89beedc47 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -70,6 +70,7 @@ from .base import PASSIVE_RETURN_NO_VALUE
from .base import PassiveFlag
from .base import RELATED_OBJECT_OK # noqa
from .base import SQL_OK # noqa
+from .base import SQLORMExpression
from .base import state_str
from .. import event
from .. import exc
@@ -131,8 +132,8 @@ SelfQueryableAttribute = TypeVar(
@inspection._self_inspects
class QueryableAttribute(
- roles.ExpressionElementRole[_T],
_DeclarativeMapped[_T],
+ SQLORMExpression[_T],
interfaces.InspectionAttr,
interfaces.PropComparator[_T],
roles.JoinTargetRole,
diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py
index b46c78799..032364ff4 100644
--- a/lib/sqlalchemy/orm/base.py
+++ b/lib/sqlalchemy/orm/base.py
@@ -31,7 +31,7 @@ from ._typing import insp_is_mapper
from .. import exc as sa_exc
from .. import inspection
from .. import util
-from ..sql import roles
+from ..sql.elements import SQLColumnExpression
from ..sql.elements import SQLCoreOperations
from ..util import FastIntFlag
from ..util.langhelpers import TypingOnly
@@ -52,6 +52,7 @@ if typing.TYPE_CHECKING:
from ..sql._typing import _ColumnExpressionArgument
from ..sql._typing import _InfoType
from ..sql.elements import ColumnElement
+ from ..sql.operators import OperatorType
_T = TypeVar("_T", bound=Any)
@@ -740,9 +741,31 @@ class _MappedAnnotationBase(Generic[_T], TypingOnly):
__slots__ = ()
+class SQLORMExpression(
+ SQLORMOperations[_T], SQLColumnExpression[_T], TypingOnly
+):
+ """A type that may be used to indicate any ORM-level attribute or
+ object that acts in place of one, in the context of SQL expression
+ construction.
+
+ :class:`.SQLORMExpression` extends from the Core
+ :class:`.SQLColumnExpression` to add additional SQL methods that are ORM
+ specific, such as :meth:`.PropComparator.of_type`, and is part of the bases
+ for :class:`.InstrumentedAttribute`. It may be used in :pep:`484` typing to
+ indicate arguments or return values that should behave as ORM-level
+ attribute expressions.
+
+ .. versionadded:: 2.0.0b4
+
+
+ """
+
+ __slots__ = ()
+
+
class Mapped(
+ SQLORMExpression[_T],
ORMDescriptor[_T],
- roles.TypedColumnsClauseRole[_T],
_MappedAnnotationBase[_T],
):
"""Represent an ORM mapped attribute on a mapped class.
@@ -830,6 +853,22 @@ class _DeclarativeMapped(Mapped[_T], _MappedAttribute[_T]):
__slots__ = ()
+ # MappedSQLExpression, Relationship, Composite etc. dont actually do
+ # SQL expression behavior. yet there is code that compares them with
+ # __eq__(), __ne__(), etc. Since #8847 made Mapped even more full
+ # featured including ColumnOperators, we need to have those methods
+ # be no-ops for these objects, so return NotImplemented to fall back
+ # to normal comparison behavior.
+ def operate(self, op: OperatorType, *other: Any, **kwargs: Any) -> Any:
+ return NotImplemented
+
+ __sa_operate__ = operate
+
+ def reverse_operate(
+ self, op: OperatorType, other: Any, **kwargs: Any
+ ) -> Any:
+ return NotImplemented
+
class DynamicMapped(_MappedAnnotationBase[_T]):
"""Represent the ORM mapped attribute type for a "dynamic" relationship.
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index e61e82126..ff003f654 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -26,6 +26,7 @@ from typing import Callable
from typing import cast
from typing import ClassVar
from typing import Dict
+from typing import Generic
from typing import Iterator
from typing import List
from typing import NamedTuple
@@ -65,6 +66,7 @@ from ..sql import visitors
from ..sql.base import _NoArg
from ..sql.base import ExecutableOption
from ..sql.cache_key import HasCacheKey
+from ..sql.operators import ColumnOperators
from ..sql.schema import Column
from ..sql.type_api import TypeEngine
from ..util.typing import RODescriptorReference
@@ -595,7 +597,7 @@ class MapperProperty(
@inspection._self_inspects
-class PropComparator(SQLORMOperations[_T]):
+class PropComparator(SQLORMOperations[_T], Generic[_T], ColumnOperators):
r"""Defines SQL operations for ORM mapped attributes.
SQLAlchemy allows for operators to
@@ -676,7 +678,6 @@ class PropComparator(SQLORMOperations[_T]):
:attr:`.TypeEngine.comparator_factory`
"""
-
__slots__ = "prop", "_parententity", "_adapt_to_entity"
__visit_name__ = "orm_prop_comparator"
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index c1da267f4..785a1a098 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -47,7 +47,6 @@ from ..sql import coercions
from ..sql import roles
from ..sql import sqltypes
from ..sql.base import _NoArg
-from ..sql.elements import SQLCoreOperations
from ..sql.roles import DDLConstraintColumnRole
from ..sql.schema import Column
from ..sql.schema import SchemaConst
@@ -500,7 +499,6 @@ class MappedSQLExpression(ColumnProperty[_T], _DeclarativeMapped[_T]):
class MappedColumn(
DDLConstraintColumnRole,
- SQLCoreOperations[_T],
_IntrospectsAnnotations,
_MapsColumns[_T],
_DeclarativeMapped[_T],
diff --git a/lib/sqlalchemy/sql/__init__.py b/lib/sqlalchemy/sql/__init__.py
index 5702d6c25..8dae9c3f5 100644
--- a/lib/sqlalchemy/sql/__init__.py
+++ b/lib/sqlalchemy/sql/__init__.py
@@ -82,6 +82,7 @@ from .expression import Select as Select
from .expression import select as select
from .expression import Selectable as Selectable
from .expression import SelectLabelStyle as SelectLabelStyle
+from .expression import SQLColumnExpression as SQLColumnExpression
from .expression import StatementLambdaElement as StatementLambdaElement
from .expression import Subquery as Subquery
from .expression import table as table
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index d9a1a9358..914d2b326 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -1115,6 +1115,26 @@ class SQLCoreOperations(Generic[_T], ColumnOperators, TypingOnly):
...
+class SQLColumnExpression(
+ SQLCoreOperations[_T], roles.ExpressionElementRole[_T], TypingOnly
+):
+ """A type that may be used to indicate any SQL column element or object
+ that acts in place of one.
+
+ :class:`.SQLColumnExpression` is a base of
+ :class:`.ColumnElement`, as well as within the bases of ORM elements
+ such as :class:`.InstrumentedAttribute`, and may be used in :pep:`484`
+ typing to indicate arguments or return values that should behave
+ as column expressions.
+
+ .. versionadded:: 2.0.0b4
+
+
+ """
+
+ __slots__ = ()
+
+
_SQO = SQLCoreOperations
SelfColumnElement = TypeVar("SelfColumnElement", bound="ColumnElement[Any]")
@@ -1131,7 +1151,7 @@ class ColumnElement(
roles.DMLColumnRole,
roles.DDLConstraintColumnRole,
roles.DDLExpressionRole,
- SQLCoreOperations[_T],
+ SQLColumnExpression[_T],
DQLDMLClauseElement,
):
"""Represent a column-oriented SQL expression suitable for usage in the
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index d08bbf4eb..2498bfb37 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -95,6 +95,7 @@ from .elements import quoted_name as quoted_name
from .elements import ReleaseSavepointClause as ReleaseSavepointClause
from .elements import RollbackToSavepointClause as RollbackToSavepointClause
from .elements import SavepointClause as SavepointClause
+from .elements import SQLColumnExpression as SQLColumnExpression
from .elements import TextClause as TextClause
from .elements import True_ as True_
from .elements import Tuple as Tuple