diff options
Diffstat (limited to 'lib/sqlalchemy')
41 files changed, 5843 insertions, 4578 deletions
diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index 22be3d42f..7d402d44e 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -6,146 +6,141 @@ # the MIT License: https://www.opensource.org/licenses/mit-license.php from . import util as _util -from .engine import create_engine -from .engine import create_mock_engine -from .engine import engine_from_config -from .inspection import inspect -from .schema import BLANK_SCHEMA -from .schema import CheckConstraint -from .schema import Column -from .schema import ColumnDefault -from .schema import Computed -from .schema import Constraint -from .schema import DDL -from .schema import DefaultClause -from .schema import FetchedValue -from .schema import ForeignKey -from .schema import ForeignKeyConstraint -from .schema import Identity -from .schema import Index -from .schema import MetaData -from .schema import PrimaryKeyConstraint -from .schema import Sequence -from .schema import Table -from .schema import UniqueConstraint -from .sql import alias -from .sql import all_ -from .sql import and_ -from .sql import any_ -from .sql import asc -from .sql import between -from .sql import bindparam -from .sql import case -from .sql import cast -from .sql import collate -from .sql import column -from .sql import delete -from .sql import desc -from .sql import distinct -from .sql import except_ -from .sql import except_all -from .sql import exists -from .sql import extract -from .sql import false -from .sql import func -from .sql import funcfilter -from .sql import insert -from .sql import intersect -from .sql import intersect_all -from .sql import join -from .sql import LABEL_STYLE_DEFAULT -from .sql import LABEL_STYLE_DISAMBIGUATE_ONLY -from .sql import LABEL_STYLE_NONE -from .sql import LABEL_STYLE_TABLENAME_PLUS_COL -from .sql import lambda_stmt -from .sql import lateral -from .sql import literal -from .sql import literal_column -from .sql import modifier -from .sql import not_ -from .sql import null -from .sql import nulls_first -from .sql import nulls_last -from .sql import nullsfirst -from .sql import nullslast -from .sql import or_ -from .sql import outerjoin -from .sql import outparam -from .sql import over -from .sql import select -from .sql import table -from .sql import tablesample -from .sql import text -from .sql import true -from .sql import tuple_ -from .sql import type_coerce -from .sql import union -from .sql import union_all -from .sql import update -from .sql import values -from .sql import within_group -from .types import ARRAY -from .types import BIGINT -from .types import BigInteger -from .types import BINARY -from .types import BLOB -from .types import BOOLEAN -from .types import Boolean -from .types import CHAR -from .types import CLOB -from .types import DATE -from .types import Date -from .types import DATETIME -from .types import DateTime -from .types import DECIMAL -from .types import Enum -from .types import FLOAT -from .types import Float -from .types import INT -from .types import INTEGER -from .types import Integer -from .types import Interval -from .types import JSON -from .types import LargeBinary -from .types import NCHAR -from .types import NUMERIC -from .types import Numeric -from .types import NVARCHAR -from .types import PickleType -from .types import REAL -from .types import SMALLINT -from .types import SmallInteger -from .types import String -from .types import TEXT -from .types import Text -from .types import TIME -from .types import Time -from .types import TIMESTAMP -from .types import TupleType -from .types import TypeDecorator -from .types import Unicode -from .types import UnicodeText -from .types import VARBINARY -from .types import VARCHAR +from .engine import create_engine as create_engine +from .engine import create_mock_engine as create_mock_engine +from .engine import engine_from_config as engine_from_config +from .inspection import inspect as inspect +from .schema import BLANK_SCHEMA as BLANK_SCHEMA +from .schema import CheckConstraint as CheckConstraint +from .schema import Column as Column +from .schema import ColumnDefault as ColumnDefault +from .schema import Computed as Computed +from .schema import Constraint as Constraint +from .schema import DDL as DDL +from .schema import DefaultClause as DefaultClause +from .schema import FetchedValue as FetchedValue +from .schema import ForeignKey as ForeignKey +from .schema import ForeignKeyConstraint as ForeignKeyConstraint +from .schema import Identity as Identity +from .schema import Index as Index +from .schema import MetaData as MetaData +from .schema import PrimaryKeyConstraint as PrimaryKeyConstraint +from .schema import Sequence as Sequence +from .schema import Table as Table +from .schema import UniqueConstraint as UniqueConstraint +from .sql import alias as alias +from .sql import all_ as all_ +from .sql import and_ as and_ +from .sql import any_ as any_ +from .sql import asc as asc +from .sql import between as between +from .sql import bindparam as bindparam +from .sql import case as case +from .sql import cast as cast +from .sql import collate as collate +from .sql import column as column +from .sql import delete as delete +from .sql import desc as desc +from .sql import distinct as distinct +from .sql import except_ as except_ +from .sql import except_all as except_all +from .sql import exists as exists +from .sql import extract as extract +from .sql import false as false +from .sql import func as func +from .sql import funcfilter as funcfilter +from .sql import insert as insert +from .sql import intersect as intersect +from .sql import intersect_all as intersect_all +from .sql import join as join +from .sql import label as label +from .sql import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT +from .sql import LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY +from .sql import LABEL_STYLE_NONE as LABEL_STYLE_NONE +from .sql import lambda_stmt as lambda_stmt +from .sql import lateral as lateral +from .sql import literal as literal +from .sql import literal_column as literal_column +from .sql import modifier as modifier +from .sql import not_ as not_ +from .sql import null as null +from .sql import nulls_first as nulls_first +from .sql import nulls_last as nulls_last +from .sql import nullsfirst as nullsfirst +from .sql import nullslast as nullslast +from .sql import or_ as or_ +from .sql import outerjoin as outerjoin +from .sql import outparam as outparam +from .sql import over as over +from .sql import select as select +from .sql import table as table +from .sql import tablesample as tablesample +from .sql import text as text +from .sql import true as true +from .sql import tuple_ as tuple_ +from .sql import type_coerce as type_coerce +from .sql import union as union +from .sql import union_all as union_all +from .sql import update as update +from .sql import values as values +from .sql import within_group as within_group +from .types import ARRAY as ARRAY +from .types import BIGINT as BIGINT +from .types import BigInteger as BigInteger +from .types import BINARY as BINARY +from .types import BLOB as BLOB +from .types import BOOLEAN as BOOLEAN +from .types import Boolean as Boolean +from .types import CHAR as CHAR +from .types import CLOB as CLOB +from .types import DATE as DATE +from .types import Date as Date +from .types import DATETIME as DATETIME +from .types import DateTime as DateTime +from .types import DECIMAL as DECIMAL +from .types import Enum as Enum +from .types import FLOAT as FLOAT +from .types import Float as Float +from .types import INT as INT +from .types import INTEGER as INTEGER +from .types import Integer as Integer +from .types import Interval as Interval +from .types import JSON as JSON +from .types import LargeBinary as LargeBinary +from .types import NCHAR as NCHAR +from .types import NUMERIC as NUMERIC +from .types import Numeric as Numeric +from .types import NVARCHAR as NVARCHAR +from .types import PickleType as PickleType +from .types import REAL as REAL +from .types import SMALLINT as SMALLINT +from .types import SmallInteger as SmallInteger +from .types import String as String +from .types import TEXT as TEXT +from .types import Text as Text +from .types import TIME as TIME +from .types import Time as Time +from .types import TIMESTAMP as TIMESTAMP +from .types import TupleType as TupleType +from .types import TypeDecorator as TypeDecorator +from .types import Unicode as Unicode +from .types import UnicodeText as UnicodeText +from .types import VARBINARY as VARBINARY +from .types import VARCHAR as VARCHAR + +if True: + # work around zimports bug + from .sql import ( + LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL, + ) __version__ = "2.0.0b1" def __go(lcls): - global __all__ - - from . import events from . import util as _sa_util - import inspect as _inspect - - __all__ = sorted( - name - for name, obj in lcls.items() - if not (name.startswith("_") or _inspect.ismodule(obj)) - ) - _sa_util.preloaded.import_prefix("sqlalchemy") from . import exc diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py index b57e89a41..d48cca2a8 100644 --- a/lib/sqlalchemy/dialects/mssql/base.py +++ b/lib/sqlalchemy/dialects/mssql/base.py @@ -848,7 +848,6 @@ from ...types import SMALLINT from ...types import TEXT from ...types import VARCHAR from ...util import update_wrapper -from ...util.langhelpers import public_factory # https://sqlserverbuilds.blogspot.com/ @@ -1347,41 +1346,40 @@ class SQL_VARIANT(sqltypes.TypeEngine): __visit_name__ = "SQL_VARIANT" -class TryCast(sql.elements.Cast): - """Represent a SQL Server TRY_CAST expression.""" +def try_cast(*arg, **kw): + """Create a TRY_CAST expression. - __visit_name__ = "try_cast" + :class:`.TryCast` is a subclass of SQLAlchemy's :class:`.Cast` + construct, and works in the same way, except that the SQL expression + rendered is "TRY_CAST" rather than "CAST":: - stringify_dialect = "mssql" - inherit_cache = True + from sqlalchemy import select + from sqlalchemy import Numeric + from sqlalchemy.dialects.mssql import try_cast - def __init__(self, *arg, **kw): - """Create a TRY_CAST expression. + stmt = select( + try_cast(product_table.c.unit_price, Numeric(10, 4)) + ) - :class:`.TryCast` is a subclass of SQLAlchemy's :class:`.Cast` - construct, and works in the same way, except that the SQL expression - rendered is "TRY_CAST" rather than "CAST":: + The above would render:: - from sqlalchemy import select - from sqlalchemy import Numeric - from sqlalchemy.dialects.mssql import try_cast + SELECT TRY_CAST (product_table.unit_price AS NUMERIC(10, 4)) + FROM product_table - stmt = select( - try_cast(product_table.c.unit_price, Numeric(10, 4)) - ) + .. versionadded:: 1.3.7 - The above would render:: + """ + return TryCast(*arg, **kw) - SELECT TRY_CAST (product_table.unit_price AS NUMERIC(10, 4)) - FROM product_table - .. versionadded:: 1.3.7 +class TryCast(sql.elements.Cast): + """Represent a SQL Server TRY_CAST expression.""" - """ - super(TryCast, self).__init__(*arg, **kw) + __visit_name__ = "try_cast" + stringify_dialect = "mssql" + inherit_cache = True -try_cast = public_factory(TryCast, ".dialects.mssql.try_cast") # old names. MSDateTime = _MSDateTime diff --git a/lib/sqlalchemy/dialects/mysql/dml.py b/lib/sqlalchemy/dialects/mysql/dml.py index a21d49e0b..eb4a9f798 100644 --- a/lib/sqlalchemy/dialects/mysql/dml.py +++ b/lib/sqlalchemy/dialects/mysql/dml.py @@ -14,12 +14,30 @@ from ...sql.base import ColumnCollection from ...sql.dml import Insert as StandardInsert from ...sql.elements import ClauseElement from ...sql.expression import alias -from ...util.langhelpers import public_factory __all__ = ("Insert", "insert") +def insert(table): + """Construct a MySQL/MariaDB-specific variant :class:`_mysql.Insert` + construct. + + .. container:: inherited_member + + The :func:`sqlalchemy.dialects.mysql.insert` function creates + a :class:`sqlalchemy.dialects.mysql.Insert`. This class is based + on the dialect-agnostic :class:`_sql.Insert` construct which may + be constructed using the :func:`_sql.insert` function in + SQLAlchemy Core. + + The :class:`_mysql.Insert` construct includes additional methods + :meth:`_mysql.Insert.on_duplicate_key_update`. + + """ + return Insert(table) + + SelfInsert = typing.TypeVar("SelfInsert", bound="Insert") @@ -145,11 +163,6 @@ class Insert(StandardInsert): return self -insert = public_factory( - Insert, ".dialects.mysql.insert", ".dialects.mysql.Insert" -) - - class OnDuplicateClause(ClauseElement): __visit_name__ = "on_duplicate_key_update" diff --git a/lib/sqlalchemy/dialects/postgresql/array.py b/lib/sqlalchemy/dialects/postgresql/array.py index f30a409c7..abe17ea35 100644 --- a/lib/sqlalchemy/dialects/postgresql/array.py +++ b/lib/sqlalchemy/dialects/postgresql/array.py @@ -94,13 +94,12 @@ class array(expression.ClauseList, expression.ColumnElement): coercions.expect(roles.ExpressionElementRole, c) for c in clauses ] - super(array, self).__init__(*clauses, **kw) - self._type_tuple = [arg.type for arg in clauses] main_type = kw.pop( "type_", self._type_tuple[0] if self._type_tuple else sqltypes.NULLTYPE, ) + super(array, self).__init__(*clauses, **kw) if isinstance(main_type, ARRAY): self.type = ARRAY( diff --git a/lib/sqlalchemy/dialects/postgresql/dml.py b/lib/sqlalchemy/dialects/postgresql/dml.py index 48b180e81..09dbd9558 100644 --- a/lib/sqlalchemy/dialects/postgresql/dml.py +++ b/lib/sqlalchemy/dialects/postgresql/dml.py @@ -17,11 +17,31 @@ from ...sql.base import ColumnCollection from ...sql.dml import Insert as StandardInsert from ...sql.elements import ClauseElement from ...sql.expression import alias -from ...util.langhelpers import public_factory __all__ = ("Insert", "insert") + +def insert(table): + """Construct a PostgreSQL-specific variant :class:`_postgresql.Insert` + construct. + + .. container:: inherited_member + + The :func:`sqlalchemy.dialects.postgresql.insert` function creates + a :class:`sqlalchemy.dialects.postgresql.Insert`. This class is based + on the dialect-agnostic :class:`_sql.Insert` construct which may + be constructed using the :func:`_sql.insert` function in + SQLAlchemy Core. + + The :class:`_postgresql.Insert` construct includes additional methods + :meth:`_postgresql.Insert.on_conflict_do_update`, + :meth:`_postgresql.Insert.on_conflict_do_nothing`. + + """ + return Insert(table) + + SelfInsert = typing.TypeVar("SelfInsert", bound="Insert") @@ -183,11 +203,6 @@ class Insert(StandardInsert): return self -insert = public_factory( - Insert, ".dialects.postgresql.insert", ".dialects.postgresql.Insert" -) - - class OnConflictClause(ClauseElement): stringify_dialect = "postgresql" diff --git a/lib/sqlalchemy/dialects/sqlite/dml.py b/lib/sqlalchemy/dialects/sqlite/dml.py index 9284070df..7dee7e3b6 100644 --- a/lib/sqlalchemy/dialects/sqlite/dml.py +++ b/lib/sqlalchemy/dialects/sqlite/dml.py @@ -15,11 +15,31 @@ from ...sql.base import ColumnCollection from ...sql.dml import Insert as StandardInsert from ...sql.elements import ClauseElement from ...sql.expression import alias -from ...util.langhelpers import public_factory __all__ = ("Insert", "insert") + +def insert(table): + """Construct a sqlite-specific variant :class:`_sqlite.Insert` + construct. + + .. container:: inherited_member + + The :func:`sqlalchemy.dialects.sqlite.insert` function creates + a :class:`sqlalchemy.dialects.sqlite.Insert`. This class is based + on the dialect-agnostic :class:`_sql.Insert` construct which may + be constructed using the :func:`_sql.insert` function in + SQLAlchemy Core. + + The :class:`_sqlite.Insert` construct includes additional methods + :meth:`_sqlite.Insert.on_conflict_do_update`, + :meth:`_sqlite.Insert.on_conflict_do_nothing`. + + """ + return Insert(table) + + SelfInsert = typing.TypeVar("SelfInsert", bound="Insert") @@ -151,11 +171,6 @@ class Insert(StandardInsert): return self -insert = public_factory( - Insert, ".dialects.sqlite.insert", ".dialects.sqlite.Insert" -) - - class OnConflictClause(ClauseElement): stringify_dialect = "sqlite" diff --git a/lib/sqlalchemy/future/__init__.py b/lib/sqlalchemy/future/__init__.py index 3c16e9c0e..057e3bfbe 100644 --- a/lib/sqlalchemy/future/__init__.py +++ b/lib/sqlalchemy/future/__init__.py @@ -8,11 +8,7 @@ """Future 2.0 API features. """ -from .engine import Connection -from .engine import create_engine -from .engine import Engine -from ..sql.selectable import Select -from ..util.langhelpers import public_factory - - -select = public_factory(Select._create, ".future.select") +from .engine import Connection as Connection +from .engine import create_engine as create_engine +from .engine import Engine as Engine +from ..sql._selectable_constructors import select as select diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 0d197bf87..17167a7de 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -13,323 +13,115 @@ documentation for an overview of how this module is used. """ -from . import exc +from . import exc as exc from . import mapper as mapperlib -from . import strategy_options -from .attributes import AttributeEvent -from .attributes import InstrumentedAttribute -from .attributes import Mapped -from .attributes import QueryableAttribute -from .context import QueryContext -from .decl_api import as_declarative -from .decl_api import declarative_base -from .decl_api import declarative_mixin -from .decl_api import DeclarativeMeta -from .decl_api import declared_attr -from .decl_api import has_inherited_table -from .decl_api import registry -from .decl_api import synonym_for -from .descriptor_props import CompositeProperty -from .descriptor_props import SynonymProperty -from .identity import IdentityMap -from .instrumentation import ClassManager -from .interfaces import EXT_CONTINUE -from .interfaces import EXT_SKIP -from .interfaces import EXT_STOP -from .interfaces import InspectionAttr -from .interfaces import InspectionAttrInfo -from .interfaces import MANYTOMANY -from .interfaces import MANYTOONE -from .interfaces import MapperProperty -from .interfaces import NOT_EXTENSION -from .interfaces import ONETOMANY -from .interfaces import PropComparator -from .interfaces import UserDefinedOption -from .loading import merge_frozen_result -from .loading import merge_result -from .mapper import class_mapper -from .mapper import configure_mappers -from .mapper import Mapper -from .mapper import reconstructor -from .mapper import validates -from .properties import ColumnProperty -from .query import AliasOption -from .query import FromStatement -from .query import Query -from .relationships import foreign -from .relationships import RelationshipProperty -from .relationships import remote -from .scoping import scoped_session -from .session import close_all_sessions -from .session import make_transient -from .session import make_transient_to_detached -from .session import object_session -from .session import ORMExecuteState -from .session import Session -from .session import sessionmaker -from .session import SessionTransaction -from .state import AttributeState -from .state import InstanceState -from .strategy_options import contains_eager -from .strategy_options import defaultload -from .strategy_options import defer -from .strategy_options import immediateload -from .strategy_options import joinedload -from .strategy_options import lazyload -from .strategy_options import Load -from .strategy_options import load_only -from .strategy_options import noload -from .strategy_options import raiseload -from .strategy_options import selectin_polymorphic -from .strategy_options import selectinload -from .strategy_options import subqueryload -from .strategy_options import undefer -from .strategy_options import undefer_group -from .strategy_options import with_expression -from .unitofwork import UOWTransaction -from .util import aliased -from .util import Bundle -from .util import CascadeOptions -from .util import join -from .util import LoaderCriteriaOption -from .util import object_mapper -from .util import outerjoin -from .util import polymorphic_union -from .util import was_deleted -from .util import with_parent -from .util import with_polymorphic -from .. import sql as _sql +from . import strategy_options as strategy_options +from ._orm_constructors import _mapper_fn as mapper +from ._orm_constructors import backref as backref +from ._orm_constructors import clear_mappers as clear_mappers +from ._orm_constructors import column_property as column_property +from ._orm_constructors import composite as composite +from ._orm_constructors import contains_alias as contains_alias +from ._orm_constructors import create_session as create_session +from ._orm_constructors import deferred as deferred +from ._orm_constructors import dynamic_loader as dynamic_loader +from ._orm_constructors import query_expression as query_expression +from ._orm_constructors import relationship as relationship +from ._orm_constructors import synonym as synonym +from ._orm_constructors import with_loader_criteria as with_loader_criteria +from .attributes import AttributeEvent as AttributeEvent +from .attributes import InstrumentedAttribute as InstrumentedAttribute +from .attributes import Mapped as Mapped +from .attributes import QueryableAttribute as QueryableAttribute +from .context import QueryContext as QueryContext +from .decl_api import as_declarative as as_declarative +from .decl_api import declarative_base as declarative_base +from .decl_api import declarative_mixin as declarative_mixin +from .decl_api import DeclarativeMeta as DeclarativeMeta +from .decl_api import declared_attr as declared_attr +from .decl_api import has_inherited_table as has_inherited_table +from .decl_api import registry as registry +from .decl_api import synonym_for as synonym_for +from .descriptor_props import CompositeProperty as CompositeProperty +from .descriptor_props import SynonymProperty as SynonymProperty +from .dynamic import AppenderQuery as AppenderQuery +from .events import AttributeEvents as AttributeEvents +from .events import InstanceEvents as InstanceEvents +from .events import InstrumentationEvents as InstrumentationEvents +from .events import MapperEvents as MapperEvents +from .events import QueryEvents as QueryEvents +from .events import SessionEvents as SessionEvents +from .identity import IdentityMap as IdentityMap +from .instrumentation import ClassManager as ClassManager +from .interfaces import EXT_CONTINUE as EXT_CONTINUE +from .interfaces import EXT_SKIP as EXT_SKIP +from .interfaces import EXT_STOP as EXT_STOP +from .interfaces import InspectionAttr as InspectionAttr +from .interfaces import InspectionAttrInfo as InspectionAttrInfo +from .interfaces import MANYTOMANY as MANYTOMANY +from .interfaces import MANYTOONE as MANYTOONE +from .interfaces import MapperProperty as MapperProperty +from .interfaces import NOT_EXTENSION as NOT_EXTENSION +from .interfaces import ONETOMANY as ONETOMANY +from .interfaces import PropComparator as PropComparator +from .interfaces import UserDefinedOption as UserDefinedOption +from .loading import merge_frozen_result as merge_frozen_result +from .loading import merge_result as merge_result +from .mapper import class_mapper as class_mapper +from .mapper import configure_mappers as configure_mappers +from .mapper import Mapper as Mapper +from .mapper import reconstructor as reconstructor +from .mapper import validates as validates +from .properties import ColumnProperty as ColumnProperty +from .query import AliasOption as AliasOption +from .query import FromStatement as FromStatement +from .query import Query as Query +from .relationships import foreign as foreign +from .relationships import RelationshipProperty as RelationshipProperty +from .relationships import remote as remote +from .scoping import scoped_session as scoped_session +from .session import close_all_sessions as close_all_sessions +from .session import make_transient as make_transient +from .session import make_transient_to_detached as make_transient_to_detached +from .session import object_session as object_session +from .session import ORMExecuteState as ORMExecuteState +from .session import Session as Session +from .session import sessionmaker as sessionmaker +from .session import SessionTransaction as SessionTransaction +from .state import AttributeState as AttributeState +from .state import InstanceState as InstanceState +from .strategy_options import contains_eager as contains_eager +from .strategy_options import defaultload as defaultload +from .strategy_options import defer as defer +from .strategy_options import immediateload as immediateload +from .strategy_options import joinedload as joinedload +from .strategy_options import lazyload as lazyload +from .strategy_options import Load as Load +from .strategy_options import load_only as load_only +from .strategy_options import noload as noload +from .strategy_options import raiseload as raiseload +from .strategy_options import selectin_polymorphic as selectin_polymorphic +from .strategy_options import selectinload as selectinload +from .strategy_options import subqueryload as subqueryload +from .strategy_options import undefer as undefer +from .strategy_options import undefer_group as undefer_group +from .strategy_options import with_expression as with_expression +from .unitofwork import UOWTransaction as UOWTransaction +from .util import aliased as aliased +from .util import Bundle as Bundle +from .util import CascadeOptions as CascadeOptions +from .util import join as join +from .util import LoaderCriteriaOption as LoaderCriteriaOption +from .util import object_mapper as object_mapper +from .util import outerjoin as outerjoin +from .util import polymorphic_union as polymorphic_union +from .util import was_deleted as was_deleted +from .util import with_parent as with_parent +from .util import with_polymorphic as with_polymorphic from .. import util as _sa_util -from ..exc import InvalidRequestError -from ..util.langhelpers import public_factory - - -def create_session(bind=None, **kwargs): - r"""Create a new :class:`.Session` - with no automation enabled by default. - - This function is used primarily for testing. The usual - route to :class:`.Session` creation is via its constructor - or the :func:`.sessionmaker` function. - - :param bind: optional, a single Connectable to use for all - database access in the created - :class:`~sqlalchemy.orm.session.Session`. - - :param \*\*kwargs: optional, passed through to the - :class:`.Session` constructor. - - :returns: an :class:`~sqlalchemy.orm.session.Session` instance - - The defaults of create_session() are the opposite of that of - :func:`sessionmaker`; ``autoflush`` and ``expire_on_commit`` are - False. - - Usage:: - - >>> from sqlalchemy.orm import create_session - >>> session = create_session() - - It is recommended to use :func:`sessionmaker` instead of - create_session(). - - """ - - kwargs.setdefault("autoflush", False) - kwargs.setdefault("expire_on_commit", False) - return Session(bind=bind, **kwargs) - - -with_loader_criteria = public_factory(LoaderCriteriaOption, ".orm") - -relationship = public_factory(RelationshipProperty, ".orm.relationship") - - -def mapper(*arg, **kw): - """Placeholder for the now-removed ``mapper()`` function. - - Classical mappings should be performed using the - :meth:`_orm.registry.map_imperatively` method. - - This symbol remains in SQLAlchemy 2.0 to suit the deprecated use case - of using the ``mapper()`` function as a target for ORM event listeners, - which failed to be marked as deprecated in the 1.4 series. - - Global ORM mapper listeners should instead use the :class:`_orm.Mapper` - class as the target. - - .. versionchanged:: 2.0 The ``mapper()`` function was removed; the - symbol remains temporarily as a placeholder for the event listening - use case. - - """ - raise InvalidRequestError( - "The 'sqlalchemy.orm.mapper()' function is removed as of " - "SQLAlchemy 2.0. Use the " - "'sqlalchemy.orm.registry.map_imperatively()` " - "method of the ``sqlalchemy.orm.registry`` class to perform " - "classical mapping." - ) - - -def dynamic_loader(argument, **kw): - """Construct a dynamically-loading mapper property. - - This is essentially the same as - using the ``lazy='dynamic'`` argument with :func:`relationship`:: - - dynamic_loader(SomeClass) - - # is the same as - - relationship(SomeClass, lazy="dynamic") - - See the section :ref:`dynamic_relationship` for more details - on dynamic loading. - - """ - kw["lazy"] = "dynamic" - return relationship(argument, **kw) - - -column_property = public_factory(ColumnProperty, ".orm.column_property") -composite = public_factory(CompositeProperty, ".orm.composite") - - -def backref(name, **kwargs): - """Create a back reference with explicit keyword arguments, which are the - same arguments one can send to :func:`relationship`. - - Used with the ``backref`` keyword argument to :func:`relationship` in - place of a string argument, e.g.:: - - 'items':relationship( - SomeItem, backref=backref('parent', lazy='subquery')) - - .. seealso:: - - :ref:`relationships_backref` - - """ - - return (name, kwargs) - - -def deferred(*columns, **kw): - r"""Indicate a column-based mapped attribute that by default will - not load unless accessed. - - :param \*columns: columns to be mapped. This is typically a single - :class:`_schema.Column` object, - however a collection is supported in order - to support multiple columns mapped under the same attribute. - - :param raiseload: boolean, if True, indicates an exception should be raised - if the load operation is to take place. - - .. versionadded:: 1.4 - - .. seealso:: - - :ref:`deferred_raiseload` - - :param \**kw: additional keyword arguments passed to - :class:`.ColumnProperty`. - - .. seealso:: - - :ref:`deferred` - - """ - return ColumnProperty(deferred=True, *columns, **kw) - - -def query_expression(default_expr=_sql.null()): - """Indicate an attribute that populates from a query-time SQL expression. - - :param default_expr: Optional SQL expression object that will be used in - all cases if not assigned later with :func:`_orm.with_expression`. - E.g.:: - - from sqlalchemy.sql import literal - - class C(Base): - #... - my_expr = query_expression(literal(1)) - - .. versionadded:: 1.3.18 - - - .. versionadded:: 1.2 - - .. seealso:: - - :ref:`mapper_querytime_expression` - - """ - prop = ColumnProperty(default_expr) - prop.strategy_key = (("query_expression", True),) - return prop - - -synonym = public_factory(SynonymProperty, ".orm.synonym") - - -def clear_mappers(): - """Remove all mappers from all classes. - - .. versionchanged:: 1.4 This function now locates all - :class:`_orm.registry` objects and calls upon the - :meth:`_orm.registry.dispose` method of each. - - This function removes all instrumentation from classes and disposes - of their associated mappers. Once called, the classes are unmapped - and can be later re-mapped with new mappers. - - :func:`.clear_mappers` is *not* for normal use, as there is literally no - valid usage for it outside of very specific testing scenarios. Normally, - mappers are permanent structural components of user-defined classes, and - are never discarded independently of their class. If a mapped class - itself is garbage collected, its mapper is automatically disposed of as - well. As such, :func:`.clear_mappers` is only for usage in test suites - that re-use the same classes with different mappings, which is itself an - extremely rare use case - the only such use case is in fact SQLAlchemy's - own test suite, and possibly the test suites of other ORM extension - libraries which intend to test various combinations of mapper construction - upon a fixed set of classes. - - """ - - mapperlib._dispose_registries(mapperlib._all_registries(), False) - - -contains_alias = public_factory(AliasOption, ".orm.contains_alias") - -if True: - from .events import AttributeEvents - from .events import MapperEvents - from .events import InstanceEvents - from .events import InstrumentationEvents - from .events import QueryEvents - from .events import SessionEvents def __go(lcls): - global __all__ - global AppenderQuery - from .. import util as sa_util - from . import dynamic - from . import events - from . import loading - import inspect as _inspect - - from .dynamic import AppenderQuery - - __all__ = sorted( - name - for name, obj in lcls.items() - if not (name.startswith("_") or _inspect.ismodule(obj)) - ) _sa_util.preloaded.import_prefix("sqlalchemy.orm") _sa_util.preloaded.import_prefix("sqlalchemy.ext") diff --git a/lib/sqlalchemy/orm/_orm_constructors.py b/lib/sqlalchemy/orm/_orm_constructors.py new file mode 100644 index 000000000..be0d23d00 --- /dev/null +++ b/lib/sqlalchemy/orm/_orm_constructors.py @@ -0,0 +1,1557 @@ +# orm/_orm_constructors.py +# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors +# <see AUTHORS file> +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php + +import typing +from typing import Callable +from typing import Type +from typing import Union + +from . import mapper as mapperlib +from .descriptor_props import CompositeProperty +from .descriptor_props import SynonymProperty +from .properties import ColumnProperty +from .query import AliasOption +from .relationships import RelationshipProperty +from .session import Session +from .util import LoaderCriteriaOption +from .. import sql +from .. import util +from ..exc import InvalidRequestError + +_RC = typing.TypeVar("_RC") +_T = typing.TypeVar("_T") + + +@util.deprecated( + "1.4", + "The :class:`.AliasOption` object is not necessary " + "for entities to be matched up to a query that is established " + "via :meth:`.Query.from_statement` and now does nothing.", +) +def contains_alias(alias) -> "AliasOption": + r"""Return a :class:`.MapperOption` that will indicate to the + :class:`_query.Query` + that the main table has been aliased. + + """ + return AliasOption(alias) + + +def column_property( + column: sql.ColumnElement[_T], *additional_columns, **kwargs +) -> "ColumnProperty[_T]": + r"""Provide a column-level property for use with a mapping. + + Column-based properties can normally be applied to the mapper's + ``properties`` dictionary using the :class:`_schema.Column` + element directly. + Use this function when the given column is not directly present within + the mapper's selectable; examples include SQL expressions, functions, + and scalar SELECT queries. + + The :func:`_orm.column_property` function returns an instance of + :class:`.ColumnProperty`. + + Columns that aren't present in the mapper's selectable won't be + persisted by the mapper and are effectively "read-only" attributes. + + :param \*cols: + list of Column objects to be mapped. + + :param active_history=False: + When ``True``, indicates that the "previous" value for a + scalar attribute should be loaded when replaced, if not + already loaded. Normally, history tracking logic for + simple non-primary-key scalar values only needs to be + aware of the "new" value in order to perform a flush. This + flag is available for applications that make use of + :func:`.attributes.get_history` or :meth:`.Session.is_modified` + which also need to know + the "previous" value of the attribute. + + :param comparator_factory: a class which extends + :class:`.ColumnProperty.Comparator` which provides custom SQL + clause generation for comparison operations. + + :param group: + a group name for this property when marked as deferred. + + :param deferred: + when True, the column property is "deferred", meaning that + it does not load immediately, and is instead loaded when the + attribute is first accessed on an instance. See also + :func:`~sqlalchemy.orm.deferred`. + + :param doc: + optional string that will be applied as the doc on the + class-bound descriptor. + + :param expire_on_flush=True: + Disable expiry on flush. A column_property() which refers + to a SQL expression (and not a single table-bound column) + is considered to be a "read only" property; populating it + has no effect on the state of data, and it can only return + database state. For this reason a column_property()'s value + is expired whenever the parent object is involved in a + flush, that is, has any kind of "dirty" state within a flush. + Setting this parameter to ``False`` will have the effect of + leaving any existing value present after the flush proceeds. + Note however that the :class:`.Session` with default expiration + settings still expires + all attributes after a :meth:`.Session.commit` call, however. + + :param info: Optional data dictionary which will be populated into the + :attr:`.MapperProperty.info` attribute of this object. + + :param raiseload: if True, indicates the column should raise an error + when undeferred, rather than loading the value. This can be + altered at query time by using the :func:`.deferred` option with + raiseload=False. + + .. versionadded:: 1.4 + + .. seealso:: + + :ref:`deferred_raiseload` + + .. seealso:: + + :ref:`column_property_options` - to map columns while including + mapping options + + :ref:`mapper_column_property_sql_expressions` - to map SQL + expressions + + """ + return ColumnProperty(column, *additional_columns, **kwargs) + + +def composite(class_: Type[_T], *attrs, **kwargs) -> "CompositeProperty[_T]": + r"""Return a composite column-based property for use with a Mapper. + + See the mapping documentation section :ref:`mapper_composite` for a + full usage example. + + The :class:`.MapperProperty` returned by :func:`.composite` + is the :class:`.CompositeProperty`. + + :param class\_: + The "composite type" class, or any classmethod or callable which + will produce a new instance of the composite object given the + column values in order. + + :param \*cols: + List of Column objects to be mapped. + + :param active_history=False: + When ``True``, indicates that the "previous" value for a + scalar attribute should be loaded when replaced, if not + already loaded. See the same flag on :func:`.column_property`. + + :param group: + A group name for this property when marked as deferred. + + :param deferred: + When True, the column property is "deferred", meaning that it does + not load immediately, and is instead loaded when the attribute is + first accessed on an instance. See also + :func:`~sqlalchemy.orm.deferred`. + + :param comparator_factory: a class which extends + :class:`.CompositeProperty.Comparator` which provides custom SQL + clause generation for comparison operations. + + :param doc: + optional string that will be applied as the doc on the + class-bound descriptor. + + :param info: Optional data dictionary which will be populated into the + :attr:`.MapperProperty.info` attribute of this object. + + """ + return CompositeProperty(class_, *attrs, **kwargs) + + +def with_loader_criteria( + entity_or_base, + where_criteria, + loader_only=False, + include_aliases=False, + propagate_to_loaders=True, + track_closure_variables=True, +) -> "LoaderCriteriaOption": + """Add additional WHERE criteria to the load for all occurrences of + a particular entity. + + .. versionadded:: 1.4 + + The :func:`_orm.with_loader_criteria` option is intended to add + limiting criteria to a particular kind of entity in a query, + **globally**, meaning it will apply to the entity as it appears + in the SELECT query as well as within any subqueries, join + conditions, and relationship loads, including both eager and lazy + loaders, without the need for it to be specified in any particular + part of the query. The rendering logic uses the same system used by + single table inheritance to ensure a certain discriminator is applied + to a table. + + E.g., using :term:`2.0-style` queries, we can limit the way the + ``User.addresses`` collection is loaded, regardless of the kind + of loading used:: + + from sqlalchemy.orm import with_loader_criteria + + stmt = select(User).options( + selectinload(User.addresses), + with_loader_criteria(Address, Address.email_address != 'foo')) + ) + + Above, the "selectinload" for ``User.addresses`` will apply the + given filtering criteria to the WHERE clause. + + Another example, where the filtering will be applied to the + ON clause of the join, in this example using :term:`1.x style` + queries:: + + q = session.query(User).outerjoin(User.addresses).options( + with_loader_criteria(Address, Address.email_address != 'foo')) + ) + + The primary purpose of :func:`_orm.with_loader_criteria` is to use + it in the :meth:`_orm.SessionEvents.do_orm_execute` event handler + to ensure that all occurrences of a particular entity are filtered + in a certain way, such as filtering for access control roles. It + also can be used to apply criteria to relationship loads. In the + example below, we can apply a certain set of rules to all queries + emitted by a particular :class:`_orm.Session`:: + + session = Session(bind=engine) + + @event.listens_for("do_orm_execute", session) + def _add_filtering_criteria(execute_state): + + if ( + execute_state.is_select + and not execute_state.is_column_load + and not execute_state.is_relationship_load + ): + execute_state.statement = execute_state.statement.options( + with_loader_criteria( + SecurityRole, + lambda cls: cls.role.in_(['some_role']), + include_aliases=True + ) + ) + + In the above example, the :meth:`_orm.SessionEvents.do_orm_execute` + event will intercept all queries emitted using the + :class:`_orm.Session`. For those queries which are SELECT statements + and are not attribute or relationship loads a custom + :func:`_orm.with_loader_criteria` option is added to the query. The + :func:`_orm.with_loader_criteria` option will be used in the given + statement and will also be automatically propagated to all relationship + loads that descend from this query. + + The criteria argument given is a ``lambda`` that accepts a ``cls`` + argument. The given class will expand to include all mapped subclass + and need not itself be a mapped class. + + .. tip:: + + When using :func:`_orm.with_loader_criteria` option in + conjunction with the :func:`_orm.contains_eager` loader option, + it's important to note that :func:`_orm.with_loader_criteria` only + affects the part of the query that determines what SQL is rendered + in terms of the WHERE and FROM clauses. The + :func:`_orm.contains_eager` option does not affect the rendering of + the SELECT statement outside of the columns clause, so does not have + any interaction with the :func:`_orm.with_loader_criteria` option. + However, the way things "work" is that :func:`_orm.contains_eager` + is meant to be used with a query that is already selecting from the + additional entities in some way, where + :func:`_orm.with_loader_criteria` can apply it's additional + criteria. + + In the example below, assuming a mapping relationship as + ``A -> A.bs -> B``, the given :func:`_orm.with_loader_criteria` + option will affect the way in which the JOIN is rendered:: + + stmt = select(A).join(A.bs).options( + contains_eager(A.bs), + with_loader_criteria(B, B.flag == 1) + ) + + Above, the given :func:`_orm.with_loader_criteria` option will + affect the ON clause of the JOIN that is specified by + ``.join(A.bs)``, so is applied as expected. The + :func:`_orm.contains_eager` option has the effect that columns from + ``B`` are added to the columns clause:: + + SELECT + b.id, b.a_id, b.data, b.flag, + a.id AS id_1, + a.data AS data_1 + FROM a JOIN b ON a.id = b.a_id AND b.flag = :flag_1 + + + The use of the :func:`_orm.contains_eager` option within the above + statement has no effect on the behavior of the + :func:`_orm.with_loader_criteria` option. If the + :func:`_orm.contains_eager` option were omitted, the SQL would be + the same as regards the FROM and WHERE clauses, where + :func:`_orm.with_loader_criteria` continues to add its criteria to + the ON clause of the JOIN. The addition of + :func:`_orm.contains_eager` only affects the columns clause, in that + additional columns against ``b`` are added which are then consumed + by the ORM to produce ``B`` instances. + + .. warning:: The use of a lambda inside of the call to + :func:`_orm.with_loader_criteria` is only invoked **once per unique + class**. Custom functions should not be invoked within this lambda. + See :ref:`engine_lambda_caching` for an overview of the "lambda SQL" + feature, which is for advanced use only. + + :param entity_or_base: a mapped class, or a class that is a super + class of a particular set of mapped classes, to which the rule + will apply. + + :param where_criteria: a Core SQL expression that applies limiting + criteria. This may also be a "lambda:" or Python function that + accepts a target class as an argument, when the given class is + a base with many different mapped subclasses. + + :param include_aliases: if True, apply the rule to :func:`_orm.aliased` + constructs as well. + + :param propagate_to_loaders: defaults to True, apply to relationship + loaders such as lazy loaders. + + + .. seealso:: + + :ref:`examples_session_orm_events` - includes examples of using + :func:`_orm.with_loader_criteria`. + + :ref:`do_orm_execute_global_criteria` - basic example on how to + combine :func:`_orm.with_loader_criteria` with the + :meth:`_orm.SessionEvents.do_orm_execute` event. + + :param track_closure_variables: when False, closure variables inside + of a lambda expression will not be used as part of + any cache key. This allows more complex expressions to be used + inside of a lambda expression but requires that the lambda ensures + it returns the identical SQL every time given a particular class. + + .. versionadded:: 1.4.0b2 + + """ + return LoaderCriteriaOption( + entity_or_base, + where_criteria, + loader_only, + include_aliases, + propagate_to_loaders, + track_closure_variables, + ) + + +def relationship( + argument: Union[str, Type[_RC], Callable[[], Type[_RC]]], + secondary=None, + primaryjoin=None, + secondaryjoin=None, + foreign_keys=None, + uselist=None, + order_by=False, + backref=None, + back_populates=None, + overlaps=None, + post_update=False, + cascade=False, + viewonly=False, + lazy="select", + collection_class=None, + passive_deletes=RelationshipProperty._persistence_only["passive_deletes"], + passive_updates=RelationshipProperty._persistence_only["passive_updates"], + remote_side=None, + enable_typechecks=RelationshipProperty._persistence_only[ + "enable_typechecks" + ], + join_depth=None, + comparator_factory=None, + single_parent=False, + innerjoin=False, + distinct_target_key=None, + doc=None, + active_history=RelationshipProperty._persistence_only["active_history"], + cascade_backrefs=RelationshipProperty._persistence_only[ + "cascade_backrefs" + ], + load_on_pending=False, + bake_queries=True, + _local_remote_pairs=None, + query_class=None, + info=None, + omit_join=None, + sync_backref=None, + _legacy_inactive_history_style=False, +) -> RelationshipProperty[_RC]: + """Provide a relationship between two mapped classes. + + This corresponds to a parent-child or associative table relationship. + The constructed class is an instance of + :class:`.RelationshipProperty`. + + A typical :func:`_orm.relationship`, used in a classical mapping:: + + mapper(Parent, properties={ + 'children': relationship(Child) + }) + + Some arguments accepted by :func:`_orm.relationship` + optionally accept a + callable function, which when called produces the desired value. + The callable is invoked by the parent :class:`_orm.Mapper` at "mapper + initialization" time, which happens only when mappers are first used, + and is assumed to be after all mappings have been constructed. This + can be used to resolve order-of-declaration and other dependency + issues, such as if ``Child`` is declared below ``Parent`` in the same + file:: + + mapper(Parent, properties={ + "children":relationship(lambda: Child, + order_by=lambda: Child.id) + }) + + When using the :ref:`declarative_toplevel` extension, the Declarative + initializer allows string arguments to be passed to + :func:`_orm.relationship`. These string arguments are converted into + callables that evaluate the string as Python code, using the + Declarative class-registry as a namespace. This allows the lookup of + related classes to be automatic via their string name, and removes the + need for related classes to be imported into the local module space + before the dependent classes have been declared. It is still required + that the modules in which these related classes appear are imported + anywhere in the application at some point before the related mappings + are actually used, else a lookup error will be raised when the + :func:`_orm.relationship` + attempts to resolve the string reference to the + related class. An example of a string- resolved class is as + follows:: + + from sqlalchemy.ext.declarative import declarative_base + + Base = declarative_base() + + class Parent(Base): + __tablename__ = 'parent' + id = Column(Integer, primary_key=True) + children = relationship("Child", order_by="Child.id") + + .. seealso:: + + :ref:`relationship_config_toplevel` - Full introductory and + reference documentation for :func:`_orm.relationship`. + + :ref:`orm_tutorial_relationship` - ORM tutorial introduction. + + :param argument: + A mapped class, or actual :class:`_orm.Mapper` instance, + representing + the target of the relationship. + + :paramref:`_orm.relationship.argument` + may also be passed as a callable + function which is evaluated at mapper initialization time, and may + be passed as a string name when using Declarative. + + .. warning:: Prior to SQLAlchemy 1.3.16, this value is interpreted + using Python's ``eval()`` function. + **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. + See :ref:`declarative_relationship_eval` for details on + declarative evaluation of :func:`_orm.relationship` arguments. + + .. versionchanged 1.3.16:: + + The string evaluation of the main "argument" no longer accepts an + open ended Python expression, instead only accepting a string + class name or dotted package-qualified name. + + .. seealso:: + + :ref:`declarative_configuring_relationships` - further detail + on relationship configuration when using Declarative. + + :param secondary: + For a many-to-many relationship, specifies the intermediary + table, and is typically an instance of :class:`_schema.Table`. + In less common circumstances, the argument may also be specified + as an :class:`_expression.Alias` construct, or even a + :class:`_expression.Join` construct. + + :paramref:`_orm.relationship.secondary` may + also be passed as a callable function which is evaluated at + mapper initialization time. When using Declarative, it may also + be a string argument noting the name of a :class:`_schema.Table` + that is + present in the :class:`_schema.MetaData` + collection associated with the + parent-mapped :class:`_schema.Table`. + + .. warning:: When passed as a Python-evaluable string, the + argument is interpreted using Python's ``eval()`` function. + **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. + See :ref:`declarative_relationship_eval` for details on + declarative evaluation of :func:`_orm.relationship` arguments. + + The :paramref:`_orm.relationship.secondary` keyword argument is + typically applied in the case where the intermediary + :class:`_schema.Table` + is not otherwise expressed in any direct class mapping. If the + "secondary" table is also explicitly mapped elsewhere (e.g. as in + :ref:`association_pattern`), one should consider applying the + :paramref:`_orm.relationship.viewonly` flag so that this + :func:`_orm.relationship` + is not used for persistence operations which + may conflict with those of the association object pattern. + + .. seealso:: + + :ref:`relationships_many_to_many` - Reference example of "many + to many". + + :ref:`orm_tutorial_many_to_many` - ORM tutorial introduction to + many-to-many relationships. + + :ref:`self_referential_many_to_many` - Specifics on using + many-to-many in a self-referential case. + + :ref:`declarative_many_to_many` - Additional options when using + Declarative. + + :ref:`association_pattern` - an alternative to + :paramref:`_orm.relationship.secondary` + when composing association + table relationships, allowing additional attributes to be + specified on the association table. + + :ref:`composite_secondary_join` - a lesser-used pattern which + in some cases can enable complex :func:`_orm.relationship` SQL + conditions to be used. + + .. versionadded:: 0.9.2 :paramref:`_orm.relationship.secondary` + works + more effectively when referring to a :class:`_expression.Join` + instance. + + :param active_history=False: + When ``True``, indicates that the "previous" value for a + many-to-one reference should be loaded when replaced, if + not already loaded. Normally, history tracking logic for + simple many-to-ones only needs to be aware of the "new" + value in order to perform a flush. This flag is available + for applications that make use of + :func:`.attributes.get_history` which also need to know + the "previous" value of the attribute. + + :param backref: + Indicates the string name of a property to be placed on the related + mapper's class that will handle this relationship in the other + direction. The other property will be created automatically + when the mappers are configured. Can also be passed as a + :func:`.backref` object to control the configuration of the + new relationship. + + .. seealso:: + + :ref:`relationships_backref` - Introductory documentation and + examples. + + :paramref:`_orm.relationship.back_populates` - alternative form + of backref specification. + + :func:`.backref` - allows control over :func:`_orm.relationship` + configuration when using :paramref:`_orm.relationship.backref`. + + + :param back_populates: + Takes a string name and has the same meaning as + :paramref:`_orm.relationship.backref`, except the complementing + property is **not** created automatically, and instead must be + configured explicitly on the other mapper. The complementing + property should also indicate + :paramref:`_orm.relationship.back_populates` to this relationship to + ensure proper functioning. + + .. seealso:: + + :ref:`relationships_backref` - Introductory documentation and + examples. + + :paramref:`_orm.relationship.backref` - alternative form + of backref specification. + + :param overlaps: + A string name or comma-delimited set of names of other relationships + on either this mapper, a descendant mapper, or a target mapper with + which this relationship may write to the same foreign keys upon + persistence. The only effect this has is to eliminate the + warning that this relationship will conflict with another upon + persistence. This is used for such relationships that are truly + capable of conflicting with each other on write, but the application + will ensure that no such conflicts occur. + + .. versionadded:: 1.4 + + .. seealso:: + + :ref:`error_qzyx` - usage example + + :param bake_queries=True: + Enable :ref:`lambda caching <engine_lambda_caching>` for loader + strategies, if applicable, which adds a performance gain to the + construction of SQL constructs used by loader strategies, in addition + to the usual SQL statement caching used throughout SQLAlchemy. This + parameter currently applies only to the "lazy" and "selectin" loader + strategies. There is generally no reason to set this parameter to + False. + + .. versionchanged:: 1.4 Relationship loaders no longer use the + previous "baked query" system of query caching. The "lazy" + and "selectin" loaders make use of the "lambda cache" system + for the construction of SQL constructs, + as well as the usual SQL caching system that is throughout + SQLAlchemy as of the 1.4 series. + + :param cascade: + A comma-separated list of cascade rules which determines how + Session operations should be "cascaded" from parent to child. + This defaults to ``False``, which means the default cascade + should be used - this default cascade is ``"save-update, merge"``. + + The available cascades are ``save-update``, ``merge``, + ``expunge``, ``delete``, ``delete-orphan``, and ``refresh-expire``. + An additional option, ``all`` indicates shorthand for + ``"save-update, merge, refresh-expire, + expunge, delete"``, and is often used as in ``"all, delete-orphan"`` + to indicate that related objects should follow along with the + parent object in all cases, and be deleted when de-associated. + + .. seealso:: + + :ref:`unitofwork_cascades` - Full detail on each of the available + cascade options. + + :ref:`tutorial_delete_cascade` - Tutorial example describing + a delete cascade. + + :param cascade_backrefs=False: + Legacy; this flag is always False. + + .. versionchanged:: 2.0 "cascade_backrefs" functionality has been + removed. + + :param collection_class: + A class or callable that returns a new list-holding object. will + be used in place of a plain list for storing elements. + + .. seealso:: + + :ref:`custom_collections` - Introductory documentation and + examples. + + :param comparator_factory: + A class which extends :class:`.RelationshipProperty.Comparator` + which provides custom SQL clause generation for comparison + operations. + + .. seealso:: + + :class:`.PropComparator` - some detail on redefining comparators + at this level. + + :ref:`custom_comparators` - Brief intro to this feature. + + + :param distinct_target_key=None: + Indicate if a "subquery" eager load should apply the DISTINCT + keyword to the innermost SELECT statement. When left as ``None``, + the DISTINCT keyword will be applied in those cases when the target + columns do not comprise the full primary key of the target table. + When set to ``True``, the DISTINCT keyword is applied to the + innermost SELECT unconditionally. + + It may be desirable to set this flag to False when the DISTINCT is + reducing performance of the innermost subquery beyond that of what + duplicate innermost rows may be causing. + + .. versionchanged:: 0.9.0 - + :paramref:`_orm.relationship.distinct_target_key` now defaults to + ``None``, so that the feature enables itself automatically for + those cases where the innermost query targets a non-unique + key. + + .. seealso:: + + :ref:`loading_toplevel` - includes an introduction to subquery + eager loading. + + :param doc: + Docstring which will be applied to the resulting descriptor. + + :param foreign_keys: + + A list of columns which are to be used as "foreign key" + columns, or columns which refer to the value in a remote + column, within the context of this :func:`_orm.relationship` + object's :paramref:`_orm.relationship.primaryjoin` condition. + That is, if the :paramref:`_orm.relationship.primaryjoin` + condition of this :func:`_orm.relationship` is ``a.id == + b.a_id``, and the values in ``b.a_id`` are required to be + present in ``a.id``, then the "foreign key" column of this + :func:`_orm.relationship` is ``b.a_id``. + + In normal cases, the :paramref:`_orm.relationship.foreign_keys` + parameter is **not required.** :func:`_orm.relationship` will + automatically determine which columns in the + :paramref:`_orm.relationship.primaryjoin` condition are to be + considered "foreign key" columns based on those + :class:`_schema.Column` objects that specify + :class:`_schema.ForeignKey`, + or are otherwise listed as referencing columns in a + :class:`_schema.ForeignKeyConstraint` construct. + :paramref:`_orm.relationship.foreign_keys` is only needed when: + + 1. There is more than one way to construct a join from the local + table to the remote table, as there are multiple foreign key + references present. Setting ``foreign_keys`` will limit the + :func:`_orm.relationship` + to consider just those columns specified + here as "foreign". + + 2. The :class:`_schema.Table` being mapped does not actually have + :class:`_schema.ForeignKey` or + :class:`_schema.ForeignKeyConstraint` + constructs present, often because the table + was reflected from a database that does not support foreign key + reflection (MySQL MyISAM). + + 3. The :paramref:`_orm.relationship.primaryjoin` + argument is used to + construct a non-standard join condition, which makes use of + columns or expressions that do not normally refer to their + "parent" column, such as a join condition expressed by a + complex comparison using a SQL function. + + The :func:`_orm.relationship` construct will raise informative + error messages that suggest the use of the + :paramref:`_orm.relationship.foreign_keys` parameter when + presented with an ambiguous condition. In typical cases, + if :func:`_orm.relationship` doesn't raise any exceptions, the + :paramref:`_orm.relationship.foreign_keys` parameter is usually + not needed. + + :paramref:`_orm.relationship.foreign_keys` may also be passed as a + callable function which is evaluated at mapper initialization time, + and may be passed as a Python-evaluable string when using + Declarative. + + .. warning:: When passed as a Python-evaluable string, the + argument is interpreted using Python's ``eval()`` function. + **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. + See :ref:`declarative_relationship_eval` for details on + declarative evaluation of :func:`_orm.relationship` arguments. + + .. seealso:: + + :ref:`relationship_foreign_keys` + + :ref:`relationship_custom_foreign` + + :func:`.foreign` - allows direct annotation of the "foreign" + columns within a :paramref:`_orm.relationship.primaryjoin` + condition. + + :param info: Optional data dictionary which will be populated into the + :attr:`.MapperProperty.info` attribute of this object. + + :param innerjoin=False: + When ``True``, joined eager loads will use an inner join to join + against related tables instead of an outer join. The purpose + of this option is generally one of performance, as inner joins + generally perform better than outer joins. + + This flag can be set to ``True`` when the relationship references an + object via many-to-one using local foreign keys that are not + nullable, or when the reference is one-to-one or a collection that + is guaranteed to have one or at least one entry. + + The option supports the same "nested" and "unnested" options as + that of :paramref:`_orm.joinedload.innerjoin`. See that flag + for details on nested / unnested behaviors. + + .. seealso:: + + :paramref:`_orm.joinedload.innerjoin` - the option as specified by + loader option, including detail on nesting behavior. + + :ref:`what_kind_of_loading` - Discussion of some details of + various loader options. + + + :param join_depth: + When non-``None``, an integer value indicating how many levels + deep "eager" loaders should join on a self-referring or cyclical + relationship. The number counts how many times the same Mapper + shall be present in the loading condition along a particular join + branch. When left at its default of ``None``, eager loaders + will stop chaining when they encounter a the same target mapper + which is already higher up in the chain. This option applies + both to joined- and subquery- eager loaders. + + .. seealso:: + + :ref:`self_referential_eager_loading` - Introductory documentation + and examples. + + :param lazy='select': specifies + How the related items should be loaded. Default value is + ``select``. Values include: + + * ``select`` - items should be loaded lazily when the property is + first accessed, using a separate SELECT statement, or identity map + fetch for simple many-to-one references. + + * ``immediate`` - items should be loaded as the parents are loaded, + using a separate SELECT statement, or identity map fetch for + simple many-to-one references. + + * ``joined`` - items should be loaded "eagerly" in the same query as + that of the parent, using a JOIN or LEFT OUTER JOIN. Whether + the join is "outer" or not is determined by the + :paramref:`_orm.relationship.innerjoin` parameter. + + * ``subquery`` - items should be loaded "eagerly" as the parents are + loaded, using one additional SQL statement, which issues a JOIN to + a subquery of the original statement, for each collection + requested. + + * ``selectin`` - items should be loaded "eagerly" as the parents + are loaded, using one or more additional SQL statements, which + issues a JOIN to the immediate parent object, specifying primary + key identifiers using an IN clause. + + .. versionadded:: 1.2 + + * ``noload`` - no loading should occur at any time. This is to + support "write-only" attributes, or attributes which are + populated in some manner specific to the application. + + * ``raise`` - lazy loading is disallowed; accessing + the attribute, if its value were not already loaded via eager + loading, will raise an :exc:`~sqlalchemy.exc.InvalidRequestError`. + This strategy can be used when objects are to be detached from + their attached :class:`.Session` after they are loaded. + + .. versionadded:: 1.1 + + * ``raise_on_sql`` - lazy loading that emits SQL is disallowed; + accessing the attribute, if its value were not already loaded via + eager loading, will raise an + :exc:`~sqlalchemy.exc.InvalidRequestError`, **if the lazy load + needs to emit SQL**. If the lazy load can pull the related value + from the identity map or determine that it should be None, the + value is loaded. This strategy can be used when objects will + remain associated with the attached :class:`.Session`, however + additional SELECT statements should be blocked. + + .. versionadded:: 1.1 + + * ``dynamic`` - the attribute will return a pre-configured + :class:`_query.Query` object for all read + operations, onto which further filtering operations can be + applied before iterating the results. See + the section :ref:`dynamic_relationship` for more details. + + * True - a synonym for 'select' + + * False - a synonym for 'joined' + + * None - a synonym for 'noload' + + .. seealso:: + + :doc:`/orm/loading_relationships` - Full documentation on + relationship loader configuration. + + :ref:`dynamic_relationship` - detail on the ``dynamic`` option. + + :ref:`collections_noload_raiseload` - notes on "noload" and "raise" + + :param load_on_pending=False: + Indicates loading behavior for transient or pending parent objects. + + When set to ``True``, causes the lazy-loader to + issue a query for a parent object that is not persistent, meaning it + has never been flushed. This may take effect for a pending object + when autoflush is disabled, or for a transient object that has been + "attached" to a :class:`.Session` but is not part of its pending + collection. + + The :paramref:`_orm.relationship.load_on_pending` + flag does not improve + behavior when the ORM is used normally - object references should be + constructed at the object level, not at the foreign key level, so + that they are present in an ordinary way before a flush proceeds. + This flag is not not intended for general use. + + .. seealso:: + + :meth:`.Session.enable_relationship_loading` - this method + establishes "load on pending" behavior for the whole object, and + also allows loading on objects that remain transient or + detached. + + :param order_by: + Indicates the ordering that should be applied when loading these + items. :paramref:`_orm.relationship.order_by` + is expected to refer to + one of the :class:`_schema.Column` + objects to which the target class is + mapped, or the attribute itself bound to the target class which + refers to the column. + + :paramref:`_orm.relationship.order_by` + may also be passed as a callable + function which is evaluated at mapper initialization time, and may + be passed as a Python-evaluable string when using Declarative. + + .. warning:: When passed as a Python-evaluable string, the + argument is interpreted using Python's ``eval()`` function. + **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. + See :ref:`declarative_relationship_eval` for details on + declarative evaluation of :func:`_orm.relationship` arguments. + + :param passive_deletes=False: + Indicates loading behavior during delete operations. + + A value of True indicates that unloaded child items should not + be loaded during a delete operation on the parent. Normally, + when a parent item is deleted, all child items are loaded so + that they can either be marked as deleted, or have their + foreign key to the parent set to NULL. Marking this flag as + True usually implies an ON DELETE <CASCADE|SET NULL> rule is in + place which will handle updating/deleting child rows on the + database side. + + Additionally, setting the flag to the string value 'all' will + disable the "nulling out" of the child foreign keys, when the parent + object is deleted and there is no delete or delete-orphan cascade + enabled. This is typically used when a triggering or error raise + scenario is in place on the database side. Note that the foreign + key attributes on in-session child objects will not be changed after + a flush occurs so this is a very special use-case setting. + Additionally, the "nulling out" will still occur if the child + object is de-associated with the parent. + + .. seealso:: + + :ref:`passive_deletes` - Introductory documentation + and examples. + + :param passive_updates=True: + Indicates the persistence behavior to take when a referenced + primary key value changes in place, indicating that the referencing + foreign key columns will also need their value changed. + + When True, it is assumed that ``ON UPDATE CASCADE`` is configured on + the foreign key in the database, and that the database will + handle propagation of an UPDATE from a source column to + dependent rows. When False, the SQLAlchemy + :func:`_orm.relationship` + construct will attempt to emit its own UPDATE statements to + modify related targets. However note that SQLAlchemy **cannot** + emit an UPDATE for more than one level of cascade. Also, + setting this flag to False is not compatible in the case where + the database is in fact enforcing referential integrity, unless + those constraints are explicitly "deferred", if the target backend + supports it. + + It is highly advised that an application which is employing + mutable primary keys keeps ``passive_updates`` set to True, + and instead uses the referential integrity features of the database + itself in order to handle the change efficiently and fully. + + .. seealso:: + + :ref:`passive_updates` - Introductory documentation and + examples. + + :paramref:`.mapper.passive_updates` - a similar flag which + takes effect for joined-table inheritance mappings. + + :param post_update: + This indicates that the relationship should be handled by a + second UPDATE statement after an INSERT or before a + DELETE. Currently, it also will issue an UPDATE after the + instance was UPDATEd as well, although this technically should + be improved. This flag is used to handle saving bi-directional + dependencies between two individual rows (i.e. each row + references the other), where it would otherwise be impossible to + INSERT or DELETE both rows fully since one row exists before the + other. Use this flag when a particular mapping arrangement will + incur two rows that are dependent on each other, such as a table + that has a one-to-many relationship to a set of child rows, and + also has a column that references a single child row within that + list (i.e. both tables contain a foreign key to each other). If + a flush operation returns an error that a "cyclical + dependency" was detected, this is a cue that you might want to + use :paramref:`_orm.relationship.post_update` to "break" the cycle. + + .. seealso:: + + :ref:`post_update` - Introductory documentation and examples. + + :param primaryjoin: + A SQL expression that will be used as the primary + join of the child object against the parent object, or in a + many-to-many relationship the join of the parent object to the + association table. By default, this value is computed based on the + foreign key relationships of the parent and child tables (or + association table). + + :paramref:`_orm.relationship.primaryjoin` may also be passed as a + callable function which is evaluated at mapper initialization time, + and may be passed as a Python-evaluable string when using + Declarative. + + .. warning:: When passed as a Python-evaluable string, the + argument is interpreted using Python's ``eval()`` function. + **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. + See :ref:`declarative_relationship_eval` for details on + declarative evaluation of :func:`_orm.relationship` arguments. + + .. seealso:: + + :ref:`relationship_primaryjoin` + + :param remote_side: + Used for self-referential relationships, indicates the column or + list of columns that form the "remote side" of the relationship. + + :paramref:`_orm.relationship.remote_side` may also be passed as a + callable function which is evaluated at mapper initialization time, + and may be passed as a Python-evaluable string when using + Declarative. + + .. warning:: When passed as a Python-evaluable string, the + argument is interpreted using Python's ``eval()`` function. + **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. + See :ref:`declarative_relationship_eval` for details on + declarative evaluation of :func:`_orm.relationship` arguments. + + .. seealso:: + + :ref:`self_referential` - in-depth explanation of how + :paramref:`_orm.relationship.remote_side` + is used to configure self-referential relationships. + + :func:`.remote` - an annotation function that accomplishes the + same purpose as :paramref:`_orm.relationship.remote_side`, + typically + when a custom :paramref:`_orm.relationship.primaryjoin` condition + is used. + + :param query_class: + A :class:`_query.Query` + subclass that will be used internally by the + ``AppenderQuery`` returned by a "dynamic" relationship, that + is, a relationship that specifies ``lazy="dynamic"`` or was + otherwise constructed using the :func:`_orm.dynamic_loader` + function. + + .. seealso:: + + :ref:`dynamic_relationship` - Introduction to "dynamic" + relationship loaders. + + :param secondaryjoin: + A SQL expression that will be used as the join of + an association table to the child object. By default, this value is + computed based on the foreign key relationships of the association + and child tables. + + :paramref:`_orm.relationship.secondaryjoin` may also be passed as a + callable function which is evaluated at mapper initialization time, + and may be passed as a Python-evaluable string when using + Declarative. + + .. warning:: When passed as a Python-evaluable string, the + argument is interpreted using Python's ``eval()`` function. + **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. + See :ref:`declarative_relationship_eval` for details on + declarative evaluation of :func:`_orm.relationship` arguments. + + .. seealso:: + + :ref:`relationship_primaryjoin` + + :param single_parent: + When True, installs a validator which will prevent objects + from being associated with more than one parent at a time. + This is used for many-to-one or many-to-many relationships that + should be treated either as one-to-one or one-to-many. Its usage + is optional, except for :func:`_orm.relationship` constructs which + are many-to-one or many-to-many and also + specify the ``delete-orphan`` cascade option. The + :func:`_orm.relationship` construct itself will raise an error + instructing when this option is required. + + .. seealso:: + + :ref:`unitofwork_cascades` - includes detail on when the + :paramref:`_orm.relationship.single_parent` + flag may be appropriate. + + :param uselist: + A boolean that indicates if this property should be loaded as a + list or a scalar. In most cases, this value is determined + automatically by :func:`_orm.relationship` at mapper configuration + time, based on the type and direction + of the relationship - one to many forms a list, many to one + forms a scalar, many to many is a list. If a scalar is desired + where normally a list would be present, such as a bi-directional + one-to-one relationship, set :paramref:`_orm.relationship.uselist` + to + False. + + The :paramref:`_orm.relationship.uselist` + flag is also available on an + existing :func:`_orm.relationship` + construct as a read-only attribute, + which can be used to determine if this :func:`_orm.relationship` + deals + with collections or scalar attributes:: + + >>> User.addresses.property.uselist + True + + .. seealso:: + + :ref:`relationships_one_to_one` - Introduction to the "one to + one" relationship pattern, which is typically when the + :paramref:`_orm.relationship.uselist` flag is needed. + + :param viewonly=False: + When set to ``True``, the relationship is used only for loading + objects, and not for any persistence operation. A + :func:`_orm.relationship` which specifies + :paramref:`_orm.relationship.viewonly` can work + with a wider range of SQL operations within the + :paramref:`_orm.relationship.primaryjoin` condition, including + operations that feature the use of a variety of comparison operators + as well as SQL functions such as :func:`_expression.cast`. The + :paramref:`_orm.relationship.viewonly` + flag is also of general use when defining any kind of + :func:`_orm.relationship` that doesn't represent + the full set of related objects, to prevent modifications of the + collection from resulting in persistence operations. + + When using the :paramref:`_orm.relationship.viewonly` flag in + conjunction with backrefs, the originating relationship for a + particular state change will not produce state changes within the + viewonly relationship. This is the behavior implied by + :paramref:`_orm.relationship.sync_backref` being set to False. + + .. versionchanged:: 1.3.17 - the + :paramref:`_orm.relationship.sync_backref` flag is set to False + when using viewonly in conjunction with backrefs. + + .. seealso:: + + :paramref:`_orm.relationship.sync_backref` + + :param sync_backref: + A boolean that enables the events used to synchronize the in-Python + attributes when this relationship is target of either + :paramref:`_orm.relationship.backref` or + :paramref:`_orm.relationship.back_populates`. + + Defaults to ``None``, which indicates that an automatic value should + be selected based on the value of the + :paramref:`_orm.relationship.viewonly` flag. When left at its + default, changes in state will be back-populated only if neither + sides of a relationship is viewonly. + + .. versionadded:: 1.3.17 + + .. versionchanged:: 1.4 - A relationship that specifies + :paramref:`_orm.relationship.viewonly` automatically implies + that :paramref:`_orm.relationship.sync_backref` is ``False``. + + .. seealso:: + + :paramref:`_orm.relationship.viewonly` + + :param omit_join: + Allows manual control over the "selectin" automatic join + optimization. Set to ``False`` to disable the "omit join" feature + added in SQLAlchemy 1.3; or leave as ``None`` to leave automatic + optimization in place. + + .. note:: This flag may only be set to ``False``. It is not + necessary to set it to ``True`` as the "omit_join" optimization is + automatically detected; if it is not detected, then the + optimization is not supported. + + .. versionchanged:: 1.3.11 setting ``omit_join`` to True will now + emit a warning as this was not the intended use of this flag. + + .. versionadded:: 1.3 + + + """ + return RelationshipProperty( + argument, + secondary, + primaryjoin, + secondaryjoin, + foreign_keys, + uselist, + order_by, + backref, + back_populates, + overlaps, + post_update, + cascade, + viewonly, + lazy, + collection_class, + passive_deletes, + passive_updates, + remote_side, + enable_typechecks, + join_depth, + comparator_factory, + single_parent, + innerjoin, + distinct_target_key, + doc, + active_history, + cascade_backrefs, + load_on_pending, + bake_queries, + _local_remote_pairs, + query_class, + info, + omit_join, + sync_backref, + _legacy_inactive_history_style, + ) + + +def synonym( + name, + map_column=None, + descriptor=None, + comparator_factory=None, + doc=None, + info=None, +) -> "SynonymProperty": + """Denote an attribute name as a synonym to a mapped property, + in that the attribute will mirror the value and expression behavior + of another attribute. + + e.g.:: + + class MyClass(Base): + __tablename__ = 'my_table' + + id = Column(Integer, primary_key=True) + job_status = Column(String(50)) + + status = synonym("job_status") + + + :param name: the name of the existing mapped property. This + can refer to the string name ORM-mapped attribute + configured on the class, including column-bound attributes + and relationships. + + :param descriptor: a Python :term:`descriptor` that will be used + as a getter (and potentially a setter) when this attribute is + accessed at the instance level. + + :param map_column: **For classical mappings and mappings against + an existing Table object only**. if ``True``, the :func:`.synonym` + construct will locate the :class:`_schema.Column` + object upon the mapped + table that would normally be associated with the attribute name of + this synonym, and produce a new :class:`.ColumnProperty` that instead + maps this :class:`_schema.Column` + to the alternate name given as the "name" + argument of the synonym; in this way, the usual step of redefining + the mapping of the :class:`_schema.Column` + to be under a different name is + unnecessary. This is usually intended to be used when a + :class:`_schema.Column` + is to be replaced with an attribute that also uses a + descriptor, that is, in conjunction with the + :paramref:`.synonym.descriptor` parameter:: + + my_table = Table( + "my_table", metadata, + Column('id', Integer, primary_key=True), + Column('job_status', String(50)) + ) + + class MyClass: + @property + def _job_status_descriptor(self): + return "Status: %s" % self._job_status + + + mapper( + MyClass, my_table, properties={ + "job_status": synonym( + "_job_status", map_column=True, + descriptor=MyClass._job_status_descriptor) + } + ) + + Above, the attribute named ``_job_status`` is automatically + mapped to the ``job_status`` column:: + + >>> j1 = MyClass() + >>> j1._job_status = "employed" + >>> j1.job_status + Status: employed + + When using Declarative, in order to provide a descriptor in + conjunction with a synonym, use the + :func:`sqlalchemy.ext.declarative.synonym_for` helper. However, + note that the :ref:`hybrid properties <mapper_hybrids>` feature + should usually be preferred, particularly when redefining attribute + behavior. + + :param info: Optional data dictionary which will be populated into the + :attr:`.InspectionAttr.info` attribute of this object. + + .. versionadded:: 1.0.0 + + :param comparator_factory: A subclass of :class:`.PropComparator` + that will provide custom comparison behavior at the SQL expression + level. + + .. note:: + + For the use case of providing an attribute which redefines both + Python-level and SQL-expression level behavior of an attribute, + please refer to the Hybrid attribute introduced at + :ref:`mapper_hybrids` for a more effective technique. + + .. seealso:: + + :ref:`synonyms` - Overview of synonyms + + :func:`.synonym_for` - a helper oriented towards Declarative + + :ref:`mapper_hybrids` - The Hybrid Attribute extension provides an + updated approach to augmenting attribute behavior more flexibly + than can be achieved with synonyms. + + """ + return SynonymProperty( + name, map_column, descriptor, comparator_factory, doc, info + ) + + +def create_session(bind=None, **kwargs): + r"""Create a new :class:`.Session` + with no automation enabled by default. + + This function is used primarily for testing. The usual + route to :class:`.Session` creation is via its constructor + or the :func:`.sessionmaker` function. + + :param bind: optional, a single Connectable to use for all + database access in the created + :class:`~sqlalchemy.orm.session.Session`. + + :param \*\*kwargs: optional, passed through to the + :class:`.Session` constructor. + + :returns: an :class:`~sqlalchemy.orm.session.Session` instance + + The defaults of create_session() are the opposite of that of + :func:`sessionmaker`; ``autoflush`` and ``expire_on_commit`` are + False. + + Usage:: + + >>> from sqlalchemy.orm import create_session + >>> session = create_session() + + It is recommended to use :func:`sessionmaker` instead of + create_session(). + + """ + + kwargs.setdefault("autoflush", False) + kwargs.setdefault("expire_on_commit", False) + return Session(bind=bind, **kwargs) + + +def _mapper_fn(*arg, **kw): + """Placeholder for the now-removed ``mapper()`` function. + + Classical mappings should be performed using the + :meth:`_orm.registry.map_imperatively` method. + + This symbol remains in SQLAlchemy 2.0 to suit the deprecated use case + of using the ``mapper()`` function as a target for ORM event listeners, + which failed to be marked as deprecated in the 1.4 series. + + Global ORM mapper listeners should instead use the :class:`_orm.Mapper` + class as the target. + + .. versionchanged:: 2.0 The ``mapper()`` function was removed; the + symbol remains temporarily as a placeholder for the event listening + use case. + + """ + raise InvalidRequestError( + "The 'sqlalchemy.orm.mapper()' function is removed as of " + "SQLAlchemy 2.0. Use the " + "'sqlalchemy.orm.registry.map_imperatively()` " + "method of the ``sqlalchemy.orm.registry`` class to perform " + "classical mapping." + ) + + +def dynamic_loader(argument, **kw): + """Construct a dynamically-loading mapper property. + + This is essentially the same as + using the ``lazy='dynamic'`` argument with :func:`relationship`:: + + dynamic_loader(SomeClass) + + # is the same as + + relationship(SomeClass, lazy="dynamic") + + See the section :ref:`dynamic_relationship` for more details + on dynamic loading. + + """ + kw["lazy"] = "dynamic" + return relationship(argument, **kw) + + +def backref(name, **kwargs): + """Create a back reference with explicit keyword arguments, which are the + same arguments one can send to :func:`relationship`. + + Used with the ``backref`` keyword argument to :func:`relationship` in + place of a string argument, e.g.:: + + 'items':relationship( + SomeItem, backref=backref('parent', lazy='subquery')) + + .. seealso:: + + :ref:`relationships_backref` + + """ + + return (name, kwargs) + + +def deferred(*columns, **kw): + r"""Indicate a column-based mapped attribute that by default will + not load unless accessed. + + :param \*columns: columns to be mapped. This is typically a single + :class:`_schema.Column` object, + however a collection is supported in order + to support multiple columns mapped under the same attribute. + + :param raiseload: boolean, if True, indicates an exception should be raised + if the load operation is to take place. + + .. versionadded:: 1.4 + + .. seealso:: + + :ref:`deferred_raiseload` + + :param \**kw: additional keyword arguments passed to + :class:`.ColumnProperty`. + + .. seealso:: + + :ref:`deferred` + + """ + return ColumnProperty(deferred=True, *columns, **kw) + + +def query_expression(default_expr=sql.null()): + """Indicate an attribute that populates from a query-time SQL expression. + + :param default_expr: Optional SQL expression object that will be used in + all cases if not assigned later with :func:`_orm.with_expression`. + E.g.:: + + from sqlalchemy.sql import literal + + class C(Base): + #... + my_expr = query_expression(literal(1)) + + .. versionadded:: 1.3.18 + + + .. versionadded:: 1.2 + + .. seealso:: + + :ref:`mapper_querytime_expression` + + """ + prop = ColumnProperty(default_expr) + prop.strategy_key = (("query_expression", True),) + return prop + + +def clear_mappers(): + """Remove all mappers from all classes. + + .. versionchanged:: 1.4 This function now locates all + :class:`_orm.registry` objects and calls upon the + :meth:`_orm.registry.dispose` method of each. + + This function removes all instrumentation from classes and disposes + of their associated mappers. Once called, the classes are unmapped + and can be later re-mapped with new mappers. + + :func:`.clear_mappers` is *not* for normal use, as there is literally no + valid usage for it outside of very specific testing scenarios. Normally, + mappers are permanent structural components of user-defined classes, and + are never discarded independently of their class. If a mapped class + itself is garbage collected, its mapper is automatically disposed of as + well. As such, :func:`.clear_mappers` is only for usage in test suites + that re-use the same classes with different mappings, which is itself an + extremely rare use case - the only such use case is in fact SQLAlchemy's + own test suite, and possibly the test suites of other ORM extension + libraries which intend to test various combinations of mapper construction + upon a fixed set of classes. + + """ + + mapperlib._dispose_registries(mapperlib._all_registries(), False) diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py index 10902e970..93e2d609a 100644 --- a/lib/sqlalchemy/orm/base.py +++ b/lib/sqlalchemy/orm/base.py @@ -11,6 +11,9 @@ import operator import typing +from typing import Any +from typing import Generic +from typing import TypeVar from . import exc from .. import exc as sa_exc @@ -19,6 +22,8 @@ from .. import util from ..util import typing as compat_typing +_T = TypeVar("_T", bound=Any) + PASSIVE_NO_RESULT = util.symbol( "PASSIVE_NO_RESULT", """Symbol returned by a loader callable or other attribute/history @@ -574,7 +579,7 @@ class InspectionAttrInfo(InspectionAttr): return {} -class _MappedAttribute: +class _MappedAttribute(Generic[_T]): """Mixin for attributes which should be replaced by mapper-assigned attributes. diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py index cba7cf07d..e8ac892ce 100644 --- a/lib/sqlalchemy/orm/context.py +++ b/lib/sqlalchemy/orm/context.py @@ -34,6 +34,7 @@ from ..sql.base import Options from ..sql.selectable import LABEL_STYLE_DISAMBIGUATE_ONLY from ..sql.selectable import LABEL_STYLE_NONE from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL +from ..sql.selectable import Select from ..sql.selectable import SelectState from ..sql.visitors import InternalTraversal @@ -1132,7 +1133,6 @@ class ORMSelectCompileState(ORMCompileState, SelectState): group_by, ): - Select = future.Select statement = Select._create_raw_select( _raw_columns=raw_columns, _from_obj=from_obj, diff --git a/lib/sqlalchemy/orm/decl_api.py b/lib/sqlalchemy/orm/decl_api.py index 2a5b1bb2b..99b2e9b6f 100644 --- a/lib/sqlalchemy/orm/decl_api.py +++ b/lib/sqlalchemy/orm/decl_api.py @@ -14,7 +14,7 @@ from . import clsregistry from . import exc as orm_exc from . import instrumentation from . import interfaces -from . import mapper as mapperlib +from . import mapperlib from .base import _inspect_mapped_class from .decl_base import _add_attribute from .decl_base import _as_declarative diff --git a/lib/sqlalchemy/orm/decl_base.py b/lib/sqlalchemy/orm/decl_base.py index 4316271f0..fb736806c 100644 --- a/lib/sqlalchemy/orm/decl_base.py +++ b/lib/sqlalchemy/orm/decl_base.py @@ -12,7 +12,7 @@ from sqlalchemy.orm import attributes from sqlalchemy.orm import instrumentation from . import clsregistry from . import exc as orm_exc -from . import mapper as mapperlib +from . import mapperlib from .attributes import InstrumentedAttribute from .attributes import QueryableAttribute from .base import _is_mapped_class diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index d5083dcf1..80fce86d0 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -10,6 +10,9 @@ that exist as configurational elements, but don't participate as actively in the load/persist ORM loop. """ +from typing import Any +from typing import Type +from typing import TypeVar from . import attributes from . import util as orm_util @@ -24,8 +27,10 @@ from .. import util from ..sql import expression from ..sql import operators +_T = TypeVar("_T", bound=Any) -class DescriptorProperty(MapperProperty): + +class DescriptorProperty(MapperProperty[_T]): """:class:`.MapperProperty` which proxies access to a user-defined descriptor.""" @@ -86,7 +91,7 @@ class DescriptorProperty(MapperProperty): mapper.class_manager.instrument_attribute(self.key, proxy_attr) -class CompositeProperty(DescriptorProperty): +class CompositeProperty(DescriptorProperty[_T]): """Defines a "composite" mapped attribute, representing a collection of columns as one attribute. @@ -99,49 +104,7 @@ class CompositeProperty(DescriptorProperty): """ - def __init__(self, class_, *attrs, **kwargs): - r"""Return a composite column-based property for use with a Mapper. - - See the mapping documentation section :ref:`mapper_composite` for a - full usage example. - - The :class:`.MapperProperty` returned by :func:`.composite` - is the :class:`.CompositeProperty`. - - :param class\_: - The "composite type" class, or any classmethod or callable which - will produce a new instance of the composite object given the - column values in order. - - :param \*cols: - List of Column objects to be mapped. - - :param active_history=False: - When ``True``, indicates that the "previous" value for a - scalar attribute should be loaded when replaced, if not - already loaded. See the same flag on :func:`.column_property`. - - :param group: - A group name for this property when marked as deferred. - - :param deferred: - When True, the column property is "deferred", meaning that it does - not load immediately, and is instead loaded when the attribute is - first accessed on an instance. See also - :func:`~sqlalchemy.orm.deferred`. - - :param comparator_factory: a class which extends - :class:`.CompositeProperty.Comparator` which provides custom SQL - clause generation for comparison operations. - - :param doc: - optional string that will be applied as the doc on the - class-bound descriptor. - - :param info: Optional data dictionary which will be populated into the - :attr:`.MapperProperty.info` attribute of this object. - - """ + def __init__(self, class_: Type[_T], *attrs, **kwargs): super(CompositeProperty, self).__init__() self.attrs = attrs @@ -548,109 +511,6 @@ class SynonymProperty(DescriptorProperty): doc=None, info=None, ): - """Denote an attribute name as a synonym to a mapped property, - in that the attribute will mirror the value and expression behavior - of another attribute. - - e.g.:: - - class MyClass(Base): - __tablename__ = 'my_table' - - id = Column(Integer, primary_key=True) - job_status = Column(String(50)) - - status = synonym("job_status") - - - :param name: the name of the existing mapped property. This - can refer to the string name ORM-mapped attribute - configured on the class, including column-bound attributes - and relationships. - - :param descriptor: a Python :term:`descriptor` that will be used - as a getter (and potentially a setter) when this attribute is - accessed at the instance level. - - :param map_column: **For classical mappings and mappings against - an existing Table object only**. if ``True``, the :func:`.synonym` - construct will locate the :class:`_schema.Column` - object upon the mapped - table that would normally be associated with the attribute name of - this synonym, and produce a new :class:`.ColumnProperty` that instead - maps this :class:`_schema.Column` - to the alternate name given as the "name" - argument of the synonym; in this way, the usual step of redefining - the mapping of the :class:`_schema.Column` - to be under a different name is - unnecessary. This is usually intended to be used when a - :class:`_schema.Column` - is to be replaced with an attribute that also uses a - descriptor, that is, in conjunction with the - :paramref:`.synonym.descriptor` parameter:: - - my_table = Table( - "my_table", metadata, - Column('id', Integer, primary_key=True), - Column('job_status', String(50)) - ) - - class MyClass: - @property - def _job_status_descriptor(self): - return "Status: %s" % self._job_status - - - mapper( - MyClass, my_table, properties={ - "job_status": synonym( - "_job_status", map_column=True, - descriptor=MyClass._job_status_descriptor) - } - ) - - Above, the attribute named ``_job_status`` is automatically - mapped to the ``job_status`` column:: - - >>> j1 = MyClass() - >>> j1._job_status = "employed" - >>> j1.job_status - Status: employed - - When using Declarative, in order to provide a descriptor in - conjunction with a synonym, use the - :func:`sqlalchemy.ext.declarative.synonym_for` helper. However, - note that the :ref:`hybrid properties <mapper_hybrids>` feature - should usually be preferred, particularly when redefining attribute - behavior. - - :param info: Optional data dictionary which will be populated into the - :attr:`.InspectionAttr.info` attribute of this object. - - .. versionadded:: 1.0.0 - - :param comparator_factory: A subclass of :class:`.PropComparator` - that will provide custom comparison behavior at the SQL expression - level. - - .. note:: - - For the use case of providing an attribute which redefines both - Python-level and SQL-expression level behavior of an attribute, - please refer to the Hybrid attribute introduced at - :ref:`mapper_hybrids` for a more effective technique. - - .. seealso:: - - :ref:`synonyms` - Overview of synonyms - - :func:`.synonym_for` - a helper oriented towards Declarative - - :ref:`mapper_hybrids` - The Hybrid Attribute extension provides an - updated approach to augmenting attribute behavior more flexibly - than can be achieved with synonyms. - - """ super(SynonymProperty, self).__init__() self.name = name diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index 7b2be9f05..ade47480d 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -15,12 +15,12 @@ basic add/delete mutation. from . import attributes from . import exc as orm_exc from . import interfaces -from . import object_mapper -from . import object_session from . import relationships from . import strategies from . import util as orm_util +from .base import object_mapper from .query import Query +from .session import object_session from .. import exc from .. import log from .. import util diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index d842df221..df265db57 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -17,6 +17,8 @@ are exposed when inspecting mappings. """ import collections +from typing import Any +from typing import TypeVar from . import exc as orm_exc from . import path_registry @@ -39,6 +41,7 @@ from ..sql import visitors from ..sql.base import ExecutableOption from ..sql.cache_key import HasCacheKey +_T = TypeVar("_T", bound=Any) __all__ = ( "EXT_CONTINUE", @@ -77,7 +80,7 @@ class ORMFromClauseRole(roles.StrictFromClauseRole): @inspection._self_inspects class MapperProperty( - HasCacheKey, _MappedAttribute, InspectionAttr, util.MemoizedSlots + HasCacheKey, _MappedAttribute[_T], InspectionAttr, util.MemoizedSlots ): """Represent a particular class attribute mapped by :class:`_orm.Mapper`. diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index dbcbc8a1b..87829d643 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -14,11 +14,13 @@ This is a semi-private module; the main configurational API of the ORM is available in :class:`~sqlalchemy.orm.`. """ - from collections import deque from functools import reduce from itertools import chain import sys +from typing import Generic +from typing import Type +from typing import TypeVar import weakref from . import attributes @@ -58,6 +60,9 @@ from ..util import HasMemoized _mapper_registries = weakref.WeakKeyDictionary() +_MC = TypeVar("_MC") + + def _all_registries(): with _CONFIGURE_MUTEX: return set(_mapper_registries) @@ -88,6 +93,7 @@ class Mapper( ORMEntityColumnsClauseRole, sql_base.MemoizedHasCacheKey, InspectionAttr, + Generic[_MC], ): """Defines an association between a Python class and a database table or other relational structure, so that ORM operations against the class may @@ -115,7 +121,7 @@ class Mapper( ) def __init__( self, - class_, + class_: Type[_MC], local_table=None, properties=None, primary_key=None, diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 1fd5dbaf8..8ee26315e 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -12,6 +12,10 @@ mapped attributes. """ +from typing import Any +from typing import Generic +from typing import TypeVar + from . import attributes from . import strategy_options from .descriptor_props import CompositeProperty @@ -22,10 +26,12 @@ from .interfaces import StrategizedProperty from .relationships import RelationshipProperty from .util import _orm_full_deannotate from .. import log +from .. import sql from .. import util from ..sql import coercions from ..sql import roles +_T = TypeVar("_T", bound=Any) __all__ = [ "ColumnProperty", @@ -37,7 +43,7 @@ __all__ = [ @log.class_logger -class ColumnProperty(StrategizedProperty): +class ColumnProperty(StrategizedProperty, Generic[_T]): """Describes an object attribute that corresponds to a table column. Public constructor is the :func:`_orm.column_property` function. @@ -70,91 +76,11 @@ class ColumnProperty(StrategizedProperty): "raiseload", ) - def __init__(self, *columns, **kwargs): - r"""Provide a column-level property for use with a mapping. - - Column-based properties can normally be applied to the mapper's - ``properties`` dictionary using the :class:`_schema.Column` - element directly. - Use this function when the given column is not directly present within - the mapper's selectable; examples include SQL expressions, functions, - and scalar SELECT queries. - - The :func:`_orm.column_property` function returns an instance of - :class:`.ColumnProperty`. - - Columns that aren't present in the mapper's selectable won't be - persisted by the mapper and are effectively "read-only" attributes. - - :param \*cols: - list of Column objects to be mapped. - - :param active_history=False: - When ``True``, indicates that the "previous" value for a - scalar attribute should be loaded when replaced, if not - already loaded. Normally, history tracking logic for - simple non-primary-key scalar values only needs to be - aware of the "new" value in order to perform a flush. This - flag is available for applications that make use of - :func:`.attributes.get_history` or :meth:`.Session.is_modified` - which also need to know - the "previous" value of the attribute. - - :param comparator_factory: a class which extends - :class:`.ColumnProperty.Comparator` which provides custom SQL - clause generation for comparison operations. - - :param group: - a group name for this property when marked as deferred. - - :param deferred: - when True, the column property is "deferred", meaning that - it does not load immediately, and is instead loaded when the - attribute is first accessed on an instance. See also - :func:`~sqlalchemy.orm.deferred`. - - :param doc: - optional string that will be applied as the doc on the - class-bound descriptor. - - :param expire_on_flush=True: - Disable expiry on flush. A column_property() which refers - to a SQL expression (and not a single table-bound column) - is considered to be a "read only" property; populating it - has no effect on the state of data, and it can only return - database state. For this reason a column_property()'s value - is expired whenever the parent object is involved in a - flush, that is, has any kind of "dirty" state within a flush. - Setting this parameter to ``False`` will have the effect of - leaving any existing value present after the flush proceeds. - Note however that the :class:`.Session` with default expiration - settings still expires - all attributes after a :meth:`.Session.commit` call, however. - - :param info: Optional data dictionary which will be populated into the - :attr:`.MapperProperty.info` attribute of this object. - - :param raiseload: if True, indicates the column should raise an error - when undeferred, rather than loading the value. This can be - altered at query time by using the :func:`.deferred` option with - raiseload=False. - - .. versionadded:: 1.4 - - .. seealso:: - - :ref:`deferred_raiseload` - - .. seealso:: - - :ref:`column_property_options` - to map columns while including - mapping options - - :ref:`mapper_column_property_sql_expressions` - to map SQL - expressions - - """ + def __init__( + self, column: sql.ColumnElement[_T], *additional_columns, **kwargs + ): super(ColumnProperty, self).__init__() + columns = (column,) + additional_columns self._orig_columns = [ coercions.expect(roles.LabeledColumnExprRole, c) for c in columns ] diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index e6d16f178..15259f130 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -2809,6 +2809,8 @@ class FromStatement(GroupedElement, SelectBase, Executable): class AliasOption(interfaces.LoaderOption): + inherit_cache = False + @util.deprecated( "1.4", "The :class:`.AliasOption` is not necessary " @@ -2822,8 +2824,6 @@ class AliasOption(interfaces.LoaderOption): """ - inherit_cache = False - def process_compile_state(self, compile_state): pass diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index fb4723e29..330d45430 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -15,6 +15,11 @@ and `secondaryjoin` aspects of :func:`_orm.relationship`. """ import collections import re +from typing import Callable +from typing import Generic +from typing import Type +from typing import TypeVar +from typing import Union import weakref from . import attributes @@ -48,6 +53,8 @@ from ..sql.util import join_condition from ..sql.util import selectables_overlap from ..sql.util import visit_binary_product +_RC = TypeVar("_RC") + def remote(expr): """Annotate a portion of a primaryjoin expression @@ -89,7 +96,7 @@ def foreign(expr): @log.class_logger -class RelationshipProperty(StrategizedProperty): +class RelationshipProperty(StrategizedProperty, Generic[_RC]): """Describes an object property that holds a single item or list of items that correspond to a related database table. @@ -118,7 +125,7 @@ class RelationshipProperty(StrategizedProperty): def __init__( self, - argument, + argument: Union[str, Type[_RC], Callable[[], Type[_RC]]], secondary=None, primaryjoin=None, secondaryjoin=None, @@ -154,821 +161,6 @@ class RelationshipProperty(StrategizedProperty): sync_backref=None, _legacy_inactive_history_style=False, ): - """Provide a relationship between two mapped classes. - - This corresponds to a parent-child or associative table relationship. - The constructed class is an instance of - :class:`.RelationshipProperty`. - - A typical :func:`_orm.relationship`, used in a classical mapping:: - - mapper(Parent, properties={ - 'children': relationship(Child) - }) - - Some arguments accepted by :func:`_orm.relationship` - optionally accept a - callable function, which when called produces the desired value. - The callable is invoked by the parent :class:`_orm.Mapper` at "mapper - initialization" time, which happens only when mappers are first used, - and is assumed to be after all mappings have been constructed. This - can be used to resolve order-of-declaration and other dependency - issues, such as if ``Child`` is declared below ``Parent`` in the same - file:: - - mapper(Parent, properties={ - "children":relationship(lambda: Child, - order_by=lambda: Child.id) - }) - - When using the :ref:`declarative_toplevel` extension, the Declarative - initializer allows string arguments to be passed to - :func:`_orm.relationship`. These string arguments are converted into - callables that evaluate the string as Python code, using the - Declarative class-registry as a namespace. This allows the lookup of - related classes to be automatic via their string name, and removes the - need for related classes to be imported into the local module space - before the dependent classes have been declared. It is still required - that the modules in which these related classes appear are imported - anywhere in the application at some point before the related mappings - are actually used, else a lookup error will be raised when the - :func:`_orm.relationship` - attempts to resolve the string reference to the - related class. An example of a string- resolved class is as - follows:: - - from sqlalchemy.ext.declarative import declarative_base - - Base = declarative_base() - - class Parent(Base): - __tablename__ = 'parent' - id = Column(Integer, primary_key=True) - children = relationship("Child", order_by="Child.id") - - .. seealso:: - - :ref:`relationship_config_toplevel` - Full introductory and - reference documentation for :func:`_orm.relationship`. - - :ref:`orm_tutorial_relationship` - ORM tutorial introduction. - - :param argument: - A mapped class, or actual :class:`_orm.Mapper` instance, - representing - the target of the relationship. - - :paramref:`_orm.relationship.argument` - may also be passed as a callable - function which is evaluated at mapper initialization time, and may - be passed as a string name when using Declarative. - - .. warning:: Prior to SQLAlchemy 1.3.16, this value is interpreted - using Python's ``eval()`` function. - **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. - See :ref:`declarative_relationship_eval` for details on - declarative evaluation of :func:`_orm.relationship` arguments. - - .. versionchanged 1.3.16:: - - The string evaluation of the main "argument" no longer accepts an - open ended Python expression, instead only accepting a string - class name or dotted package-qualified name. - - .. seealso:: - - :ref:`declarative_configuring_relationships` - further detail - on relationship configuration when using Declarative. - - :param secondary: - For a many-to-many relationship, specifies the intermediary - table, and is typically an instance of :class:`_schema.Table`. - In less common circumstances, the argument may also be specified - as an :class:`_expression.Alias` construct, or even a - :class:`_expression.Join` construct. - - :paramref:`_orm.relationship.secondary` may - also be passed as a callable function which is evaluated at - mapper initialization time. When using Declarative, it may also - be a string argument noting the name of a :class:`_schema.Table` - that is - present in the :class:`_schema.MetaData` - collection associated with the - parent-mapped :class:`_schema.Table`. - - .. warning:: When passed as a Python-evaluable string, the - argument is interpreted using Python's ``eval()`` function. - **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. - See :ref:`declarative_relationship_eval` for details on - declarative evaluation of :func:`_orm.relationship` arguments. - - The :paramref:`_orm.relationship.secondary` keyword argument is - typically applied in the case where the intermediary - :class:`_schema.Table` - is not otherwise expressed in any direct class mapping. If the - "secondary" table is also explicitly mapped elsewhere (e.g. as in - :ref:`association_pattern`), one should consider applying the - :paramref:`_orm.relationship.viewonly` flag so that this - :func:`_orm.relationship` - is not used for persistence operations which - may conflict with those of the association object pattern. - - .. seealso:: - - :ref:`relationships_many_to_many` - Reference example of "many - to many". - - :ref:`orm_tutorial_many_to_many` - ORM tutorial introduction to - many-to-many relationships. - - :ref:`self_referential_many_to_many` - Specifics on using - many-to-many in a self-referential case. - - :ref:`declarative_many_to_many` - Additional options when using - Declarative. - - :ref:`association_pattern` - an alternative to - :paramref:`_orm.relationship.secondary` - when composing association - table relationships, allowing additional attributes to be - specified on the association table. - - :ref:`composite_secondary_join` - a lesser-used pattern which - in some cases can enable complex :func:`_orm.relationship` SQL - conditions to be used. - - .. versionadded:: 0.9.2 :paramref:`_orm.relationship.secondary` - works - more effectively when referring to a :class:`_expression.Join` - instance. - - :param active_history=False: - When ``True``, indicates that the "previous" value for a - many-to-one reference should be loaded when replaced, if - not already loaded. Normally, history tracking logic for - simple many-to-ones only needs to be aware of the "new" - value in order to perform a flush. This flag is available - for applications that make use of - :func:`.attributes.get_history` which also need to know - the "previous" value of the attribute. - - :param backref: - Indicates the string name of a property to be placed on the related - mapper's class that will handle this relationship in the other - direction. The other property will be created automatically - when the mappers are configured. Can also be passed as a - :func:`.backref` object to control the configuration of the - new relationship. - - .. seealso:: - - :ref:`relationships_backref` - Introductory documentation and - examples. - - :paramref:`_orm.relationship.back_populates` - alternative form - of backref specification. - - :func:`.backref` - allows control over :func:`_orm.relationship` - configuration when using :paramref:`_orm.relationship.backref`. - - - :param back_populates: - Takes a string name and has the same meaning as - :paramref:`_orm.relationship.backref`, except the complementing - property is **not** created automatically, and instead must be - configured explicitly on the other mapper. The complementing - property should also indicate - :paramref:`_orm.relationship.back_populates` to this relationship to - ensure proper functioning. - - .. seealso:: - - :ref:`relationships_backref` - Introductory documentation and - examples. - - :paramref:`_orm.relationship.backref` - alternative form - of backref specification. - - :param overlaps: - A string name or comma-delimited set of names of other relationships - on either this mapper, a descendant mapper, or a target mapper with - which this relationship may write to the same foreign keys upon - persistence. The only effect this has is to eliminate the - warning that this relationship will conflict with another upon - persistence. This is used for such relationships that are truly - capable of conflicting with each other on write, but the application - will ensure that no such conflicts occur. - - .. versionadded:: 1.4 - - .. seealso:: - - :ref:`error_qzyx` - usage example - - :param bake_queries=True: - Enable :ref:`lambda caching <engine_lambda_caching>` for loader - strategies, if applicable, which adds a performance gain to the - construction of SQL constructs used by loader strategies, in addition - to the usual SQL statement caching used throughout SQLAlchemy. This - parameter currently applies only to the "lazy" and "selectin" loader - strategies. There is generally no reason to set this parameter to - False. - - .. versionchanged:: 1.4 Relationship loaders no longer use the - previous "baked query" system of query caching. The "lazy" - and "selectin" loaders make use of the "lambda cache" system - for the construction of SQL constructs, - as well as the usual SQL caching system that is throughout - SQLAlchemy as of the 1.4 series. - - :param cascade: - A comma-separated list of cascade rules which determines how - Session operations should be "cascaded" from parent to child. - This defaults to ``False``, which means the default cascade - should be used - this default cascade is ``"save-update, merge"``. - - The available cascades are ``save-update``, ``merge``, - ``expunge``, ``delete``, ``delete-orphan``, and ``refresh-expire``. - An additional option, ``all`` indicates shorthand for - ``"save-update, merge, refresh-expire, - expunge, delete"``, and is often used as in ``"all, delete-orphan"`` - to indicate that related objects should follow along with the - parent object in all cases, and be deleted when de-associated. - - .. seealso:: - - :ref:`unitofwork_cascades` - Full detail on each of the available - cascade options. - - :ref:`tutorial_delete_cascade` - Tutorial example describing - a delete cascade. - - :param cascade_backrefs=False: - Legacy; this flag is always False. - - .. versionchanged:: 2.0 "cascade_backrefs" functionality has been - removed. - - :param collection_class: - A class or callable that returns a new list-holding object. will - be used in place of a plain list for storing elements. - - .. seealso:: - - :ref:`custom_collections` - Introductory documentation and - examples. - - :param comparator_factory: - A class which extends :class:`.RelationshipProperty.Comparator` - which provides custom SQL clause generation for comparison - operations. - - .. seealso:: - - :class:`.PropComparator` - some detail on redefining comparators - at this level. - - :ref:`custom_comparators` - Brief intro to this feature. - - - :param distinct_target_key=None: - Indicate if a "subquery" eager load should apply the DISTINCT - keyword to the innermost SELECT statement. When left as ``None``, - the DISTINCT keyword will be applied in those cases when the target - columns do not comprise the full primary key of the target table. - When set to ``True``, the DISTINCT keyword is applied to the - innermost SELECT unconditionally. - - It may be desirable to set this flag to False when the DISTINCT is - reducing performance of the innermost subquery beyond that of what - duplicate innermost rows may be causing. - - .. versionchanged:: 0.9.0 - - :paramref:`_orm.relationship.distinct_target_key` now defaults to - ``None``, so that the feature enables itself automatically for - those cases where the innermost query targets a non-unique - key. - - .. seealso:: - - :ref:`loading_toplevel` - includes an introduction to subquery - eager loading. - - :param doc: - Docstring which will be applied to the resulting descriptor. - - :param foreign_keys: - - A list of columns which are to be used as "foreign key" - columns, or columns which refer to the value in a remote - column, within the context of this :func:`_orm.relationship` - object's :paramref:`_orm.relationship.primaryjoin` condition. - That is, if the :paramref:`_orm.relationship.primaryjoin` - condition of this :func:`_orm.relationship` is ``a.id == - b.a_id``, and the values in ``b.a_id`` are required to be - present in ``a.id``, then the "foreign key" column of this - :func:`_orm.relationship` is ``b.a_id``. - - In normal cases, the :paramref:`_orm.relationship.foreign_keys` - parameter is **not required.** :func:`_orm.relationship` will - automatically determine which columns in the - :paramref:`_orm.relationship.primaryjoin` condition are to be - considered "foreign key" columns based on those - :class:`_schema.Column` objects that specify - :class:`_schema.ForeignKey`, - or are otherwise listed as referencing columns in a - :class:`_schema.ForeignKeyConstraint` construct. - :paramref:`_orm.relationship.foreign_keys` is only needed when: - - 1. There is more than one way to construct a join from the local - table to the remote table, as there are multiple foreign key - references present. Setting ``foreign_keys`` will limit the - :func:`_orm.relationship` - to consider just those columns specified - here as "foreign". - - 2. The :class:`_schema.Table` being mapped does not actually have - :class:`_schema.ForeignKey` or - :class:`_schema.ForeignKeyConstraint` - constructs present, often because the table - was reflected from a database that does not support foreign key - reflection (MySQL MyISAM). - - 3. The :paramref:`_orm.relationship.primaryjoin` - argument is used to - construct a non-standard join condition, which makes use of - columns or expressions that do not normally refer to their - "parent" column, such as a join condition expressed by a - complex comparison using a SQL function. - - The :func:`_orm.relationship` construct will raise informative - error messages that suggest the use of the - :paramref:`_orm.relationship.foreign_keys` parameter when - presented with an ambiguous condition. In typical cases, - if :func:`_orm.relationship` doesn't raise any exceptions, the - :paramref:`_orm.relationship.foreign_keys` parameter is usually - not needed. - - :paramref:`_orm.relationship.foreign_keys` may also be passed as a - callable function which is evaluated at mapper initialization time, - and may be passed as a Python-evaluable string when using - Declarative. - - .. warning:: When passed as a Python-evaluable string, the - argument is interpreted using Python's ``eval()`` function. - **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. - See :ref:`declarative_relationship_eval` for details on - declarative evaluation of :func:`_orm.relationship` arguments. - - .. seealso:: - - :ref:`relationship_foreign_keys` - - :ref:`relationship_custom_foreign` - - :func:`.foreign` - allows direct annotation of the "foreign" - columns within a :paramref:`_orm.relationship.primaryjoin` - condition. - - :param info: Optional data dictionary which will be populated into the - :attr:`.MapperProperty.info` attribute of this object. - - :param innerjoin=False: - When ``True``, joined eager loads will use an inner join to join - against related tables instead of an outer join. The purpose - of this option is generally one of performance, as inner joins - generally perform better than outer joins. - - This flag can be set to ``True`` when the relationship references an - object via many-to-one using local foreign keys that are not - nullable, or when the reference is one-to-one or a collection that - is guaranteed to have one or at least one entry. - - The option supports the same "nested" and "unnested" options as - that of :paramref:`_orm.joinedload.innerjoin`. See that flag - for details on nested / unnested behaviors. - - .. seealso:: - - :paramref:`_orm.joinedload.innerjoin` - the option as specified by - loader option, including detail on nesting behavior. - - :ref:`what_kind_of_loading` - Discussion of some details of - various loader options. - - - :param join_depth: - When non-``None``, an integer value indicating how many levels - deep "eager" loaders should join on a self-referring or cyclical - relationship. The number counts how many times the same Mapper - shall be present in the loading condition along a particular join - branch. When left at its default of ``None``, eager loaders - will stop chaining when they encounter a the same target mapper - which is already higher up in the chain. This option applies - both to joined- and subquery- eager loaders. - - .. seealso:: - - :ref:`self_referential_eager_loading` - Introductory documentation - and examples. - - :param lazy='select': specifies - How the related items should be loaded. Default value is - ``select``. Values include: - - * ``select`` - items should be loaded lazily when the property is - first accessed, using a separate SELECT statement, or identity map - fetch for simple many-to-one references. - - * ``immediate`` - items should be loaded as the parents are loaded, - using a separate SELECT statement, or identity map fetch for - simple many-to-one references. - - * ``joined`` - items should be loaded "eagerly" in the same query as - that of the parent, using a JOIN or LEFT OUTER JOIN. Whether - the join is "outer" or not is determined by the - :paramref:`_orm.relationship.innerjoin` parameter. - - * ``subquery`` - items should be loaded "eagerly" as the parents are - loaded, using one additional SQL statement, which issues a JOIN to - a subquery of the original statement, for each collection - requested. - - * ``selectin`` - items should be loaded "eagerly" as the parents - are loaded, using one or more additional SQL statements, which - issues a JOIN to the immediate parent object, specifying primary - key identifiers using an IN clause. - - .. versionadded:: 1.2 - - * ``noload`` - no loading should occur at any time. This is to - support "write-only" attributes, or attributes which are - populated in some manner specific to the application. - - * ``raise`` - lazy loading is disallowed; accessing - the attribute, if its value were not already loaded via eager - loading, will raise an :exc:`~sqlalchemy.exc.InvalidRequestError`. - This strategy can be used when objects are to be detached from - their attached :class:`.Session` after they are loaded. - - .. versionadded:: 1.1 - - * ``raise_on_sql`` - lazy loading that emits SQL is disallowed; - accessing the attribute, if its value were not already loaded via - eager loading, will raise an - :exc:`~sqlalchemy.exc.InvalidRequestError`, **if the lazy load - needs to emit SQL**. If the lazy load can pull the related value - from the identity map or determine that it should be None, the - value is loaded. This strategy can be used when objects will - remain associated with the attached :class:`.Session`, however - additional SELECT statements should be blocked. - - .. versionadded:: 1.1 - - * ``dynamic`` - the attribute will return a pre-configured - :class:`_query.Query` object for all read - operations, onto which further filtering operations can be - applied before iterating the results. See - the section :ref:`dynamic_relationship` for more details. - - * True - a synonym for 'select' - - * False - a synonym for 'joined' - - * None - a synonym for 'noload' - - .. seealso:: - - :doc:`/orm/loading_relationships` - Full documentation on - relationship loader configuration. - - :ref:`dynamic_relationship` - detail on the ``dynamic`` option. - - :ref:`collections_noload_raiseload` - notes on "noload" and "raise" - - :param load_on_pending=False: - Indicates loading behavior for transient or pending parent objects. - - When set to ``True``, causes the lazy-loader to - issue a query for a parent object that is not persistent, meaning it - has never been flushed. This may take effect for a pending object - when autoflush is disabled, or for a transient object that has been - "attached" to a :class:`.Session` but is not part of its pending - collection. - - The :paramref:`_orm.relationship.load_on_pending` - flag does not improve - behavior when the ORM is used normally - object references should be - constructed at the object level, not at the foreign key level, so - that they are present in an ordinary way before a flush proceeds. - This flag is not not intended for general use. - - .. seealso:: - - :meth:`.Session.enable_relationship_loading` - this method - establishes "load on pending" behavior for the whole object, and - also allows loading on objects that remain transient or - detached. - - :param order_by: - Indicates the ordering that should be applied when loading these - items. :paramref:`_orm.relationship.order_by` - is expected to refer to - one of the :class:`_schema.Column` - objects to which the target class is - mapped, or the attribute itself bound to the target class which - refers to the column. - - :paramref:`_orm.relationship.order_by` - may also be passed as a callable - function which is evaluated at mapper initialization time, and may - be passed as a Python-evaluable string when using Declarative. - - .. warning:: When passed as a Python-evaluable string, the - argument is interpreted using Python's ``eval()`` function. - **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. - See :ref:`declarative_relationship_eval` for details on - declarative evaluation of :func:`_orm.relationship` arguments. - - :param passive_deletes=False: - Indicates loading behavior during delete operations. - - A value of True indicates that unloaded child items should not - be loaded during a delete operation on the parent. Normally, - when a parent item is deleted, all child items are loaded so - that they can either be marked as deleted, or have their - foreign key to the parent set to NULL. Marking this flag as - True usually implies an ON DELETE <CASCADE|SET NULL> rule is in - place which will handle updating/deleting child rows on the - database side. - - Additionally, setting the flag to the string value 'all' will - disable the "nulling out" of the child foreign keys, when the parent - object is deleted and there is no delete or delete-orphan cascade - enabled. This is typically used when a triggering or error raise - scenario is in place on the database side. Note that the foreign - key attributes on in-session child objects will not be changed after - a flush occurs so this is a very special use-case setting. - Additionally, the "nulling out" will still occur if the child - object is de-associated with the parent. - - .. seealso:: - - :ref:`passive_deletes` - Introductory documentation - and examples. - - :param passive_updates=True: - Indicates the persistence behavior to take when a referenced - primary key value changes in place, indicating that the referencing - foreign key columns will also need their value changed. - - When True, it is assumed that ``ON UPDATE CASCADE`` is configured on - the foreign key in the database, and that the database will - handle propagation of an UPDATE from a source column to - dependent rows. When False, the SQLAlchemy - :func:`_orm.relationship` - construct will attempt to emit its own UPDATE statements to - modify related targets. However note that SQLAlchemy **cannot** - emit an UPDATE for more than one level of cascade. Also, - setting this flag to False is not compatible in the case where - the database is in fact enforcing referential integrity, unless - those constraints are explicitly "deferred", if the target backend - supports it. - - It is highly advised that an application which is employing - mutable primary keys keeps ``passive_updates`` set to True, - and instead uses the referential integrity features of the database - itself in order to handle the change efficiently and fully. - - .. seealso:: - - :ref:`passive_updates` - Introductory documentation and - examples. - - :paramref:`.mapper.passive_updates` - a similar flag which - takes effect for joined-table inheritance mappings. - - :param post_update: - This indicates that the relationship should be handled by a - second UPDATE statement after an INSERT or before a - DELETE. Currently, it also will issue an UPDATE after the - instance was UPDATEd as well, although this technically should - be improved. This flag is used to handle saving bi-directional - dependencies between two individual rows (i.e. each row - references the other), where it would otherwise be impossible to - INSERT or DELETE both rows fully since one row exists before the - other. Use this flag when a particular mapping arrangement will - incur two rows that are dependent on each other, such as a table - that has a one-to-many relationship to a set of child rows, and - also has a column that references a single child row within that - list (i.e. both tables contain a foreign key to each other). If - a flush operation returns an error that a "cyclical - dependency" was detected, this is a cue that you might want to - use :paramref:`_orm.relationship.post_update` to "break" the cycle. - - .. seealso:: - - :ref:`post_update` - Introductory documentation and examples. - - :param primaryjoin: - A SQL expression that will be used as the primary - join of the child object against the parent object, or in a - many-to-many relationship the join of the parent object to the - association table. By default, this value is computed based on the - foreign key relationships of the parent and child tables (or - association table). - - :paramref:`_orm.relationship.primaryjoin` may also be passed as a - callable function which is evaluated at mapper initialization time, - and may be passed as a Python-evaluable string when using - Declarative. - - .. warning:: When passed as a Python-evaluable string, the - argument is interpreted using Python's ``eval()`` function. - **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. - See :ref:`declarative_relationship_eval` for details on - declarative evaluation of :func:`_orm.relationship` arguments. - - .. seealso:: - - :ref:`relationship_primaryjoin` - - :param remote_side: - Used for self-referential relationships, indicates the column or - list of columns that form the "remote side" of the relationship. - - :paramref:`_orm.relationship.remote_side` may also be passed as a - callable function which is evaluated at mapper initialization time, - and may be passed as a Python-evaluable string when using - Declarative. - - .. warning:: When passed as a Python-evaluable string, the - argument is interpreted using Python's ``eval()`` function. - **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. - See :ref:`declarative_relationship_eval` for details on - declarative evaluation of :func:`_orm.relationship` arguments. - - .. seealso:: - - :ref:`self_referential` - in-depth explanation of how - :paramref:`_orm.relationship.remote_side` - is used to configure self-referential relationships. - - :func:`.remote` - an annotation function that accomplishes the - same purpose as :paramref:`_orm.relationship.remote_side`, - typically - when a custom :paramref:`_orm.relationship.primaryjoin` condition - is used. - - :param query_class: - A :class:`_query.Query` - subclass that will be used internally by the - ``AppenderQuery`` returned by a "dynamic" relationship, that - is, a relationship that specifies ``lazy="dynamic"`` or was - otherwise constructed using the :func:`_orm.dynamic_loader` - function. - - .. seealso:: - - :ref:`dynamic_relationship` - Introduction to "dynamic" - relationship loaders. - - :param secondaryjoin: - A SQL expression that will be used as the join of - an association table to the child object. By default, this value is - computed based on the foreign key relationships of the association - and child tables. - - :paramref:`_orm.relationship.secondaryjoin` may also be passed as a - callable function which is evaluated at mapper initialization time, - and may be passed as a Python-evaluable string when using - Declarative. - - .. warning:: When passed as a Python-evaluable string, the - argument is interpreted using Python's ``eval()`` function. - **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. - See :ref:`declarative_relationship_eval` for details on - declarative evaluation of :func:`_orm.relationship` arguments. - - .. seealso:: - - :ref:`relationship_primaryjoin` - - :param single_parent: - When True, installs a validator which will prevent objects - from being associated with more than one parent at a time. - This is used for many-to-one or many-to-many relationships that - should be treated either as one-to-one or one-to-many. Its usage - is optional, except for :func:`_orm.relationship` constructs which - are many-to-one or many-to-many and also - specify the ``delete-orphan`` cascade option. The - :func:`_orm.relationship` construct itself will raise an error - instructing when this option is required. - - .. seealso:: - - :ref:`unitofwork_cascades` - includes detail on when the - :paramref:`_orm.relationship.single_parent` - flag may be appropriate. - - :param uselist: - A boolean that indicates if this property should be loaded as a - list or a scalar. In most cases, this value is determined - automatically by :func:`_orm.relationship` at mapper configuration - time, based on the type and direction - of the relationship - one to many forms a list, many to one - forms a scalar, many to many is a list. If a scalar is desired - where normally a list would be present, such as a bi-directional - one-to-one relationship, set :paramref:`_orm.relationship.uselist` - to - False. - - The :paramref:`_orm.relationship.uselist` - flag is also available on an - existing :func:`_orm.relationship` - construct as a read-only attribute, - which can be used to determine if this :func:`_orm.relationship` - deals - with collections or scalar attributes:: - - >>> User.addresses.property.uselist - True - - .. seealso:: - - :ref:`relationships_one_to_one` - Introduction to the "one to - one" relationship pattern, which is typically when the - :paramref:`_orm.relationship.uselist` flag is needed. - - :param viewonly=False: - When set to ``True``, the relationship is used only for loading - objects, and not for any persistence operation. A - :func:`_orm.relationship` which specifies - :paramref:`_orm.relationship.viewonly` can work - with a wider range of SQL operations within the - :paramref:`_orm.relationship.primaryjoin` condition, including - operations that feature the use of a variety of comparison operators - as well as SQL functions such as :func:`_expression.cast`. The - :paramref:`_orm.relationship.viewonly` - flag is also of general use when defining any kind of - :func:`_orm.relationship` that doesn't represent - the full set of related objects, to prevent modifications of the - collection from resulting in persistence operations. - - When using the :paramref:`_orm.relationship.viewonly` flag in - conjunction with backrefs, the originating relationship for a - particular state change will not produce state changes within the - viewonly relationship. This is the behavior implied by - :paramref:`_orm.relationship.sync_backref` being set to False. - - .. versionchanged:: 1.3.17 - the - :paramref:`_orm.relationship.sync_backref` flag is set to False - when using viewonly in conjunction with backrefs. - - .. seealso:: - - :paramref:`_orm.relationship.sync_backref` - - :param sync_backref: - A boolean that enables the events used to synchronize the in-Python - attributes when this relationship is target of either - :paramref:`_orm.relationship.backref` or - :paramref:`_orm.relationship.back_populates`. - - Defaults to ``None``, which indicates that an automatic value should - be selected based on the value of the - :paramref:`_orm.relationship.viewonly` flag. When left at its - default, changes in state will be back-populated only if neither - sides of a relationship is viewonly. - - .. versionadded:: 1.3.17 - - .. versionchanged:: 1.4 - A relationship that specifies - :paramref:`_orm.relationship.viewonly` automatically implies - that :paramref:`_orm.relationship.sync_backref` is ``False``. - - .. seealso:: - - :paramref:`_orm.relationship.viewonly` - - :param omit_join: - Allows manual control over the "selectin" automatic join - optimization. Set to ``False`` to disable the "omit join" feature - added in SQLAlchemy 1.3; or leave as ``None`` to leave automatic - optimization in place. - - .. note:: This flag may only be set to ``False``. It is not - necessary to set it to ``True`` as the "omit_join" optimization is - automatically detected; if it is not detected, then the - optimization is not supported. - - .. versionchanged:: 1.3.11 setting ``omit_join`` to True will now - emit a warning as this was not the intended use of this flag. - - .. versionadded:: 1.3 - - - """ super(RelationshipProperty, self).__init__() self.uselist = uselist diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py index ead0d06ef..47da85716 100644 --- a/lib/sqlalchemy/orm/scoping.py +++ b/lib/sqlalchemy/orm/scoping.py @@ -5,8 +5,8 @@ # This module is part of SQLAlchemy and is released under # the MIT License: https://www.opensource.org/licenses/mit-license.php -from . import class_mapper from . import exc as orm_exc +from .base import class_mapper from .session import Session from .. import exc as sa_exc from ..util import create_proxy_methods diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index e0dd5941d..c1ce3b845 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -968,172 +968,6 @@ class LoaderCriteriaOption(CriteriaOption): propagate_to_loaders=True, track_closure_variables=True, ): - """Add additional WHERE criteria to the load for all occurrences of - a particular entity. - - .. versionadded:: 1.4 - - The :func:`_orm.with_loader_criteria` option is intended to add - limiting criteria to a particular kind of entity in a query, - **globally**, meaning it will apply to the entity as it appears - in the SELECT query as well as within any subqueries, join - conditions, and relationship loads, including both eager and lazy - loaders, without the need for it to be specified in any particular - part of the query. The rendering logic uses the same system used by - single table inheritance to ensure a certain discriminator is applied - to a table. - - E.g., using :term:`2.0-style` queries, we can limit the way the - ``User.addresses`` collection is loaded, regardless of the kind - of loading used:: - - from sqlalchemy.orm import with_loader_criteria - - stmt = select(User).options( - selectinload(User.addresses), - with_loader_criteria(Address, Address.email_address != 'foo')) - ) - - Above, the "selectinload" for ``User.addresses`` will apply the - given filtering criteria to the WHERE clause. - - Another example, where the filtering will be applied to the - ON clause of the join, in this example using :term:`1.x style` - queries:: - - q = session.query(User).outerjoin(User.addresses).options( - with_loader_criteria(Address, Address.email_address != 'foo')) - ) - - The primary purpose of :func:`_orm.with_loader_criteria` is to use - it in the :meth:`_orm.SessionEvents.do_orm_execute` event handler - to ensure that all occurrences of a particular entity are filtered - in a certain way, such as filtering for access control roles. It - also can be used to apply criteria to relationship loads. In the - example below, we can apply a certain set of rules to all queries - emitted by a particular :class:`_orm.Session`:: - - session = Session(bind=engine) - - @event.listens_for("do_orm_execute", session) - def _add_filtering_criteria(execute_state): - - if ( - execute_state.is_select - and not execute_state.is_column_load - and not execute_state.is_relationship_load - ): - execute_state.statement = execute_state.statement.options( - with_loader_criteria( - SecurityRole, - lambda cls: cls.role.in_(['some_role']), - include_aliases=True - ) - ) - - In the above example, the :meth:`_orm.SessionEvents.do_orm_execute` - event will intercept all queries emitted using the - :class:`_orm.Session`. For those queries which are SELECT statements - and are not attribute or relationship loads a custom - :func:`_orm.with_loader_criteria` option is added to the query. The - :func:`_orm.with_loader_criteria` option will be used in the given - statement and will also be automatically propagated to all relationship - loads that descend from this query. - - The criteria argument given is a ``lambda`` that accepts a ``cls`` - argument. The given class will expand to include all mapped subclass - and need not itself be a mapped class. - - .. tip:: - - When using :func:`_orm.with_loader_criteria` option in - conjunction with the :func:`_orm.contains_eager` loader option, - it's important to note that :func:`_orm.with_loader_criteria` only - affects the part of the query that determines what SQL is rendered - in terms of the WHERE and FROM clauses. The - :func:`_orm.contains_eager` option does not affect the rendering of - the SELECT statement outside of the columns clause, so does not have - any interaction with the :func:`_orm.with_loader_criteria` option. - However, the way things "work" is that :func:`_orm.contains_eager` - is meant to be used with a query that is already selecting from the - additional entities in some way, where - :func:`_orm.with_loader_criteria` can apply it's additional - criteria. - - In the example below, assuming a mapping relationship as - ``A -> A.bs -> B``, the given :func:`_orm.with_loader_criteria` - option will affect the way in which the JOIN is rendered:: - - stmt = select(A).join(A.bs).options( - contains_eager(A.bs), - with_loader_criteria(B, B.flag == 1) - ) - - Above, the given :func:`_orm.with_loader_criteria` option will - affect the ON clause of the JOIN that is specified by - ``.join(A.bs)``, so is applied as expected. The - :func:`_orm.contains_eager` option has the effect that columns from - ``B`` are added to the columns clause:: - - SELECT - b.id, b.a_id, b.data, b.flag, - a.id AS id_1, - a.data AS data_1 - FROM a JOIN b ON a.id = b.a_id AND b.flag = :flag_1 - - - The use of the :func:`_orm.contains_eager` option within the above - statement has no effect on the behavior of the - :func:`_orm.with_loader_criteria` option. If the - :func:`_orm.contains_eager` option were omitted, the SQL would be - the same as regards the FROM and WHERE clauses, where - :func:`_orm.with_loader_criteria` continues to add its criteria to - the ON clause of the JOIN. The addition of - :func:`_orm.contains_eager` only affects the columns clause, in that - additional columns against ``b`` are added which are then consumed - by the ORM to produce ``B`` instances. - - .. warning:: The use of a lambda inside of the call to - :func:`_orm.with_loader_criteria` is only invoked **once per unique - class**. Custom functions should not be invoked within this lambda. - See :ref:`engine_lambda_caching` for an overview of the "lambda SQL" - feature, which is for advanced use only. - - :param entity_or_base: a mapped class, or a class that is a super - class of a particular set of mapped classes, to which the rule - will apply. - - :param where_criteria: a Core SQL expression that applies limiting - criteria. This may also be a "lambda:" or Python function that - accepts a target class as an argument, when the given class is - a base with many different mapped subclasses. - - :param include_aliases: if True, apply the rule to :func:`_orm.aliased` - constructs as well. - - :param propagate_to_loaders: defaults to True, apply to relationship - loaders such as lazy loaders. - - - .. seealso:: - - :ref:`examples_session_orm_events` - includes examples of using - :func:`_orm.with_loader_criteria`. - - :ref:`do_orm_execute_global_criteria` - basic example on how to - combine :func:`_orm.with_loader_criteria` with the - :meth:`_orm.SessionEvents.do_orm_execute` event. - - :param track_closure_variables: when False, closure variables inside - of a lambda expression will not be used as part of - any cache key. This allows more complex expressions to be used - inside of a lambda expression but requires that the lambda ensures - it returns the identical SQL every time given a particular class. - - .. versionadded:: 1.4.0b2 - - """ - entity = inspection.inspect(entity_or_base, False) if entity is None: self.root_entity = entity_or_base diff --git a/lib/sqlalchemy/sql/__init__.py b/lib/sqlalchemy/sql/__init__.py index 9d66c8f61..b558f9bf5 100644 --- a/lib/sqlalchemy/sql/__init__.py +++ b/lib/sqlalchemy/sql/__init__.py @@ -5,121 +5,109 @@ # This module is part of SQLAlchemy and is released under # the MIT License: https://www.opensource.org/licenses/mit-license.php -from .base import Executable -from .compiler import COLLECT_CARTESIAN_PRODUCTS -from .compiler import FROM_LINTING -from .compiler import NO_LINTING -from .compiler import WARN_LINTING -from .expression import Alias -from .expression import alias -from .expression import all_ -from .expression import and_ -from .expression import any_ -from .expression import asc -from .expression import between -from .expression import bindparam -from .expression import case -from .expression import cast -from .expression import ClauseElement -from .expression import collate -from .expression import column -from .expression import ColumnCollection -from .expression import ColumnElement -from .expression import CompoundSelect -from .expression import cte -from .expression import Delete -from .expression import delete -from .expression import desc -from .expression import distinct -from .expression import except_ -from .expression import except_all -from .expression import exists -from .expression import extract -from .expression import false -from .expression import False_ -from .expression import FromClause -from .expression import func -from .expression import funcfilter -from .expression import Insert -from .expression import insert -from .expression import intersect -from .expression import intersect_all -from .expression import Join -from .expression import join -from .expression import label -from .expression import LABEL_STYLE_DEFAULT -from .expression import LABEL_STYLE_DISAMBIGUATE_ONLY -from .expression import LABEL_STYLE_NONE -from .expression import LABEL_STYLE_TABLENAME_PLUS_COL -from .expression import lambda_stmt -from .expression import LambdaElement -from .expression import lateral -from .expression import literal -from .expression import literal_column -from .expression import modifier -from .expression import not_ -from .expression import null -from .expression import nulls_first -from .expression import nulls_last -from .expression import nullsfirst -from .expression import nullslast -from .expression import or_ -from .expression import outerjoin -from .expression import outparam -from .expression import over -from .expression import quoted_name -from .expression import Select -from .expression import select -from .expression import Selectable -from .expression import StatementLambdaElement -from .expression import Subquery -from .expression import table -from .expression import TableClause -from .expression import TableSample -from .expression import tablesample -from .expression import text -from .expression import true -from .expression import True_ -from .expression import tuple_ -from .expression import type_coerce -from .expression import union -from .expression import union_all -from .expression import Update -from .expression import update -from .expression import Values -from .expression import values -from .expression import within_group -from .visitors import ClauseVisitor +from .base import Executable as Executable +from .compiler import COLLECT_CARTESIAN_PRODUCTS as COLLECT_CARTESIAN_PRODUCTS +from .compiler import FROM_LINTING as FROM_LINTING +from .compiler import NO_LINTING as NO_LINTING +from .compiler import WARN_LINTING as WARN_LINTING +from .expression import Alias as Alias +from .expression import alias as alias +from .expression import all_ as all_ +from .expression import and_ as and_ +from .expression import any_ as any_ +from .expression import asc as asc +from .expression import between as between +from .expression import bindparam as bindparam +from .expression import case as case +from .expression import cast as cast +from .expression import ClauseElement as ClauseElement +from .expression import collate as collate +from .expression import column as column +from .expression import ColumnCollection as ColumnCollection +from .expression import ColumnElement as ColumnElement +from .expression import CompoundSelect as CompoundSelect +from .expression import cte as cte +from .expression import Delete as Delete +from .expression import delete as delete +from .expression import desc as desc +from .expression import distinct as distinct +from .expression import except_ as except_ +from .expression import except_all as except_all +from .expression import exists as exists +from .expression import extract as extract +from .expression import false as false +from .expression import False_ as False_ +from .expression import FromClause as FromClause +from .expression import func as func +from .expression import funcfilter as funcfilter +from .expression import Insert as Insert +from .expression import insert as insert +from .expression import intersect as intersect +from .expression import intersect_all as intersect_all +from .expression import Join as Join +from .expression import join as join +from .expression import label as label +from .expression import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT +from .expression import lambda_stmt as lambda_stmt +from .expression import LambdaElement as LambdaElement +from .expression import lateral as lateral +from .expression import literal as literal +from .expression import literal_column as literal_column +from .expression import modifier as modifier +from .expression import not_ as not_ +from .expression import null as null +from .expression import nulls_first as nulls_first +from .expression import nulls_last as nulls_last +from .expression import nullsfirst as nullsfirst +from .expression import nullslast as nullslast +from .expression import or_ as or_ +from .expression import outerjoin as outerjoin +from .expression import outparam as outparam +from .expression import over as over +from .expression import quoted_name as quoted_name +from .expression import Select as Select +from .expression import select as select +from .expression import Selectable as Selectable +from .expression import StatementLambdaElement as StatementLambdaElement +from .expression import Subquery as Subquery +from .expression import table as table +from .expression import TableClause as TableClause +from .expression import TableSample as TableSample +from .expression import tablesample as tablesample +from .expression import text as text +from .expression import true as true +from .expression import True_ as True_ +from .expression import tuple_ as tuple_ +from .expression import type_coerce as type_coerce +from .expression import union as union +from .expression import union_all as union_all +from .expression import Update as Update +from .expression import update as update +from .expression import Values as Values +from .expression import values as values +from .expression import within_group as within_group +from .visitors import ClauseVisitor as ClauseVisitor - -def __go(lcls): - global __all__ - from .. import util as _sa_util - - import inspect as _inspect - - __all__ = sorted( - name - for name, obj in lcls.items() - if not (name.startswith("_") or _inspect.ismodule(obj)) +if True: + # work around zimports bug + from .expression import ( + LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY, + ) + from .expression import LABEL_STYLE_NONE as LABEL_STYLE_NONE + from .expression import ( + LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL, ) - from .annotation import _prepare_annotations - from .annotation import Annotated - from .elements import AnnotatedColumnElement - from .elements import ClauseList - from .selectable import AnnotatedFromClause - # from .traversals import _preconfigure_traversals +def __go(lcls): + from .. import util as _sa_util from . import base from . import coercions from . import elements - from . import events from . import lambdas from . import selectable from . import schema - from . import sqltypes from . import traversals from . import type_api @@ -130,20 +118,19 @@ def __go(lcls): coercions.lambdas = lambdas coercions.schema = schema coercions.selectable = selectable - coercions.sqltypes = sqltypes coercions.traversals = traversals + from .annotation import _prepare_annotations + from .annotation import Annotated + from .elements import AnnotatedColumnElement + from .elements import ClauseList + from .selectable import AnnotatedFromClause + _prepare_annotations(ColumnElement, AnnotatedColumnElement) _prepare_annotations(FromClause, AnnotatedFromClause) _prepare_annotations(ClauseList, Annotated) - # this is expensive at import time; elements that are used can create - # their traversals on demand - # _preconfigure_traversals(ClauseElement) - _sa_util.preloaded.import_prefix("sqlalchemy.sql") - from . import naming - __go(locals()) diff --git a/lib/sqlalchemy/sql/_dml_constructors.py b/lib/sqlalchemy/sql/_dml_constructors.py new file mode 100644 index 000000000..e62edf5e6 --- /dev/null +++ b/lib/sqlalchemy/sql/_dml_constructors.py @@ -0,0 +1,231 @@ +# sql/_dml_constructors.py +# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors +# <see AUTHORS file> +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php + +from .dml import Delete +from .dml import Insert +from .dml import Update + + +def insert(table): + """Construct an :class:`_expression.Insert` object. + + E.g.:: + + from sqlalchemy import insert + + stmt = ( + insert(user_table). + values(name='username', fullname='Full Username') + ) + + Similar functionality is available via the + :meth:`_expression.TableClause.insert` method on + :class:`_schema.Table`. + + .. seealso:: + + :ref:`coretutorial_insert_expressions` - in the + :ref:`1.x tutorial <sqlexpression_toplevel>` + + :ref:`tutorial_core_insert` - in the :ref:`unified_tutorial` + + + :param table: :class:`_expression.TableClause` + which is the subject of the + insert. + + :param values: collection of values to be inserted; see + :meth:`_expression.Insert.values` + for a description of allowed formats here. + Can be omitted entirely; a :class:`_expression.Insert` construct + will also dynamically render the VALUES clause at execution time + based on the parameters passed to :meth:`_engine.Connection.execute`. + + :param inline: if True, no attempt will be made to retrieve the + SQL-generated default values to be provided within the statement; + in particular, + this allows SQL expressions to be rendered 'inline' within the + statement without the need to pre-execute them beforehand; for + backends that support "returning", this turns off the "implicit + returning" feature for the statement. + + If both :paramref:`_expression.Insert.values` and compile-time bind + parameters are present, the compile-time bind parameters override the + information specified within :paramref:`_expression.Insert.values` on a + per-key basis. + + The keys within :paramref:`_expression.Insert.values` can be either + :class:`~sqlalchemy.schema.Column` objects or their string + identifiers. Each key may reference one of: + + * a literal data value (i.e. string, number, etc.); + * a Column object; + * a SELECT statement. + + If a ``SELECT`` statement is specified which references this + ``INSERT`` statement's table, the statement will be correlated + against the ``INSERT`` statement. + + .. seealso:: + + :ref:`coretutorial_insert_expressions` - SQL Expression Tutorial + + :ref:`inserts_and_updates` - SQL Expression Tutorial + + """ + return Insert(table) + + +def update(table): + r"""Construct an :class:`_expression.Update` object. + + E.g.:: + + from sqlalchemy import update + + stmt = ( + update(user_table). + where(user_table.c.id == 5). + values(name='user #5') + ) + + Similar functionality is available via the + :meth:`_expression.TableClause.update` method on + :class:`_schema.Table`. + + .. seealso:: + + :ref:`inserts_and_updates` - in the + :ref:`1.x tutorial <sqlexpression_toplevel>` + + :ref:`tutorial_core_update_delete` - in the :ref:`unified_tutorial` + + + + :param table: A :class:`_schema.Table` + object representing the database + table to be updated. + + :param whereclause: Optional SQL expression describing the ``WHERE`` + condition of the ``UPDATE`` statement; is equivalent to using the + more modern :meth:`~Update.where()` method to specify the ``WHERE`` + clause. + + :param values: + Optional dictionary which specifies the ``SET`` conditions of the + ``UPDATE``. If left as ``None``, the ``SET`` + conditions are determined from those parameters passed to the + statement during the execution and/or compilation of the + statement. When compiled standalone without any parameters, + the ``SET`` clause generates for all columns. + + Modern applications may prefer to use the generative + :meth:`_expression.Update.values` method to set the values of the + UPDATE statement. + + :param inline: + if True, SQL defaults present on :class:`_schema.Column` objects via + the ``default`` keyword will be compiled 'inline' into the statement + and not pre-executed. This means that their values will not + be available in the dictionary returned from + :meth:`_engine.CursorResult.last_updated_params`. + + :param preserve_parameter_order: if True, the update statement is + expected to receive parameters **only** via the + :meth:`_expression.Update.values` method, + and they must be passed as a Python + ``list`` of 2-tuples. The rendered UPDATE statement will emit the SET + clause for each referenced column maintaining this order. + + .. versionadded:: 1.0.10 + + .. seealso:: + + :ref:`updates_order_parameters` - illustrates the + :meth:`_expression.Update.ordered_values` method. + + If both ``values`` and compile-time bind parameters are present, the + compile-time bind parameters override the information specified + within ``values`` on a per-key basis. + + The keys within ``values`` can be either :class:`_schema.Column` + objects or their string identifiers (specifically the "key" of the + :class:`_schema.Column`, normally but not necessarily equivalent to + its "name"). Normally, the + :class:`_schema.Column` objects used here are expected to be + part of the target :class:`_schema.Table` that is the table + to be updated. However when using MySQL, a multiple-table + UPDATE statement can refer to columns from any of + the tables referred to in the WHERE clause. + + The values referred to in ``values`` are typically: + + * a literal data value (i.e. string, number, etc.) + * a SQL expression, such as a related :class:`_schema.Column`, + a scalar-returning :func:`_expression.select` construct, + etc. + + When combining :func:`_expression.select` constructs within the + values clause of an :func:`_expression.update` + construct, the subquery represented + by the :func:`_expression.select` should be *correlated* to the + parent table, that is, providing criterion which links the table inside + the subquery to the outer table being updated:: + + users.update().values( + name=select(addresses.c.email_address).\ + where(addresses.c.user_id==users.c.id).\ + scalar_subquery() + ) + + .. seealso:: + + :ref:`inserts_and_updates` - SQL Expression + Language Tutorial + + + """ + return Update(table) + + +def delete(table): + r"""Construct :class:`_expression.Delete` object. + + E.g.:: + + from sqlalchemy import delete + + stmt = ( + delete(user_table). + where(user_table.c.id == 5) + ) + + Similar functionality is available via the + :meth:`_expression.TableClause.delete` method on + :class:`_schema.Table`. + + .. seealso:: + + :ref:`inserts_and_updates` - in the + :ref:`1.x tutorial <sqlexpression_toplevel>` + + :ref:`tutorial_core_update_delete` - in the :ref:`unified_tutorial` + + + :param table: The table to delete rows from. + + :param whereclause: Optional SQL expression describing the ``WHERE`` + condition of the ``DELETE`` statement; is equivalent to using the + more modern :meth:`~Delete.where()` method to specify the ``WHERE`` + clause. + + .. seealso:: + + :ref:`deletes` - SQL Expression Tutorial + + """ + return Delete(table) diff --git a/lib/sqlalchemy/sql/_elements_constructors.py b/lib/sqlalchemy/sql/_elements_constructors.py new file mode 100644 index 000000000..a8c9372e0 --- /dev/null +++ b/lib/sqlalchemy/sql/_elements_constructors.py @@ -0,0 +1,1637 @@ +# sql/_elements_constructors.py +# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors +# <see AUTHORS file> +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php + +import typing +from typing import Any +from typing import cast as _typing_cast +from typing import Optional +from typing import overload +from typing import Type +from typing import TypeVar +from typing import Union + +from . import coercions +from . import operators +from . import roles +from .base import NO_ARG +from .coercions import _document_text_coercion +from .elements import BindParameter +from .elements import BooleanClauseList +from .elements import Case +from .elements import Cast +from .elements import CollationClause +from .elements import CollectionAggregate +from .elements import ColumnClause +from .elements import ColumnElement +from .elements import Extract +from .elements import False_ +from .elements import FunctionFilter +from .elements import Label +from .elements import Null +from .elements import Over +from .elements import TextClause +from .elements import True_ +from .elements import Tuple +from .elements import TypeCoerce +from .elements import UnaryExpression +from .elements import WithinGroup + +if typing.TYPE_CHECKING: + from elements import BinaryExpression + + from . import sqltypes + from .functions import FunctionElement + from .selectable import FromClause + from .type_api import TypeEngine + +_T = TypeVar("_T") + + +def all_(expr): + """Produce an ALL expression. + + For dialects such as that of PostgreSQL, this operator applies + to usage of the :class:`_types.ARRAY` datatype, for that of + MySQL, it may apply to a subquery. e.g.:: + + # renders on PostgreSQL: + # '5 = ALL (somearray)' + expr = 5 == all_(mytable.c.somearray) + + # renders on MySQL: + # '5 = ALL (SELECT value FROM table)' + expr = 5 == all_(select(table.c.value)) + + Comparison to NULL may work using ``None``:: + + None == all_(mytable.c.somearray) + + The any_() / all_() operators also feature a special "operand flipping" + behavior such that if any_() / all_() are used on the left side of a + comparison using a standalone operator such as ``==``, ``!=``, etc. + (not including operator methods such as + :meth:`_sql.ColumnOperators.is_`) the rendered expression is flipped:: + + # would render '5 = ALL (column)` + all_(mytable.c.column) == 5 + + Or with ``None``, which note will not perform + the usual step of rendering "IS" as is normally the case for NULL:: + + # would render 'NULL = ALL(somearray)' + all_(mytable.c.somearray) == None + + .. versionchanged:: 1.4.26 repaired the use of any_() / all_() + comparing to NULL on the right side to be flipped to the left. + + The column-level :meth:`_sql.ColumnElement.all_` method (not to be + confused with :class:`_types.ARRAY` level + :meth:`_types.ARRAY.Comparator.all`) is shorthand for + ``all_(col)``:: + + 5 == mytable.c.somearray.all_() + + .. seealso:: + + :meth:`_sql.ColumnOperators.all_` + + :func:`_expression.any_` + + """ + return CollectionAggregate._create_all(expr) + + +def and_(*clauses): + r"""Produce a conjunction of expressions joined by ``AND``. + + E.g.:: + + from sqlalchemy import and_ + + stmt = select(users_table).where( + and_( + users_table.c.name == 'wendy', + users_table.c.enrolled == True + ) + ) + + The :func:`.and_` conjunction is also available using the + Python ``&`` operator (though note that compound expressions + need to be parenthesized in order to function with Python + operator precedence behavior):: + + stmt = select(users_table).where( + (users_table.c.name == 'wendy') & + (users_table.c.enrolled == True) + ) + + The :func:`.and_` operation is also implicit in some cases; + the :meth:`_expression.Select.where` + method for example can be invoked multiple + times against a statement, which will have the effect of each + clause being combined using :func:`.and_`:: + + stmt = select(users_table).\ + where(users_table.c.name == 'wendy').\ + where(users_table.c.enrolled == True) + + The :func:`.and_` construct must be given at least one positional + argument in order to be valid; a :func:`.and_` construct with no + arguments is ambiguous. To produce an "empty" or dynamically + generated :func:`.and_` expression, from a given list of expressions, + a "default" element of ``True`` should be specified:: + + criteria = and_(True, *expressions) + + The above expression will compile to SQL as the expression ``true`` + or ``1 = 1``, depending on backend, if no other expressions are + present. If expressions are present, then the ``True`` value is + ignored as it does not affect the outcome of an AND expression that + has other elements. + + .. deprecated:: 1.4 The :func:`.and_` element now requires that at + least one argument is passed; creating the :func:`.and_` construct + with no arguments is deprecated, and will emit a deprecation warning + while continuing to produce a blank SQL string. + + .. seealso:: + + :func:`.or_` + + """ + return BooleanClauseList.and_(*clauses) + + +def any_(expr): + """Produce an ANY expression. + + For dialects such as that of PostgreSQL, this operator applies + to usage of the :class:`_types.ARRAY` datatype, for that of + MySQL, it may apply to a subquery. e.g.:: + + # renders on PostgreSQL: + # '5 = ANY (somearray)' + expr = 5 == any_(mytable.c.somearray) + + # renders on MySQL: + # '5 = ANY (SELECT value FROM table)' + expr = 5 == any_(select(table.c.value)) + + Comparison to NULL may work using ``None`` or :func:`_sql.null`:: + + None == any_(mytable.c.somearray) + + The any_() / all_() operators also feature a special "operand flipping" + behavior such that if any_() / all_() are used on the left side of a + comparison using a standalone operator such as ``==``, ``!=``, etc. + (not including operator methods such as + :meth:`_sql.ColumnOperators.is_`) the rendered expression is flipped:: + + # would render '5 = ANY (column)` + any_(mytable.c.column) == 5 + + Or with ``None``, which note will not perform + the usual step of rendering "IS" as is normally the case for NULL:: + + # would render 'NULL = ANY(somearray)' + any_(mytable.c.somearray) == None + + .. versionchanged:: 1.4.26 repaired the use of any_() / all_() + comparing to NULL on the right side to be flipped to the left. + + The column-level :meth:`_sql.ColumnElement.any_` method (not to be + confused with :class:`_types.ARRAY` level + :meth:`_types.ARRAY.Comparator.any`) is shorthand for + ``any_(col)``:: + + 5 = mytable.c.somearray.any_() + + .. seealso:: + + :meth:`_sql.ColumnOperators.any_` + + :func:`_expression.all_` + + """ + return CollectionAggregate._create_any(expr) + + +def asc(column): + """Produce an ascending ``ORDER BY`` clause element. + + e.g.:: + + from sqlalchemy import asc + stmt = select(users_table).order_by(asc(users_table.c.name)) + + will produce SQL as:: + + SELECT id, name FROM user ORDER BY name ASC + + The :func:`.asc` function is a standalone version of the + :meth:`_expression.ColumnElement.asc` + method available on all SQL expressions, + e.g.:: + + + stmt = select(users_table).order_by(users_table.c.name.asc()) + + :param column: A :class:`_expression.ColumnElement` (e.g. + scalar SQL expression) + with which to apply the :func:`.asc` operation. + + .. seealso:: + + :func:`.desc` + + :func:`.nulls_first` + + :func:`.nulls_last` + + :meth:`_expression.Select.order_by` + + """ + return UnaryExpression._create_asc(column) + + +def collate(expression, collation): + """Return the clause ``expression COLLATE collation``. + + e.g.:: + + collate(mycolumn, 'utf8_bin') + + produces:: + + mycolumn COLLATE utf8_bin + + The collation expression is also quoted if it is a case sensitive + identifier, e.g. contains uppercase characters. + + .. versionchanged:: 1.2 quoting is automatically applied to COLLATE + expressions if they are case sensitive. + + """ + return CollationClause._create_collation_expression(expression, collation) + + +def between(expr, lower_bound, upper_bound, symmetric=False): + """Produce a ``BETWEEN`` predicate clause. + + E.g.:: + + from sqlalchemy import between + stmt = select(users_table).where(between(users_table.c.id, 5, 7)) + + Would produce SQL resembling:: + + SELECT id, name FROM user WHERE id BETWEEN :id_1 AND :id_2 + + The :func:`.between` function is a standalone version of the + :meth:`_expression.ColumnElement.between` method available on all + SQL expressions, as in:: + + stmt = select(users_table).where(users_table.c.id.between(5, 7)) + + All arguments passed to :func:`.between`, including the left side + column expression, are coerced from Python scalar values if a + the value is not a :class:`_expression.ColumnElement` subclass. + For example, + three fixed values can be compared as in:: + + print(between(5, 3, 7)) + + Which would produce:: + + :param_1 BETWEEN :param_2 AND :param_3 + + :param expr: a column expression, typically a + :class:`_expression.ColumnElement` + instance or alternatively a Python scalar expression to be coerced + into a column expression, serving as the left side of the ``BETWEEN`` + expression. + + :param lower_bound: a column or Python scalar expression serving as the + lower bound of the right side of the ``BETWEEN`` expression. + + :param upper_bound: a column or Python scalar expression serving as the + upper bound of the right side of the ``BETWEEN`` expression. + + :param symmetric: if True, will render " BETWEEN SYMMETRIC ". Note + that not all databases support this syntax. + + .. versionadded:: 0.9.5 + + .. seealso:: + + :meth:`_expression.ColumnElement.between` + + """ + expr = coercions.expect(roles.ExpressionElementRole, expr) + return expr.between(lower_bound, upper_bound, symmetric=symmetric) + + +def outparam(key, type_=None): + """Create an 'OUT' parameter for usage in functions (stored procedures), + for databases which support them. + + The ``outparam`` can be used like a regular function parameter. + The "output" value will be available from the + :class:`~sqlalchemy.engine.CursorResult` object via its ``out_parameters`` + attribute, which returns a dictionary containing the values. + + """ + return BindParameter(key, None, type_=type_, unique=False, isoutparam=True) + + +@overload +def not_(clause: "BinaryExpression[_T]") -> "BinaryExpression[_T]": + ... + + +@overload +def not_(clause: "ColumnElement[_T]") -> "UnaryExpression[_T]": + ... + + +def not_(clause: "ColumnElement[_T]") -> "ColumnElement[_T]": + """Return a negation of the given clause, i.e. ``NOT(clause)``. + + The ``~`` operator is also overloaded on all + :class:`_expression.ColumnElement` subclasses to produce the + same result. + + """ + + return operators.inv( + _typing_cast( + "ColumnElement[_T]", + coercions.expect(roles.ExpressionElementRole, clause), + ) + ) + + +def bindparam( + key, + value=NO_ARG, + type_: Optional[Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"]] = None, + unique=False, + required=NO_ARG, + quote=None, + callable_=None, + expanding=False, + isoutparam=False, + literal_execute=False, + _compared_to_operator=None, + _compared_to_type=None, + _is_crud=False, +) -> "BindParameter[_T]": + r"""Produce a "bound expression". + + The return value is an instance of :class:`.BindParameter`; this + is a :class:`_expression.ColumnElement` + subclass which represents a so-called + "placeholder" value in a SQL expression, the value of which is + supplied at the point at which the statement in executed against a + database connection. + + In SQLAlchemy, the :func:`.bindparam` construct has + the ability to carry along the actual value that will be ultimately + used at expression time. In this way, it serves not just as + a "placeholder" for eventual population, but also as a means of + representing so-called "unsafe" values which should not be rendered + directly in a SQL statement, but rather should be passed along + to the :term:`DBAPI` as values which need to be correctly escaped + and potentially handled for type-safety. + + When using :func:`.bindparam` explicitly, the use case is typically + one of traditional deferment of parameters; the :func:`.bindparam` + construct accepts a name which can then be referred to at execution + time:: + + from sqlalchemy import bindparam + + stmt = select(users_table).\ + where(users_table.c.name == bindparam('username')) + + The above statement, when rendered, will produce SQL similar to:: + + SELECT id, name FROM user WHERE name = :username + + In order to populate the value of ``:username`` above, the value + would typically be applied at execution time to a method + like :meth:`_engine.Connection.execute`:: + + result = connection.execute(stmt, username='wendy') + + Explicit use of :func:`.bindparam` is also common when producing + UPDATE or DELETE statements that are to be invoked multiple times, + where the WHERE criterion of the statement is to change on each + invocation, such as:: + + stmt = (users_table.update(). + where(user_table.c.name == bindparam('username')). + values(fullname=bindparam('fullname')) + ) + + connection.execute( + stmt, [{"username": "wendy", "fullname": "Wendy Smith"}, + {"username": "jack", "fullname": "Jack Jones"}, + ] + ) + + SQLAlchemy's Core expression system makes wide use of + :func:`.bindparam` in an implicit sense. It is typical that Python + literal values passed to virtually all SQL expression functions are + coerced into fixed :func:`.bindparam` constructs. For example, given + a comparison operation such as:: + + expr = users_table.c.name == 'Wendy' + + The above expression will produce a :class:`.BinaryExpression` + construct, where the left side is the :class:`_schema.Column` object + representing the ``name`` column, and the right side is a + :class:`.BindParameter` representing the literal value:: + + print(repr(expr.right)) + BindParameter('%(4327771088 name)s', 'Wendy', type_=String()) + + The expression above will render SQL such as:: + + user.name = :name_1 + + Where the ``:name_1`` parameter name is an anonymous name. The + actual string ``Wendy`` is not in the rendered string, but is carried + along where it is later used within statement execution. If we + invoke a statement like the following:: + + stmt = select(users_table).where(users_table.c.name == 'Wendy') + result = connection.execute(stmt) + + We would see SQL logging output as:: + + SELECT "user".id, "user".name + FROM "user" + WHERE "user".name = %(name_1)s + {'name_1': 'Wendy'} + + Above, we see that ``Wendy`` is passed as a parameter to the database, + while the placeholder ``:name_1`` is rendered in the appropriate form + for the target database, in this case the PostgreSQL database. + + Similarly, :func:`.bindparam` is invoked automatically when working + with :term:`CRUD` statements as far as the "VALUES" portion is + concerned. The :func:`_expression.insert` construct produces an + ``INSERT`` expression which will, at statement execution time, generate + bound placeholders based on the arguments passed, as in:: + + stmt = users_table.insert() + result = connection.execute(stmt, name='Wendy') + + The above will produce SQL output as:: + + INSERT INTO "user" (name) VALUES (%(name)s) + {'name': 'Wendy'} + + The :class:`_expression.Insert` construct, at + compilation/execution time, rendered a single :func:`.bindparam` + mirroring the column name ``name`` as a result of the single ``name`` + parameter we passed to the :meth:`_engine.Connection.execute` method. + + :param key: + the key (e.g. the name) for this bind param. + Will be used in the generated + SQL statement for dialects that use named parameters. This + value may be modified when part of a compilation operation, + if other :class:`BindParameter` objects exist with the same + key, or if its length is too long and truncation is + required. + + :param value: + Initial value for this bind param. Will be used at statement + execution time as the value for this parameter passed to the + DBAPI, if no other value is indicated to the statement execution + method for this particular parameter name. Defaults to ``None``. + + :param callable\_: + A callable function that takes the place of "value". The function + will be called at statement execution time to determine the + ultimate value. Used for scenarios where the actual bind + value cannot be determined at the point at which the clause + construct is created, but embedded bind values are still desirable. + + :param type\_: + A :class:`.TypeEngine` class or instance representing an optional + datatype for this :func:`.bindparam`. If not passed, a type + may be determined automatically for the bind, based on the given + value; for example, trivial Python types such as ``str``, + ``int``, ``bool`` + may result in the :class:`.String`, :class:`.Integer` or + :class:`.Boolean` types being automatically selected. + + The type of a :func:`.bindparam` is significant especially in that + the type will apply pre-processing to the value before it is + passed to the database. For example, a :func:`.bindparam` which + refers to a datetime value, and is specified as holding the + :class:`.DateTime` type, may apply conversion needed to the + value (such as stringification on SQLite) before passing the value + to the database. + + :param unique: + if True, the key name of this :class:`.BindParameter` will be + modified if another :class:`.BindParameter` of the same name + already has been located within the containing + expression. This flag is used generally by the internals + when producing so-called "anonymous" bound expressions, it + isn't generally applicable to explicitly-named :func:`.bindparam` + constructs. + + :param required: + If ``True``, a value is required at execution time. If not passed, + it defaults to ``True`` if neither :paramref:`.bindparam.value` + or :paramref:`.bindparam.callable` were passed. If either of these + parameters are present, then :paramref:`.bindparam.required` + defaults to ``False``. + + :param quote: + True if this parameter name requires quoting and is not + currently known as a SQLAlchemy reserved word; this currently + only applies to the Oracle backend, where bound names must + sometimes be quoted. + + :param isoutparam: + if True, the parameter should be treated like a stored procedure + "OUT" parameter. This applies to backends such as Oracle which + support OUT parameters. + + :param expanding: + if True, this parameter will be treated as an "expanding" parameter + at execution time; the parameter value is expected to be a sequence, + rather than a scalar value, and the string SQL statement will + be transformed on a per-execution basis to accommodate the sequence + with a variable number of parameter slots passed to the DBAPI. + This is to allow statement caching to be used in conjunction with + an IN clause. + + .. seealso:: + + :meth:`.ColumnOperators.in_` + + :ref:`baked_in` - with baked queries + + .. note:: The "expanding" feature does not support "executemany"- + style parameter sets. + + .. versionadded:: 1.2 + + .. versionchanged:: 1.3 the "expanding" bound parameter feature now + supports empty lists. + + + .. seealso:: + + :ref:`coretutorial_bind_param` + + :ref:`coretutorial_insert_expressions` + + :func:`.outparam` + + :param literal_execute: + if True, the bound parameter will be rendered in the compile phase + with a special "POSTCOMPILE" token, and the SQLAlchemy compiler will + render the final value of the parameter into the SQL statement at + statement execution time, omitting the value from the parameter + dictionary / list passed to DBAPI ``cursor.execute()``. This + produces a similar effect as that of using the ``literal_binds``, + compilation flag, however takes place as the statement is sent to + the DBAPI ``cursor.execute()`` method, rather than when the statement + is compiled. The primary use of this + capability is for rendering LIMIT / OFFSET clauses for database + drivers that can't accommodate for bound parameters in these + contexts, while allowing SQL constructs to be cacheable at the + compilation level. + + .. versionadded:: 1.4 Added "post compile" bound parameters + + .. seealso:: + + :ref:`change_4808`. + + """ + return BindParameter( + key, + value, + type_, + unique, + required, + quote, + callable_, + expanding, + isoutparam, + literal_execute, + _compared_to_operator, + _compared_to_type, + _is_crud, + ) + + +def case(*whens, value=None, else_=None) -> "Case[Any]": + r"""Produce a ``CASE`` expression. + + The ``CASE`` construct in SQL is a conditional object that + acts somewhat analogously to an "if/then" construct in other + languages. It returns an instance of :class:`.Case`. + + :func:`.case` in its usual form is passed a series of "when" + constructs, that is, a list of conditions and results as tuples:: + + from sqlalchemy import case + + stmt = select(users_table).\ + where( + case( + (users_table.c.name == 'wendy', 'W'), + (users_table.c.name == 'jack', 'J'), + else_='E' + ) + ) + + The above statement will produce SQL resembling:: + + SELECT id, name FROM user + WHERE CASE + WHEN (name = :name_1) THEN :param_1 + WHEN (name = :name_2) THEN :param_2 + ELSE :param_3 + END + + When simple equality expressions of several values against a single + parent column are needed, :func:`.case` also has a "shorthand" format + used via the + :paramref:`.case.value` parameter, which is passed a column + expression to be compared. In this form, the :paramref:`.case.whens` + parameter is passed as a dictionary containing expressions to be + compared against keyed to result expressions. The statement below is + equivalent to the preceding statement:: + + stmt = select(users_table).\ + where( + case( + {"wendy": "W", "jack": "J"}, + value=users_table.c.name, + else_='E' + ) + ) + + The values which are accepted as result values in + :paramref:`.case.whens` as well as with :paramref:`.case.else_` are + coerced from Python literals into :func:`.bindparam` constructs. + SQL expressions, e.g. :class:`_expression.ColumnElement` constructs, + are accepted + as well. To coerce a literal string expression into a constant + expression rendered inline, use the :func:`_expression.literal_column` + construct, + as in:: + + from sqlalchemy import case, literal_column + + case( + ( + orderline.c.qty > 100, + literal_column("'greaterthan100'") + ), + ( + orderline.c.qty > 10, + literal_column("'greaterthan10'") + ), + else_=literal_column("'lessthan10'") + ) + + The above will render the given constants without using bound + parameters for the result values (but still for the comparison + values), as in:: + + CASE + WHEN (orderline.qty > :qty_1) THEN 'greaterthan100' + WHEN (orderline.qty > :qty_2) THEN 'greaterthan10' + ELSE 'lessthan10' + END + + :param \*whens: The criteria to be compared against, + :paramref:`.case.whens` accepts two different forms, based on + whether or not :paramref:`.case.value` is used. + + .. versionchanged:: 1.4 the :func:`_sql.case` + function now accepts the series of WHEN conditions positionally + + In the first form, it accepts a list of 2-tuples; each 2-tuple + consists of ``(<sql expression>, <value>)``, where the SQL + expression is a boolean expression and "value" is a resulting value, + e.g.:: + + case( + (users_table.c.name == 'wendy', 'W'), + (users_table.c.name == 'jack', 'J') + ) + + In the second form, it accepts a Python dictionary of comparison + values mapped to a resulting value; this form requires + :paramref:`.case.value` to be present, and values will be compared + using the ``==`` operator, e.g.:: + + case( + {"wendy": "W", "jack": "J"}, + value=users_table.c.name + ) + + :param value: An optional SQL expression which will be used as a + fixed "comparison point" for candidate values within a dictionary + passed to :paramref:`.case.whens`. + + :param else\_: An optional SQL expression which will be the evaluated + result of the ``CASE`` construct if all expressions within + :paramref:`.case.whens` evaluate to false. When omitted, most + databases will produce a result of NULL if none of the "when" + expressions evaluate to true. + + + """ + return Case(*whens, value=value, else_=else_) + + +def cast( + expression: ColumnElement, + type_: Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"], +) -> "Cast[_T]": + r"""Produce a ``CAST`` expression. + + :func:`.cast` returns an instance of :class:`.Cast`. + + E.g.:: + + from sqlalchemy import cast, Numeric + + stmt = select(cast(product_table.c.unit_price, Numeric(10, 4))) + + The above statement will produce SQL resembling:: + + SELECT CAST(unit_price AS NUMERIC(10, 4)) FROM product + + The :func:`.cast` function performs two distinct functions when + used. The first is that it renders the ``CAST`` expression within + the resulting SQL string. The second is that it associates the given + type (e.g. :class:`.TypeEngine` class or instance) with the column + expression on the Python side, which means the expression will take + on the expression operator behavior associated with that type, + as well as the bound-value handling and result-row-handling behavior + of the type. + + .. versionchanged:: 0.9.0 :func:`.cast` now applies the given type + to the expression such that it takes effect on the bound-value, + e.g. the Python-to-database direction, in addition to the + result handling, e.g. database-to-Python, direction. + + An alternative to :func:`.cast` is the :func:`.type_coerce` function. + This function performs the second task of associating an expression + with a specific type, but does not render the ``CAST`` expression + in SQL. + + :param expression: A SQL expression, such as a + :class:`_expression.ColumnElement` + expression or a Python string which will be coerced into a bound + literal value. + + :param type\_: A :class:`.TypeEngine` class or instance indicating + the type to which the ``CAST`` should apply. + + .. seealso:: + + :ref:`coretutorial_casts` + + :func:`.type_coerce` - an alternative to CAST that coerces the type + on the Python side only, which is often sufficient to generate the + correct SQL and data coercion. + + + """ + return Cast(expression, type_) + + +def column( + text: str, + type_: Optional[Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"]] = None, + is_literal: bool = False, + _selectable: Optional["FromClause"] = None, +) -> "ColumnClause[_T]": + """Produce a :class:`.ColumnClause` object. + + The :class:`.ColumnClause` is a lightweight analogue to the + :class:`_schema.Column` class. The :func:`_expression.column` + function can + be invoked with just a name alone, as in:: + + from sqlalchemy import column + + id, name = column("id"), column("name") + stmt = select(id, name).select_from("user") + + The above statement would produce SQL like:: + + SELECT id, name FROM user + + Once constructed, :func:`_expression.column` + may be used like any other SQL + expression element such as within :func:`_expression.select` + constructs:: + + from sqlalchemy.sql import column + + id, name = column("id"), column("name") + stmt = select(id, name).select_from("user") + + The text handled by :func:`_expression.column` + is assumed to be handled + like the name of a database column; if the string contains mixed case, + special characters, or matches a known reserved word on the target + backend, the column expression will render using the quoting + behavior determined by the backend. To produce a textual SQL + expression that is rendered exactly without any quoting, + use :func:`_expression.literal_column` instead, + or pass ``True`` as the + value of :paramref:`_expression.column.is_literal`. Additionally, + full SQL + statements are best handled using the :func:`_expression.text` + construct. + + :func:`_expression.column` can be used in a table-like + fashion by combining it with the :func:`.table` function + (which is the lightweight analogue to :class:`_schema.Table` + ) to produce + a working table construct with minimal boilerplate:: + + from sqlalchemy import table, column, select + + user = table("user", + column("id"), + column("name"), + column("description"), + ) + + stmt = select(user.c.description).where(user.c.name == 'wendy') + + A :func:`_expression.column` / :func:`.table` + construct like that illustrated + above can be created in an + ad-hoc fashion and is not associated with any + :class:`_schema.MetaData`, DDL, or events, unlike its + :class:`_schema.Table` counterpart. + + .. versionchanged:: 1.0.0 :func:`_expression.column` can now + be imported from the plain ``sqlalchemy`` namespace like any + other SQL element. + + :param text: the text of the element. + + :param type: :class:`_types.TypeEngine` object which can associate + this :class:`.ColumnClause` with a type. + + :param is_literal: if True, the :class:`.ColumnClause` is assumed to + be an exact expression that will be delivered to the output with no + quoting rules applied regardless of case sensitive settings. the + :func:`_expression.literal_column()` function essentially invokes + :func:`_expression.column` while passing ``is_literal=True``. + + .. seealso:: + + :class:`_schema.Column` + + :func:`_expression.literal_column` + + :func:`.table` + + :func:`_expression.text` + + :ref:`sqlexpression_literal_column` + + """ + self = ColumnClause.__new__(ColumnClause) + self.__init__(text, type_, is_literal, _selectable) + return self + + +def desc(column): + """Produce a descending ``ORDER BY`` clause element. + + e.g.:: + + from sqlalchemy import desc + + stmt = select(users_table).order_by(desc(users_table.c.name)) + + will produce SQL as:: + + SELECT id, name FROM user ORDER BY name DESC + + The :func:`.desc` function is a standalone version of the + :meth:`_expression.ColumnElement.desc` + method available on all SQL expressions, + e.g.:: + + + stmt = select(users_table).order_by(users_table.c.name.desc()) + + :param column: A :class:`_expression.ColumnElement` (e.g. + scalar SQL expression) + with which to apply the :func:`.desc` operation. + + .. seealso:: + + :func:`.asc` + + :func:`.nulls_first` + + :func:`.nulls_last` + + :meth:`_expression.Select.order_by` + + """ + return UnaryExpression._create_desc(column) + + +def distinct(expr): + """Produce an column-expression-level unary ``DISTINCT`` clause. + + This applies the ``DISTINCT`` keyword to an individual column + expression, and is typically contained within an aggregate function, + as in:: + + from sqlalchemy import distinct, func + stmt = select(func.count(distinct(users_table.c.name))) + + The above would produce an expression resembling:: + + SELECT COUNT(DISTINCT name) FROM user + + The :func:`.distinct` function is also available as a column-level + method, e.g. :meth:`_expression.ColumnElement.distinct`, as in:: + + stmt = select(func.count(users_table.c.name.distinct())) + + The :func:`.distinct` operator is different from the + :meth:`_expression.Select.distinct` method of + :class:`_expression.Select`, + which produces a ``SELECT`` statement + with ``DISTINCT`` applied to the result set as a whole, + e.g. a ``SELECT DISTINCT`` expression. See that method for further + information. + + .. seealso:: + + :meth:`_expression.ColumnElement.distinct` + + :meth:`_expression.Select.distinct` + + :data:`.func` + + """ + return UnaryExpression._create_distinct(expr) + + +def extract(field: str, expr: ColumnElement) -> "Extract[sqltypes.Integer]": + """Return a :class:`.Extract` construct. + + This is typically available as :func:`.extract` + as well as ``func.extract`` from the + :data:`.func` namespace. + + :param field: The field to extract. + + :param expr: A column or Python scalar expression serving as the + right side of the ``EXTRACT`` expression. + + E.g.:: + + from sqlalchemy import extract + from sqlalchemy import table, column + + logged_table = table("user", + column("id"), + column("date_created"), + ) + + stmt = select(logged_table.c.id).where( + extract("YEAR", logged_table.c.date_created) == 2021 + ) + + In the above example, the statement is used to select ids from the + database where the ``YEAR`` component matches a specific value. + + Similarly, one can also select an extracted component:: + + stmt = select( + extract("YEAR", logged_table.c.date_created) + ).where(logged_table.c.id == 1) + + The implementation of ``EXTRACT`` may vary across database backends. + Users are reminded to consult their database documentation. + """ + return Extract(field, expr) + + +def false(): + """Return a :class:`.False_` construct. + + E.g.:: + + >>> from sqlalchemy import false + >>> print(select(t.c.x).where(false())) + SELECT x FROM t WHERE false + + A backend which does not support true/false constants will render as + an expression against 1 or 0:: + + >>> print(select(t.c.x).where(false())) + SELECT x FROM t WHERE 0 = 1 + + The :func:`.true` and :func:`.false` constants also feature + "short circuit" operation within an :func:`.and_` or :func:`.or_` + conjunction:: + + >>> print(select(t.c.x).where(or_(t.c.x > 5, true()))) + SELECT x FROM t WHERE true + + >>> print(select(t.c.x).where(and_(t.c.x > 5, false()))) + SELECT x FROM t WHERE false + + .. versionchanged:: 0.9 :func:`.true` and :func:`.false` feature + better integrated behavior within conjunctions and on dialects + that don't support true/false constants. + + .. seealso:: + + :func:`.true` + + """ + + return False_._instance() + + +def funcfilter(func, *criterion) -> "FunctionFilter": + """Produce a :class:`.FunctionFilter` object against a function. + + Used against aggregate and window functions, + for database backends that support the "FILTER" clause. + + E.g.:: + + from sqlalchemy import funcfilter + funcfilter(func.count(1), MyClass.name == 'some name') + + Would produce "COUNT(1) FILTER (WHERE myclass.name = 'some name')". + + This function is also available from the :data:`~.expression.func` + construct itself via the :meth:`.FunctionElement.filter` method. + + .. versionadded:: 1.0.0 + + .. seealso:: + + :ref:`tutorial_functions_within_group` - in the + :ref:`unified_tutorial` + + :meth:`.FunctionElement.filter` + + """ + return FunctionFilter(func, *criterion) + + +def label( + name: str, + element: ColumnElement[_T], + type_: Optional[Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"]] = None, +) -> "Label[_T]": + """Return a :class:`Label` object for the + given :class:`_expression.ColumnElement`. + + A label changes the name of an element in the columns clause of a + ``SELECT`` statement, typically via the ``AS`` SQL keyword. + + This functionality is more conveniently available via the + :meth:`_expression.ColumnElement.label` method on + :class:`_expression.ColumnElement`. + + :param name: label name + + :param obj: a :class:`_expression.ColumnElement`. + + """ + return Label(name, element, type_) + + +def null(): + """Return a constant :class:`.Null` construct.""" + + return Null._instance() + + +def nulls_first(column): + """Produce the ``NULLS FIRST`` modifier for an ``ORDER BY`` expression. + + :func:`.nulls_first` is intended to modify the expression produced + by :func:`.asc` or :func:`.desc`, and indicates how NULL values + should be handled when they are encountered during ordering:: + + + from sqlalchemy import desc, nulls_first + + stmt = select(users_table).order_by( + nulls_first(desc(users_table.c.name))) + + The SQL expression from the above would resemble:: + + SELECT id, name FROM user ORDER BY name DESC NULLS FIRST + + Like :func:`.asc` and :func:`.desc`, :func:`.nulls_first` is typically + invoked from the column expression itself using + :meth:`_expression.ColumnElement.nulls_first`, + rather than as its standalone + function version, as in:: + + stmt = select(users_table).order_by( + users_table.c.name.desc().nulls_first()) + + .. versionchanged:: 1.4 :func:`.nulls_first` is renamed from + :func:`.nullsfirst` in previous releases. + The previous name remains available for backwards compatibility. + + .. seealso:: + + :func:`.asc` + + :func:`.desc` + + :func:`.nulls_last` + + :meth:`_expression.Select.order_by` + + """ + return UnaryExpression._create_nulls_first(column) + + +def nulls_last(column): + """Produce the ``NULLS LAST`` modifier for an ``ORDER BY`` expression. + + :func:`.nulls_last` is intended to modify the expression produced + by :func:`.asc` or :func:`.desc`, and indicates how NULL values + should be handled when they are encountered during ordering:: + + + from sqlalchemy import desc, nulls_last + + stmt = select(users_table).order_by( + nulls_last(desc(users_table.c.name))) + + The SQL expression from the above would resemble:: + + SELECT id, name FROM user ORDER BY name DESC NULLS LAST + + Like :func:`.asc` and :func:`.desc`, :func:`.nulls_last` is typically + invoked from the column expression itself using + :meth:`_expression.ColumnElement.nulls_last`, + rather than as its standalone + function version, as in:: + + stmt = select(users_table).order_by( + users_table.c.name.desc().nulls_last()) + + .. versionchanged:: 1.4 :func:`.nulls_last` is renamed from + :func:`.nullslast` in previous releases. + The previous name remains available for backwards compatibility. + + .. seealso:: + + :func:`.asc` + + :func:`.desc` + + :func:`.nulls_first` + + :meth:`_expression.Select.order_by` + + """ + return UnaryExpression._create_nulls_last(column) + + +def or_(*clauses): + """Produce a conjunction of expressions joined by ``OR``. + + E.g.:: + + from sqlalchemy import or_ + + stmt = select(users_table).where( + or_( + users_table.c.name == 'wendy', + users_table.c.name == 'jack' + ) + ) + + The :func:`.or_` conjunction is also available using the + Python ``|`` operator (though note that compound expressions + need to be parenthesized in order to function with Python + operator precedence behavior):: + + stmt = select(users_table).where( + (users_table.c.name == 'wendy') | + (users_table.c.name == 'jack') + ) + + The :func:`.or_` construct must be given at least one positional + argument in order to be valid; a :func:`.or_` construct with no + arguments is ambiguous. To produce an "empty" or dynamically + generated :func:`.or_` expression, from a given list of expressions, + a "default" element of ``False`` should be specified:: + + or_criteria = or_(False, *expressions) + + The above expression will compile to SQL as the expression ``false`` + or ``0 = 1``, depending on backend, if no other expressions are + present. If expressions are present, then the ``False`` value is + ignored as it does not affect the outcome of an OR expression which + has other elements. + + .. deprecated:: 1.4 The :func:`.or_` element now requires that at + least one argument is passed; creating the :func:`.or_` construct + with no arguments is deprecated, and will emit a deprecation warning + while continuing to produce a blank SQL string. + + .. seealso:: + + :func:`.and_` + + """ + return BooleanClauseList.or_(*clauses) + + +def over( + element: "FunctionElement[_T]", + partition_by=None, + order_by=None, + range_=None, + rows=None, +) -> "Over[_T]": + r"""Produce an :class:`.Over` object against a function. + + Used against aggregate or so-called "window" functions, + for database backends that support window functions. + + :func:`_expression.over` is usually called using + the :meth:`.FunctionElement.over` method, e.g.:: + + func.row_number().over(order_by=mytable.c.some_column) + + Would produce:: + + ROW_NUMBER() OVER(ORDER BY some_column) + + Ranges are also possible using the :paramref:`.expression.over.range_` + and :paramref:`.expression.over.rows` parameters. These + mutually-exclusive parameters each accept a 2-tuple, which contains + a combination of integers and None:: + + func.row_number().over( + order_by=my_table.c.some_column, range_=(None, 0)) + + The above would produce:: + + ROW_NUMBER() OVER(ORDER BY some_column + RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) + + A value of ``None`` indicates "unbounded", a + value of zero indicates "current row", and negative / positive + integers indicate "preceding" and "following": + + * RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING:: + + func.row_number().over(order_by='x', range_=(-5, 10)) + + * ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:: + + func.row_number().over(order_by='x', rows=(None, 0)) + + * RANGE BETWEEN 2 PRECEDING AND UNBOUNDED FOLLOWING:: + + func.row_number().over(order_by='x', range_=(-2, None)) + + * RANGE BETWEEN 1 FOLLOWING AND 3 FOLLOWING:: + + func.row_number().over(order_by='x', range_=(1, 3)) + + .. versionadded:: 1.1 support for RANGE / ROWS within a window + + + :param element: a :class:`.FunctionElement`, :class:`.WithinGroup`, + or other compatible construct. + :param partition_by: a column element or string, or a list + of such, that will be used as the PARTITION BY clause + of the OVER construct. + :param order_by: a column element or string, or a list + of such, that will be used as the ORDER BY clause + of the OVER construct. + :param range\_: optional range clause for the window. This is a + tuple value which can contain integer values or ``None``, + and will render a RANGE BETWEEN PRECEDING / FOLLOWING clause. + + .. versionadded:: 1.1 + + :param rows: optional rows clause for the window. This is a tuple + value which can contain integer values or None, and will render + a ROWS BETWEEN PRECEDING / FOLLOWING clause. + + .. versionadded:: 1.1 + + This function is also available from the :data:`~.expression.func` + construct itself via the :meth:`.FunctionElement.over` method. + + .. seealso:: + + :ref:`tutorial_window_functions` - in the :ref:`unified_tutorial` + + :data:`.expression.func` + + :func:`_expression.within_group` + + """ + return Over(element, partition_by, order_by, range_, rows) + + +@_document_text_coercion("text", ":func:`.text`", ":paramref:`.text.text`") +def text(text): + r"""Construct a new :class:`_expression.TextClause` clause, + representing + a textual SQL string directly. + + E.g.:: + + from sqlalchemy import text + + t = text("SELECT * FROM users") + result = connection.execute(t) + + The advantages :func:`_expression.text` + provides over a plain string are + backend-neutral support for bind parameters, per-statement + execution options, as well as + bind parameter and result-column typing behavior, allowing + SQLAlchemy type constructs to play a role when executing + a statement that is specified literally. The construct can also + be provided with a ``.c`` collection of column elements, allowing + it to be embedded in other SQL expression constructs as a subquery. + + Bind parameters are specified by name, using the format ``:name``. + E.g.:: + + t = text("SELECT * FROM users WHERE id=:user_id") + result = connection.execute(t, user_id=12) + + For SQL statements where a colon is required verbatim, as within + an inline string, use a backslash to escape:: + + t = text("SELECT * FROM users WHERE name='\:username'") + + The :class:`_expression.TextClause` + construct includes methods which can + provide information about the bound parameters as well as the column + values which would be returned from the textual statement, assuming + it's an executable SELECT type of statement. The + :meth:`_expression.TextClause.bindparams` + method is used to provide bound + parameter detail, and :meth:`_expression.TextClause.columns` + method allows + specification of return columns including names and types:: + + t = text("SELECT * FROM users WHERE id=:user_id").\ + bindparams(user_id=7).\ + columns(id=Integer, name=String) + + for id, name in connection.execute(t): + print(id, name) + + The :func:`_expression.text` construct is used in cases when + a literal string SQL fragment is specified as part of a larger query, + such as for the WHERE clause of a SELECT statement:: + + s = select(users.c.id, users.c.name).where(text("id=:user_id")) + result = connection.execute(s, user_id=12) + + :func:`_expression.text` is also used for the construction + of a full, standalone statement using plain text. + As such, SQLAlchemy refers + to it as an :class:`.Executable` object and may be used + like any other statement passed to an ``.execute()`` method. + + :param text: + the text of the SQL statement to be created. Use ``:<param>`` + to specify bind parameters; they will be compiled to their + engine-specific format. + + .. seealso:: + + :ref:`sqlexpression_text` - in the Core tutorial + + + """ + return TextClause(text) + + +def true(): + """Return a constant :class:`.True_` construct. + + E.g.:: + + >>> from sqlalchemy import true + >>> print(select(t.c.x).where(true())) + SELECT x FROM t WHERE true + + A backend which does not support true/false constants will render as + an expression against 1 or 0:: + + >>> print(select(t.c.x).where(true())) + SELECT x FROM t WHERE 1 = 1 + + The :func:`.true` and :func:`.false` constants also feature + "short circuit" operation within an :func:`.and_` or :func:`.or_` + conjunction:: + + >>> print(select(t.c.x).where(or_(t.c.x > 5, true()))) + SELECT x FROM t WHERE true + + >>> print(select(t.c.x).where(and_(t.c.x > 5, false()))) + SELECT x FROM t WHERE false + + .. versionchanged:: 0.9 :func:`.true` and :func:`.false` feature + better integrated behavior within conjunctions and on dialects + that don't support true/false constants. + + .. seealso:: + + :func:`.false` + + """ + + return True_._instance() + + +def tuple_(*clauses: roles.ExpressionElementRole, types=None) -> "Tuple": + """Return a :class:`.Tuple`. + + Main usage is to produce a composite IN construct using + :meth:`.ColumnOperators.in_` :: + + from sqlalchemy import tuple_ + + tuple_(table.c.col1, table.c.col2).in_( + [(1, 2), (5, 12), (10, 19)] + ) + + .. versionchanged:: 1.3.6 Added support for SQLite IN tuples. + + .. warning:: + + The composite IN construct is not supported by all backends, and is + currently known to work on PostgreSQL, MySQL, and SQLite. + Unsupported backends will raise a subclass of + :class:`~sqlalchemy.exc.DBAPIError` when such an expression is + invoked. + + """ + return Tuple(*clauses, types=types) + + +def type_coerce( + expression: "ColumnElement", + type_: Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"], +) -> "TypeCoerce[_T]": + r"""Associate a SQL expression with a particular type, without rendering + ``CAST``. + + E.g.:: + + from sqlalchemy import type_coerce + + stmt = select(type_coerce(log_table.date_string, StringDateTime())) + + The above construct will produce a :class:`.TypeCoerce` object, which + does not modify the rendering in any way on the SQL side, with the + possible exception of a generated label if used in a columns clause + context:: + + SELECT date_string AS date_string FROM log + + When result rows are fetched, the ``StringDateTime`` type processor + will be applied to result rows on behalf of the ``date_string`` column. + + .. note:: the :func:`.type_coerce` construct does not render any + SQL syntax of its own, including that it does not imply + parenthesization. Please use :meth:`.TypeCoerce.self_group` + if explicit parenthesization is required. + + In order to provide a named label for the expression, use + :meth:`_expression.ColumnElement.label`:: + + stmt = select( + type_coerce(log_table.date_string, StringDateTime()).label('date') + ) + + + A type that features bound-value handling will also have that behavior + take effect when literal values or :func:`.bindparam` constructs are + passed to :func:`.type_coerce` as targets. + For example, if a type implements the + :meth:`.TypeEngine.bind_expression` + method or :meth:`.TypeEngine.bind_processor` method or equivalent, + these functions will take effect at statement compilation/execution + time when a literal value is passed, as in:: + + # bound-value handling of MyStringType will be applied to the + # literal value "some string" + stmt = select(type_coerce("some string", MyStringType)) + + When using :func:`.type_coerce` with composed expressions, note that + **parenthesis are not applied**. If :func:`.type_coerce` is being + used in an operator context where the parenthesis normally present from + CAST are necessary, use the :meth:`.TypeCoerce.self_group` method:: + + >>> some_integer = column("someint", Integer) + >>> some_string = column("somestr", String) + >>> expr = type_coerce(some_integer + 5, String) + some_string + >>> print(expr) + someint + :someint_1 || somestr + >>> expr = type_coerce(some_integer + 5, String).self_group() + some_string + >>> print(expr) + (someint + :someint_1) || somestr + + :param expression: A SQL expression, such as a + :class:`_expression.ColumnElement` + expression or a Python string which will be coerced into a bound + literal value. + + :param type\_: A :class:`.TypeEngine` class or instance indicating + the type to which the expression is coerced. + + .. seealso:: + + :ref:`coretutorial_casts` + + :func:`.cast` + + """ # noqa + return TypeCoerce(expression, type_) + + +def within_group( + element: "FunctionElement[_T]", *order_by: roles.OrderByRole +) -> "WithinGroup[_T]": + r"""Produce a :class:`.WithinGroup` object against a function. + + Used against so-called "ordered set aggregate" and "hypothetical + set aggregate" functions, including :class:`.percentile_cont`, + :class:`.rank`, :class:`.dense_rank`, etc. + + :func:`_expression.within_group` is usually called using + the :meth:`.FunctionElement.within_group` method, e.g.:: + + from sqlalchemy import within_group + stmt = select( + department.c.id, + func.percentile_cont(0.5).within_group( + department.c.salary.desc() + ) + ) + + The above statement would produce SQL similar to + ``SELECT department.id, percentile_cont(0.5) + WITHIN GROUP (ORDER BY department.salary DESC)``. + + :param element: a :class:`.FunctionElement` construct, typically + generated by :data:`~.expression.func`. + :param \*order_by: one or more column elements that will be used + as the ORDER BY clause of the WITHIN GROUP construct. + + .. versionadded:: 1.1 + + .. seealso:: + + :ref:`tutorial_functions_within_group` - in the + :ref:`unified_tutorial` + + :data:`.expression.func` + + :func:`_expression.over` + + """ + return WithinGroup(element, *order_by) diff --git a/lib/sqlalchemy/sql/_selectable_constructors.py b/lib/sqlalchemy/sql/_selectable_constructors.py new file mode 100644 index 000000000..4b67c12f0 --- /dev/null +++ b/lib/sqlalchemy/sql/_selectable_constructors.py @@ -0,0 +1,467 @@ +# sql/_selectable_constructors.py +# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors +# <see AUTHORS file> +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php + +from typing import Any +from typing import Type +from typing import Union + +from . import coercions +from . import roles +from .elements import ColumnClause +from .selectable import Alias +from .selectable import CompoundSelect +from .selectable import Exists +from .selectable import Join +from .selectable import Lateral +from .selectable import Select +from .selectable import TableClause +from .selectable import TableSample +from .selectable import Values + + +def alias(selectable, name=None, flat=False): + """Return an :class:`_expression.Alias` object. + + An :class:`_expression.Alias` represents any + :class:`_expression.FromClause` + with an alternate name assigned within SQL, typically using the ``AS`` + clause when generated, e.g. ``SELECT * FROM table AS aliasname``. + + Similar functionality is available via the + :meth:`_expression.FromClause.alias` + method available on all :class:`_expression.FromClause` subclasses. + In terms of + a SELECT object as generated from the :func:`_expression.select` + function, the :meth:`_expression.SelectBase.alias` method returns an + :class:`_expression.Alias` or similar object which represents a named, + parenthesized subquery. + + When an :class:`_expression.Alias` is created from a + :class:`_schema.Table` object, + this has the effect of the table being rendered + as ``tablename AS aliasname`` in a SELECT statement. + + For :func:`_expression.select` objects, the effect is that of + creating a named subquery, i.e. ``(select ...) AS aliasname``. + + The ``name`` parameter is optional, and provides the name + to use in the rendered SQL. If blank, an "anonymous" name + will be deterministically generated at compile time. + Deterministic means the name is guaranteed to be unique against + other constructs used in the same statement, and will also be the + same name for each successive compilation of the same statement + object. + + :param selectable: any :class:`_expression.FromClause` subclass, + such as a table, select statement, etc. + + :param name: string name to be assigned as the alias. + If ``None``, a name will be deterministically generated + at compile time. + + :param flat: Will be passed through to if the given selectable + is an instance of :class:`_expression.Join` - see + :meth:`_expression.Join.alias` + for details. + + """ + return Alias._factory(selectable, name=name, flat=flat) + + +def cte(selectable, name=None, recursive=False): + r"""Return a new :class:`_expression.CTE`, + or Common Table Expression instance. + + Please see :meth:`_expression.HasCTE.cte` for detail on CTE usage. + + """ + return coercions.expect(roles.HasCTERole, selectable).cte( + name=name, recursive=recursive + ) + + +def except_(*selects): + r"""Return an ``EXCEPT`` of multiple selectables. + + The returned object is an instance of + :class:`_expression.CompoundSelect`. + + :param \*selects: + a list of :class:`_expression.Select` instances. + + """ + return CompoundSelect._create_except(*selects) + + +def except_all(*selects): + r"""Return an ``EXCEPT ALL`` of multiple selectables. + + The returned object is an instance of + :class:`_expression.CompoundSelect`. + + :param \*selects: + a list of :class:`_expression.Select` instances. + + """ + return CompoundSelect._create_except_all(*selects) + + +def exists(__argument=None): + """Construct a new :class:`_expression.Exists` construct. + + The :func:`_sql.exists` can be invoked by itself to produce an + :class:`_sql.Exists` construct, which will accept simple WHERE + criteria:: + + exists_criteria = exists().where(table1.c.col1 == table2.c.col2) + + However, for greater flexibility in constructing the SELECT, an + existing :class:`_sql.Select` construct may be converted to an + :class:`_sql.Exists`, most conveniently by making use of the + :meth:`_sql.SelectBase.exists` method:: + + exists_criteria = ( + select(table2.c.col2). + where(table1.c.col1 == table2.c.col2). + exists() + ) + + The EXISTS criteria is then used inside of an enclosing SELECT:: + + stmt = select(table1.c.col1).where(exists_criteria) + + The above statement will then be of the form:: + + SELECT col1 FROM table1 WHERE EXISTS + (SELECT table2.col2 FROM table2 WHERE table2.col2 = table1.col1) + + .. seealso:: + + :ref:`tutorial_exists` - in the :term:`2.0 style` tutorial. + + :meth:`_sql.SelectBase.exists` - method to transform a ``SELECT`` to an + ``EXISTS`` clause. + + """ # noqa E501 + + return Exists(__argument) + + +def intersect(*selects): + r"""Return an ``INTERSECT`` of multiple selectables. + + The returned object is an instance of + :class:`_expression.CompoundSelect`. + + :param \*selects: + a list of :class:`_expression.Select` instances. + + """ + return CompoundSelect._create_intersect(*selects) + + +def intersect_all(*selects): + r"""Return an ``INTERSECT ALL`` of multiple selectables. + + The returned object is an instance of + :class:`_expression.CompoundSelect`. + + :param \*selects: + a list of :class:`_expression.Select` instances. + + + """ + return CompoundSelect._create_intersect_all(*selects) + + +def join(left, right, onclause=None, isouter=False, full=False): + """Produce a :class:`_expression.Join` object, given two + :class:`_expression.FromClause` + expressions. + + E.g.:: + + j = join(user_table, address_table, + user_table.c.id == address_table.c.user_id) + stmt = select(user_table).select_from(j) + + would emit SQL along the lines of:: + + SELECT user.id, user.name FROM user + JOIN address ON user.id = address.user_id + + Similar functionality is available given any + :class:`_expression.FromClause` object (e.g. such as a + :class:`_schema.Table`) using + the :meth:`_expression.FromClause.join` method. + + :param left: The left side of the join. + + :param right: the right side of the join; this is any + :class:`_expression.FromClause` object such as a + :class:`_schema.Table` object, and + may also be a selectable-compatible object such as an ORM-mapped + class. + + :param onclause: a SQL expression representing the ON clause of the + join. If left at ``None``, :meth:`_expression.FromClause.join` + will attempt to + join the two tables based on a foreign key relationship. + + :param isouter: if True, render a LEFT OUTER JOIN, instead of JOIN. + + :param full: if True, render a FULL OUTER JOIN, instead of JOIN. + + .. versionadded:: 1.1 + + .. seealso:: + + :meth:`_expression.FromClause.join` - method form, + based on a given left side. + + :class:`_expression.Join` - the type of object produced. + + """ + + return Join(left, right, onclause, isouter, full) + + +def lateral(selectable, name=None): + """Return a :class:`_expression.Lateral` object. + + :class:`_expression.Lateral` is an :class:`_expression.Alias` + subclass that represents + a subquery with the LATERAL keyword applied to it. + + The special behavior of a LATERAL subquery is that it appears in the + FROM clause of an enclosing SELECT, but may correlate to other + FROM clauses of that SELECT. It is a special case of subquery + only supported by a small number of backends, currently more recent + PostgreSQL versions. + + .. versionadded:: 1.1 + + .. seealso:: + + :ref:`lateral_selects` - overview of usage. + + """ + return Lateral._factory(selectable, name=name) + + +def outerjoin(left, right, onclause=None, full=False): + """Return an ``OUTER JOIN`` clause element. + + The returned object is an instance of :class:`_expression.Join`. + + Similar functionality is also available via the + :meth:`_expression.FromClause.outerjoin` method on any + :class:`_expression.FromClause`. + + :param left: The left side of the join. + + :param right: The right side of the join. + + :param onclause: Optional criterion for the ``ON`` clause, is + derived from foreign key relationships established between + left and right otherwise. + + To chain joins together, use the :meth:`_expression.FromClause.join` + or + :meth:`_expression.FromClause.outerjoin` methods on the resulting + :class:`_expression.Join` object. + + """ + return Join(left, right, onclause, isouter=True, full=full) + + +def select(*entities: Union[roles.ColumnsClauseRole, Type]) -> "Select": + r"""Construct a new :class:`_expression.Select`. + + + .. versionadded:: 1.4 - The :func:`_sql.select` function now accepts + column arguments positionally. The top-level :func:`_sql.select` + function will automatically use the 1.x or 2.x style API based on + the incoming arguments; using :func:`_future.select` from the + ``sqlalchemy.future`` module will enforce that only the 2.x style + constructor is used. + + Similar functionality is also available via the + :meth:`_expression.FromClause.select` method on any + :class:`_expression.FromClause`. + + .. seealso:: + + :ref:`coretutorial_selecting` - Core Tutorial description of + :func:`_expression.select`. + + :param \*entities: + Entities to SELECT from. For Core usage, this is typically a series + of :class:`_expression.ColumnElement` and / or + :class:`_expression.FromClause` + objects which will form the columns clause of the resulting + statement. For those objects that are instances of + :class:`_expression.FromClause` (typically :class:`_schema.Table` + or :class:`_expression.Alias` + objects), the :attr:`_expression.FromClause.c` + collection is extracted + to form a collection of :class:`_expression.ColumnElement` objects. + + This parameter will also accept :class:`_expression.TextClause` + constructs as + given, as well as ORM-mapped classes. + + """ + + return Select(*entities) + + +def table(name: str, *columns: ColumnClause, **kw: Any) -> "TableClause": + """Produce a new :class:`_expression.TableClause`. + + The object returned is an instance of + :class:`_expression.TableClause`, which + represents the "syntactical" portion of the schema-level + :class:`_schema.Table` object. + It may be used to construct lightweight table constructs. + + .. versionchanged:: 1.0.0 :func:`_expression.table` can now + be imported from the plain ``sqlalchemy`` namespace like any + other SQL element. + + + :param name: Name of the table. + + :param columns: A collection of :func:`_expression.column` constructs. + + :param schema: The schema name for this table. + + .. versionadded:: 1.3.18 :func:`_expression.table` can now + accept a ``schema`` argument. + """ + + return TableClause(name, *columns, **kw) + + +def tablesample(selectable, sampling, name=None, seed=None): + """Return a :class:`_expression.TableSample` object. + + :class:`_expression.TableSample` is an :class:`_expression.Alias` + subclass that represents + a table with the TABLESAMPLE clause applied to it. + :func:`_expression.tablesample` + is also available from the :class:`_expression.FromClause` + class via the + :meth:`_expression.FromClause.tablesample` method. + + The TABLESAMPLE clause allows selecting a randomly selected approximate + percentage of rows from a table. It supports multiple sampling methods, + most commonly BERNOULLI and SYSTEM. + + e.g.:: + + from sqlalchemy import func + + selectable = people.tablesample( + func.bernoulli(1), + name='alias', + seed=func.random()) + stmt = select(selectable.c.people_id) + + Assuming ``people`` with a column ``people_id``, the above + statement would render as:: + + SELECT alias.people_id FROM + people AS alias TABLESAMPLE bernoulli(:bernoulli_1) + REPEATABLE (random()) + + .. versionadded:: 1.1 + + :param sampling: a ``float`` percentage between 0 and 100 or + :class:`_functions.Function`. + + :param name: optional alias name + + :param seed: any real-valued SQL expression. When specified, the + REPEATABLE sub-clause is also rendered. + + """ + return TableSample._factory(selectable, sampling, name=name, seed=seed) + + +def union(*selects, **kwargs): + r"""Return a ``UNION`` of multiple selectables. + + The returned object is an instance of + :class:`_expression.CompoundSelect`. + + A similar :func:`union()` method is available on all + :class:`_expression.FromClause` subclasses. + + :param \*selects: + a list of :class:`_expression.Select` instances. + + :param \**kwargs: + available keyword arguments are the same as those of + :func:`select`. + + """ + return CompoundSelect._create_union(*selects, **kwargs) + + +def union_all(*selects): + r"""Return a ``UNION ALL`` of multiple selectables. + + The returned object is an instance of + :class:`_expression.CompoundSelect`. + + A similar :func:`union_all()` method is available on all + :class:`_expression.FromClause` subclasses. + + :param \*selects: + a list of :class:`_expression.Select` instances. + + """ + return CompoundSelect._create_union_all(*selects) + + +def values(*columns, name=None, literal_binds=False) -> "Values": + r"""Construct a :class:`_expression.Values` construct. + + The column expressions and the actual data for + :class:`_expression.Values` are given in two separate steps. The + constructor receives the column expressions typically as + :func:`_expression.column` constructs, + and the data is then passed via the + :meth:`_expression.Values.data` method as a list, + which can be called multiple + times to add more data, e.g.:: + + from sqlalchemy import column + from sqlalchemy import values + + value_expr = values( + column('id', Integer), + column('name', String), + name="my_values" + ).data( + [(1, 'name1'), (2, 'name2'), (3, 'name3')] + ) + + :param \*columns: column expressions, typically composed using + :func:`_expression.column` objects. + + :param name: the name for this VALUES construct. If omitted, the + VALUES construct will be unnamed in a SQL expression. Different + backends may have different requirements here. + + :param literal_binds: Defaults to False. Whether or not to render + the data values inline in the SQL output, rather than using bound + parameters. + + """ + return Values(*columns, literal_binds=literal_binds, name=name) diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index fe2b498c8..3bec73f7d 100644 --- a/lib/sqlalchemy/sql/coercions.py +++ b/lib/sqlalchemy/sql/coercions.py @@ -8,6 +8,14 @@ import collections.abc as collections_abc import numbers import re +import typing +from typing import Any +from typing import Callable +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 @@ -20,13 +28,24 @@ from .. import exc from .. import inspection from .. import util +if not typing.TYPE_CHECKING: + elements = None + lambdas = None + schema = None + selectable = None + traversals = None -elements = None -lambdas = None -schema = None -selectable = None -sqltypes = None -traversals = None +if typing.TYPE_CHECKING: + from . import elements + from . import lambdas + from . import schema + 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) def _is_literal(element): @@ -110,14 +129,93 @@ def _expression_collection_was_a_list(attrname, fnname, args): return args +@overload def expect( - role, - element, - apply_propagate_attrs=None, - argname=None, - post_inspect=False, - **kw, -): + 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": + ... + + +@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, + *, + apply_propagate_attrs: Optional["ClauseElement"] = None, + 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"]: if ( role.allows_lambda # note callable() will not invoke a __getattr__() method, whereas diff --git a/lib/sqlalchemy/sql/default_comparator.py b/lib/sqlalchemy/sql/default_comparator.py index 55a586285..1759e686e 100644 --- a/lib/sqlalchemy/sql/default_comparator.py +++ b/lib/sqlalchemy/sql/default_comparator.py @@ -8,6 +8,15 @@ """Default implementation of SQL comparison operations. """ +import typing +from typing import Any +from typing import Callable +from typing import Dict +from typing import NoReturn +from typing import Optional +from typing import Tuple +from typing import Type +from typing import Union from . import coercions from . import operators @@ -16,28 +25,38 @@ from . import type_api from .elements import and_ from .elements import BinaryExpression from .elements import ClauseList -from .elements import collate +from .elements import CollationClause from .elements import CollectionAggregate from .elements import False_ from .elements import Null from .elements import or_ from .elements import True_ from .elements import UnaryExpression +from .operators import OperatorType from .. import exc from .. import util +_T = typing.TypeVar("_T", bound=Any) + +if typing.TYPE_CHECKING: + from .elements import ColumnElement + from .sqltypes import TypeEngine + def _boolean_compare( - expr, - op, - obj, - negate=None, - reverse=False, + expr: "ColumnElement", + op: OperatorType, + obj: roles.BinaryElementRole, + *, + negate_op: Optional[OperatorType] = None, + reverse: bool = False, _python_is_types=(util.NoneType, bool), _any_all_expr=False, - result_type=None, - **kwargs, -): + result_type: Optional[ + Union[Type["TypeEngine[bool]"], "TypeEngine[bool]"] + ] = None, + **kwargs: Any, +) -> BinaryExpression[bool]: if result_type is None: result_type = type_api.BOOLEANTYPE @@ -54,7 +73,7 @@ def _boolean_compare( coercions.expect(roles.ConstExprRole, obj), op, type_=result_type, - negate=negate, + negate=negate_op, modifiers=kwargs, ) elif op in ( @@ -66,7 +85,7 @@ def _boolean_compare( coercions.expect(roles.ConstExprRole, obj), op, type_=result_type, - negate=negate, + negate=negate_op, modifiers=kwargs, ) elif _any_all_expr: @@ -104,11 +123,21 @@ def _boolean_compare( if reverse: return BinaryExpression( - obj, expr, op, type_=result_type, negate=negate, modifiers=kwargs + obj, + expr, + op, + type_=result_type, + negate=negate_op, + modifiers=kwargs, ) else: return BinaryExpression( - expr, obj, op, type_=result_type, negate=negate, modifiers=kwargs + expr, + obj, + op, + type_=result_type, + negate=negate_op, + modifiers=kwargs, ) @@ -124,15 +153,26 @@ def _custom_op_operate(expr, op, obj, reverse=False, result_type=None, **kw): ) -def _binary_operate(expr, op, obj, reverse=False, result_type=None, **kw): - obj = coercions.expect( +def _binary_operate( + expr: "ColumnElement", + op: OperatorType, + obj: roles.BinaryElementRole, + *, + reverse=False, + result_type: Optional[ + Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"] + ] = None, + **kw: Any, +) -> BinaryExpression[_T]: + + coerced_obj = coercions.expect( roles.BinaryElementRole, obj, expr=expr, operator=op ) if reverse: - left, right = obj, expr + left, right = coerced_obj, expr else: - left, right = expr, obj + left, right = expr, coerced_obj if result_type is None: op, result_type = left.comparator._adapt_expression( @@ -142,7 +182,7 @@ def _binary_operate(expr, op, obj, reverse=False, result_type=None, **kw): return BinaryExpression(left, right, op, type_=result_type, modifiers=kw) -def _conjunction_operate(expr, op, other, **kw): +def _conjunction_operate(expr, op, other, **kw) -> "ColumnElement": if op is operators.and_: return and_(expr, other) elif op is operators.or_: @@ -151,11 +191,11 @@ def _conjunction_operate(expr, op, other, **kw): raise NotImplementedError() -def _scalar(expr, op, fn, **kw): +def _scalar(expr, op, fn, **kw) -> "ColumnElement": return fn(expr) -def _in_impl(expr, op, seq_or_selectable, negate_op, **kw): +def _in_impl(expr, op, seq_or_selectable, negate_op, **kw) -> "ColumnElement": seq_or_selectable = coercions.expect( roles.InElementRole, seq_or_selectable, expr=expr, operator=op ) @@ -163,11 +203,11 @@ def _in_impl(expr, op, seq_or_selectable, negate_op, **kw): op, negate_op = seq_or_selectable._annotations["in_ops"] return _boolean_compare( - expr, op, seq_or_selectable, negate=negate_op, **kw + expr, op, seq_or_selectable, negate_op=negate_op, **kw ) -def _getitem_impl(expr, op, other, **kw): +def _getitem_impl(expr, op, other, **kw) -> "ColumnElement": if isinstance(expr.type, type_api.INDEXABLE): other = coercions.expect( roles.BinaryElementRole, other, expr=expr, operator=op @@ -177,13 +217,13 @@ def _getitem_impl(expr, op, other, **kw): _unsupported_impl(expr, op, other, **kw) -def _unsupported_impl(expr, op, *arg, **kw): +def _unsupported_impl(expr, op, *arg, **kw) -> NoReturn: raise NotImplementedError( "Operator '%s' is not supported on " "this expression" % op.__name__ ) -def _inv_impl(expr, op, **kw): +def _inv_impl(expr, op, **kw) -> "ColumnElement": """See :meth:`.ColumnOperators.__inv__`.""" # undocumented element currently used by the ORM for @@ -194,12 +234,12 @@ def _inv_impl(expr, op, **kw): return expr._negate() -def _neg_impl(expr, op, **kw): +def _neg_impl(expr, op, **kw) -> "ColumnElement": """See :meth:`.ColumnOperators.__neg__`.""" return UnaryExpression(expr, operator=operators.neg, type_=expr.type) -def _match_impl(expr, op, other, **kw): +def _match_impl(expr, op, other, **kw) -> "ColumnElement": """See :meth:`.ColumnOperators.match`.""" return _boolean_compare( @@ -212,21 +252,21 @@ def _match_impl(expr, op, other, **kw): operator=operators.match_op, ), result_type=type_api.MATCHTYPE, - negate=operators.not_match_op + negate_op=operators.not_match_op if op is operators.match_op else operators.match_op, **kw, ) -def _distinct_impl(expr, op, **kw): +def _distinct_impl(expr, op, **kw) -> "ColumnElement": """See :meth:`.ColumnOperators.distinct`.""" return UnaryExpression( expr, operator=operators.distinct_op, type_=expr.type ) -def _between_impl(expr, op, cleft, cright, **kw): +def _between_impl(expr, op, cleft, cright, **kw) -> "ColumnElement": """See :meth:`.ColumnOperators.between`.""" return BinaryExpression( expr, @@ -255,11 +295,11 @@ def _between_impl(expr, op, cleft, cright, **kw): ) -def _collate_impl(expr, op, other, **kw): - return collate(expr, other) +def _collate_impl(expr, op, collation, **kw) -> "ColumnElement": + return CollationClause._create_collation_expression(expr, collation) -def _regexp_match_impl(expr, op, pattern, flags, **kw): +def _regexp_match_impl(expr, op, pattern, flags, **kw) -> "ColumnElement": if flags is not None: flags = coercions.expect( roles.BinaryElementRole, @@ -272,14 +312,16 @@ def _regexp_match_impl(expr, op, pattern, flags, **kw): op, pattern, flags=flags, - negate=operators.not_regexp_match_op + negate_op=operators.not_regexp_match_op if op is operators.regexp_match_op else operators.regexp_match_op, **kw, ) -def _regexp_replace_impl(expr, op, pattern, replacement, flags, **kw): +def _regexp_replace_impl( + expr, op, pattern, replacement, flags, **kw +) -> "ColumnElement": replacement = coercions.expect( roles.BinaryElementRole, replacement, @@ -299,59 +341,118 @@ def _regexp_replace_impl(expr, op, pattern, replacement, flags, **kw): # a mapping of operators with the method they use, along with -# their negated operator for comparison operators -operator_lookup = { - "and_": (_conjunction_operate,), - "or_": (_conjunction_operate,), - "inv": (_inv_impl,), - "add": (_binary_operate,), - "mul": (_binary_operate,), - "sub": (_binary_operate,), - "div": (_binary_operate,), - "mod": (_binary_operate,), - "truediv": (_binary_operate,), - "floordiv": (_binary_operate,), - "custom_op": (_custom_op_operate,), - "json_path_getitem_op": (_binary_operate,), - "json_getitem_op": (_binary_operate,), - "concat_op": (_binary_operate,), - "any_op": (_scalar, CollectionAggregate._create_any), - "all_op": (_scalar, CollectionAggregate._create_all), - "lt": (_boolean_compare, operators.ge), - "le": (_boolean_compare, operators.gt), - "ne": (_boolean_compare, operators.eq), - "gt": (_boolean_compare, operators.le), - "ge": (_boolean_compare, operators.lt), - "eq": (_boolean_compare, operators.ne), - "is_distinct_from": (_boolean_compare, operators.is_not_distinct_from), - "is_not_distinct_from": (_boolean_compare, operators.is_distinct_from), - "like_op": (_boolean_compare, operators.not_like_op), - "ilike_op": (_boolean_compare, operators.not_ilike_op), - "not_like_op": (_boolean_compare, operators.like_op), - "not_ilike_op": (_boolean_compare, operators.ilike_op), - "contains_op": (_boolean_compare, operators.not_contains_op), - "startswith_op": (_boolean_compare, operators.not_startswith_op), - "endswith_op": (_boolean_compare, operators.not_endswith_op), - "desc_op": (_scalar, UnaryExpression._create_desc), - "asc_op": (_scalar, UnaryExpression._create_asc), - "nulls_first_op": (_scalar, UnaryExpression._create_nulls_first), - "nulls_last_op": (_scalar, UnaryExpression._create_nulls_last), - "in_op": (_in_impl, operators.not_in_op), - "not_in_op": (_in_impl, operators.in_op), - "is_": (_boolean_compare, operators.is_), - "is_not": (_boolean_compare, operators.is_not), - "collate": (_collate_impl,), - "match_op": (_match_impl,), - "not_match_op": (_match_impl,), - "distinct_op": (_distinct_impl,), - "between_op": (_between_impl,), - "not_between_op": (_between_impl,), - "neg": (_neg_impl,), - "getitem": (_getitem_impl,), - "lshift": (_unsupported_impl,), - "rshift": (_unsupported_impl,), - "contains": (_unsupported_impl,), - "regexp_match_op": (_regexp_match_impl,), - "not_regexp_match_op": (_regexp_match_impl,), - "regexp_replace_op": (_regexp_replace_impl,), +# additional keyword arguments to be passed +operator_lookup: Dict[ + str, Tuple[Callable[..., "ColumnElement"], util.immutabledict] +] = { + "and_": (_conjunction_operate, util.EMPTY_DICT), + "or_": (_conjunction_operate, util.EMPTY_DICT), + "inv": (_inv_impl, util.EMPTY_DICT), + "add": (_binary_operate, util.EMPTY_DICT), + "mul": (_binary_operate, util.EMPTY_DICT), + "sub": (_binary_operate, util.EMPTY_DICT), + "div": (_binary_operate, util.EMPTY_DICT), + "mod": (_binary_operate, util.EMPTY_DICT), + "truediv": (_binary_operate, util.EMPTY_DICT), + "floordiv": (_binary_operate, util.EMPTY_DICT), + "custom_op": (_custom_op_operate, util.EMPTY_DICT), + "json_path_getitem_op": (_binary_operate, util.EMPTY_DICT), + "json_getitem_op": (_binary_operate, util.EMPTY_DICT), + "concat_op": (_binary_operate, util.EMPTY_DICT), + "any_op": ( + _scalar, + util.immutabledict({"fn": CollectionAggregate._create_any}), + ), + "all_op": ( + _scalar, + util.immutabledict({"fn": CollectionAggregate._create_all}), + ), + "lt": (_boolean_compare, util.immutabledict({"negate_op": operators.ge})), + "le": (_boolean_compare, util.immutabledict({"negate_op": operators.gt})), + "ne": (_boolean_compare, util.immutabledict({"negate_op": operators.eq})), + "gt": (_boolean_compare, util.immutabledict({"negate_op": operators.le})), + "ge": (_boolean_compare, util.immutabledict({"negate_op": operators.lt})), + "eq": (_boolean_compare, util.immutabledict({"negate_op": operators.ne})), + "is_distinct_from": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.is_not_distinct_from}), + ), + "is_not_distinct_from": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.is_distinct_from}), + ), + "like_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.not_like_op}), + ), + "ilike_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.not_ilike_op}), + ), + "not_like_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.like_op}), + ), + "not_ilike_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.ilike_op}), + ), + "contains_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.not_contains_op}), + ), + "startswith_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.not_startswith_op}), + ), + "endswith_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.not_endswith_op}), + ), + "desc_op": ( + _scalar, + util.immutabledict({"fn": UnaryExpression._create_desc}), + ), + "asc_op": ( + _scalar, + util.immutabledict({"fn": UnaryExpression._create_asc}), + ), + "nulls_first_op": ( + _scalar, + util.immutabledict({"fn": UnaryExpression._create_nulls_first}), + ), + "nulls_last_op": ( + _scalar, + util.immutabledict({"fn": UnaryExpression._create_nulls_last}), + ), + "in_op": ( + _in_impl, + util.immutabledict({"negate_op": operators.not_in_op}), + ), + "not_in_op": ( + _in_impl, + util.immutabledict({"negate_op": operators.in_op}), + ), + "is_": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.is_}), + ), + "is_not": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.is_not}), + ), + "collate": (_collate_impl, util.EMPTY_DICT), + "match_op": (_match_impl, util.EMPTY_DICT), + "not_match_op": (_match_impl, util.EMPTY_DICT), + "distinct_op": (_distinct_impl, util.EMPTY_DICT), + "between_op": (_between_impl, util.EMPTY_DICT), + "not_between_op": (_between_impl, util.EMPTY_DICT), + "neg": (_neg_impl, util.EMPTY_DICT), + "getitem": (_getitem_impl, util.EMPTY_DICT), + "lshift": (_unsupported_impl, util.EMPTY_DICT), + "rshift": (_unsupported_impl, util.EMPTY_DICT), + "contains": (_unsupported_impl, util.EMPTY_DICT), + "regexp_match_op": (_regexp_match_impl, util.EMPTY_DICT), + "not_regexp_match_op": (_regexp_match_impl, util.EMPTY_DICT), + "regexp_replace_op": (_regexp_replace_impl, util.EMPTY_DICT), } diff --git a/lib/sqlalchemy/sql/dml.py b/lib/sqlalchemy/sql/dml.py index 5f2424466..33dca66cd 100644 --- a/lib/sqlalchemy/sql/dml.py +++ b/lib/sqlalchemy/sql/dml.py @@ -763,76 +763,7 @@ class Insert(ValuesBase): + HasCTE._has_ctes_traverse_internals ) - def __init__( - self, - table, - ): - """Construct an :class:`_expression.Insert` object. - - E.g.:: - - from sqlalchemy import insert - - stmt = ( - insert(user_table). - values(name='username', fullname='Full Username') - ) - - Similar functionality is available via the - :meth:`_expression.TableClause.insert` method on - :class:`_schema.Table`. - - .. seealso:: - - :ref:`coretutorial_insert_expressions` - in the - :ref:`1.x tutorial <sqlexpression_toplevel>` - - :ref:`tutorial_core_insert` - in the :ref:`unified_tutorial` - - - :param table: :class:`_expression.TableClause` - which is the subject of the - insert. - - :param values: collection of values to be inserted; see - :meth:`_expression.Insert.values` - for a description of allowed formats here. - Can be omitted entirely; a :class:`_expression.Insert` construct - will also dynamically render the VALUES clause at execution time - based on the parameters passed to :meth:`_engine.Connection.execute`. - - :param inline: if True, no attempt will be made to retrieve the - SQL-generated default values to be provided within the statement; - in particular, - this allows SQL expressions to be rendered 'inline' within the - statement without the need to pre-execute them beforehand; for - backends that support "returning", this turns off the "implicit - returning" feature for the statement. - - If both :paramref:`_expression.Insert.values` and compile-time bind - parameters are present, the compile-time bind parameters override the - information specified within :paramref:`_expression.Insert.values` on a - per-key basis. - - The keys within :paramref:`_expression.Insert.values` can be either - :class:`~sqlalchemy.schema.Column` objects or their string - identifiers. Each key may reference one of: - - * a literal data value (i.e. string, number, etc.); - * a Column object; - * a SELECT statement. - - If a ``SELECT`` statement is specified which references this - ``INSERT`` statement's table, the statement will be correlated - against the ``INSERT`` statement. - - .. seealso:: - - :ref:`coretutorial_insert_expressions` - SQL Expression Tutorial - - :ref:`inserts_and_updates` - SQL Expression Tutorial - - """ + def __init__(self, table): super(Insert, self).__init__(table) @_generative @@ -1045,118 +976,7 @@ class Update(DMLWhereBase, ValuesBase): + HasCTE._has_ctes_traverse_internals ) - def __init__( - self, - table, - ): - r"""Construct an :class:`_expression.Update` object. - - E.g.:: - - from sqlalchemy import update - - stmt = ( - update(user_table). - where(user_table.c.id == 5). - values(name='user #5') - ) - - Similar functionality is available via the - :meth:`_expression.TableClause.update` method on - :class:`_schema.Table`. - - .. seealso:: - - :ref:`inserts_and_updates` - in the - :ref:`1.x tutorial <sqlexpression_toplevel>` - - :ref:`tutorial_core_update_delete` - in the :ref:`unified_tutorial` - - - - :param table: A :class:`_schema.Table` - object representing the database - table to be updated. - - :param whereclause: Optional SQL expression describing the ``WHERE`` - condition of the ``UPDATE`` statement; is equivalent to using the - more modern :meth:`~Update.where()` method to specify the ``WHERE`` - clause. - - :param values: - Optional dictionary which specifies the ``SET`` conditions of the - ``UPDATE``. If left as ``None``, the ``SET`` - conditions are determined from those parameters passed to the - statement during the execution and/or compilation of the - statement. When compiled standalone without any parameters, - the ``SET`` clause generates for all columns. - - Modern applications may prefer to use the generative - :meth:`_expression.Update.values` method to set the values of the - UPDATE statement. - - :param inline: - if True, SQL defaults present on :class:`_schema.Column` objects via - the ``default`` keyword will be compiled 'inline' into the statement - and not pre-executed. This means that their values will not - be available in the dictionary returned from - :meth:`_engine.CursorResult.last_updated_params`. - - :param preserve_parameter_order: if True, the update statement is - expected to receive parameters **only** via the - :meth:`_expression.Update.values` method, - and they must be passed as a Python - ``list`` of 2-tuples. The rendered UPDATE statement will emit the SET - clause for each referenced column maintaining this order. - - .. versionadded:: 1.0.10 - - .. seealso:: - - :ref:`updates_order_parameters` - illustrates the - :meth:`_expression.Update.ordered_values` method. - - If both ``values`` and compile-time bind parameters are present, the - compile-time bind parameters override the information specified - within ``values`` on a per-key basis. - - The keys within ``values`` can be either :class:`_schema.Column` - objects or their string identifiers (specifically the "key" of the - :class:`_schema.Column`, normally but not necessarily equivalent to - its "name"). Normally, the - :class:`_schema.Column` objects used here are expected to be - part of the target :class:`_schema.Table` that is the table - to be updated. However when using MySQL, a multiple-table - UPDATE statement can refer to columns from any of - the tables referred to in the WHERE clause. - - The values referred to in ``values`` are typically: - - * a literal data value (i.e. string, number, etc.) - * a SQL expression, such as a related :class:`_schema.Column`, - a scalar-returning :func:`_expression.select` construct, - etc. - - When combining :func:`_expression.select` constructs within the - values clause of an :func:`_expression.update` - construct, the subquery represented - by the :func:`_expression.select` should be *correlated* to the - parent table, that is, providing criterion which links the table inside - the subquery to the outer table being updated:: - - users.update().values( - name=select(addresses.c.email_address).\ - where(addresses.c.user_id==users.c.id).\ - scalar_subquery() - ) - - .. seealso:: - - :ref:`inserts_and_updates` - SQL Expression - Language Tutorial - - - """ + def __init__(self, table): super(Update, self).__init__(table) @_generative @@ -1244,45 +1064,7 @@ class Delete(DMLWhereBase, UpdateBase): + HasCTE._has_ctes_traverse_internals ) - def __init__( - self, - table, - ): - r"""Construct :class:`_expression.Delete` object. - - E.g.:: - - from sqlalchemy import delete - - stmt = ( - delete(user_table). - where(user_table.c.id == 5) - ) - - Similar functionality is available via the - :meth:`_expression.TableClause.delete` method on - :class:`_schema.Table`. - - .. seealso:: - - :ref:`inserts_and_updates` - in the - :ref:`1.x tutorial <sqlexpression_toplevel>` - - :ref:`tutorial_core_update_delete` - in the :ref:`unified_tutorial` - - - :param table: The table to delete rows from. - - :param whereclause: Optional SQL expression describing the ``WHERE`` - condition of the ``DELETE`` statement; is equivalent to using the - more modern :meth:`~Delete.where()` method to specify the ``WHERE`` - clause. - - .. seealso:: - - :ref:`deletes` - SQL Expression Tutorial - - """ + def __init__(self, table): self.table = coercions.expect( roles.DMLTableRole, table, apply_propagate_attrs=self ) diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index a025cce35..705a89889 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -14,6 +14,16 @@ import itertools import operator import re import typing +from typing import Any +from typing import Callable +from typing import Generic +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Text as typing_Text +from typing import Type +from typing import TypeVar +from typing import Union from . import coercions from . import operators @@ -31,7 +41,7 @@ from .base import NO_ARG from .base import SingletonConstant from .cache_key import MemoizedHasCacheKey from .cache_key import NO_CACHE -from .coercions import _document_text_coercion +from .coercions import _document_text_coercion # noqa from .traversals import HasCopyInternals from .visitors import cloned_traverse from .visitors import InternalTraversal @@ -41,86 +51,20 @@ from .. import exc from .. import inspection from .. import util +if typing.TYPE_CHECKING: + from decimal import Decimal -def collate(expression, collation): - """Return the clause ``expression COLLATE collation``. + from .selectable import FromClause + from .selectable import Select + from .sqltypes import Boolean # noqa + from .type_api import TypeEngine - e.g.:: +_NUMERIC = Union[complex, "Decimal"] - collate(mycolumn, 'utf8_bin') - - produces:: - - mycolumn COLLATE utf8_bin - - The collation expression is also quoted if it is a case sensitive - identifier, e.g. contains uppercase characters. - - .. versionchanged:: 1.2 quoting is automatically applied to COLLATE - expressions if they are case sensitive. - - """ - - expr = coercions.expect(roles.ExpressionElementRole, expression) - return BinaryExpression( - expr, CollationClause(collation), operators.collate, type_=expr.type - ) - - -def between(expr, lower_bound, upper_bound, symmetric=False): - """Produce a ``BETWEEN`` predicate clause. - - E.g.:: - - from sqlalchemy import between - stmt = select(users_table).where(between(users_table.c.id, 5, 7)) - - Would produce SQL resembling:: - - SELECT id, name FROM user WHERE id BETWEEN :id_1 AND :id_2 - - The :func:`.between` function is a standalone version of the - :meth:`_expression.ColumnElement.between` method available on all - SQL expressions, as in:: - - stmt = select(users_table).where(users_table.c.id.between(5, 7)) - - All arguments passed to :func:`.between`, including the left side - column expression, are coerced from Python scalar values if a - the value is not a :class:`_expression.ColumnElement` subclass. - For example, - three fixed values can be compared as in:: - - print(between(5, 3, 7)) - - Which would produce:: - - :param_1 BETWEEN :param_2 AND :param_3 - - :param expr: a column expression, typically a - :class:`_expression.ColumnElement` - instance or alternatively a Python scalar expression to be coerced - into a column expression, serving as the left side of the ``BETWEEN`` - expression. - - :param lower_bound: a column or Python scalar expression serving as the - lower bound of the right side of the ``BETWEEN`` expression. - - :param upper_bound: a column or Python scalar expression serving as the - upper bound of the right side of the ``BETWEEN`` expression. - - :param symmetric: if True, will render " BETWEEN SYMMETRIC ". Note - that not all databases support this syntax. - - .. versionadded:: 0.9.5 - - .. seealso:: - - :meth:`_expression.ColumnElement.between` - - """ - expr = coercions.expect(roles.ExpressionElementRole, expr) - return expr.between(lower_bound, upper_bound, symmetric=symmetric) +_T = TypeVar("_T", bound="Any") +_OPT = TypeVar("_OPT", bound="Any") +_NT = TypeVar("_NT", bound="_NUMERIC") +_ST = TypeVar("_ST", bound="typing_Text") def literal(value, type_=None): @@ -145,28 +89,40 @@ def literal(value, type_=None): return coercions.expect(roles.LiteralValueRole, value, type_=type_) -def outparam(key, type_=None): - """Create an 'OUT' parameter for usage in functions (stored procedures), - for databases which support them. +def literal_column(text, type_=None): + r"""Produce a :class:`.ColumnClause` object that has the + :paramref:`_expression.column.is_literal` flag set to True. + + :func:`_expression.literal_column` is similar to + :func:`_expression.column`, except that + it is more often used as a "standalone" column expression that renders + exactly as stated; while :func:`_expression.column` + stores a string name that + will be assumed to be part of a table and may be quoted as such, + :func:`_expression.literal_column` can be that, + or any other arbitrary column-oriented + expression. + + :param text: the text of the expression; can be any SQL expression. + Quoting rules will not be applied. To specify a column-name expression + which should be subject to quoting rules, use the :func:`column` + function. - The ``outparam`` can be used like a regular function parameter. - The "output" value will be available from the - :class:`~sqlalchemy.engine.CursorResult` object via its ``out_parameters`` - attribute, which returns a dictionary containing the values. + :param type\_: an optional :class:`~sqlalchemy.types.TypeEngine` + object which will + provide result-set translation and additional expression semantics for + this column. If left as ``None`` the type will be :class:`.NullType`. - """ - return BindParameter(key, None, type_=type_, unique=False, isoutparam=True) + .. seealso:: + :func:`_expression.column` -def not_(clause): - """Return a negation of the given clause, i.e. ``NOT(clause)``. + :func:`_expression.text` - The ``~`` operator is also overloaded on all - :class:`_expression.ColumnElement` subclasses to produce the - same result. + :ref:`sqlexpression_literal_column` """ - return operators.inv(coercions.expect(roles.ExpressionElementRole, clause)) + return ColumnClause(text, type_=type_, is_literal=True) class CompilerElement(Traversible): @@ -258,6 +214,9 @@ class CompilerElement(Traversible): return str(self.compile()) +SelfClauseElement = TypeVar("SelfClauseElement", bound="ClauseElement") + + @inspection._self_inspects class ClauseElement( SupportsWrappingAnnotations, @@ -313,7 +272,7 @@ class ClauseElement( self._propagate_attrs = util.immutabledict(values) return self - def _clone(self, **kw): + def _clone(self: SelfClauseElement, **kw) -> SelfClauseElement: """Create a shallow copy of this ClauseElement. This method may be used by a generative API. Its also used as @@ -624,8 +583,9 @@ class ColumnElement( roles.DMLColumnRole, roles.DDLConstraintColumnRole, roles.DDLExpressionRole, - operators.ColumnOperators, + 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. @@ -841,11 +801,11 @@ class ColumnElement( return super(ColumnElement, self)._negate() @util.memoized_property - def type(self): + def type(self) -> "TypeEngine[_T]": return type_api.NULLTYPE @HasMemoized.memoized_attribute - def comparator(self): + def comparator(self) -> "TypeEngine.Comparator[_T]": try: comparator_factory = self.type.comparator_factory except AttributeError as err: @@ -869,10 +829,347 @@ class ColumnElement( ) ) from err - def operate(self, op, *other, **kwargs): + # annotations for comparison methods + # these are from operators->Operators / ColumnOperators, + # redefined with the specific types returned by ColumnElement hierarchies + if typing.TYPE_CHECKING: + + def op( + self, + opstring: Any, + precedence: int = 0, + is_comparison: bool = False, + return_type: Optional[ + Union[Type["TypeEngine[_OPT]"], "TypeEngine[_OPT]"] + ] = None, + python_impl=None, + ) -> Callable[[Any], "BinaryExpression[_OPT]"]: + ... + + def bool_op( + self, opstring: Any, precedence: int = 0, python_impl=None + ) -> Callable[[Any], "BinaryExpression[bool]"]: + ... + + def __and__(self, other: Any) -> "BooleanClauseList": + ... + + def __or__(self, other: Any) -> "BooleanClauseList": + ... + + def __invert__(self) -> "UnaryExpression[_T]": + ... + + def __lt__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __le__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __eq__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __ne__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def is_distinct_from(self, other: Any) -> "BinaryExpression[bool]": + ... + + def is_not_distinct_from(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __gt__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __ge__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __neg__(self) -> "UnaryExpression[_T]": + ... + + def __contains__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __getitem__(self, index: Any) -> "ColumnElement": + ... + + @overload + def concat(self, other: Any) -> "BinaryExpression[_ST]": + ... + + @overload + def concat(self, other: Any) -> "BinaryExpression": + ... + + def concat(self, other: Any) -> "BinaryExpression": + ... + + def like(self, other: Any, escape=None) -> "BinaryExpression[bool]": + ... + + def ilike(self, other: Any, escape=None) -> "BinaryExpression[bool]": + ... + + def in_( + self, + other: Union[Sequence[Any], "BindParameter", "Select"], + ) -> "BinaryExpression[bool]": + ... + + def not_in( + self, + other: Union[Sequence[Any], "BindParameter", "Select"], + ) -> "BinaryExpression[bool]": + ... + + def not_like( + self, other: Any, escape=None + ) -> "BinaryExpression[bool]": + ... + + def not_ilike( + self, other: Any, escape=None + ) -> "BinaryExpression[bool]": + ... + + def is_(self, other: Any) -> "BinaryExpression[bool]": + ... + + def is_not(self, other: Any) -> "BinaryExpression[bool]": + ... + + def startswith( + self, other: Any, escape=None, autoescape=False + ) -> "BinaryExpression[bool]": + ... + + def endswith( + self, other: Any, escape=None, autoescape=False + ) -> "BinaryExpression[bool]": + ... + + def contains( + self, other: Any, escape=None, autoescape=False + ) -> "BinaryExpression[bool]": + ... + + def match(self, other: Any, **kwargs) -> "BinaryExpression[bool]": + ... + + def regexp_match( + self, pattern, flags=None + ) -> "BinaryExpression[bool]": + ... + + def regexp_replace( + self, pattern, replacement, flags=None + ) -> "BinaryExpression": + ... + + def desc(self) -> "UnaryExpression[_T]": + ... + + def asc(self) -> "UnaryExpression[_T]": + ... + + def nulls_first(self) -> "UnaryExpression[_T]": + ... + + def nulls_last(self) -> "UnaryExpression[_T]": + ... + + def collate(self, collation) -> "CollationClause": + ... + + def between( + self, cleft, cright, symmetric=False + ) -> "BinaryExpression[bool]": + ... + + def distinct(self: "ColumnElement[_T]") -> "UnaryExpression[_T]": + ... + + def any_(self) -> "CollectionAggregate": + ... + + def all_(self) -> "CollectionAggregate": + ... + + # numeric overloads. These need more tweaking + + @overload + def __add__( + self: "ColumnElement[_NT]", other: "Union[ColumnElement[_NT], _NT]" + ) -> "BinaryExpression[_NT]": + ... + + @overload + def __add__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __add__( + self: "ColumnElement[_ST]", other: Any + ) -> "BinaryExpression[_ST]": + ... + + def __add__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __radd__(self, other: Any) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __radd__(self, other: Any) -> "BinaryExpression": + ... + + def __radd__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __sub__( + self: "ColumnElement[_NT]", other: "Union[ColumnElement[_NT], _NT]" + ) -> "BinaryExpression[_NT]": + ... + + @overload + def __sub__(self, other: Any) -> "BinaryExpression": + ... + + def __sub__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __rsub__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __rsub__(self, other: Any) -> "BinaryExpression": + ... + + def __rsub__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __mul__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __mul__(self, other: Any) -> "BinaryExpression": + ... + + def __mul__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __rmul__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __rmul__(self, other: Any) -> "BinaryExpression": + ... + + def __rmul__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __mod__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __mod__(self, other: Any) -> "BinaryExpression": + ... + + def __mod__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __rmod__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __rmod__(self, other: Any) -> "BinaryExpression": + ... + + def __rmod__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __truediv__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __truediv__(self, other: Any) -> "BinaryExpression": + ... + + def __truediv__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __rtruediv__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __rtruediv__(self, other: Any) -> "BinaryExpression": + ... + + def __rtruediv__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __floordiv__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __floordiv__(self, other: Any) -> "BinaryExpression": + ... + + def __floordiv__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __rfloordiv__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __rfloordiv__(self, other: Any) -> "BinaryExpression": + ... + + def __rfloordiv__(self, other: Any) -> "BinaryExpression": + ... + + def operate( + self, + op: operators.OperatorType, + *other: Any, + **kwargs, + ) -> "ColumnElement": return op(self.comparator, *other, **kwargs) - def reverse_operate(self, op, other, **kwargs): + def reverse_operate( + self, op: operators.OperatorType, other: Any, **kwargs + ) -> "ColumnElement": return op(other, self.comparator, **kwargs) def _bind_param(self, operator, obj, type_=None, expanding=False): @@ -975,7 +1272,12 @@ class ColumnElement( return None def _make_proxy( - self, selectable, name=None, key=None, name_is_truncatable=False, **kw + self, + selectable, + name: Optional[str] = None, + key=None, + name_is_truncatable=False, + **kw, ): """Create a new :class:`_expression.ColumnElement` representing this :class:`_expression.ColumnElement` as it appears in the select list of @@ -1031,7 +1333,7 @@ class ColumnElement( """ return Label(name, self, self.type) - def _anon_label(self, seed, add_hash=None): + def _anon_label(self, seed, add_hash=None) -> "_anonymous_label": while self._is_clone_of is not None: self = self._is_clone_of @@ -1066,7 +1368,7 @@ class ColumnElement( return _anonymous_label.safe_construct(hash_value, seed or "anon") @util.memoized_property - def _anon_name_label(self): + def _anon_name_label(self) -> "_anonymous_label": """Provides a constant 'anonymous label' for this ColumnElement. This is a label() expression which will be named at compile time. @@ -1214,7 +1516,10 @@ class WrapsColumnExpression: return self._dedupe_anon_tq_label_idx(idx) -class BindParameter(roles.InElementRole, ColumnElement): +SelfBindParameter = TypeVar("SelfBindParameter", bound="BindParameter") + + +class BindParameter(roles.InElementRole, ColumnElement[_T]): r"""Represent a "bound expression". :class:`.BindParameter` is invoked explicitly using the @@ -1267,238 +1572,6 @@ class BindParameter(roles.InElementRole, ColumnElement): _compared_to_type=None, _is_crud=False, ): - r"""Produce a "bound expression". - - The return value is an instance of :class:`.BindParameter`; this - is a :class:`_expression.ColumnElement` - subclass which represents a so-called - "placeholder" value in a SQL expression, the value of which is - supplied at the point at which the statement in executed against a - database connection. - - In SQLAlchemy, the :func:`.bindparam` construct has - the ability to carry along the actual value that will be ultimately - used at expression time. In this way, it serves not just as - a "placeholder" for eventual population, but also as a means of - representing so-called "unsafe" values which should not be rendered - directly in a SQL statement, but rather should be passed along - to the :term:`DBAPI` as values which need to be correctly escaped - and potentially handled for type-safety. - - When using :func:`.bindparam` explicitly, the use case is typically - one of traditional deferment of parameters; the :func:`.bindparam` - construct accepts a name which can then be referred to at execution - time:: - - from sqlalchemy import bindparam - - stmt = select(users_table).\ - where(users_table.c.name == bindparam('username')) - - The above statement, when rendered, will produce SQL similar to:: - - SELECT id, name FROM user WHERE name = :username - - In order to populate the value of ``:username`` above, the value - would typically be applied at execution time to a method - like :meth:`_engine.Connection.execute`:: - - result = connection.execute(stmt, username='wendy') - - Explicit use of :func:`.bindparam` is also common when producing - UPDATE or DELETE statements that are to be invoked multiple times, - where the WHERE criterion of the statement is to change on each - invocation, such as:: - - stmt = (users_table.update(). - where(user_table.c.name == bindparam('username')). - values(fullname=bindparam('fullname')) - ) - - connection.execute( - stmt, [{"username": "wendy", "fullname": "Wendy Smith"}, - {"username": "jack", "fullname": "Jack Jones"}, - ] - ) - - SQLAlchemy's Core expression system makes wide use of - :func:`.bindparam` in an implicit sense. It is typical that Python - literal values passed to virtually all SQL expression functions are - coerced into fixed :func:`.bindparam` constructs. For example, given - a comparison operation such as:: - - expr = users_table.c.name == 'Wendy' - - The above expression will produce a :class:`.BinaryExpression` - construct, where the left side is the :class:`_schema.Column` object - representing the ``name`` column, and the right side is a - :class:`.BindParameter` representing the literal value:: - - print(repr(expr.right)) - BindParameter('%(4327771088 name)s', 'Wendy', type_=String()) - - The expression above will render SQL such as:: - - user.name = :name_1 - - Where the ``:name_1`` parameter name is an anonymous name. The - actual string ``Wendy`` is not in the rendered string, but is carried - along where it is later used within statement execution. If we - invoke a statement like the following:: - - stmt = select(users_table).where(users_table.c.name == 'Wendy') - result = connection.execute(stmt) - - We would see SQL logging output as:: - - SELECT "user".id, "user".name - FROM "user" - WHERE "user".name = %(name_1)s - {'name_1': 'Wendy'} - - Above, we see that ``Wendy`` is passed as a parameter to the database, - while the placeholder ``:name_1`` is rendered in the appropriate form - for the target database, in this case the PostgreSQL database. - - Similarly, :func:`.bindparam` is invoked automatically when working - with :term:`CRUD` statements as far as the "VALUES" portion is - concerned. The :func:`_expression.insert` construct produces an - ``INSERT`` expression which will, at statement execution time, generate - bound placeholders based on the arguments passed, as in:: - - stmt = users_table.insert() - result = connection.execute(stmt, name='Wendy') - - The above will produce SQL output as:: - - INSERT INTO "user" (name) VALUES (%(name)s) - {'name': 'Wendy'} - - The :class:`_expression.Insert` construct, at - compilation/execution time, rendered a single :func:`.bindparam` - mirroring the column name ``name`` as a result of the single ``name`` - parameter we passed to the :meth:`_engine.Connection.execute` method. - - :param key: - the key (e.g. the name) for this bind param. - Will be used in the generated - SQL statement for dialects that use named parameters. This - value may be modified when part of a compilation operation, - if other :class:`BindParameter` objects exist with the same - key, or if its length is too long and truncation is - required. - - :param value: - Initial value for this bind param. Will be used at statement - execution time as the value for this parameter passed to the - DBAPI, if no other value is indicated to the statement execution - method for this particular parameter name. Defaults to ``None``. - - :param callable\_: - A callable function that takes the place of "value". The function - will be called at statement execution time to determine the - ultimate value. Used for scenarios where the actual bind - value cannot be determined at the point at which the clause - construct is created, but embedded bind values are still desirable. - - :param type\_: - A :class:`.TypeEngine` class or instance representing an optional - datatype for this :func:`.bindparam`. If not passed, a type - may be determined automatically for the bind, based on the given - value; for example, trivial Python types such as ``str``, - ``int``, ``bool`` - may result in the :class:`.String`, :class:`.Integer` or - :class:`.Boolean` types being automatically selected. - - The type of a :func:`.bindparam` is significant especially in that - the type will apply pre-processing to the value before it is - passed to the database. For example, a :func:`.bindparam` which - refers to a datetime value, and is specified as holding the - :class:`.DateTime` type, may apply conversion needed to the - value (such as stringification on SQLite) before passing the value - to the database. - - :param unique: - if True, the key name of this :class:`.BindParameter` will be - modified if another :class:`.BindParameter` of the same name - already has been located within the containing - expression. This flag is used generally by the internals - when producing so-called "anonymous" bound expressions, it - isn't generally applicable to explicitly-named :func:`.bindparam` - constructs. - - :param required: - If ``True``, a value is required at execution time. If not passed, - it defaults to ``True`` if neither :paramref:`.bindparam.value` - or :paramref:`.bindparam.callable` were passed. If either of these - parameters are present, then :paramref:`.bindparam.required` - defaults to ``False``. - - :param quote: - True if this parameter name requires quoting and is not - currently known as a SQLAlchemy reserved word; this currently - only applies to the Oracle backend, where bound names must - sometimes be quoted. - - :param isoutparam: - if True, the parameter should be treated like a stored procedure - "OUT" parameter. This applies to backends such as Oracle which - support OUT parameters. - - :param expanding: - if True, this parameter will be treated as an "expanding" parameter - at execution time; the parameter value is expected to be a sequence, - rather than a scalar value, and the string SQL statement will - be transformed on a per-execution basis to accommodate the sequence - with a variable number of parameter slots passed to the DBAPI. - This is to allow statement caching to be used in conjunction with - an IN clause. - - .. seealso:: - - :meth:`.ColumnOperators.in_` - - :ref:`baked_in` - with baked queries - - .. note:: The "expanding" feature does not support "executemany"- - style parameter sets. - - .. versionadded:: 1.2 - - .. versionchanged:: 1.3 the "expanding" bound parameter feature now - supports empty lists. - - - .. seealso:: - - :ref:`coretutorial_bind_param` - - :ref:`coretutorial_insert_expressions` - - :func:`.outparam` - - :param literal_execute: - if True, the bound parameter will be rendered in the compile phase - with a special "POSTCOMPILE" token, and the SQLAlchemy compiler will - render the final value of the parameter into the SQL statement at - statement execution time, omitting the value from the parameter - dictionary / list passed to DBAPI ``cursor.execute()``. This - produces a similar effect as that of using the ``literal_binds``, - compilation flag, however takes place as the statement is sent to - the DBAPI ``cursor.execute()`` method, rather than when the statement - is compiled. The primary use of this - capability is for rendering LIMIT / OFFSET clauses for database - drivers that can't accommodate for bound parameters in these - contexts, while allowing SQL constructs to be cacheable at the - compilation level. - - .. versionadded:: 1.4 Added "post compile" bound parameters - - .. seealso:: - - :ref:`change_4808`. - - """ if required is NO_ARG: required = value is NO_ARG and callable_ is None if value is NO_ARG: @@ -1641,7 +1714,9 @@ class BindParameter(roles.InElementRole, ColumnElement): c.type = type_ return c - def _clone(self, maintain_key=False, **kw): + def _clone( + self: SelfBindParameter, maintain_key=False, **kw + ) -> SelfBindParameter: c = ClauseElement._clone(self, **kw) if not maintain_key and self.unique: c.key = _anonymous_label.safe_construct( @@ -1799,85 +1874,6 @@ class TextClause( # to the list of bindparams self.text = self._bind_params_regex.sub(repl, text) - @classmethod - @_document_text_coercion("text", ":func:`.text`", ":paramref:`.text.text`") - def _create_text(cls, text): - r"""Construct a new :class:`_expression.TextClause` clause, - representing - a textual SQL string directly. - - E.g.:: - - from sqlalchemy import text - - t = text("SELECT * FROM users") - result = connection.execute(t) - - The advantages :func:`_expression.text` - provides over a plain string are - backend-neutral support for bind parameters, per-statement - execution options, as well as - bind parameter and result-column typing behavior, allowing - SQLAlchemy type constructs to play a role when executing - a statement that is specified literally. The construct can also - be provided with a ``.c`` collection of column elements, allowing - it to be embedded in other SQL expression constructs as a subquery. - - Bind parameters are specified by name, using the format ``:name``. - E.g.:: - - t = text("SELECT * FROM users WHERE id=:user_id") - result = connection.execute(t, user_id=12) - - For SQL statements where a colon is required verbatim, as within - an inline string, use a backslash to escape:: - - t = text("SELECT * FROM users WHERE name='\:username'") - - The :class:`_expression.TextClause` - construct includes methods which can - provide information about the bound parameters as well as the column - values which would be returned from the textual statement, assuming - it's an executable SELECT type of statement. The - :meth:`_expression.TextClause.bindparams` - method is used to provide bound - parameter detail, and :meth:`_expression.TextClause.columns` - method allows - specification of return columns including names and types:: - - t = text("SELECT * FROM users WHERE id=:user_id").\ - bindparams(user_id=7).\ - columns(id=Integer, name=String) - - for id, name in connection.execute(t): - print(id, name) - - The :func:`_expression.text` construct is used in cases when - a literal string SQL fragment is specified as part of a larger query, - such as for the WHERE clause of a SELECT statement:: - - s = select(users.c.id, users.c.name).where(text("id=:user_id")) - result = connection.execute(s, user_id=12) - - :func:`_expression.text` is also used for the construction - of a full, standalone statement using plain text. - As such, SQLAlchemy refers - to it as an :class:`.Executable` object and may be used - like any other statement passed to an ``.execute()`` method. - - :param text: - the text of the SQL statement to be created. Use ``:<param>`` - to specify bind parameters; they will be compiled to their - engine-specific format. - - .. seealso:: - - :ref:`sqlexpression_text` - in the Core tutorial - - - """ - return TextClause(text) - @_generative def bindparams( self: SelfTextClause, *binds, **names_to_values @@ -2204,40 +2200,6 @@ class False_(SingletonConstant, roles.ConstExprRole, ColumnElement): @classmethod def _instance(cls): - """Return a :class:`.False_` construct. - - E.g.:: - - >>> from sqlalchemy import false - >>> print(select(t.c.x).where(false())) - SELECT x FROM t WHERE false - - A backend which does not support true/false constants will render as - an expression against 1 or 0:: - - >>> print(select(t.c.x).where(false())) - SELECT x FROM t WHERE 0 = 1 - - The :func:`.true` and :func:`.false` constants also feature - "short circuit" operation within an :func:`.and_` or :func:`.or_` - conjunction:: - - >>> print(select(t.c.x).where(or_(t.c.x > 5, true()))) - SELECT x FROM t WHERE true - - >>> print(select(t.c.x).where(and_(t.c.x > 5, false()))) - SELECT x FROM t WHERE false - - .. versionchanged:: 0.9 :func:`.true` and :func:`.false` feature - better integrated behavior within conjunctions and on dialects - that don't support true/false constants. - - .. seealso:: - - :func:`.true` - - """ - return False_() @@ -2272,40 +2234,6 @@ class True_(SingletonConstant, roles.ConstExprRole, ColumnElement): @classmethod def _instance(cls): - """Return a constant :class:`.True_` construct. - - E.g.:: - - >>> from sqlalchemy import true - >>> print(select(t.c.x).where(true())) - SELECT x FROM t WHERE true - - A backend which does not support true/false constants will render as - an expression against 1 or 0:: - - >>> print(select(t.c.x).where(true())) - SELECT x FROM t WHERE 1 = 1 - - The :func:`.true` and :func:`.false` constants also feature - "short circuit" operation within an :func:`.and_` or :func:`.or_` - conjunction:: - - >>> print(select(t.c.x).where(or_(t.c.x > 5, true()))) - SELECT x FROM t WHERE true - - >>> print(select(t.c.x).where(and_(t.c.x > 5, false()))) - SELECT x FROM t WHERE false - - .. versionchanged:: 0.9 :func:`.true` and :func:`.false` feature - better integrated behavior within conjunctions and on dialects - that don't support true/false constants. - - .. seealso:: - - :func:`.false` - - """ - return True_() @@ -2334,15 +2262,23 @@ class ClauseList( ("operator", InternalTraversal.dp_operator), ] - def __init__(self, *clauses, **kwargs): - self.operator = kwargs.pop("operator", operators.comma_op) - self.group = kwargs.pop("group", True) - self.group_contents = kwargs.pop("group_contents", True) - if kwargs.pop("_flatten_sub_clauses", False): + def __init__( + self, + *clauses, + operator=operators.comma_op, + group=True, + group_contents=True, + _flatten_sub_clauses=False, + _literal_as_text_role: Type[roles.SQLRole] = roles.WhereHavingRole, + ): + self.operator = operator + self.group = group + self.group_contents = group_contents + if _flatten_sub_clauses: clauses = util.flatten_iterator(clauses) - self._text_converter_role = text_converter_role = kwargs.pop( - "_literal_as_text_role", roles.WhereHavingRole - ) + self._text_converter_role: Type[roles.SQLRole] = _literal_as_text_role + text_converter_role: Type[roles.SQLRole] = _literal_as_text_role + if self.group_contents: self.clauses = [ coercions.expect( @@ -2404,7 +2340,7 @@ class ClauseList( return self -class BooleanClauseList(ClauseList, ColumnElement): +class BooleanClauseList(ClauseList, ColumnElement[bool]): __visit_name__ = "clauselist" inherit_cache = True @@ -2531,60 +2467,7 @@ class BooleanClauseList(ClauseList, ColumnElement): def and_(cls, *clauses): r"""Produce a conjunction of expressions joined by ``AND``. - E.g.:: - - from sqlalchemy import and_ - - stmt = select(users_table).where( - and_( - users_table.c.name == 'wendy', - users_table.c.enrolled == True - ) - ) - - The :func:`.and_` conjunction is also available using the - Python ``&`` operator (though note that compound expressions - need to be parenthesized in order to function with Python - operator precedence behavior):: - - stmt = select(users_table).where( - (users_table.c.name == 'wendy') & - (users_table.c.enrolled == True) - ) - - The :func:`.and_` operation is also implicit in some cases; - the :meth:`_expression.Select.where` - method for example can be invoked multiple - times against a statement, which will have the effect of each - clause being combined using :func:`.and_`:: - - stmt = select(users_table).\ - where(users_table.c.name == 'wendy').\ - where(users_table.c.enrolled == True) - - The :func:`.and_` construct must be given at least one positional - argument in order to be valid; a :func:`.and_` construct with no - arguments is ambiguous. To produce an "empty" or dynamically - generated :func:`.and_` expression, from a given list of expressions, - a "default" element of ``True`` should be specified:: - - criteria = and_(True, *expressions) - - The above expression will compile to SQL as the expression ``true`` - or ``1 = 1``, depending on backend, if no other expressions are - present. If expressions are present, then the ``True`` value is - ignored as it does not affect the outcome of an AND expression that - has other elements. - - .. deprecated:: 1.4 The :func:`.and_` element now requires that at - least one argument is passed; creating the :func:`.and_` construct - with no arguments is deprecated, and will emit a deprecation warning - while continuing to produce a blank SQL string. - - .. seealso:: - - :func:`.or_` - + See :func:`_sql.and_` for full documentation. """ return cls._construct( operators.and_, True_._singleton, False_._singleton, *clauses @@ -2594,50 +2477,7 @@ class BooleanClauseList(ClauseList, ColumnElement): def or_(cls, *clauses): """Produce a conjunction of expressions joined by ``OR``. - E.g.:: - - from sqlalchemy import or_ - - stmt = select(users_table).where( - or_( - users_table.c.name == 'wendy', - users_table.c.name == 'jack' - ) - ) - - The :func:`.or_` conjunction is also available using the - Python ``|`` operator (though note that compound expressions - need to be parenthesized in order to function with Python - operator precedence behavior):: - - stmt = select(users_table).where( - (users_table.c.name == 'wendy') | - (users_table.c.name == 'jack') - ) - - The :func:`.or_` construct must be given at least one positional - argument in order to be valid; a :func:`.or_` construct with no - arguments is ambiguous. To produce an "empty" or dynamically - generated :func:`.or_` expression, from a given list of expressions, - a "default" element of ``False`` should be specified:: - - or_criteria = or_(False, *expressions) - - The above expression will compile to SQL as the expression ``false`` - or ``0 = 1``, depending on backend, if no other expressions are - present. If expressions are present, then the ``False`` value is - ignored as it does not affect the outcome of an OR expression which - has other elements. - - .. deprecated:: 1.4 The :func:`.or_` element now requires that at - least one argument is passed; creating the :func:`.or_` construct - with no arguments is deprecated, and will emit a deprecation warning - while continuing to produce a blank SQL string. - - .. seealso:: - - :func:`.and_` - + See :func:`_sql.or_` for full documentation. """ return cls._construct( operators.or_, False_._singleton, True_._singleton, *clauses @@ -2669,32 +2509,9 @@ class Tuple(ClauseList, ColumnElement): _traverse_internals = ClauseList._traverse_internals + [] @util.preload_module("sqlalchemy.sql.sqltypes") - def __init__(self, *clauses, **kw): - """Return a :class:`.Tuple`. - - Main usage is to produce a composite IN construct using - :meth:`.ColumnOperators.in_` :: - - from sqlalchemy import tuple_ - - tuple_(table.c.col1, table.c.col2).in_( - [(1, 2), (5, 12), (10, 19)] - ) - - .. versionchanged:: 1.3.6 Added support for SQLite IN tuples. - - .. warning:: - - The composite IN construct is not supported by all backends, and is - currently known to work on PostgreSQL, MySQL, and SQLite. - Unsupported backends will raise a subclass of - :class:`~sqlalchemy.exc.DBAPIError` when such an expression is - invoked. - - """ + def __init__(self, *clauses, types=None): sqltypes = util.preloaded.sql_sqltypes - types = kw.pop("types", None) if types is None: clauses = [ coercions.expect(roles.ExpressionElementRole, c) @@ -2716,7 +2533,7 @@ class Tuple(ClauseList, ColumnElement): ] self.type = sqltypes.TupleType(*[arg.type for arg in clauses]) - super(Tuple, self).__init__(*clauses, **kw) + super(Tuple, self).__init__(*clauses) @property def _select_iterable(self): @@ -2752,7 +2569,7 @@ class Tuple(ClauseList, ColumnElement): return self -class Case(ColumnElement): +class Case(ColumnElement[_T]): """Represent a ``CASE`` expression. :class:`.Case` is produced using the :func:`.case` factory function, @@ -2785,127 +2602,10 @@ class Case(ColumnElement): ("else_", InternalTraversal.dp_clauseelement), ] - def __init__(self, *whens, value=None, else_=None): - r"""Produce a ``CASE`` expression. - - The ``CASE`` construct in SQL is a conditional object that - acts somewhat analogously to an "if/then" construct in other - languages. It returns an instance of :class:`.Case`. - - :func:`.case` in its usual form is passed a series of "when" - constructs, that is, a list of conditions and results as tuples:: - - from sqlalchemy import case - - stmt = select(users_table).\ - where( - case( - (users_table.c.name == 'wendy', 'W'), - (users_table.c.name == 'jack', 'J'), - else_='E' - ) - ) - - The above statement will produce SQL resembling:: - - SELECT id, name FROM user - WHERE CASE - WHEN (name = :name_1) THEN :param_1 - WHEN (name = :name_2) THEN :param_2 - ELSE :param_3 - END - - When simple equality expressions of several values against a single - parent column are needed, :func:`.case` also has a "shorthand" format - used via the - :paramref:`.case.value` parameter, which is passed a column - expression to be compared. In this form, the :paramref:`.case.whens` - parameter is passed as a dictionary containing expressions to be - compared against keyed to result expressions. The statement below is - equivalent to the preceding statement:: - - stmt = select(users_table).\ - where( - case( - {"wendy": "W", "jack": "J"}, - value=users_table.c.name, - else_='E' - ) - ) - - The values which are accepted as result values in - :paramref:`.case.whens` as well as with :paramref:`.case.else_` are - coerced from Python literals into :func:`.bindparam` constructs. - SQL expressions, e.g. :class:`_expression.ColumnElement` constructs, - are accepted - as well. To coerce a literal string expression into a constant - expression rendered inline, use the :func:`_expression.literal_column` - construct, - as in:: - - from sqlalchemy import case, literal_column - - case( - ( - orderline.c.qty > 100, - literal_column("'greaterthan100'") - ), - ( - orderline.c.qty > 10, - literal_column("'greaterthan10'") - ), - else_=literal_column("'lessthan10'") - ) - - The above will render the given constants without using bound - parameters for the result values (but still for the comparison - values), as in:: - - CASE - WHEN (orderline.qty > :qty_1) THEN 'greaterthan100' - WHEN (orderline.qty > :qty_2) THEN 'greaterthan10' - ELSE 'lessthan10' - END - - :param \*whens: The criteria to be compared against, - :paramref:`.case.whens` accepts two different forms, based on - whether or not :paramref:`.case.value` is used. - - .. versionchanged:: 1.4 the :func:`_sql.case` - function now accepts the series of WHEN conditions positionally - - In the first form, it accepts a list of 2-tuples; each 2-tuple - consists of ``(<sql expression>, <value>)``, where the SQL - expression is a boolean expression and "value" is a resulting value, - e.g.:: - - case( - (users_table.c.name == 'wendy', 'W'), - (users_table.c.name == 'jack', 'J') - ) - - In the second form, it accepts a Python dictionary of comparison - values mapped to a resulting value; this form requires - :paramref:`.case.value` to be present, and values will be compared - using the ``==`` operator, e.g.:: - - case( - {"wendy": "W", "jack": "J"}, - value=users_table.c.name - ) - - :param value: An optional SQL expression which will be used as a - fixed "comparison point" for candidate values within a dictionary - passed to :paramref:`.case.whens`. - - :param else\_: An optional SQL expression which will be the evaluated - result of the ``CASE`` construct if all expressions within - :paramref:`.case.whens` evaluate to false. When omitted, most - databases will produce a result of NULL if none of the "when" - expressions evaluate to true. + # for case(), the type is derived from the whens. so for the moment + # users would have to cast() the case to get a specific type - - """ + def __init__(self, *whens, value=None, else_=None): whens = coercions._expression_collection_was_a_list( "whens", "case", whens @@ -2952,43 +2652,7 @@ class Case(ColumnElement): ) -def literal_column(text, type_=None): - r"""Produce a :class:`.ColumnClause` object that has the - :paramref:`_expression.column.is_literal` flag set to True. - - :func:`_expression.literal_column` is similar to - :func:`_expression.column`, except that - it is more often used as a "standalone" column expression that renders - exactly as stated; while :func:`_expression.column` - stores a string name that - will be assumed to be part of a table and may be quoted as such, - :func:`_expression.literal_column` can be that, - or any other arbitrary column-oriented - expression. - - :param text: the text of the expression; can be any SQL expression. - Quoting rules will not be applied. To specify a column-name expression - which should be subject to quoting rules, use the :func:`column` - function. - - :param type\_: an optional :class:`~sqlalchemy.types.TypeEngine` - object which will - provide result-set translation and additional expression semantics for - this column. If left as ``None`` the type will be :class:`.NullType`. - - .. seealso:: - - :func:`_expression.column` - - :func:`_expression.text` - - :ref:`sqlexpression_literal_column` - - """ - return ColumnClause(text, type_=type_, is_literal=True) - - -class Cast(WrapsColumnExpression, ColumnElement): +class Cast(WrapsColumnExpression, ColumnElement[_T]): """Represent a ``CAST`` expression. :class:`.Cast` is produced using the :func:`.cast` factory function, @@ -3020,57 +2684,6 @@ class Cast(WrapsColumnExpression, ColumnElement): ] def __init__(self, expression, type_): - r"""Produce a ``CAST`` expression. - - :func:`.cast` returns an instance of :class:`.Cast`. - - E.g.:: - - from sqlalchemy import cast, Numeric - - stmt = select(cast(product_table.c.unit_price, Numeric(10, 4))) - - The above statement will produce SQL resembling:: - - SELECT CAST(unit_price AS NUMERIC(10, 4)) FROM product - - The :func:`.cast` function performs two distinct functions when - used. The first is that it renders the ``CAST`` expression within - the resulting SQL string. The second is that it associates the given - type (e.g. :class:`.TypeEngine` class or instance) with the column - expression on the Python side, which means the expression will take - on the expression operator behavior associated with that type, - as well as the bound-value handling and result-row-handling behavior - of the type. - - .. versionchanged:: 0.9.0 :func:`.cast` now applies the given type - to the expression such that it takes effect on the bound-value, - e.g. the Python-to-database direction, in addition to the - result handling, e.g. database-to-Python, direction. - - An alternative to :func:`.cast` is the :func:`.type_coerce` function. - This function performs the second task of associating an expression - with a specific type, but does not render the ``CAST`` expression - in SQL. - - :param expression: A SQL expression, such as a - :class:`_expression.ColumnElement` - expression or a Python string which will be coerced into a bound - literal value. - - :param type\_: A :class:`.TypeEngine` class or instance indicating - the type to which the ``CAST`` should apply. - - .. seealso:: - - :ref:`coretutorial_casts` - - :func:`.type_coerce` - an alternative to CAST that coerces the type - on the Python side only, which is often sufficient to generate the - correct SQL and data coercion. - - - """ self.type = type_api.to_instance(type_) self.clause = coercions.expect( roles.ExpressionElementRole, @@ -3089,7 +2702,7 @@ class Cast(WrapsColumnExpression, ColumnElement): return self.clause -class TypeCoerce(WrapsColumnExpression, ColumnElement): +class TypeCoerce(WrapsColumnExpression, ColumnElement[_T]): """Represent a Python-side type-coercion wrapper. :class:`.TypeCoerce` supplies the :func:`_expression.type_coerce` @@ -3115,80 +2728,6 @@ class TypeCoerce(WrapsColumnExpression, ColumnElement): ] def __init__(self, expression, type_): - r"""Associate a SQL expression with a particular type, without rendering - ``CAST``. - - E.g.:: - - from sqlalchemy import type_coerce - - stmt = select(type_coerce(log_table.date_string, StringDateTime())) - - The above construct will produce a :class:`.TypeCoerce` object, which - does not modify the rendering in any way on the SQL side, with the - possible exception of a generated label if used in a columns clause - context:: - - SELECT date_string AS date_string FROM log - - When result rows are fetched, the ``StringDateTime`` type processor - will be applied to result rows on behalf of the ``date_string`` column. - - .. note:: the :func:`.type_coerce` construct does not render any - SQL syntax of its own, including that it does not imply - parenthesization. Please use :meth:`.TypeCoerce.self_group` - if explicit parenthesization is required. - - In order to provide a named label for the expression, use - :meth:`_expression.ColumnElement.label`:: - - stmt = select( - type_coerce(log_table.date_string, StringDateTime()).label('date') - ) - - - A type that features bound-value handling will also have that behavior - take effect when literal values or :func:`.bindparam` constructs are - passed to :func:`.type_coerce` as targets. - For example, if a type implements the - :meth:`.TypeEngine.bind_expression` - method or :meth:`.TypeEngine.bind_processor` method or equivalent, - these functions will take effect at statement compilation/execution - time when a literal value is passed, as in:: - - # bound-value handling of MyStringType will be applied to the - # literal value "some string" - stmt = select(type_coerce("some string", MyStringType)) - - When using :func:`.type_coerce` with composed expressions, note that - **parenthesis are not applied**. If :func:`.type_coerce` is being - used in an operator context where the parenthesis normally present from - CAST are necessary, use the :meth:`.TypeCoerce.self_group` method:: - - >>> some_integer = column("someint", Integer) - >>> some_string = column("somestr", String) - >>> expr = type_coerce(some_integer + 5, String) + some_string - >>> print(expr) - someint + :someint_1 || somestr - >>> expr = type_coerce(some_integer + 5, String).self_group() + some_string - >>> print(expr) - (someint + :someint_1) || somestr - - :param expression: A SQL expression, such as a - :class:`_expression.ColumnElement` - expression or a Python string which will be coerced into a bound - literal value. - - :param type\_: A :class:`.TypeEngine` class or instance indicating - the type to which the expression is coerced. - - .. seealso:: - - :ref:`coretutorial_casts` - - :func:`.cast` - - """ # noqa self.type = type_api.to_instance(type_) self.clause = coercions.expect( roles.ExpressionElementRole, @@ -3222,7 +2761,7 @@ class TypeCoerce(WrapsColumnExpression, ColumnElement): return self -class Extract(ColumnElement): +class Extract(ColumnElement[_T]): """Represent a SQL EXTRACT clause, ``extract(field FROM expr)``.""" __visit_name__ = "extract" @@ -3232,44 +2771,7 @@ class Extract(ColumnElement): ("field", InternalTraversal.dp_string), ] - def __init__(self, field, expr, **kwargs): - """Return a :class:`.Extract` construct. - - This is typically available as :func:`.extract` - as well as ``func.extract`` from the - :data:`.func` namespace. - - :param field: The field to extract. - - :param expr: A column or Python scalar expression serving as the - right side of the ``EXTRACT`` expression. - - E.g.:: - - from sqlalchemy import extract - from sqlalchemy import table, column - - logged_table = table("user", - column("id"), - column("date_created"), - ) - - stmt = select(logged_table.c.id).where( - extract("YEAR", logged_table.c.date_created) == 2021 - ) - - In the above example, the statement is used to select ids from the - database where the ``YEAR`` component matches a specific value. - - Similarly, one can also select an extracted component:: - - stmt = select( - extract("YEAR", logged_table.c.date_created) - ).where(logged_table.c.id == 1) - - The implementation of ``EXTRACT`` may vary across database backends. - Users are reminded to consult their database documentation. - """ + def __init__(self, field, expr): self.type = type_api.INTEGERTYPE self.field = field self.expr = coercions.expect(roles.ExpressionElementRole, expr) @@ -3314,10 +2816,10 @@ class _textual_label_reference(ColumnElement): @util.memoized_property def _text_clause(self): - return TextClause._create_text(self.element) + return TextClause(self.element) -class UnaryExpression(ColumnElement): +class UnaryExpression(ColumnElement[_T]): """Define a 'unary' expression. A unary expression has a single column expression @@ -3344,7 +2846,7 @@ class UnaryExpression(ColumnElement): element, operator=None, modifier=None, - type_=None, + type_: Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"] = None, wraps_column_expression=False, ): self.operator = operator @@ -3353,51 +2855,11 @@ class UnaryExpression(ColumnElement): self.element = element.self_group( against=self.operator or self.modifier ) - self.type = type_api.to_instance(type_) + self.type: TypeEngine[_T] = type_api.to_instance(type_) self.wraps_column_expression = wraps_column_expression @classmethod def _create_nulls_first(cls, column): - """Produce the ``NULLS FIRST`` modifier for an ``ORDER BY`` expression. - - :func:`.nulls_first` is intended to modify the expression produced - by :func:`.asc` or :func:`.desc`, and indicates how NULL values - should be handled when they are encountered during ordering:: - - - from sqlalchemy import desc, nulls_first - - stmt = select(users_table).order_by( - nulls_first(desc(users_table.c.name))) - - The SQL expression from the above would resemble:: - - SELECT id, name FROM user ORDER BY name DESC NULLS FIRST - - Like :func:`.asc` and :func:`.desc`, :func:`.nulls_first` is typically - invoked from the column expression itself using - :meth:`_expression.ColumnElement.nulls_first`, - rather than as its standalone - function version, as in:: - - stmt = select(users_table).order_by( - users_table.c.name.desc().nulls_first()) - - .. versionchanged:: 1.4 :func:`.nulls_first` is renamed from - :func:`.nullsfirst` in previous releases. - The previous name remains available for backwards compatibility. - - .. seealso:: - - :func:`.asc` - - :func:`.desc` - - :func:`.nulls_last` - - :meth:`_expression.Select.order_by` - - """ return UnaryExpression( coercions.expect(roles.ByOfRole, column), modifier=operators.nulls_first_op, @@ -3406,46 +2868,6 @@ class UnaryExpression(ColumnElement): @classmethod def _create_nulls_last(cls, column): - """Produce the ``NULLS LAST`` modifier for an ``ORDER BY`` expression. - - :func:`.nulls_last` is intended to modify the expression produced - by :func:`.asc` or :func:`.desc`, and indicates how NULL values - should be handled when they are encountered during ordering:: - - - from sqlalchemy import desc, nulls_last - - stmt = select(users_table).order_by( - nulls_last(desc(users_table.c.name))) - - The SQL expression from the above would resemble:: - - SELECT id, name FROM user ORDER BY name DESC NULLS LAST - - Like :func:`.asc` and :func:`.desc`, :func:`.nulls_last` is typically - invoked from the column expression itself using - :meth:`_expression.ColumnElement.nulls_last`, - rather than as its standalone - function version, as in:: - - stmt = select(users_table).order_by( - users_table.c.name.desc().nulls_last()) - - .. versionchanged:: 1.4 :func:`.nulls_last` is renamed from - :func:`.nullslast` in previous releases. - The previous name remains available for backwards compatibility. - - .. seealso:: - - :func:`.asc` - - :func:`.desc` - - :func:`.nulls_first` - - :meth:`_expression.Select.order_by` - - """ return UnaryExpression( coercions.expect(roles.ByOfRole, column), modifier=operators.nulls_last_op, @@ -3454,41 +2876,6 @@ class UnaryExpression(ColumnElement): @classmethod def _create_desc(cls, column): - """Produce a descending ``ORDER BY`` clause element. - - e.g.:: - - from sqlalchemy import desc - - stmt = select(users_table).order_by(desc(users_table.c.name)) - - will produce SQL as:: - - SELECT id, name FROM user ORDER BY name DESC - - The :func:`.desc` function is a standalone version of the - :meth:`_expression.ColumnElement.desc` - method available on all SQL expressions, - e.g.:: - - - stmt = select(users_table).order_by(users_table.c.name.desc()) - - :param column: A :class:`_expression.ColumnElement` (e.g. - scalar SQL expression) - with which to apply the :func:`.desc` operation. - - .. seealso:: - - :func:`.asc` - - :func:`.nulls_first` - - :func:`.nulls_last` - - :meth:`_expression.Select.order_by` - - """ return UnaryExpression( coercions.expect(roles.ByOfRole, column), modifier=operators.desc_op, @@ -3497,40 +2884,6 @@ class UnaryExpression(ColumnElement): @classmethod def _create_asc(cls, column): - """Produce an ascending ``ORDER BY`` clause element. - - e.g.:: - - from sqlalchemy import asc - stmt = select(users_table).order_by(asc(users_table.c.name)) - - will produce SQL as:: - - SELECT id, name FROM user ORDER BY name ASC - - The :func:`.asc` function is a standalone version of the - :meth:`_expression.ColumnElement.asc` - method available on all SQL expressions, - e.g.:: - - - stmt = select(users_table).order_by(users_table.c.name.asc()) - - :param column: A :class:`_expression.ColumnElement` (e.g. - scalar SQL expression) - with which to apply the :func:`.asc` operation. - - .. seealso:: - - :func:`.desc` - - :func:`.nulls_first` - - :func:`.nulls_last` - - :meth:`_expression.Select.order_by` - - """ return UnaryExpression( coercions.expect(roles.ByOfRole, column), modifier=operators.asc_op, @@ -3539,41 +2892,6 @@ class UnaryExpression(ColumnElement): @classmethod def _create_distinct(cls, expr): - """Produce an column-expression-level unary ``DISTINCT`` clause. - - This applies the ``DISTINCT`` keyword to an individual column - expression, and is typically contained within an aggregate function, - as in:: - - from sqlalchemy import distinct, func - stmt = select(func.count(distinct(users_table.c.name))) - - The above would produce an expression resembling:: - - SELECT COUNT(DISTINCT name) FROM user - - The :func:`.distinct` function is also available as a column-level - method, e.g. :meth:`_expression.ColumnElement.distinct`, as in:: - - stmt = select(func.count(users_table.c.name.distinct())) - - The :func:`.distinct` operator is different from the - :meth:`_expression.Select.distinct` method of - :class:`_expression.Select`, - which produces a ``SELECT`` statement - with ``DISTINCT`` applied to the result set as a whole, - e.g. a ``SELECT DISTINCT`` expression. See that method for further - information. - - .. seealso:: - - :meth:`_expression.ColumnElement.distinct` - - :meth:`_expression.Select.distinct` - - :data:`.func` - - """ expr = coercions.expect(roles.ExpressionElementRole, expr) return UnaryExpression( expr, @@ -3625,57 +2943,6 @@ class CollectionAggregate(UnaryExpression): @classmethod def _create_any(cls, expr): - """Produce an ANY expression. - - For dialects such as that of PostgreSQL, this operator applies - to usage of the :class:`_types.ARRAY` datatype, for that of - MySQL, it may apply to a subquery. e.g.:: - - # renders on PostgreSQL: - # '5 = ANY (somearray)' - expr = 5 == any_(mytable.c.somearray) - - # renders on MySQL: - # '5 = ANY (SELECT value FROM table)' - expr = 5 == any_(select(table.c.value)) - - Comparison to NULL may work using ``None`` or :func:`_sql.null`:: - - None == any_(mytable.c.somearray) - - The any_() / all_() operators also feature a special "operand flipping" - behavior such that if any_() / all_() are used on the left side of a - comparison using a standalone operator such as ``==``, ``!=``, etc. - (not including operator methods such as - :meth:`_sql.ColumnOperators.is_`) the rendered expression is flipped:: - - # would render '5 = ANY (column)` - any_(mytable.c.column) == 5 - - Or with ``None``, which note will not perform - the usual step of rendering "IS" as is normally the case for NULL:: - - # would render 'NULL = ANY(somearray)' - any_(mytable.c.somearray) == None - - .. versionchanged:: 1.4.26 repaired the use of any_() / all_() - comparing to NULL on the right side to be flipped to the left. - - The column-level :meth:`_sql.ColumnElement.any_` method (not to be - confused with :class:`_types.ARRAY` level - :meth:`_types.ARRAY.Comparator.any`) is shorthand for - ``any_(col)``:: - - 5 = mytable.c.somearray.any_() - - .. seealso:: - - :meth:`_sql.ColumnOperators.any_` - - :func:`_expression.all_` - - """ - expr = coercions.expect(roles.ExpressionElementRole, expr) expr = expr.self_group() @@ -3688,56 +2955,6 @@ class CollectionAggregate(UnaryExpression): @classmethod def _create_all(cls, expr): - """Produce an ALL expression. - - For dialects such as that of PostgreSQL, this operator applies - to usage of the :class:`_types.ARRAY` datatype, for that of - MySQL, it may apply to a subquery. e.g.:: - - # renders on PostgreSQL: - # '5 = ALL (somearray)' - expr = 5 == all_(mytable.c.somearray) - - # renders on MySQL: - # '5 = ALL (SELECT value FROM table)' - expr = 5 == all_(select(table.c.value)) - - Comparison to NULL may work using ``None``:: - - None == all_(mytable.c.somearray) - - The any_() / all_() operators also feature a special "operand flipping" - behavior such that if any_() / all_() are used on the left side of a - comparison using a standalone operator such as ``==``, ``!=``, etc. - (not including operator methods such as - :meth:`_sql.ColumnOperators.is_`) the rendered expression is flipped:: - - # would render '5 = ALL (column)` - all_(mytable.c.column) == 5 - - Or with ``None``, which note will not perform - the usual step of rendering "IS" as is normally the case for NULL:: - - # would render 'NULL = ALL(somearray)' - all_(mytable.c.somearray) == None - - .. versionchanged:: 1.4.26 repaired the use of any_() / all_() - comparing to NULL on the right side to be flipped to the left. - - The column-level :meth:`_sql.ColumnElement.all_` method (not to be - confused with :class:`_types.ARRAY` level - :meth:`_types.ARRAY.Comparator.all`) is shorthand for - ``all_(col)``:: - - 5 == mytable.c.somearray.all_() - - .. seealso:: - - :meth:`_sql.ColumnOperators.all_` - - :func:`_expression.any_` - - """ expr = coercions.expect(roles.ExpressionElementRole, expr) expr = expr.self_group() return CollectionAggregate( @@ -3792,7 +3009,7 @@ class AsBoolean(WrapsColumnExpression, UnaryExpression): return AsBoolean(self.element, self.negate, self.operator) -class BinaryExpression(ColumnElement): +class BinaryExpression(ColumnElement[_T]): """Represent an expression that is ``LEFT <operator> RIGHT``. A :class:`.BinaryExpression` is generated automatically @@ -3827,7 +3044,15 @@ class BinaryExpression(ColumnElement): """ def __init__( - self, left, right, operator, type_=None, negate=None, modifiers=None + self, + left: ColumnElement, + right: Union[ColumnElement, ClauseList], + operator, + type_: Optional[ + Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"] + ] = None, + negate=None, + modifiers=None, ): # allow compatibility with libraries that # refer to BinaryExpression directly and pass strings @@ -3838,7 +3063,7 @@ class BinaryExpression(ColumnElement): self.left = left.self_group(against=operator) self.right = right.self_group(against=operator) self.operator = operator - self.type = type_api.to_instance(type_) + self.type: TypeEngine[_T] = type_api.to_instance(type_) self.negate = negate self._is_implicitly_boolean = operators.is_boolean(operator) @@ -3855,6 +3080,13 @@ class BinaryExpression(ColumnElement): __nonzero__ = __bool__ + if typing.TYPE_CHECKING: + + def __invert__( + self: "BinaryExpression[_T]", + ) -> "BinaryExpression[_T]": + ... + @property def is_comparison(self): return operators.is_comparison(self.operator) @@ -3996,7 +3228,7 @@ RANGE_UNBOUNDED = util.symbol("RANGE_UNBOUNDED") RANGE_CURRENT = util.symbol("RANGE_CURRENT") -class Over(ColumnElement): +class Over(ColumnElement[_T]): """Represent an OVER clause. This is a special operator against a so-called @@ -4026,88 +3258,6 @@ class Over(ColumnElement): def __init__( self, element, partition_by=None, order_by=None, range_=None, rows=None ): - r"""Produce an :class:`.Over` object against a function. - - Used against aggregate or so-called "window" functions, - for database backends that support window functions. - - :func:`_expression.over` is usually called using - the :meth:`.FunctionElement.over` method, e.g.:: - - func.row_number().over(order_by=mytable.c.some_column) - - Would produce:: - - ROW_NUMBER() OVER(ORDER BY some_column) - - Ranges are also possible using the :paramref:`.expression.over.range_` - and :paramref:`.expression.over.rows` parameters. These - mutually-exclusive parameters each accept a 2-tuple, which contains - a combination of integers and None:: - - func.row_number().over( - order_by=my_table.c.some_column, range_=(None, 0)) - - The above would produce:: - - ROW_NUMBER() OVER(ORDER BY some_column - RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - - A value of ``None`` indicates "unbounded", a - value of zero indicates "current row", and negative / positive - integers indicate "preceding" and "following": - - * RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING:: - - func.row_number().over(order_by='x', range_=(-5, 10)) - - * ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:: - - func.row_number().over(order_by='x', rows=(None, 0)) - - * RANGE BETWEEN 2 PRECEDING AND UNBOUNDED FOLLOWING:: - - func.row_number().over(order_by='x', range_=(-2, None)) - - * RANGE BETWEEN 1 FOLLOWING AND 3 FOLLOWING:: - - func.row_number().over(order_by='x', range_=(1, 3)) - - .. versionadded:: 1.1 support for RANGE / ROWS within a window - - - :param element: a :class:`.FunctionElement`, :class:`.WithinGroup`, - or other compatible construct. - :param partition_by: a column element or string, or a list - of such, that will be used as the PARTITION BY clause - of the OVER construct. - :param order_by: a column element or string, or a list - of such, that will be used as the ORDER BY clause - of the OVER construct. - :param range\_: optional range clause for the window. This is a - tuple value which can contain integer values or ``None``, - and will render a RANGE BETWEEN PRECEDING / FOLLOWING clause. - - .. versionadded:: 1.1 - - :param rows: optional rows clause for the window. This is a tuple - value which can contain integer values or None, and will render - a ROWS BETWEEN PRECEDING / FOLLOWING clause. - - .. versionadded:: 1.1 - - This function is also available from the :data:`~.expression.func` - construct itself via the :meth:`.FunctionElement.over` method. - - .. seealso:: - - :ref:`tutorial_window_functions` - in the :ref:`unified_tutorial` - - :data:`.expression.func` - - :func:`_expression.within_group` - - """ self.element = element if order_by is not None: self.order_by = ClauseList( @@ -4191,7 +3341,7 @@ class Over(ColumnElement): ) -class WithinGroup(ColumnElement): +class WithinGroup(ColumnElement[_T]): """Represent a WITHIN GROUP (ORDER BY) clause. This is a special operator against so-called @@ -4218,44 +3368,6 @@ class WithinGroup(ColumnElement): order_by = None def __init__(self, element, *order_by): - r"""Produce a :class:`.WithinGroup` object against a function. - - Used against so-called "ordered set aggregate" and "hypothetical - set aggregate" functions, including :class:`.percentile_cont`, - :class:`.rank`, :class:`.dense_rank`, etc. - - :func:`_expression.within_group` is usually called using - the :meth:`.FunctionElement.within_group` method, e.g.:: - - from sqlalchemy import within_group - stmt = select( - department.c.id, - func.percentile_cont(0.5).within_group( - department.c.salary.desc() - ) - ) - - The above statement would produce SQL similar to - ``SELECT department.id, percentile_cont(0.5) - WITHIN GROUP (ORDER BY department.salary DESC)``. - - :param element: a :class:`.FunctionElement` construct, typically - generated by :data:`~.expression.func`. - :param \*order_by: one or more column elements that will be used - as the ORDER BY clause of the WITHIN GROUP construct. - - .. versionadded:: 1.1 - - .. seealso:: - - :ref:`tutorial_functions_within_group` - in the - :ref:`unified_tutorial` - - :data:`.expression.func` - - :func:`_expression.over` - - """ self.element = element if order_by is not None: self.order_by = ClauseList( @@ -4332,31 +3444,6 @@ class FunctionFilter(ColumnElement): criterion = None def __init__(self, func, *criterion): - """Produce a :class:`.FunctionFilter` object against a function. - - Used against aggregate and window functions, - for database backends that support the "FILTER" clause. - - E.g.:: - - from sqlalchemy import funcfilter - funcfilter(func.count(1), MyClass.name == 'some name') - - Would produce "COUNT(1) FILTER (WHERE myclass.name = 'some name')". - - This function is also available from the :data:`~.expression.func` - construct itself via the :meth:`.FunctionElement.filter` method. - - .. versionadded:: 1.0.0 - - .. seealso:: - - :ref:`tutorial_functions_within_group` - in the - :ref:`unified_tutorial` - - :meth:`.FunctionElement.filter` - - """ self.func = func self.filter(*criterion) @@ -4431,7 +3518,7 @@ class FunctionFilter(ColumnElement): ) -class Label(roles.LabeledColumnExprRole, ColumnElement): +class Label(roles.LabeledColumnExprRole, ColumnElement[_T]): """Represents a column label (AS). Represent a label, as typically applied to any column-level @@ -4448,22 +3535,6 @@ class Label(roles.LabeledColumnExprRole, ColumnElement): ] def __init__(self, name, element, type_=None): - """Return a :class:`Label` object for the - given :class:`_expression.ColumnElement`. - - A label changes the name of an element in the columns clause of a - ``SELECT`` statement, typically via the ``AS`` SQL keyword. - - This functionality is more conveniently available via the - :meth:`_expression.ColumnElement.label` method on - :class:`_expression.ColumnElement`. - - :param name: label name - - :param obj: a :class:`_expression.ColumnElement`. - - """ - orig_element = element element = coercions.expect( roles.ExpressionElementRole, @@ -4583,7 +3654,7 @@ class Label(roles.LabeledColumnExprRole, ColumnElement): return self.key, e -class NamedColumn(ColumnElement): +class NamedColumn(ColumnElement[_T]): is_literal = False table = None @@ -4673,7 +3744,7 @@ class ColumnClause( roles.LabeledColumnExprRole, roles.StrAsPlainColumnRole, Immutable, - NamedColumn, + NamedColumn[_T], ): """Represents a column expression from any textual string. @@ -4728,101 +3799,18 @@ class ColumnClause( _is_multiparam_column = False - def __init__(self, text, type_=None, is_literal=False, _selectable=None): - """Produce a :class:`.ColumnClause` object. - - The :class:`.ColumnClause` is a lightweight analogue to the - :class:`_schema.Column` class. The :func:`_expression.column` - function can - be invoked with just a name alone, as in:: - - from sqlalchemy import column - - id, name = column("id"), column("name") - stmt = select(id, name).select_from("user") - - The above statement would produce SQL like:: - - SELECT id, name FROM user - - Once constructed, :func:`_expression.column` - may be used like any other SQL - expression element such as within :func:`_expression.select` - constructs:: - - from sqlalchemy.sql import column - - id, name = column("id"), column("name") - stmt = select(id, name).select_from("user") - - The text handled by :func:`_expression.column` - is assumed to be handled - like the name of a database column; if the string contains mixed case, - special characters, or matches a known reserved word on the target - backend, the column expression will render using the quoting - behavior determined by the backend. To produce a textual SQL - expression that is rendered exactly without any quoting, - use :func:`_expression.literal_column` instead, - or pass ``True`` as the - value of :paramref:`_expression.column.is_literal`. Additionally, - full SQL - statements are best handled using the :func:`_expression.text` - construct. - - :func:`_expression.column` can be used in a table-like - fashion by combining it with the :func:`.table` function - (which is the lightweight analogue to :class:`_schema.Table` - ) to produce - a working table construct with minimal boilerplate:: - - from sqlalchemy import table, column, select - - user = table("user", - column("id"), - column("name"), - column("description"), - ) - - stmt = select(user.c.description).where(user.c.name == 'wendy') - - A :func:`_expression.column` / :func:`.table` - construct like that illustrated - above can be created in an - ad-hoc fashion and is not associated with any - :class:`_schema.MetaData`, DDL, or events, unlike its - :class:`_schema.Table` counterpart. - - .. versionchanged:: 1.0.0 :func:`_expression.column` can now - be imported from the plain ``sqlalchemy`` namespace like any - other SQL element. - - :param text: the text of the element. - - :param type: :class:`_types.TypeEngine` object which can associate - this :class:`.ColumnClause` with a type. - - :param is_literal: if True, the :class:`.ColumnClause` is assumed to - be an exact expression that will be delivered to the output with no - quoting rules applied regardless of case sensitive settings. the - :func:`_expression.literal_column()` function essentially invokes - :func:`_expression.column` while passing ``is_literal=True``. - - .. seealso:: - - :class:`_schema.Column` - - :func:`_expression.literal_column` - - :func:`.table` - - :func:`_expression.text` - - :ref:`sqlexpression_literal_column` - - """ + def __init__( + self, + text: str, + type_: Optional[ + Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"] + ] = None, + is_literal: bool = False, + _selectable: Optional["FromClause"] = None, + ): self.key = self.name = text self.table = _selectable - self.type = type_api.to_instance(type_) + self.type: TypeEngine[_T] = type_api.to_instance(type_) self.is_literal = is_literal def get_children(self, column_tables=False, **kw): @@ -5010,6 +3998,16 @@ class CollationClause(ColumnElement): _traverse_internals = [("collation", InternalTraversal.dp_string)] + @classmethod + def _create_collation_expression(cls, expression, collation): + expr = coercions.expect(roles.ExpressionElementRole, expression) + return BinaryExpression( + expr, + CollationClause(collation), + operators.collate, + type_=expression.type, + ) + def __init__(self, collation): self.collation = collation @@ -5280,7 +4278,7 @@ class _anonymous_label(_truncated_label): @classmethod def safe_construct( cls, seed, body, enclosing_label=None, sanitize_key=False - ): + ) -> "_anonymous_label": if sanitize_key: body = re.sub(r"[%\(\) \$]+", "_", body).strip("_") diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 54f67b930..680eae754 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -10,263 +10,143 @@ """ -__all__ = [ - "Alias", - "AliasedReturnsRows", - "any_", - "all_", - "CacheKey", - "ClauseElement", - "ColumnCollection", - "ColumnElement", - "CompoundSelect", - "Delete", - "FromClause", - "Insert", - "Join", - "Lateral", - "LambdaElement", - "StatementLambdaElement", - "Select", - "Selectable", - "TableClause", - "TableValuedAlias", - "Update", - "Values", - "alias", - "and_", - "asc", - "between", - "bindparam", - "case", - "cast", - "column", - "custom_op", - "cte", - "delete", - "desc", - "distinct", - "except_", - "except_all", - "exists", - "extract", - "func", - "modifier", - "collate", - "insert", - "intersect", - "intersect_all", - "join", - "label", - "lateral", - "lambda_stmt", - "literal", - "literal_column", - "not_", - "null", - "nulls_first", - "nulls_last", - "or_", - "outparam", - "outerjoin", - "over", - "select", - "table", - "text", - "tuple_", - "type_coerce", - "quoted_name", - "union", - "union_all", - "update", - "quoted_name", - "within_group", - "Subquery", - "TableSample", - "tablesample", - "values", -] +from ._dml_constructors import delete as delete +from ._dml_constructors import insert as insert +from ._dml_constructors import update as update +from ._elements_constructors import all_ as all_ +from ._elements_constructors import and_ as and_ +from ._elements_constructors import any_ as any_ +from ._elements_constructors import asc as asc +from ._elements_constructors import between as between +from ._elements_constructors import bindparam as bindparam +from ._elements_constructors import case as case +from ._elements_constructors import cast as cast +from ._elements_constructors import collate as collate +from ._elements_constructors import column as column +from ._elements_constructors import desc as desc +from ._elements_constructors import distinct as distinct +from ._elements_constructors import extract as extract +from ._elements_constructors import false as false +from ._elements_constructors import funcfilter as funcfilter +from ._elements_constructors import label as label +from ._elements_constructors import not_ as not_ +from ._elements_constructors import null as null +from ._elements_constructors import nulls_first as nulls_first +from ._elements_constructors import nulls_last as nulls_last +from ._elements_constructors import or_ as or_ +from ._elements_constructors import outparam as outparam +from ._elements_constructors import over as over +from ._elements_constructors import text as text +from ._elements_constructors import true as true +from ._elements_constructors import tuple_ as tuple_ +from ._elements_constructors import type_coerce as type_coerce +from ._elements_constructors import typing as typing +from ._elements_constructors import within_group as within_group +from ._selectable_constructors import alias as alias +from ._selectable_constructors import cte as cte +from ._selectable_constructors import except_ as except_ +from ._selectable_constructors import except_all as except_all +from ._selectable_constructors import exists as exists +from ._selectable_constructors import intersect as intersect +from ._selectable_constructors import intersect_all as intersect_all +from ._selectable_constructors import join as join +from ._selectable_constructors import lateral as lateral +from ._selectable_constructors import outerjoin as outerjoin +from ._selectable_constructors import select as select +from ._selectable_constructors import table as table +from ._selectable_constructors import tablesample as tablesample +from ._selectable_constructors import union as union +from ._selectable_constructors import union_all as union_all +from ._selectable_constructors import values as values +from .base import _from_objects as _from_objects +from .base import _select_iterables as _select_iterables +from .base import ColumnCollection as ColumnCollection +from .base import Executable as Executable +from .cache_key import CacheKey as CacheKey +from .dml import Delete as Delete +from .dml import Insert as Insert +from .dml import Update as Update +from .dml import UpdateBase as UpdateBase +from .dml import ValuesBase as ValuesBase +from .elements import _truncated_label as _truncated_label +from .elements import BinaryExpression as BinaryExpression +from .elements import BindParameter as BindParameter +from .elements import BooleanClauseList as BooleanClauseList +from .elements import Case as Case +from .elements import Cast as Cast +from .elements import ClauseElement as ClauseElement +from .elements import ClauseList as ClauseList +from .elements import CollectionAggregate as CollectionAggregate +from .elements import ColumnClause as ColumnClause +from .elements import ColumnElement as ColumnElement +from .elements import Extract as Extract +from .elements import False_ as False_ +from .elements import FunctionFilter as FunctionFilter +from .elements import Grouping as Grouping +from .elements import Label as Label +from .elements import literal as literal +from .elements import literal_column as literal_column +from .elements import Null as Null +from .elements import Over as Over +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 TextClause as TextClause +from .elements import True_ as True_ +from .elements import Tuple as Tuple +from .elements import TypeClause as TypeClause +from .elements import TypeCoerce as TypeCoerce +from .elements import UnaryExpression as UnaryExpression +from .elements import WithinGroup as WithinGroup +from .functions import func as func +from .functions import Function as Function +from .functions import FunctionElement as FunctionElement +from .functions import modifier as modifier +from .lambdas import lambda_stmt as lambda_stmt +from .lambdas import LambdaElement as LambdaElement +from .lambdas import StatementLambdaElement as StatementLambdaElement +from .operators import ColumnOperators as ColumnOperators +from .operators import custom_op as custom_op +from .operators import Operators as Operators +from .selectable import Alias as Alias +from .selectable import AliasedReturnsRows as AliasedReturnsRows +from .selectable import CompoundSelect as CompoundSelect +from .selectable import CTE as CTE +from .selectable import Exists as Exists +from .selectable import FromClause as FromClause +from .selectable import FromGrouping as FromGrouping +from .selectable import GenerativeSelect as GenerativeSelect +from .selectable import HasCTE as HasCTE +from .selectable import HasPrefixes as HasPrefixes +from .selectable import HasSuffixes as HasSuffixes +from .selectable import Join as Join +from .selectable import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT +from .selectable import LABEL_STYLE_NONE as LABEL_STYLE_NONE +from .selectable import Lateral as Lateral +from .selectable import ReturnsRows as ReturnsRows +from .selectable import ScalarSelect as ScalarSelect +from .selectable import Select as Select +from .selectable import Selectable as Selectable +from .selectable import SelectBase as SelectBase +from .selectable import Subquery as Subquery +from .selectable import TableClause as TableClause +from .selectable import TableSample as TableSample +from .selectable import TableValuedAlias as TableValuedAlias +from .selectable import TextAsFrom as TextAsFrom +from .selectable import TextualSelect as TextualSelect +from .selectable import Values as Values +from .visitors import Visitable as Visitable -from typing import Callable +if True: + # work around zimports + from .selectable import ( + LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY, + ) + from .selectable import ( + LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL, + ) -from .base import _from_objects -from .base import _select_iterables -from .base import ColumnCollection -from .base import Executable -from .cache_key import CacheKey -from .dml import Delete -from .dml import Insert -from .dml import Update -from .dml import UpdateBase -from .dml import ValuesBase -from .elements import _truncated_label -from .elements import between -from .elements import BinaryExpression -from .elements import BindParameter -from .elements import BooleanClauseList -from .elements import Case -from .elements import Cast -from .elements import ClauseElement -from .elements import ClauseList -from .elements import collate -from .elements import CollectionAggregate -from .elements import ColumnClause -from .elements import ColumnElement -from .elements import Extract -from .elements import False_ -from .elements import FunctionFilter -from .elements import Grouping -from .elements import Label -from .elements import literal -from .elements import literal_column -from .elements import not_ -from .elements import Null -from .elements import outparam -from .elements import Over -from .elements import quoted_name -from .elements import ReleaseSavepointClause -from .elements import RollbackToSavepointClause -from .elements import SavepointClause -from .elements import TextClause -from .elements import True_ -from .elements import Tuple -from .elements import TypeClause -from .elements import TypeCoerce -from .elements import UnaryExpression -from .elements import WithinGroup -from .functions import func -from .functions import Function -from .functions import FunctionElement -from .functions import modifier -from .lambdas import lambda_stmt -from .lambdas import LambdaElement -from .lambdas import StatementLambdaElement -from .operators import ColumnOperators -from .operators import custom_op -from .operators import Operators -from .selectable import Alias -from .selectable import AliasedReturnsRows -from .selectable import CompoundSelect -from .selectable import CTE -from .selectable import Exists -from .selectable import FromClause -from .selectable import FromGrouping -from .selectable import GenerativeSelect -from .selectable import HasCTE -from .selectable import HasPrefixes -from .selectable import HasSuffixes -from .selectable import Join -from .selectable import LABEL_STYLE_DEFAULT -from .selectable import LABEL_STYLE_DISAMBIGUATE_ONLY -from .selectable import LABEL_STYLE_NONE -from .selectable import LABEL_STYLE_TABLENAME_PLUS_COL -from .selectable import Lateral -from .selectable import ReturnsRows -from .selectable import ScalarSelect -from .selectable import Select -from .selectable import Selectable -from .selectable import SelectBase -from .selectable import Subquery -from .selectable import TableClause -from .selectable import TableSample -from .selectable import TableValuedAlias -from .selectable import TextAsFrom -from .selectable import TextualSelect -from .selectable import Values -from .visitors import Visitable -from ..util.langhelpers import public_factory - -# 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_") -and_ = public_factory(BooleanClauseList.and_, ".sql.expression.and_") -alias = public_factory(Alias._factory, ".sql.expression.alias") -tablesample = public_factory( - TableSample._factory, ".sql.expression.tablesample" -) -lateral = public_factory(Lateral._factory, ".sql.expression.lateral") -or_ = public_factory(BooleanClauseList.or_, ".sql.expression.or_") -bindparam = public_factory(BindParameter, ".sql.expression.bindparam") -select = public_factory(Select._create, ".sql.expression.select") -text = public_factory(TextClause._create_text, ".sql.expression.text") -table = public_factory(TableClause, ".sql.expression.table") -column = public_factory(ColumnClause, ".sql.expression.column") -over = public_factory(Over, ".sql.expression.over") -within_group = public_factory(WithinGroup, ".sql.expression.within_group") -label = public_factory(Label, ".sql.expression.label") -case = public_factory(Case, ".sql.expression.case") -cast = public_factory(Cast, ".sql.expression.cast") -cte = public_factory(CTE._factory, ".sql.expression.cte") -values = public_factory(Values, ".sql.expression.values") -extract = public_factory(Extract, ".sql.expression.extract") -tuple_ = public_factory(Tuple, ".sql.expression.tuple_") -except_ = public_factory( - CompoundSelect._create_except, ".sql.expression.except_" -) -except_all = public_factory( - CompoundSelect._create_except_all, ".sql.expression.except_all" -) -intersect = public_factory( - CompoundSelect._create_intersect, ".sql.expression.intersect" -) -intersect_all = public_factory( - CompoundSelect._create_intersect_all, ".sql.expression.intersect_all" -) -union = public_factory(CompoundSelect._create_union, ".sql.expression.union") -union_all = public_factory( - CompoundSelect._create_union_all, ".sql.expression.union_all" -) -exists = public_factory(Exists, ".sql.expression.exists") -nulls_first = public_factory( - UnaryExpression._create_nulls_first, ".sql.expression.nulls_first" -) -nullsfirst = nulls_first # deprecated 1.4; see #5435 -nulls_last = public_factory( - UnaryExpression._create_nulls_last, ".sql.expression.nulls_last" -) -nullslast = nulls_last # deprecated 1.4; see #5435 -asc = public_factory(UnaryExpression._create_asc, ".sql.expression.asc") -desc = public_factory(UnaryExpression._create_desc, ".sql.expression.desc") -distinct = public_factory( - UnaryExpression._create_distinct, ".sql.expression.distinct" -) -type_coerce = public_factory(TypeCoerce, ".sql.expression.type_coerce") -true = public_factory(True_._instance, ".sql.expression.true") -false = public_factory(False_._instance, ".sql.expression.false") -null = public_factory(Null._instance, ".sql.expression.null") -join = public_factory(Join._create_join, ".sql.expression.join") -outerjoin = public_factory(Join._create_outerjoin, ".sql.expression.outerjoin") -insert = public_factory(Insert, ".sql.expression.insert") -update = public_factory(Update, ".sql.expression.update") -delete = public_factory(Delete, ".sql.expression.delete") -funcfilter = public_factory(FunctionFilter, ".sql.expression.funcfilter") - - -# internal functions still being called from tests and the ORM, -# these might be better off in some other namespace - - -# old names for compatibility -_Executable = Executable -_BindParamClause = BindParameter -_Label = Label -_SelectBase = SelectBase -_BinaryExpression = BinaryExpression -_Cast = Cast -_Null = Null -_False = False_ -_True = True_ -_TextClause = TextClause -_UnaryExpression = UnaryExpression -_Case = Case -_Tuple = Tuple -_Over = Over -_TypeClause = TypeClause -_Extract = Extract -_Exists = Exists -_Grouping = Grouping -_FromGrouping = FromGrouping -_ScalarSelect = ScalarSelect +nullsfirst = nulls_first +nullslast = nulls_last diff --git a/lib/sqlalchemy/sql/functions.py b/lib/sqlalchemy/sql/functions.py index 3b6da7175..7a1e80889 100644 --- a/lib/sqlalchemy/sql/functions.py +++ b/lib/sqlalchemy/sql/functions.py @@ -567,7 +567,7 @@ class FunctionElement(Executable, ColumnElement, FromClause, Generative): s = select(function_element) """ - s = Select._create(self) + s = Select(self) if self._execution_options: s = s.execution_options(**self._execution_options) return s diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py index d7a5d9348..cf61f2637 100644 --- a/lib/sqlalchemy/sql/operators.py +++ b/lib/sqlalchemy/sql/operators.py @@ -10,32 +10,84 @@ """Defines operators used in SQL expressions.""" -from operator import add -from operator import and_ -from operator import contains -from operator import eq -from operator import floordiv -from operator import ge -from operator import getitem -from operator import gt -from operator import inv -from operator import le -from operator import lshift -from operator import lt -from operator import mod -from operator import mul -from operator import ne -from operator import neg -from operator import or_ -from operator import rshift -from operator import sub -from operator import truediv +from operator import add as _uncast_add +from operator import and_ as _uncast_and_ +from operator import contains as _uncast_contains +from operator import eq as _uncast_eq +from operator import floordiv as _uncast_floordiv +from operator import ge as _uncast_ge +from operator import getitem as _uncast_getitem +from operator import gt as _uncast_gt +from operator import inv as _uncast_inv +from operator import le as _uncast_le +from operator import lshift as _uncast_lshift +from operator import lt as _uncast_lt +from operator import mod as _uncast_mod +from operator import mul as _uncast_mul +from operator import ne as _uncast_ne +from operator import neg as _uncast_neg +from operator import or_ as _uncast_or_ +from operator import rshift as _uncast_rshift +from operator import sub as _uncast_sub +from operator import truediv as _uncast_truediv +import typing +from typing import Any +from typing import Callable +from typing import cast +from typing import Generic +from typing import Optional +from typing import overload +from typing import Type +from typing import TypeVar +from typing import Union from .. import exc from .. import util - - -class Operators: +from ..util.typing import Protocol + +if typing.TYPE_CHECKING: + from .elements import BinaryExpression + from .elements import ColumnElement + from .type_api import TypeEngine + +_OP_RETURN = TypeVar("_OP_RETURN", bound=Any, covariant=True) +_T = TypeVar("_T", bound=Any) + + +class OperatorType(Protocol): + """describe an op() function.""" + + __name__: str + + def __call__( + self, left: "Operators[_OP_RETURN]", *other: Any, **kwargs: Any + ) -> "_OP_RETURN": + ... + + +add = cast(OperatorType, _uncast_add) +and_ = cast(OperatorType, _uncast_and_) +contains = cast(OperatorType, _uncast_contains) +eq = cast(OperatorType, _uncast_eq) +floordiv = cast(OperatorType, _uncast_floordiv) +ge = cast(OperatorType, _uncast_ge) +getitem = cast(OperatorType, _uncast_getitem) +gt = cast(OperatorType, _uncast_gt) +inv = cast(OperatorType, _uncast_inv) +le = cast(OperatorType, _uncast_le) +lshift = cast(OperatorType, _uncast_lshift) +lt = cast(OperatorType, _uncast_lt) +mod = cast(OperatorType, _uncast_mod) +mul = cast(OperatorType, _uncast_mul) +ne = cast(OperatorType, _uncast_ne) +neg = cast(OperatorType, _uncast_neg) +or_ = cast(OperatorType, _uncast_or_) +rshift = cast(OperatorType, _uncast_rshift) +sub = cast(OperatorType, _uncast_sub) +truediv = cast(OperatorType, _uncast_truediv) + + +class Operators(Generic[_OP_RETURN]): """Base of comparison and logical operators. Implements base methods @@ -52,7 +104,7 @@ class Operators: __slots__ = () - def __and__(self, other): + def __and__(self, other: Any) -> "Operators": """Implement the ``&`` operator. When used with SQL expressions, results in an @@ -76,7 +128,7 @@ class Operators: """ return self.operate(and_, other) - def __or__(self, other): + def __or__(self, other: Any) -> "Operators": """Implement the ``|`` operator. When used with SQL expressions, results in an @@ -100,7 +152,7 @@ class Operators: """ return self.operate(or_, other) - def __invert__(self): + def __invert__(self) -> "Operators": """Implement the ``~`` operator. When used with SQL expressions, results in a @@ -119,12 +171,14 @@ class Operators: def op( self, - opstring, - precedence=0, - is_comparison=False, - return_type=None, + opstring: Any, + precedence: int = 0, + is_comparison: bool = False, + return_type: Optional[ + Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"] + ] = None, python_impl=None, - ): + ) -> Callable[[Any], _OP_RETURN]: """Produce a generic operator function. e.g.:: @@ -205,12 +259,14 @@ class Operators: python_impl=python_impl, ) - def against(other): + def against(other: Any) -> _OP_RETURN: return operator(self, other) return against - def bool_op(self, opstring, precedence=0, python_impl=None): + def bool_op( + self, opstring: Any, precedence: int = 0, python_impl=None + ) -> Callable[[Any], _OP_RETURN]: """Return a custom boolean operator. This method is shorthand for calling @@ -230,7 +286,9 @@ class Operators: python_impl=python_impl, ) - def operate(self, op, *other, **kwargs): + def operate( + self, op: OperatorType, *other: Any, **kwargs: Any + ) -> _OP_RETURN: r"""Operate on an argument. This is the lowest level of operation, raises @@ -258,7 +316,9 @@ class Operators: __sa_operate__ = operate - def reverse_operate(self, op, other, **kwargs): + def reverse_operate( + self, op: OperatorType, other: Any, **kwargs: Any + ) -> _OP_RETURN: """Reverse operate on an argument. Usage is the same as :meth:`operate`. @@ -267,7 +327,7 @@ class Operators: raise NotImplementedError(str(op)) -class custom_op: +class custom_op(OperatorType, Generic[_T]): """Represent a 'custom' operator. :class:`.custom_op` is normally instantiated when the @@ -307,12 +367,14 @@ class custom_op: def __init__( self, - opstring, - precedence=0, - is_comparison=False, - return_type=None, - natural_self_precedent=False, - eager_grouping=False, + opstring: str, + precedence: int = 0, + is_comparison: bool = False, + return_type: Optional[ + Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"] + ] = None, + natural_self_precedent: bool = False, + eager_grouping: bool = False, python_impl=None, ): self.opstring = opstring @@ -325,13 +387,27 @@ class custom_op: ) self.python_impl = python_impl - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: return isinstance(other, custom_op) and other.opstring == self.opstring - def __hash__(self): + def __hash__(self) -> int: return id(self) - def __call__(self, left, right, **kw): + @overload + def __call__( + self, left: "ColumnElement", right: Any, **kw + ) -> "BinaryExpression[_T]": + ... + + @overload + def __call__( + self, left: "Operators[_OP_RETURN]", right: Any, **kw + ) -> _OP_RETURN: + ... + + def __call__( + self, left: "Operators[_OP_RETURN]", right: Any, **kw + ) -> _OP_RETURN: if hasattr(left, "__sa_operate__"): return left.operate(self, right, **kw) elif self.python_impl: @@ -344,7 +420,7 @@ class custom_op: ) -class ColumnOperators(Operators): +class ColumnOperators(Operators[_OP_RETURN]): """Defines boolean, comparison, and other operators for :class:`_expression.ColumnElement` expressions. @@ -387,7 +463,19 @@ class ColumnOperators(Operators): timetuple = None """Hack, allows datetime objects to be compared on the LHS.""" - def __lt__(self, other): + if typing.TYPE_CHECKING: + + def operate( + self, op: OperatorType, *other: Any, **kwargs: Any + ) -> "ColumnOperators": + ... + + def reverse_operate( + self, op: OperatorType, other: Any, **kwargs: Any + ) -> "ColumnOperators": + ... + + def __lt__(self, other: Any) -> "ColumnOperators": """Implement the ``<`` operator. In a column context, produces the clause ``a < b``. @@ -395,7 +483,7 @@ class ColumnOperators(Operators): """ return self.operate(lt, other) - def __le__(self, other): + def __le__(self, other: Any) -> "ColumnOperators": """Implement the ``<=`` operator. In a column context, produces the clause ``a <= b``. @@ -403,9 +491,10 @@ class ColumnOperators(Operators): """ return self.operate(le, other) - __hash__ = Operators.__hash__ + # TODO: not sure why we have this + __hash__ = Operators.__hash__ # type: ignore - def __eq__(self, other): + def __eq__(self, other: Any) -> "ColumnOperators": """Implement the ``==`` operator. In a column context, produces the clause ``a = b``. @@ -414,7 +503,7 @@ class ColumnOperators(Operators): """ return self.operate(eq, other) - def __ne__(self, other): + def __ne__(self, other: Any) -> "ColumnOperators": """Implement the ``!=`` operator. In a column context, produces the clause ``a != b``. @@ -423,7 +512,7 @@ class ColumnOperators(Operators): """ return self.operate(ne, other) - def is_distinct_from(self, other): + def is_distinct_from(self, other: Any) -> "ColumnOperators": """Implement the ``IS DISTINCT FROM`` operator. Renders "a IS DISTINCT FROM b" on most platforms; @@ -434,7 +523,7 @@ class ColumnOperators(Operators): """ return self.operate(is_distinct_from, other) - def is_not_distinct_from(self, other): + def is_not_distinct_from(self, other: Any) -> "ColumnOperators": """Implement the ``IS NOT DISTINCT FROM`` operator. Renders "a IS NOT DISTINCT FROM b" on most platforms; @@ -452,7 +541,7 @@ class ColumnOperators(Operators): # deprecated 1.4; see #5435 isnot_distinct_from = is_not_distinct_from - def __gt__(self, other): + def __gt__(self, other: Any) -> "ColumnOperators": """Implement the ``>`` operator. In a column context, produces the clause ``a > b``. @@ -460,7 +549,7 @@ class ColumnOperators(Operators): """ return self.operate(gt, other) - def __ge__(self, other): + def __ge__(self, other: Any) -> "ColumnOperators": """Implement the ``>=`` operator. In a column context, produces the clause ``a >= b``. @@ -468,7 +557,7 @@ class ColumnOperators(Operators): """ return self.operate(ge, other) - def __neg__(self): + def __neg__(self) -> "ColumnOperators": """Implement the ``-`` operator. In a column context, produces the clause ``-a``. @@ -476,10 +565,10 @@ class ColumnOperators(Operators): """ return self.operate(neg) - def __contains__(self, other): + def __contains__(self, other: Any) -> "ColumnOperators": return self.operate(contains, other) - def __getitem__(self, index): + def __getitem__(self, index: Any) -> "ColumnOperators": """Implement the [] operator. This can be used by some database-specific types @@ -488,7 +577,7 @@ class ColumnOperators(Operators): """ return self.operate(getitem, index) - def __lshift__(self, other): + def __lshift__(self, other: Any) -> "ColumnOperators": """implement the << operator. Not used by SQLAlchemy core, this is provided @@ -497,7 +586,7 @@ class ColumnOperators(Operators): """ return self.operate(lshift, other) - def __rshift__(self, other): + def __rshift__(self, other: Any) -> "ColumnOperators": """implement the >> operator. Not used by SQLAlchemy core, this is provided @@ -506,7 +595,7 @@ class ColumnOperators(Operators): """ return self.operate(rshift, other) - def concat(self, other): + def concat(self, other: Any) -> "ColumnOperators": """Implement the 'concat' operator. In a column context, produces the clause ``a || b``, @@ -515,7 +604,7 @@ class ColumnOperators(Operators): """ return self.operate(concat_op, other) - def like(self, other, escape=None): + def like(self, other: Any, escape=None) -> "ColumnOperators": r"""Implement the ``like`` operator. In a column context, produces the expression:: @@ -540,7 +629,7 @@ class ColumnOperators(Operators): """ return self.operate(like_op, other, escape=escape) - def ilike(self, other, escape=None): + def ilike(self, other: Any, escape=None) -> "ColumnOperators": r"""Implement the ``ilike`` operator, e.g. case insensitive LIKE. In a column context, produces an expression either of the form:: @@ -569,7 +658,7 @@ class ColumnOperators(Operators): """ return self.operate(ilike_op, other, escape=escape) - def in_(self, other): + def in_(self, other: Any) -> "ColumnOperators": """Implement the ``in`` operator. In a column context, produces the clause ``column IN <other>``. @@ -658,7 +747,7 @@ class ColumnOperators(Operators): """ return self.operate(in_op, other) - def not_in(self, other): + def not_in(self, other: Any) -> "ColumnOperators": """implement the ``NOT IN`` operator. This is equivalent to using negation with @@ -689,7 +778,7 @@ class ColumnOperators(Operators): # deprecated 1.4; see #5429 notin_ = not_in - def not_like(self, other, escape=None): + def not_like(self, other: Any, escape=None) -> "ColumnOperators": """implement the ``NOT LIKE`` operator. This is equivalent to using negation with @@ -709,7 +798,7 @@ class ColumnOperators(Operators): # deprecated 1.4; see #5435 notlike = not_like - def not_ilike(self, other, escape=None): + def not_ilike(self, other: Any, escape=None) -> "ColumnOperators": """implement the ``NOT ILIKE`` operator. This is equivalent to using negation with @@ -729,7 +818,7 @@ class ColumnOperators(Operators): # deprecated 1.4; see #5435 notilike = not_ilike - def is_(self, other): + def is_(self, other: Any) -> "ColumnOperators": """Implement the ``IS`` operator. Normally, ``IS`` is generated automatically when comparing to a @@ -742,7 +831,7 @@ class ColumnOperators(Operators): """ return self.operate(is_, other) - def is_not(self, other): + def is_not(self, other: Any) -> "ColumnOperators": """Implement the ``IS NOT`` operator. Normally, ``IS NOT`` is generated automatically when comparing to a @@ -762,7 +851,9 @@ class ColumnOperators(Operators): # deprecated 1.4; see #5429 isnot = is_not - def startswith(self, other, **kwargs): + def startswith( + self, other: Any, escape=None, autoescape=False + ) -> "ColumnOperators": r"""Implement the ``startswith`` operator. Produces a LIKE expression that tests against a match for the start @@ -839,9 +930,13 @@ class ColumnOperators(Operators): :meth:`.ColumnOperators.like` """ - return self.operate(startswith_op, other, **kwargs) + return self.operate( + startswith_op, other, escape=escape, autoescape=autoescape + ) - def endswith(self, other, **kwargs): + def endswith( + self, other: Any, escape=None, autoescape=False + ) -> "ColumnOperators": r"""Implement the 'endswith' operator. Produces a LIKE expression that tests against a match for the end @@ -918,9 +1013,13 @@ class ColumnOperators(Operators): :meth:`.ColumnOperators.like` """ - return self.operate(endswith_op, other, **kwargs) + return self.operate( + endswith_op, other, escape=escape, autoescape=autoescape + ) - def contains(self, other, **kwargs): + def contains( + self, other: Any, escape=None, autoescape=False + ) -> "ColumnOperators": r"""Implement the 'contains' operator. Produces a LIKE expression that tests against a match for the middle @@ -998,9 +1097,11 @@ class ColumnOperators(Operators): """ - return self.operate(contains_op, other, **kwargs) + return self.operate( + contains_op, other, escape=escape, autoescape=autoescape + ) - def match(self, other, **kwargs): + def match(self, other: Any, **kwargs) -> "ColumnOperators": """Implements a database-specific 'match' operator. :meth:`_sql.ColumnOperators.match` attempts to resolve to @@ -1024,7 +1125,7 @@ class ColumnOperators(Operators): """ return self.operate(match_op, other, **kwargs) - def regexp_match(self, pattern, flags=None): + def regexp_match(self, pattern, flags=None) -> "ColumnOperators": """Implements a database-specific 'regexp match' operator. E.g.:: @@ -1072,7 +1173,9 @@ class ColumnOperators(Operators): """ return self.operate(regexp_match_op, pattern, flags=flags) - def regexp_replace(self, pattern, replacement, flags=None): + def regexp_replace( + self, pattern, replacement, flags=None + ) -> "ColumnOperators": """Implements a database-specific 'regexp replace' operator. E.g.:: @@ -1111,20 +1214,23 @@ class ColumnOperators(Operators): """ return self.operate( - regexp_replace_op, pattern, replacement=replacement, flags=flags + regexp_replace_op, + pattern, + replacement=replacement, + flags=flags, ) - def desc(self): + def desc(self) -> "ColumnOperators": """Produce a :func:`_expression.desc` clause against the parent object.""" return self.operate(desc_op) - def asc(self): + def asc(self) -> "ColumnOperators": """Produce a :func:`_expression.asc` clause against the parent object.""" return self.operate(asc_op) - def nulls_first(self): + def nulls_first(self) -> "ColumnOperators": """Produce a :func:`_expression.nulls_first` clause against the parent object. @@ -1137,7 +1243,7 @@ class ColumnOperators(Operators): # deprecated 1.4; see #5435 nullsfirst = nulls_first - def nulls_last(self): + def nulls_last(self) -> "ColumnOperators": """Produce a :func:`_expression.nulls_last` clause against the parent object. @@ -1150,7 +1256,7 @@ class ColumnOperators(Operators): # deprecated 1.4; see #5429 nullslast = nulls_last - def collate(self, collation): + def collate(self, collation) -> "ColumnOperators": """Produce a :func:`_expression.collate` clause against the parent object, given the collation string. @@ -1161,7 +1267,7 @@ class ColumnOperators(Operators): """ return self.operate(collate, collation) - def __radd__(self, other): + def __radd__(self, other: Any) -> "ColumnOperators": """Implement the ``+`` operator in reverse. See :meth:`.ColumnOperators.__add__`. @@ -1169,7 +1275,7 @@ class ColumnOperators(Operators): """ return self.reverse_operate(add, other) - def __rsub__(self, other): + def __rsub__(self, other: Any) -> "ColumnOperators": """Implement the ``-`` operator in reverse. See :meth:`.ColumnOperators.__sub__`. @@ -1177,7 +1283,7 @@ class ColumnOperators(Operators): """ return self.reverse_operate(sub, other) - def __rmul__(self, other): + def __rmul__(self, other: Any) -> "ColumnOperators": """Implement the ``*`` operator in reverse. See :meth:`.ColumnOperators.__mul__`. @@ -1185,7 +1291,7 @@ class ColumnOperators(Operators): """ return self.reverse_operate(mul, other) - def __rmod__(self, other): + def __rmod__(self, other: Any) -> "ColumnOperators": """Implement the ``%`` operator in reverse. See :meth:`.ColumnOperators.__mod__`. @@ -1193,21 +1299,21 @@ class ColumnOperators(Operators): """ return self.reverse_operate(mod, other) - def between(self, cleft, cright, symmetric=False): + def between(self, cleft, cright, symmetric=False) -> "ColumnOperators": """Produce a :func:`_expression.between` clause against the parent object, given the lower and upper range. """ return self.operate(between_op, cleft, cright, symmetric=symmetric) - def distinct(self): + def distinct(self) -> "ColumnOperators": """Produce a :func:`_expression.distinct` clause against the parent object. """ return self.operate(distinct_op) - def any_(self): + def any_(self) -> "ColumnOperators": """Produce an :func:`_expression.any_` clause against the parent object. @@ -1224,7 +1330,7 @@ class ColumnOperators(Operators): """ return self.operate(any_op) - def all_(self): + def all_(self) -> "ColumnOperators": """Produce an :func:`_expression.all_` clause against the parent object. @@ -1242,7 +1348,7 @@ class ColumnOperators(Operators): """ return self.operate(all_op) - def __add__(self, other): + def __add__(self, other: Any) -> "ColumnOperators": """Implement the ``+`` operator. In a column context, produces the clause ``a + b`` @@ -1254,7 +1360,7 @@ class ColumnOperators(Operators): """ return self.operate(add, other) - def __sub__(self, other): + def __sub__(self, other: Any) -> "ColumnOperators": """Implement the ``-`` operator. In a column context, produces the clause ``a - b``. @@ -1262,7 +1368,7 @@ class ColumnOperators(Operators): """ return self.operate(sub, other) - def __mul__(self, other): + def __mul__(self, other: Any) -> "ColumnOperators": """Implement the ``*`` operator. In a column context, produces the clause ``a * b``. @@ -1270,7 +1376,7 @@ class ColumnOperators(Operators): """ return self.operate(mul, other) - def __mod__(self, other): + def __mod__(self, other: Any) -> "ColumnOperators": """Implement the ``%`` operator. In a column context, produces the clause ``a % b``. @@ -1278,7 +1384,7 @@ class ColumnOperators(Operators): """ return self.operate(mod, other) - def __truediv__(self, other): + def __truediv__(self, other: Any) -> "ColumnOperators": """Implement the ``/`` operator. In a column context, produces the clause ``a / b``, and @@ -1291,7 +1397,7 @@ class ColumnOperators(Operators): """ return self.operate(truediv, other) - def __rtruediv__(self, other): + def __rtruediv__(self, other: Any) -> "ColumnOperators": """Implement the ``/`` operator in reverse. See :meth:`.ColumnOperators.__truediv__`. @@ -1299,7 +1405,7 @@ class ColumnOperators(Operators): """ return self.reverse_operate(truediv, other) - def __floordiv__(self, other): + def __floordiv__(self, other: Any) -> "ColumnOperators": """Implement the ``//`` operator. In a column context, produces the clause ``a / b``, @@ -1311,7 +1417,7 @@ class ColumnOperators(Operators): """ return self.operate(floordiv, other) - def __rfloordiv__(self, other): + def __rfloordiv__(self, other: Any) -> "ColumnOperators": """Implement the ``//`` operator in reverse. See :meth:`.ColumnOperators.__floordiv__`. @@ -1324,6 +1430,10 @@ _commutative = {eq, ne, add, mul} _comparison = {eq, ne, lt, gt, ge, le} +def _operator_fn(fn): + return cast(OperatorType, fn) + + def commutative_op(fn): _commutative.add(fn) return fn @@ -1351,6 +1461,7 @@ def exists(): raise NotImplementedError() +@_operator_fn def is_true(a): raise NotImplementedError() @@ -1359,6 +1470,7 @@ def is_true(a): istrue = is_true +@_operator_fn def is_false(a): raise NotImplementedError() @@ -1368,11 +1480,13 @@ isfalse = is_false @comparison_op +@_operator_fn def is_distinct_from(a, b): return a.is_distinct_from(b) @comparison_op +@_operator_fn def is_not_distinct_from(a, b): return a.is_not_distinct_from(b) @@ -1382,11 +1496,13 @@ isnot_distinct_from = is_not_distinct_from @comparison_op +@_operator_fn def is_(a, b): return a.is_(b) @comparison_op +@_operator_fn def is_not(a, b): return a.is_not(b) @@ -1395,20 +1511,24 @@ def is_not(a, b): isnot = is_not +@_operator_fn def collate(a, b): return a.collate(b) +@_operator_fn def op(a, opstring, b): return a.op(opstring)(b) @comparison_op +@_operator_fn def like_op(a, b, escape=None): return a.like(b, escape=escape) @comparison_op +@_operator_fn def not_like_op(a, b, escape=None): return a.notlike(b, escape=escape) @@ -1418,11 +1538,13 @@ notlike_op = not_like_op @comparison_op +@_operator_fn def ilike_op(a, b, escape=None): return a.ilike(b, escape=escape) @comparison_op +@_operator_fn def not_ilike_op(a, b, escape=None): return a.not_ilike(b, escape=escape) @@ -1432,11 +1554,13 @@ notilike_op = not_ilike_op @comparison_op +@_operator_fn def between_op(a, b, c, symmetric=False): return a.between(b, c, symmetric=symmetric) @comparison_op +@_operator_fn def not_between_op(a, b, c, symmetric=False): return ~a.between(b, c, symmetric=symmetric) @@ -1446,11 +1570,13 @@ notbetween_op = not_between_op @comparison_op +@_operator_fn def in_op(a, b): return a.in_(b) @comparison_op +@_operator_fn def not_in_op(a, b): return a.not_in(b) @@ -1459,19 +1585,22 @@ def not_in_op(a, b): notin_op = not_in_op +@_operator_fn def distinct_op(a): return a.distinct() +@_operator_fn def any_op(a): return a.any_() +@_operator_fn def all_op(a): return a.all_() -def _escaped_like_impl(fn, other, escape, autoescape): +def _escaped_like_impl(fn, other: Any, escape, autoescape): if autoescape: if autoescape is not True: util.warn( @@ -1492,11 +1621,13 @@ def _escaped_like_impl(fn, other, escape, autoescape): @comparison_op +@_operator_fn def startswith_op(a, b, escape=None, autoescape=False): return _escaped_like_impl(a.startswith, b, escape, autoescape) @comparison_op +@_operator_fn def not_startswith_op(a, b, escape=None, autoescape=False): return ~_escaped_like_impl(a.startswith, b, escape, autoescape) @@ -1506,11 +1637,13 @@ notstartswith_op = not_startswith_op @comparison_op +@_operator_fn def endswith_op(a, b, escape=None, autoescape=False): return _escaped_like_impl(a.endswith, b, escape, autoescape) @comparison_op +@_operator_fn def not_endswith_op(a, b, escape=None, autoescape=False): return ~_escaped_like_impl(a.endswith, b, escape, autoescape) @@ -1520,11 +1653,13 @@ notendswith_op = not_endswith_op @comparison_op +@_operator_fn def contains_op(a, b, escape=None, autoescape=False): return _escaped_like_impl(a.contains, b, escape, autoescape) @comparison_op +@_operator_fn def not_contains_op(a, b, escape=None, autoescape=False): return ~_escaped_like_impl(a.contains, b, escape, autoescape) @@ -1534,25 +1669,30 @@ notcontains_op = not_contains_op @comparison_op +@_operator_fn def match_op(a, b, **kw): return a.match(b, **kw) @comparison_op +@_operator_fn def regexp_match_op(a, b, flags=None): return a.regexp_match(b, flags=flags) @comparison_op +@_operator_fn def not_regexp_match_op(a, b, flags=None): return ~a.regexp_match(b, flags=flags) +@_operator_fn def regexp_replace_op(a, b, replacement, flags=None): return a.regexp_replace(b, replacement=replacement, flags=flags) @comparison_op +@_operator_fn def not_match_op(a, b, **kw): return ~a.match(b, **kw) @@ -1561,26 +1701,32 @@ def not_match_op(a, b, **kw): notmatch_op = not_match_op +@_operator_fn def comma_op(a, b): raise NotImplementedError() +@_operator_fn def filter_op(a, b): raise NotImplementedError() +@_operator_fn def concat_op(a, b): return a.concat(b) +@_operator_fn def desc_op(a): return a.desc() +@_operator_fn def asc_op(a): return a.asc() +@_operator_fn def nulls_first_op(a): return a.nulls_first() @@ -1589,6 +1735,7 @@ def nulls_first_op(a): nullsfirst_op = nulls_first_op +@_operator_fn def nulls_last_op(a): return a.nulls_last() @@ -1597,10 +1744,12 @@ def nulls_last_op(a): nullslast_op = nulls_last_op +@_operator_fn def json_getitem_op(a, b): raise NotImplementedError() +@_operator_fn def json_path_getitem_op(a, b): raise NotImplementedError() diff --git a/lib/sqlalchemy/sql/roles.py b/lib/sqlalchemy/sql/roles.py index 9172c2dc9..787a1c25e 100644 --- a/lib/sqlalchemy/sql/roles.py +++ b/lib/sqlalchemy/sql/roles.py @@ -71,7 +71,13 @@ class ColumnListRole(SQLRole): __slots__ = () -class TruncatedLabelRole(SQLRole): +class StringRole(SQLRole): + """mixin indicating a role that results in strings""" + + __slots__ = () + + +class TruncatedLabelRole(StringRole, SQLRole): __slots__ = () _role_name = "String SQL identifier" diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py index 885cb2754..938d2c34a 100644 --- a/lib/sqlalchemy/sql/schema.py +++ b/lib/sqlalchemy/sql/schema.py @@ -29,6 +29,14 @@ as components in SQL expressions. """ import collections +import typing +from typing import Any +from typing import MutableMapping +from typing import Optional +from typing import overload +from typing import Type +from typing import TypeVar +from typing import Union from . import coercions from . import ddl @@ -52,7 +60,13 @@ from .. import event from .. import exc from .. import inspection from .. import util +from ..util.typing import Literal +if typing.TYPE_CHECKING: + from .type_api import TypeEngine + +_T = TypeVar("_T", bound="Any") +_ServerDefaultType = Union["FetchedValue", str, TextClause, ColumnElement] RETAIN_SCHEMA = util.symbol("retain_schema") @@ -1086,13 +1100,109 @@ class Table(DialectKWArgs, SchemaItem, TableClause): return self._schema_item_copy(table) -class Column(DialectKWArgs, SchemaItem, ColumnClause): +class Column(DialectKWArgs, SchemaItem, ColumnClause[_T]): """Represents a column in a database table.""" __visit_name__ = "column" inherit_cache = True + @overload + def __init__( + self: "Column[None]", + __name: str, + *args: SchemaEventTarget, + autoincrement: Union[bool, Literal["auto", "ignore_fk"]] = ..., + default: Optional[Any] = ..., + doc: Optional[str] = ..., + key: Optional[str] = ..., + index: Optional[bool] = ..., + info: MutableMapping[Any, Any] = ..., + nullable: bool = ..., + onupdate: Optional[Any] = ..., + primary_key: bool = ..., + server_default: Optional[_ServerDefaultType] = ..., + server_onupdate: Optional["FetchedValue"] = ..., + quote: Optional[bool] = ..., + unique: Optional[bool] = ..., + system: bool = ..., + comment: Optional[str] = ..., + **kwargs: Any, + ) -> None: + ... + + @overload + def __init__( + self: "Column[None]", + *args: SchemaEventTarget, + autoincrement: Union[bool, Literal["auto", "ignore_fk"]] = ..., + default: Optional[Any] = ..., + doc: Optional[str] = ..., + key: Optional[str] = ..., + index: Optional[bool] = ..., + info: MutableMapping[Any, Any] = ..., + nullable: bool = ..., + onupdate: Optional[Any] = ..., + primary_key: bool = ..., + server_default: Optional[_ServerDefaultType] = ..., + server_onupdate: Optional["FetchedValue"] = ..., + quote: Optional[bool] = ..., + unique: Optional[bool] = ..., + system: bool = ..., + comment: Optional[str] = ..., + **kwargs: Any, + ) -> None: + ... + + @overload + def __init__( + self, + __name: str, + __type: Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"], + *args: SchemaEventTarget, + autoincrement: Union[bool, Literal["auto", "ignore_fk"]] = ..., + default: Optional[Any] = ..., + doc: Optional[str] = ..., + key: Optional[str] = ..., + index: Optional[bool] = ..., + info: MutableMapping[Any, Any] = ..., + nullable: bool = ..., + onupdate: Optional[Any] = ..., + primary_key: bool = ..., + server_default: Optional[_ServerDefaultType] = ..., + server_onupdate: Optional["FetchedValue"] = ..., + quote: Optional[bool] = ..., + unique: Optional[bool] = ..., + system: bool = ..., + comment: Optional[str] = ..., + **kwargs: Any, + ) -> None: + ... + + @overload + def __init__( + self, + __type: Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"], + *args: SchemaEventTarget, + autoincrement: Union[bool, Literal["auto", "ignore_fk"]] = ..., + default: Optional[Any] = ..., + doc: Optional[str] = ..., + key: Optional[str] = ..., + index: Optional[bool] = ..., + info: MutableMapping[Any, Any] = ..., + nullable: bool = ..., + onupdate: Optional[Any] = ..., + primary_key: bool = ..., + server_default: Optional[_ServerDefaultType] = ..., + server_onupdate: Optional["FetchedValue"] = ..., + quote: Optional[bool] = ..., + unique: Optional[bool] = ..., + system: bool = ..., + comment: Optional[str] = ..., + **kwargs: Any, + ) -> None: + ... + def __init__(self, *args, **kwargs): r""" Construct a new ``Column`` object. diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index e674c4b74..fd1abd71b 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -48,7 +48,6 @@ from .base import Immutable from .base import prefix_anon_map from .coercions import _document_text_coercion from .elements import _anonymous_label -from .elements import and_ from .elements import BindParameter from .elements import BooleanClauseList from .elements import ClauseElement @@ -64,6 +63,9 @@ from .. import exc from .. import util +and_ = BooleanClauseList.and_ + + class _OffsetLimitParam(BindParameter): inherit_cache = True @@ -472,7 +474,7 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable): method which allows for arbitrary column lists. """ - return Select._create(self) + return Select(self) def join(self, right, onclause=None, isouter=False, full=False): """Return a :class:`_expression.Join` from this @@ -990,86 +992,6 @@ class Join(roles.DMLTableRole, FromClause): self.isouter = isouter self.full = full - @classmethod - def _create_outerjoin(cls, left, right, onclause=None, full=False): - """Return an ``OUTER JOIN`` clause element. - - The returned object is an instance of :class:`_expression.Join`. - - Similar functionality is also available via the - :meth:`_expression.FromClause.outerjoin` method on any - :class:`_expression.FromClause`. - - :param left: The left side of the join. - - :param right: The right side of the join. - - :param onclause: Optional criterion for the ``ON`` clause, is - derived from foreign key relationships established between - left and right otherwise. - - To chain joins together, use the :meth:`_expression.FromClause.join` - or - :meth:`_expression.FromClause.outerjoin` methods on the resulting - :class:`_expression.Join` object. - - """ - return cls(left, right, onclause, isouter=True, full=full) - - @classmethod - def _create_join( - cls, left, right, onclause=None, isouter=False, full=False - ): - """Produce a :class:`_expression.Join` object, given two - :class:`_expression.FromClause` - expressions. - - E.g.:: - - j = join(user_table, address_table, - user_table.c.id == address_table.c.user_id) - stmt = select(user_table).select_from(j) - - would emit SQL along the lines of:: - - SELECT user.id, user.name FROM user - JOIN address ON user.id = address.user_id - - Similar functionality is available given any - :class:`_expression.FromClause` object (e.g. such as a - :class:`_schema.Table`) using - the :meth:`_expression.FromClause.join` method. - - :param left: The left side of the join. - - :param right: the right side of the join; this is any - :class:`_expression.FromClause` object such as a - :class:`_schema.Table` object, and - may also be a selectable-compatible object such as an ORM-mapped - class. - - :param onclause: a SQL expression representing the ON clause of the - join. If left at ``None``, :meth:`_expression.FromClause.join` - will attempt to - join the two tables based on a foreign key relationship. - - :param isouter: if True, render a LEFT OUTER JOIN, instead of JOIN. - - :param full: if True, render a FULL OUTER JOIN, instead of JOIN. - - .. versionadded:: 1.1 - - .. seealso:: - - :meth:`_expression.FromClause.join` - method form, - based on a given left side. - - :class:`_expression.Join` - the type of object produced. - - """ - - return cls(left, right, onclause, isouter, full) - @property def description(self): return "Join object on %s(%d) and %s(%d)" % ( @@ -1161,24 +1083,7 @@ class Join(roles.DMLTableRole, FromClause): ): """Create a join condition between two tables or selectables. - e.g.:: - - join_condition(tablea, tableb) - - would produce an expression along the lines of:: - - tablea.c.id==tableb.c.tablea_id - - The join is determined based on the foreign key relationships - between the two selectables. If there are multiple ways - to join, or no way to join, an error is raised. - - :param a_subset: An optional expression that is a sub-component - of ``a``. An attempt will be made to join to just this sub-component - first before looking at the full ``a`` construct, and if found - will be successful even if there are other ways to join to ``a``. - This allows the "right side" of a join to be passed thereby - providing a "natural join". + See sqlalchemy.sql.util.join_condition() for full docs. """ constraints = cls._joincond_scan_left_right( @@ -1331,7 +1236,7 @@ class Join(roles.DMLTableRole, FromClause): FROM table_a JOIN table_b ON table_a.id = table_b.a_id """ - return Select._create(self.left, self.right).select_from(self) + return Select(self.left, self.right).select_from(self) @util.preload_module("sqlalchemy.sql.util") def _anonymous_fromclause(self, name=None, flat=False): @@ -1503,51 +1408,6 @@ class Alias(roles.DMLTableRole, AliasedReturnsRows): @classmethod def _factory(cls, selectable, name=None, flat=False): - """Return an :class:`_expression.Alias` object. - - An :class:`_expression.Alias` represents any - :class:`_expression.FromClause` - with an alternate name assigned within SQL, typically using the ``AS`` - clause when generated, e.g. ``SELECT * FROM table AS aliasname``. - - Similar functionality is available via the - :meth:`_expression.FromClause.alias` - method available on all :class:`_expression.FromClause` subclasses. - In terms of - a SELECT object as generated from the :func:`_expression.select` - function, the :meth:`_expression.SelectBase.alias` method returns an - :class:`_expression.Alias` or similar object which represents a named, - parenthesized subquery. - - When an :class:`_expression.Alias` is created from a - :class:`_schema.Table` object, - this has the effect of the table being rendered - as ``tablename AS aliasname`` in a SELECT statement. - - For :func:`_expression.select` objects, the effect is that of - creating a named subquery, i.e. ``(select ...) AS aliasname``. - - The ``name`` parameter is optional, and provides the name - to use in the rendered SQL. If blank, an "anonymous" name - will be deterministically generated at compile time. - Deterministic means the name is guaranteed to be unique against - other constructs used in the same statement, and will also be the - same name for each successive compilation of the same statement - object. - - :param selectable: any :class:`_expression.FromClause` subclass, - such as a table, select statement, etc. - - :param name: string name to be assigned as the alias. - If ``None``, a name will be deterministically generated - at compile time. - - :param flat: Will be passed through to if the given selectable - is an instance of :class:`_expression.Join` - see - :meth:`_expression.Join.alias` - for details. - - """ return coercions.expect( roles.FromClauseRole, selectable, allow_select=True ).alias(name=name, flat=flat) @@ -1724,25 +1584,6 @@ class Lateral(AliasedReturnsRows): @classmethod def _factory(cls, selectable, name=None): - """Return a :class:`_expression.Lateral` object. - - :class:`_expression.Lateral` is an :class:`_expression.Alias` - subclass that represents - a subquery with the LATERAL keyword applied to it. - - The special behavior of a LATERAL subquery is that it appears in the - FROM clause of an enclosing SELECT, but may correlate to other - FROM clauses of that SELECT. It is a special case of subquery - only supported by a small number of backends, currently more recent - PostgreSQL versions. - - .. versionadded:: 1.1 - - .. seealso:: - - :ref:`lateral_selects` - overview of usage. - - """ return coercions.expect( roles.FromClauseRole, selectable, explicit_subquery=True ).lateral(name=name) @@ -1773,48 +1614,6 @@ class TableSample(AliasedReturnsRows): @classmethod def _factory(cls, selectable, sampling, name=None, seed=None): - """Return a :class:`_expression.TableSample` object. - - :class:`_expression.TableSample` is an :class:`_expression.Alias` - subclass that represents - a table with the TABLESAMPLE clause applied to it. - :func:`_expression.tablesample` - is also available from the :class:`_expression.FromClause` - class via the - :meth:`_expression.FromClause.tablesample` method. - - The TABLESAMPLE clause allows selecting a randomly selected approximate - percentage of rows from a table. It supports multiple sampling methods, - most commonly BERNOULLI and SYSTEM. - - e.g.:: - - from sqlalchemy import func - - selectable = people.tablesample( - func.bernoulli(1), - name='alias', - seed=func.random()) - stmt = select(selectable.c.people_id) - - Assuming ``people`` with a column ``people_id``, the above - statement would render as:: - - SELECT alias.people_id FROM - people AS alias TABLESAMPLE bernoulli(:bernoulli_1) - REPEATABLE (random()) - - .. versionadded:: 1.1 - - :param sampling: a ``float`` percentage between 0 and 100 or - :class:`_functions.Function`. - - :param name: optional alias name - - :param seed: any real-valued SQL expression. When specified, the - REPEATABLE sub-clause is also rendered. - - """ return coercions.expect(roles.FromClauseRole, selectable).tablesample( sampling, name=name, seed=seed ) @@ -2493,28 +2292,6 @@ class TableClause(roles.DMLTableRole, Immutable, FromClause): """No PK or default support so no autoincrement column.""" def __init__(self, name, *columns, **kw): - """Produce a new :class:`_expression.TableClause`. - - The object returned is an instance of - :class:`_expression.TableClause`, which - represents the "syntactical" portion of the schema-level - :class:`_schema.Table` object. - It may be used to construct lightweight table constructs. - - .. versionchanged:: 1.0.0 :func:`_expression.table` can now - be imported from the plain ``sqlalchemy`` namespace like any - other SQL element. - - - :param name: Name of the table. - - :param columns: A collection of :func:`_expression.column` constructs. - - :param schema: The schema name for this table. - - .. versionadded:: 1.3.18 :func:`_expression.table` can now - accept a ``schema`` argument. - """ super(TableClause, self).__init__() self.name = name self._columns = DedupeColumnCollection() @@ -2697,41 +2474,6 @@ class Values(Generative, FromClause): ] def __init__(self, *columns, name=None, literal_binds=False): - r"""Construct a :class:`_expression.Values` construct. - - The column expressions and the actual data for - :class:`_expression.Values` are given in two separate steps. The - constructor receives the column expressions typically as - :func:`_expression.column` constructs, - and the data is then passed via the - :meth:`_expression.Values.data` method as a list, - which can be called multiple - times to add more data, e.g.:: - - from sqlalchemy import column - from sqlalchemy import values - - value_expr = values( - column('id', Integer), - column('name', String), - name="my_values" - ).data( - [(1, 'name1'), (2, 'name2'), (3, 'name3')] - ) - - :param \*columns: column expressions, typically composed using - :func:`_expression.column` objects. - - :param name: the name for this VALUES construct. If omitted, the - VALUES construct will be unnamed in a SQL expression. Different - backends may have different requirements here. - - :param literal_binds: Defaults to False. Whether or not to render - the data values inline in the SQL output, rather than using bound - parameters. - - """ - super(Values, self).__init__() self._column_args = columns self.name = name @@ -3708,91 +3450,26 @@ class CompoundSelect(HasCompileState, GenerativeSelect): @classmethod def _create_union(cls, *selects, **kwargs): - r"""Return a ``UNION`` of multiple selectables. - - The returned object is an instance of - :class:`_expression.CompoundSelect`. - - A similar :func:`union()` method is available on all - :class:`_expression.FromClause` subclasses. - - :param \*selects: - a list of :class:`_expression.Select` instances. - - :param \**kwargs: - available keyword arguments are the same as those of - :func:`select`. - - """ return CompoundSelect(CompoundSelect.UNION, *selects, **kwargs) @classmethod def _create_union_all(cls, *selects): - r"""Return a ``UNION ALL`` of multiple selectables. - - The returned object is an instance of - :class:`_expression.CompoundSelect`. - - A similar :func:`union_all()` method is available on all - :class:`_expression.FromClause` subclasses. - - :param \*selects: - a list of :class:`_expression.Select` instances. - - """ return CompoundSelect(CompoundSelect.UNION_ALL, *selects) @classmethod def _create_except(cls, *selects): - r"""Return an ``EXCEPT`` of multiple selectables. - - The returned object is an instance of - :class:`_expression.CompoundSelect`. - - :param \*selects: - a list of :class:`_expression.Select` instances. - - """ return CompoundSelect(CompoundSelect.EXCEPT, *selects) @classmethod def _create_except_all(cls, *selects): - r"""Return an ``EXCEPT ALL`` of multiple selectables. - - The returned object is an instance of - :class:`_expression.CompoundSelect`. - - :param \*selects: - a list of :class:`_expression.Select` instances. - - """ return CompoundSelect(CompoundSelect.EXCEPT_ALL, *selects) @classmethod def _create_intersect(cls, *selects): - r"""Return an ``INTERSECT`` of multiple selectables. - - The returned object is an instance of - :class:`_expression.CompoundSelect`. - - :param \*selects: - a list of :class:`_expression.Select` instances. - - """ return CompoundSelect(CompoundSelect.INTERSECT, *selects) @classmethod def _create_intersect_all(cls, *selects): - r"""Return an ``INTERSECT ALL`` of multiple selectables. - - The returned object is an instance of - :class:`_expression.CompoundSelect`. - - :param \*selects: - a list of :class:`_expression.Select` instances. - - - """ return CompoundSelect(CompoundSelect.INTERSECT_ALL, *selects) def _scalar_type(self): @@ -4413,47 +4090,26 @@ class Select( ] @classmethod - def _create( - cls, *entities: Union[roles.ColumnsClauseRole, Type] - ) -> "Select": - r"""Construct a new :class:`_expression.Select`. + 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 + pre-established state. - .. versionadded:: 1.4 - The :func:`_sql.select` function now accepts - column arguments positionally. The top-level :func:`_sql.select` - function will automatically use the 1.x or 2.x style API based on - the incoming arguments; using :func:`_future.select` from the - ``sqlalchemy.future`` module will enforce that only the 2.x style - constructor is used. + """ - Similar functionality is also available via the - :meth:`_expression.FromClause.select` method on any - :class:`_expression.FromClause`. + stmt = Select.__new__(Select) + stmt.__dict__.update(kw) + return stmt - .. seealso:: + def __init__(self, *entities: Union[roles.ColumnsClauseRole, Type]): + r"""Construct a new :class:`_expression.Select`. - :ref:`coretutorial_selecting` - Core Tutorial description of - :func:`_expression.select`. - - :param \*entities: - Entities to SELECT from. For Core usage, this is typically a series - of :class:`_expression.ColumnElement` and / or - :class:`_expression.FromClause` - objects which will form the columns clause of the resulting - statement. For those objects that are instances of - :class:`_expression.FromClause` (typically :class:`_schema.Table` - or :class:`_expression.Alias` - objects), the :attr:`_expression.FromClause.c` - collection is extracted - to form a collection of :class:`_expression.ColumnElement` objects. - - This parameter will also accept :class:`_expression.TextClause` - constructs as - given, as well as ORM-mapped classes. + The public constructor for :class:`_expression.Select` is the + :func:`_sql.select` function. """ - self = cls.__new__(cls) self._raw_columns = [ coercions.expect( roles.ColumnsClauseRole, ent, apply_propagate_attrs=self @@ -4463,24 +4119,6 @@ class Select( GenerativeSelect.__init__(self) - return self - - @classmethod - 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 - pre-established state. - - """ - - stmt = Select.__new__(Select) - stmt.__dict__.update(kw) - return stmt - - def __init__(self): - raise NotImplementedError() - def _scalar_type(self): elem = self._raw_columns[0] cols = list(elem._select_iterable) @@ -5787,52 +5425,16 @@ class Exists(UnaryExpression): """ - _from_objects = [] + _from_objects = () inherit_cache = True - def __init__(self, *args, **kwargs): - """Construct a new :class:`_expression.Exists` construct. - - The :func:`_sql.exists` can be invoked by itself to produce an - :class:`_sql.Exists` construct, which will accept simple WHERE - criteria:: - - exists_criteria = exists().where(table1.c.col1 == table2.c.col2) - - However, for greater flexibility in constructing the SELECT, an - existing :class:`_sql.Select` construct may be converted to an - :class:`_sql.Exists`, most conveniently by making use of the - :meth:`_sql.SelectBase.exists` method:: - - exists_criteria = ( - select(table2.c.col2). - where(table1.c.col1 == table2.c.col2). - exists() - ) - - The EXISTS criteria is then used inside of an enclosing SELECT:: - - stmt = select(table1.c.col1).where(exists_criteria) - - The above statement will then be of the form:: - - SELECT col1 FROM table1 WHERE EXISTS - (SELECT table2.col2 FROM table2 WHERE table2.col2 = table1.col1) - - .. seealso:: - - :ref:`tutorial_exists` - in the :term:`2.0 style` tutorial. - - :meth:`_sql.SelectBase.exists` - method to transform a ``SELECT`` to an - ``EXISTS`` clause. - - """ # noqa E501 - if args and isinstance(args[0], (SelectBase, ScalarSelect)): - s = args[0] + def __init__(self, __argument=None): + if __argument is None: + s = Select(literal_column("*")).scalar_subquery() + elif isinstance(__argument, (SelectBase, ScalarSelect)): + s = __argument else: - if not args: - args = (literal_column("*"),) - s = Select._create(*args, **kwargs).scalar_subquery() + s = Select(__argument).scalar_subquery() UnaryExpression.__init__( self, @@ -5865,7 +5467,7 @@ class Exists(UnaryExpression): """ # noqa - return Select._create(self) + return Select(self) def correlate(self, *fromclause): """Apply correlation to the subquery noted by this :class:`_sql.Exists`. diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index 93ff53663..81434fbb9 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -8,12 +8,18 @@ """SQL specific types. """ - import collections.abc as collections_abc import datetime as dt import decimal +import enum import json import pickle +from typing import Any +from typing import Sequence +from typing import Text as typing_Text +from typing import Tuple +from typing import TypeVar +from typing import Union from . import coercions from . import elements @@ -43,6 +49,9 @@ from ..util import langhelpers from ..util import OrderedDict +_T = TypeVar("_T", bound="Any") + + class _LookupExpressionAdapter: """Mixin expression adaptations based on lookup tables. @@ -121,7 +130,7 @@ class Indexable: comparator_factory = Comparator -class String(Concatenable, TypeEngine): +class String(Concatenable, TypeEngine[typing_Text]): """The base for all string and character types. @@ -136,7 +145,10 @@ class String(Concatenable, TypeEngine): __visit_name__ = "string" def __init__( - self, + # note pylance appears to require the "self" type in a constructor + # for the _T type to be correctly recognized when we send the + # class as the argument, e.g. `column("somecol", String)` + self: "String", length=None, collation=None, ): @@ -289,7 +301,7 @@ class UnicodeText(Text): super(UnicodeText, self).__init__(length=length, **kwargs) -class Integer(_LookupExpressionAdapter, TypeEngine): +class Integer(_LookupExpressionAdapter, TypeEngine[int]): """A type for ``int`` integers.""" @@ -351,7 +363,9 @@ class BigInteger(Integer): __visit_name__ = "big_integer" -class Numeric(_LookupExpressionAdapter, TypeEngine): +class Numeric( + _LookupExpressionAdapter, TypeEngine[Union[decimal.Decimal, float]] +): """A type for fixed precision numbers, such as ``NUMERIC`` or ``DECIMAL``. @@ -396,7 +410,7 @@ class Numeric(_LookupExpressionAdapter, TypeEngine): _default_decimal_return_scale = 10 def __init__( - self, + self: "Numeric", precision=None, scale=None, decimal_return_scale=None, @@ -544,7 +558,10 @@ class Float(Numeric): scale = None def __init__( - self, precision=None, asdecimal=False, decimal_return_scale=None + self: "Float", + precision=None, + asdecimal=False, + decimal_return_scale=None, ): r""" Construct a Float. @@ -583,7 +600,7 @@ class Float(Numeric): return None -class DateTime(_LookupExpressionAdapter, TypeEngine): +class DateTime(_LookupExpressionAdapter, TypeEngine[dt.datetime]): """A type for ``datetime.datetime()`` objects. @@ -645,7 +662,7 @@ class DateTime(_LookupExpressionAdapter, TypeEngine): } -class Date(_LookupExpressionAdapter, TypeEngine): +class Date(_LookupExpressionAdapter, TypeEngine[dt.date]): """A type for ``datetime.date()`` objects.""" @@ -683,7 +700,7 @@ class Date(_LookupExpressionAdapter, TypeEngine): } -class Time(_LookupExpressionAdapter, TypeEngine): +class Time(_LookupExpressionAdapter, TypeEngine[dt.time]): """A type for ``datetime.time()`` objects.""" @@ -717,7 +734,7 @@ class Time(_LookupExpressionAdapter, TypeEngine): } -class _Binary(TypeEngine): +class _Binary(TypeEngine[bytes]): """Define base behavior for binary types.""" @@ -1019,7 +1036,7 @@ class SchemaType(SchemaEventTarget): return _we_are_the_impl(variant_mapping["_default"]) -class Enum(Emulated, String, SchemaType): +class Enum(Emulated, String, TypeEngine[Union[str, enum.Enum]], SchemaType): """Generic Enum Type. The :class:`.Enum` type provides a set of possible string values @@ -1496,7 +1513,7 @@ class Enum(Emulated, String, SchemaType): return super(Enum, self).python_type -class PickleType(TypeDecorator): +class PickleType(TypeDecorator[object]): """Holds Python objects, which are serialized using pickle. PickleType builds upon the Binary type to apply Python's @@ -1597,7 +1614,7 @@ class PickleType(TypeDecorator): return x == y -class Boolean(Emulated, TypeEngine, SchemaType): +class Boolean(Emulated, TypeEngine[bool], SchemaType): """A bool datatype. @@ -1621,7 +1638,10 @@ class Boolean(Emulated, TypeEngine, SchemaType): native = True def __init__( - self, create_constraint=False, name=None, _create_events=True + self: "Boolean", + create_constraint=False, + name=None, + _create_events=True, ): """Construct a Boolean. @@ -1723,7 +1743,7 @@ class Boolean(Emulated, TypeEngine, SchemaType): return processors.int_to_boolean -class _AbstractInterval(_LookupExpressionAdapter, TypeEngine): +class _AbstractInterval(_LookupExpressionAdapter, TypeEngine[dt.timedelta]): @util.memoized_property def _expression_adaptations(self): # Based on https://www.postgresql.org/docs/current/\ @@ -1841,7 +1861,7 @@ class Interval(Emulated, _AbstractInterval, TypeDecorator): return process -class JSON(Indexable, TypeEngine): +class JSON(Indexable, TypeEngine[Any]): """Represent a SQL JSON type. .. note:: :class:`_types.JSON` @@ -2399,7 +2419,9 @@ class JSON(Indexable, TypeEngine): return process -class ARRAY(SchemaEventTarget, Indexable, Concatenable, TypeEngine): +class ARRAY( + SchemaEventTarget, Indexable, Concatenable, TypeEngine[Sequence[Any]] +): """Represent a SQL Array type. .. note:: This type serves as the basis for all ARRAY operations. @@ -2700,7 +2722,7 @@ class ARRAY(SchemaEventTarget, Indexable, Concatenable, TypeEngine): self.item_type._set_parent_with_dispatch(parent) -class TupleType(TypeEngine): +class TupleType(TypeEngine[Tuple[Any]]): """represent the composite type of a Tuple.""" _is_tuple_type = True diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py index 7981100a4..75eb1b8c5 100644 --- a/lib/sqlalchemy/sql/type_api.py +++ b/lib/sqlalchemy/sql/type_api.py @@ -10,30 +10,48 @@ """ import typing +from typing import Any +from typing import Generic +from typing import Tuple +from typing import Type +from typing import TypeVar +from typing import Union -from . import operators from .base import SchemaEventTarget from .cache_key import NO_CACHE +from .operators import ColumnOperators from .visitors import Traversible from .. import exc from .. import util # these are back-assigned by sqltypes. -BOOLEANTYPE = None -INTEGERTYPE = None -NULLTYPE = None -STRINGTYPE = None -MATCHTYPE = None -INDEXABLE = None -TABLEVALUE = None -_resolve_value_to_type = None - +if not typing.TYPE_CHECKING: + BOOLEANTYPE = None + INTEGERTYPE = None + NULLTYPE = None + STRINGTYPE = None + MATCHTYPE = None + INDEXABLE = None + TABLEVALUE = None + _resolve_value_to_type = None + +if typing.TYPE_CHECKING: + from .elements import ColumnElement + from .operators import OperatorType + from .sqltypes import _resolve_value_to_type + from .sqltypes import Boolean as BOOLEANTYPE # noqa + from .sqltypes import Indexable as INDEXABLE # noqa + from .sqltypes import MatchType as MATCHTYPE # noqa + from .sqltypes import NULLTYPE + +_T = TypeVar("_T", bound=Any) +_CT = TypeVar("_CT", bound=Any) # replace with pep-673 when applicable SelfTypeEngine = typing.TypeVar("SelfTypeEngine", bound="TypeEngine") -class TypeEngine(Traversible): +class TypeEngine(Traversible, Generic[_T]): """The ultimate base class for all SQL datatypes. Common subclasses of :class:`.TypeEngine` include @@ -55,6 +73,8 @@ class TypeEngine(Traversible): _is_array = False _is_type_decorator = False + _block_from_type_affinity = False + render_bind_cast = False """Render bind casts for :attr:`.BindTyping.RENDER_CASTS` mode. @@ -70,7 +90,10 @@ class TypeEngine(Traversible): """ - class Comparator(operators.ColumnOperators): + class Comparator( + ColumnOperators["ColumnElement"], + Generic[_CT], + ): """Base class for custom comparison operations defined at the type level. See :attr:`.TypeEngine.comparator_factory`. @@ -84,23 +107,33 @@ class TypeEngine(Traversible): def __clause_element__(self): return self.expr - def __init__(self, expr): + def __init__(self, expr: "ColumnElement[_CT]"): self.expr = expr - self.type = expr.type + self.type: TypeEngine[_CT] = expr.type @util.preload_module("sqlalchemy.sql.default_comparator") - def operate(self, op, *other, **kwargs): + def operate( + self, op: "OperatorType", *other, **kwargs + ) -> "ColumnElement": default_comparator = util.preloaded.sql_default_comparator - o = default_comparator.operator_lookup[op.__name__] - return o[0](self.expr, op, *(other + o[1:]), **kwargs) + op_fn, addtl_kw = default_comparator.operator_lookup[op.__name__] + if kwargs: + addtl_kw = addtl_kw.union(kwargs) + return op_fn(self.expr, op, *other, **addtl_kw) @util.preload_module("sqlalchemy.sql.default_comparator") - def reverse_operate(self, op, other, **kwargs): + def reverse_operate( + self, op: "OperatorType", other, **kwargs + ) -> "ColumnElement": default_comparator = util.preloaded.sql_default_comparator - o = default_comparator.operator_lookup[op.__name__] - return o[0](self.expr, op, other, reverse=True, *o[1:], **kwargs) - - def _adapt_expression(self, op, other_comparator): + op_fn, addtl_kw = default_comparator.operator_lookup[op.__name__] + if kwargs: + addtl_kw = addtl_kw.union(kwargs) + return op_fn(self.expr, op, other, reverse=True, **addtl_kw) + + def _adapt_expression( + self, op: "OperatorType", other_comparator + ) -> Tuple["OperatorType", "TypeEngine[_CT]"]: """evaluate the return type of <self> <op> <othertype>, and apply any adaptations to the given operator. @@ -611,7 +644,9 @@ class TypeEngine(Traversible): for t in self.__class__.__mro__: if t in (TypeEngine, UserDefinedType): return typ - elif issubclass(t, (TypeEngine, UserDefinedType)): + elif issubclass( + t, (TypeEngine, UserDefinedType) + ) and not t.__dict__.get("_block_from_type_affinity", False): typ = t else: return self.__class__ @@ -1202,7 +1237,7 @@ class NativeForEmulated: return cls(**kw) -class TypeDecorator(ExternalType, SchemaEventTarget, TypeEngine): +class TypeDecorator(ExternalType, SchemaEventTarget, TypeEngine[_T]): """Allows the creation of types which add additional functionality to an existing type. @@ -1882,7 +1917,9 @@ def _reconstitute_comparator(expression): return expression.comparator -def to_instance(typeobj, *arg, **kw): +def to_instance( + typeobj: Union[Type[TypeEngine[_T]], TypeEngine[_T], None], *arg, **kw +) -> TypeEngine[_T]: if typeobj is None: return NULLTYPE diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 63067585e..fa3bae835 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -46,9 +46,35 @@ from .. import exc from .. import util -join_condition = util.langhelpers.public_factory( - Join._join_condition, ".sql.util.join_condition" -) +def join_condition(a, b, a_subset=None, consider_as_foreign_keys=None): + """Create a join condition between two tables or selectables. + + e.g.:: + + join_condition(tablea, tableb) + + would produce an expression along the lines of:: + + tablea.c.id==tableb.c.tablea_id + + The join is determined based on the foreign key relationships + between the two selectables. If there are multiple ways + to join, or no way to join, an error is raised. + + :param a_subset: An optional expression that is a sub-component + of ``a``. An attempt will be made to join to just this sub-component + first before looking at the full ``a`` construct, and if found + will be successful even if there are other ways to join to ``a``. + This allows the "right side" of a join to be passed thereby + providing a "natural join". + + """ + return Join._join_condition( + a, + b, + a_subset=a_subset, + consider_as_foreign_keys=consider_as_foreign_keys, + ) def find_join_source(clauses, join_to): diff --git a/lib/sqlalchemy/util/compat.py b/lib/sqlalchemy/util/compat.py index 464914d33..f3a3debe0 100644 --- a/lib/sqlalchemy/util/compat.py +++ b/lib/sqlalchemy/util/compat.py @@ -140,14 +140,14 @@ def _formatannotation(annotation, base_module=None): """vendored from python 3.7""" if getattr(annotation, "__module__", None) == "typing": - return f'"{repr(annotation).replace("typing.", "")}"' + return f'"{repr(annotation).replace("typing.", "").replace("~", "")}"' if isinstance(annotation, type): if annotation.__module__ in ("builtins", base_module): return repr(annotation.__qualname__) return annotation.__module__ + "." + annotation.__qualname__ elif isinstance(annotation, typing.TypeVar): - return f'"{annotation}"' - return repr(annotation) + return f'"{repr(annotation).replace("~", "")}"' + return f'"{repr(annotation).replace("~", "")}"' def inspect_formatargspec( diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index 66c530867..d85b1261b 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -36,6 +36,11 @@ from . import typing as compat_typing from .. import exc _T = TypeVar("_T") +_F = TypeVar("_F", bound=Callable[..., Any]) +_MP = TypeVar("_MP", bound="memoized_property[Any]") +_MA = TypeVar("_MA", bound="HasMemoized.memoized_attribute[Any]") +_HP = TypeVar("_HP", bound="hybridproperty") +_HM = TypeVar("_HM", bound="hybridmethod") def md5_hex(x): @@ -234,103 +239,12 @@ def _exec_code_in_env(code, env, fn_name): return env[fn_name] +_PF = TypeVar("_PF") _TE = TypeVar("_TE") _P = compat_typing.ParamSpec("_P") -def public_factory( - target: typing.Callable[_P, _TE], - location: str, - class_location: Optional[str] = None, -) -> typing.Callable[_P, _TE]: - """Produce a wrapping function for the given cls or classmethod. - - Rationale here is so that the __init__ method of the - class can serve as documentation for the function. - - """ - - if isinstance(target, type): - fn = target.__init__ - callable_ = target - doc = ( - "Construct a new :class:`%s` object. \n\n" - "This constructor is mirrored as a public API function; " - "see :func:`sqlalchemy%s` " - "for a full usage and argument description." - % ( - class_location if class_location else ".%s" % target.__name__, - location, - ) - ) - else: - fn = callable_ = target - doc = ( - "This function is mirrored; see :func:`sqlalchemy%s` " - "for a description of arguments." % location - ) - - location_name = location.split(".")[-1] - spec = compat.inspect_getfullargspec(fn) - del spec[0][0] - metadata = format_argspec_plus(spec, grouped=False) - metadata["name"] = location_name - code = ( - """\ -def %(name)s%(grouped_args)s: - return cls(%(apply_kw)s) -""" - % metadata - ) - env = { - "cls": callable_, - "symbol": symbol, - "__name__": callable_.__module__, - } - exec(code, env) - - decorated = env[location_name] - - if hasattr(fn, "_linked_to"): - linked_to, linked_to_location = fn._linked_to - linked_to_doc = linked_to.__doc__ - if class_location is None: - class_location = "%s.%s" % (target.__module__, target.__name__) - - linked_to_doc = inject_docstring_text( - linked_to_doc, - ".. container:: inherited_member\n\n " - "This documentation is inherited from :func:`sqlalchemy%s`; " - "this constructor, :func:`sqlalchemy%s`, " - "creates a :class:`sqlalchemy%s` object. See that class for " - "additional details describing this subclass." - % (linked_to_location, location, class_location), - 1, - ) - decorated.__doc__ = linked_to_doc - else: - decorated.__doc__ = fn.__doc__ - - decorated.__module__ = "sqlalchemy" + location.rsplit(".", 1)[0] - if decorated.__module__ not in sys.modules: - raise ImportError( - "public_factory location %s is not in sys.modules" - % (decorated.__module__,) - ) - - if hasattr(fn, "__func__"): - fn.__func__.__doc__ = doc - if not hasattr(fn.__func__, "_linked_to"): - fn.__func__._linked_to = (decorated, location) - else: - fn.__doc__ = doc - if not hasattr(fn, "_linked_to"): - fn._linked_to = (decorated, location) - - return decorated - - class PluginLoader: def __init__(self, group, auto_fn=None): self.group = group @@ -1182,14 +1096,26 @@ class HasMemoized: self.__dict__[key] = value self._memoized_keys |= {key} - class memoized_attribute: + class memoized_attribute(Generic[_T]): """A read-only @property that is only evaluated once.""" - def __init__(self, fget, doc=None): + fget: Callable[..., _T] + __doc__: Optional[str] + __name__: str + + def __init__(self, fget: Callable[..., _T], doc: Optional[str] = None): self.fget = fget self.__doc__ = doc or fget.__doc__ self.__name__ = fget.__name__ + @overload + def __get__(self: _MA, obj: None, cls: Any) -> _MA: + ... + + @overload + def __get__(self, obj: Any, cls: Any) -> _T: + ... + def __get__(self, obj, cls): if obj is None: return self @@ -1198,7 +1124,7 @@ class HasMemoized: return result @classmethod - def memoized_instancemethod(cls, fn): + def memoized_instancemethod(cls, fn: Any) -> Any: """Decorate a method memoize its return value.""" def oneshot(self, *args, **kw): |