summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-02-27 23:05:46 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2022-03-01 21:05:14 -0500
commitafb9634fb28b00c7b0979660e3e0bfed6caafde5 (patch)
tree11afb462226f64d922f9d3c425a7d2c09c3d69d7 /lib/sqlalchemy/orm
parent7f1a3f22abffc1529100e14fcfd07a46a49fd44f (diff)
downloadsqlalchemy-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__.py6
-rw-r--r--lib/sqlalchemy/orm/base.py71
-rw-r--r--lib/sqlalchemy/orm/clsregistry.py2
-rw-r--r--lib/sqlalchemy/orm/collections.py5
-rw-r--r--lib/sqlalchemy/orm/interfaces.py68
-rw-r--r--lib/sqlalchemy/orm/mapper.py12
-rw-r--r--lib/sqlalchemy/orm/relationships.py10
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`.