diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-02-27 23:05:46 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-03-01 21:05:14 -0500 |
| commit | afb9634fb28b00c7b0979660e3e0bfed6caafde5 (patch) | |
| tree | 11afb462226f64d922f9d3c425a7d2c09c3d69d7 /lib/sqlalchemy/orm | |
| parent | 7f1a3f22abffc1529100e14fcfd07a46a49fd44f (diff) | |
| download | sqlalchemy-afb9634fb28b00c7b0979660e3e0bfed6caafde5.tar.gz | |
pep484 + abc bases for assocaitionproxy
went to this one next as it was going to be hard,
and also exercises the ORM expression hierarchy a bit.
made some adjustments to SQLCoreOperations etc.
Change-Id: Ie5dde9218dc1318252826b766d3e70b17dd24ea7
References: #6810
References: #7774
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 6 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/base.py | 71 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/clsregistry.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/collections.py | 5 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 68 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 12 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/relationships.py | 10 |
7 files changed, 113 insertions, 61 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 5a8a0f6cf..141702ae6 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -43,7 +43,11 @@ from ._orm_constructors import with_polymorphic as with_polymorphic from .attributes import AttributeEvent as AttributeEvent from .attributes import InstrumentedAttribute as InstrumentedAttribute from .attributes import QueryableAttribute as QueryableAttribute +from .base import class_mapper as class_mapper +from .base import InspectionAttrExtensionType as InspectionAttrExtensionType from .base import Mapped as Mapped +from .base import NotExtension as NotExtension +from .base import ORMDescriptor as ORMDescriptor from .context import QueryContext as QueryContext from .decl_api import add_mapped_attribute as add_mapped_attribute from .decl_api import as_declarative as as_declarative @@ -75,13 +79,11 @@ 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 diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py index b9c881cfe..c63a89c70 100644 --- a/lib/sqlalchemy/orm/base.py +++ b/lib/sqlalchemy/orm/base.py @@ -11,14 +11,17 @@ from __future__ import annotations +from enum import Enum import operator import typing from typing import Any from typing import Callable +from typing import Dict from typing import Generic from typing import Optional from typing import overload from typing import Tuple +from typing import Type from typing import TypeVar from typing import Union @@ -29,11 +32,13 @@ from .. import util from ..sql.elements import SQLCoreOperations from ..util.langhelpers import TypingOnly from ..util.typing import Concatenate +from ..util.typing import Literal from ..util.typing import ParamSpec - +from ..util.typing import Self if typing.TYPE_CHECKING: from .attributes import InstrumentedAttribute + from .mapper import Mapper _T = TypeVar("_T", bound=Any) @@ -223,16 +228,22 @@ MANYTOMANY = util.symbol( """, ) -NOT_EXTENSION = util.symbol( - "NOT_EXTENSION", + +class InspectionAttrExtensionType(Enum): + """Symbols indicating the type of extension that a + :class:`.InspectionAttr` is part of.""" + + +class NotExtension(InspectionAttrExtensionType): + NOT_EXTENSION = "not_extension" """Symbol indicating an :class:`InspectionAttr` that's not part of sqlalchemy.ext. Is assigned to the :attr:`.InspectionAttr.extension_type` attribute. - """, -) + """ + _never_set = frozenset([NEVER_SET]) @@ -455,7 +466,7 @@ def _inspect_mapped_class(class_, configure=False): return mapper -def class_mapper(class_, configure=True): +def class_mapper(class_: Type[_T], configure: bool = True) -> Mapper[_T]: """Given a class, return the primary :class:`_orm.Mapper` associated with the key. @@ -546,17 +557,15 @@ class InspectionAttr: """True if this object is an instance of :class:`_expression.ClauseElement`.""" - extension_type = NOT_EXTENSION + extension_type: InspectionAttrExtensionType = NotExtension.NOT_EXTENSION """The extension type, if any. - Defaults to :data:`.interfaces.NOT_EXTENSION` + Defaults to :attr:`.interfaces.NotExtension.NOT_EXTENSION` .. seealso:: - :data:`.HYBRID_METHOD` + :class:`.HybridExtensionType` - :data:`.HYBRID_PROPERTY` - - :data:`.ASSOCIATION_PROXY` + :class:`.AssociationProxyExtensionType` """ @@ -571,7 +580,7 @@ class InspectionAttrInfo(InspectionAttr): """ @util.memoized_property - def info(self): + def info(self) -> Dict[Any, Any]: """Info dictionary associated with the object, allowing user-defined data to be associated with this :class:`.InspectionAttr`. @@ -614,7 +623,35 @@ class SQLORMOperations(SQLCoreOperations[_T], TypingOnly): ... -class Mapped(Generic[_T], TypingOnly): +class ORMDescriptor(Generic[_T], TypingOnly): + """Represent any Python descriptor that provides a SQL expression + construct at the class level.""" + + __slots__ = () + + if typing.TYPE_CHECKING: + + @overload + def __get__(self: Self, instance: Any, owner: Literal[None]) -> Self: + ... + + @overload + def __get__( + self, instance: Literal[None], owner: Any + ) -> SQLORMOperations[_T]: + ... + + @overload + def __get__(self, instance: object, owner: Any) -> _T: + ... + + def __get__( + self, instance: object, owner: Any + ) -> Union[SQLORMOperations[_T], _T]: + ... + + +class Mapped(ORMDescriptor[_T], TypingOnly): """Represent an ORM mapped attribute on a mapped class. This class represents the complete descriptor interface for any class @@ -646,7 +683,7 @@ class Mapped(Generic[_T], TypingOnly): @overload def __get__( self, instance: None, owner: Any - ) -> "InstrumentedAttribute[_T]": + ) -> InstrumentedAttribute[_T]: ... @overload @@ -655,11 +692,11 @@ class Mapped(Generic[_T], TypingOnly): def __get__( self, instance: object, owner: Any - ) -> Union["InstrumentedAttribute[_T]", _T]: + ) -> Union[InstrumentedAttribute[_T], _T]: ... @classmethod - def _empty_constructor(cls, arg1: Any) -> "Mapped[_T]": + def _empty_constructor(cls, arg1: Any) -> Mapped[_T]: ... def __set__( diff --git a/lib/sqlalchemy/orm/clsregistry.py b/lib/sqlalchemy/orm/clsregistry.py index d0cb53e29..fe6dbfdc9 100644 --- a/lib/sqlalchemy/orm/clsregistry.py +++ b/lib/sqlalchemy/orm/clsregistry.py @@ -293,7 +293,7 @@ class _GetColumns: ) desc = mp.all_orm_descriptors[key] - if desc.extension_type is interfaces.NOT_EXTENSION: + if desc.extension_type is interfaces.NotExtension.NOT_EXTENSION: prop = desc.property if isinstance(prop, Synonym): key = prop.name diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py index 00ae9dac7..b1854de5a 100644 --- a/lib/sqlalchemy/orm/collections.py +++ b/lib/sqlalchemy/orm/collections.py @@ -107,6 +107,7 @@ from __future__ import annotations import operator import threading import typing +from typing import Any import weakref from .. import exc as sa_exc @@ -1239,13 +1240,13 @@ def _dict_decorators(): _set_binop_bases = (set, frozenset) -def _set_binops_check_strict(self, obj): +def _set_binops_check_strict(self: Any, obj: Any) -> bool: """Allow only set, frozenset and self.__class__-derived objects in binops.""" return isinstance(obj, _set_binop_bases + (self.__class__,)) -def _set_binops_check_loose(self, obj): +def _set_binops_check_loose(self: Any, obj: Any) -> bool: """Allow anything set-like to participate in set binops.""" return ( isinstance(obj, _set_binop_bases + (self.__class__,)) diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index eed973526..04fc07f61 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -31,17 +31,19 @@ from typing import Union from . import exc as orm_exc from . import path_registry -from .base import _MappedAttribute # noqa -from .base import EXT_CONTINUE -from .base import EXT_SKIP -from .base import EXT_STOP -from .base import InspectionAttr # noqa -from .base import InspectionAttrInfo # noqa -from .base import MANYTOMANY -from .base import MANYTOONE -from .base import NOT_EXTENSION -from .base import ONETOMANY +from .base import _MappedAttribute as _MappedAttribute +from .base import EXT_CONTINUE as EXT_CONTINUE +from .base import EXT_SKIP as EXT_SKIP +from .base import EXT_STOP as EXT_STOP +from .base import InspectionAttr as InspectionAttr +from .base import InspectionAttrExtensionType as InspectionAttrExtensionType +from .base import InspectionAttrInfo as InspectionAttrInfo +from .base import MANYTOMANY as MANYTOMANY +from .base import MANYTOONE as MANYTOONE +from .base import NotExtension as NotExtension +from .base import ONETOMANY as ONETOMANY from .base import SQLORMOperations +from .. import ColumnElement from .. import inspect from .. import inspection from .. import util @@ -51,6 +53,7 @@ from ..sql import visitors from ..sql._typing import _ColumnsClauseElement from ..sql.base import ExecutableOption from ..sql.cache_key import HasCacheKey +from ..sql.elements import SQLCoreOperations from ..sql.schema import Column from ..sql.type_api import TypeEngine from ..util.typing import TypedDict @@ -60,22 +63,6 @@ if typing.TYPE_CHECKING: _T = TypeVar("_T", bound=Any) -__all__ = ( - "EXT_CONTINUE", - "EXT_STOP", - "EXT_SKIP", - "ONETOMANY", - "MANYTOMANY", - "MANYTOONE", - "NOT_EXTENSION", - "LoaderStrategy", - "MapperOption", - "LoaderOption", - "MapperProperty", - "PropComparator", - "StrategizedProperty", -) - class ORMStatementRole(roles.StatementRole): __slots__ = () @@ -190,6 +177,10 @@ class MapperProperty( """ + comparator: PropComparator[_T] + """The :class:`_orm.PropComparator` instance that implements SQL + expression construction on behalf of this mapped attribute.""" + @property def _links_to_entity(self): """True if this MapperProperty refers to a mapped entity. @@ -512,6 +503,11 @@ class PropComparator( } ) + def _criterion_exists( + self, criterion: Optional[SQLCoreOperations[Any]] = None, **kwargs: Any + ) -> ColumnElement[Any]: + return self.prop.comparator._criterion_exists(criterion, **kwargs) + @property def adapter(self): """Produce a callable that adapts column expressions @@ -547,12 +543,12 @@ class PropComparator( def operate( self, op: operators.OperatorType, *other: Any, **kwargs: Any - ) -> "SQLORMOperations": + ) -> "SQLCoreOperations[Any]": ... def reverse_operate( self, op: operators.OperatorType, other: Any, **kwargs: Any - ) -> "SQLORMOperations": + ) -> "SQLCoreOperations[Any]": ... def of_type(self, class_) -> "SQLORMOperations[_T]": @@ -609,9 +605,11 @@ class PropComparator( """ return self.operate(operators.and_, *criteria) - def any(self, criterion=None, **kwargs) -> "SQLORMOperations[_T]": - r"""Return true if this collection contains any member that meets the - given criterion. + def any( + self, criterion: Optional[SQLCoreOperations[Any]] = None, **kwargs + ) -> ColumnElement[bool]: + r"""Return a SQL expression representing true if this element + references a member which meets the given criterion. The usual implementation of ``any()`` is :meth:`.Relationship.Comparator.any`. @@ -627,9 +625,11 @@ class PropComparator( return self.operate(PropComparator.any_op, criterion, **kwargs) - def has(self, criterion=None, **kwargs) -> "SQLORMOperations[_T]": - r"""Return true if this element references a member which meets the - given criterion. + def has( + self, criterion: Optional[SQLCoreOperations[Any]] = None, **kwargs + ) -> ColumnElement[bool]: + r"""Return a SQL expression representing true if this element + references a member which meets the given criterion. The usual implementation of ``has()`` is :meth:`.Relationship.Comparator.has`. diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 15e9b8431..5a34188a9 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -21,6 +21,7 @@ from functools import reduce from itertools import chain import sys import threading +from typing import Any from typing import Generic from typing import Type from typing import TypeVar @@ -113,6 +114,9 @@ class Mapper( _dispose_called = False _ready_for_configure = False + class_: Type[_MC] + """The class to which this :class:`_orm.Mapper` is mapped.""" + @util.deprecated_params( non_primary=( "1.3", @@ -1984,10 +1988,12 @@ class Mapper( else: return False - def has_property(self, key): + def has_property(self, key: str) -> bool: return key in self._props - def get_property(self, key, _configure_mappers=True): + def get_property( + self, key: str, _configure_mappers: bool = True + ) -> MapperProperty[Any]: """return a MapperProperty associated with the given key.""" if _configure_mappers: @@ -2715,7 +2721,7 @@ class Mapper( else: return _state_mapper(state) is s - def isa(self, other): + def isa(self, other: Mapper[Any]) -> bool: """Return True if the this mapper inherits from the given mapper.""" m = self diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 1b8f778c0..b4697912b 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -42,6 +42,7 @@ from .util import _orm_annotate from .util import _orm_deannotate from .util import CascadeOptions from .. import exc as sa_exc +from .. import Exists from .. import log from .. import schema from .. import sql @@ -52,6 +53,7 @@ from ..sql import expression from ..sql import operators from ..sql import roles from ..sql import visitors +from ..sql.elements import SQLCoreOperations from ..sql.util import _deep_deannotate from ..sql.util import _shallow_annotate from ..sql.util import adapt_criterion_to_null @@ -534,7 +536,11 @@ class Relationship( ) ) - def _criterion_exists(self, criterion=None, **kwargs): + def _criterion_exists( + self, + criterion: Optional[SQLCoreOperations[Any]] = None, + **kwargs: Any, + ) -> Exists[bool]: if getattr(self, "_of_type", None): info = inspect(self._of_type) target_mapper, to_selectable, is_aliased_class = ( @@ -1327,7 +1333,7 @@ class Relationship( return self.entity @util.memoized_property - def mapper(self) -> "Mapper": + def mapper(self) -> Mapper[_T]: """Return the targeted :class:`_orm.Mapper` for this :class:`.Relationship`. |
