summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/associationproxy.py
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2022-11-30 14:04:34 +0000
committerGerrit Code Review <gerrit@ci3.zzzcomputing.com>2022-11-30 14:04:34 +0000
commit1057b47bca2522e45d9621a709d033aa4fb88888 (patch)
tree4ca4cd649b2f4f5f068051a7c2d74815e9d52f1a /lib/sqlalchemy/ext/associationproxy.py
parent7857a1de32169858367446d11089c34f8daee957 (diff)
parent3e3e3ab0d46b8912649afc7c3eb63b76c19d93fe (diff)
downloadsqlalchemy-1057b47bca2522e45d9621a709d033aa4fb88888.tar.gz
Merge "annotated / DC forms for association proxy" into main
Diffstat (limited to 'lib/sqlalchemy/ext/associationproxy.py')
-rw-r--r--lib/sqlalchemy/ext/associationproxy.py223
1 files changed, 137 insertions, 86 deletions
diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py
index f4adf3d29..15193e563 100644
--- a/lib/sqlalchemy/ext/associationproxy.py
+++ b/lib/sqlalchemy/ext/associationproxy.py
@@ -19,6 +19,7 @@ import operator
import typing
from typing import AbstractSet
from typing import Any
+from typing import Callable
from typing import cast
from typing import Collection
from typing import Dict
@@ -51,8 +52,12 @@ from ..orm import InspectionAttrExtensionType
from ..orm import interfaces
from ..orm import ORMDescriptor
from ..orm.base import SQLORMOperations
+from ..orm.interfaces import _AttributeOptions
+from ..orm.interfaces import _DCAttributeOptions
+from ..orm.interfaces import _DEFAULT_ATTRIBUTE_OPTIONS
from ..sql import operators
from ..sql import or_
+from ..sql.base import _NoArg
from ..util.typing import Literal
from ..util.typing import Protocol
from ..util.typing import Self
@@ -76,7 +81,20 @@ _VT = TypeVar("_VT", bound=Any)
def association_proxy(
- target_collection: str, attr: str, **kw: Any
+ target_collection: str,
+ attr: str,
+ *,
+ creator: Optional[_CreatorProtocol] = None,
+ getset_factory: Optional[_GetSetFactoryProtocol] = None,
+ proxy_factory: Optional[_ProxyFactoryProtocol] = None,
+ proxy_bulk_set: Optional[_ProxyBulkSetProtocol] = None,
+ info: Optional[_InfoType] = None,
+ cascade_scalar_deletes: bool = False,
+ init: Union[_NoArg, bool] = _NoArg.NO_ARG,
+ repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
+ default: Optional[Any] = _NoArg.NO_ARG,
+ default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
) -> AssociationProxy[Any]:
r"""Return a Python property implementing a view of a target
attribute which references an attribute on members of the
@@ -89,47 +107,120 @@ def association_proxy(
the collection type of the target (list, dict or set), or, in the case of
a one to one relationship, a simple scalar value.
- :param target_collection: Name of the attribute we'll proxy to.
- This attribute is typically mapped by
+ :param target_collection: Name of the attribute that is the immediate
+ target. This attribute is typically mapped by
:func:`~sqlalchemy.orm.relationship` to link to a target collection, but
can also be a many-to-one or non-scalar relationship.
- :param attr: Attribute on the associated instance or instances we'll
- proxy for.
+ :param attr: Attribute on the associated instance or instances that
+ are available on instances of the target object.
- For example, given a target collection of [obj1, obj2], a list created
- by this proxy property would look like [getattr(obj1, *attr*),
- getattr(obj2, *attr*)]
+ :param creator: optional.
- If the relationship is one-to-one or otherwise uselist=False, then
- simply: getattr(obj, *attr*)
+ Defines custom behavior when new items are added to the proxied
+ collection.
- :param creator: optional.
+ By default, adding new items to the collection will trigger a
+ construction of an instance of the target object, passing the given
+ item as a positional argument to the target constructor. For cases
+ where this isn't sufficient, :paramref:`.association_proxy.creator`
+ can supply a callable that will construct the object in the
+ appropriate way, given the item that was passed.
+
+ For list- and set- oriented collections, a single argument is
+ passed to the callable. For dictionary oriented collections, two
+ arguments are passed, corresponding to the key and value.
+
+ The :paramref:`.association_proxy.creator` callable is also invoked
+ for scalar (i.e. many-to-one, one-to-one) relationships. If the
+ current value of the target relationship attribute is ``None``, the
+ callable is used to construct a new object. If an object value already
+ exists, the given attribute value is populated onto that object.
+
+ .. seealso::
+
+ :ref:`associationproxy_creator`
+
+ :param cascade_scalar_deletes: when True, indicates that setting
+ the proxied value to ``None``, or deleting it via ``del``, should
+ also remove the source object. Only applies to scalar attributes.
+ Normally, removing the proxied target will not remove the proxy
+ source, as this object may have other state that is still to be
+ kept.
+
+ .. versionadded:: 1.3
+
+ .. seealso::
+
+ :ref:`cascade_scalar_deletes` - complete usage example
+
+ :param init: Specific to :ref:`orm_declarative_native_dataclasses`,
+ specifies if the mapped attribute should be part of the ``__init__()``
+ method as generated by the dataclass process.
+
+ .. versionadded:: 2.0.0b4
+
+ :param repr: Specific to :ref:`orm_declarative_native_dataclasses`,
+ specifies if the attribute established by this :class:`.AssociationProxy`
+ should be part of the ``__repr__()`` method as generated by the dataclass
+ process.
+
+ .. versionadded:: 2.0.0b4
+
+ :param default_factory: Specific to
+ :ref:`orm_declarative_native_dataclasses`, specifies a default-value
+ generation function that will take place as part of the ``__init__()``
+ method as generated by the dataclass process.
+
+ .. versionadded:: 2.0.0b4
+
+ :param kw_only: Specific to :ref:`orm_declarative_native_dataclasses`,
+ indicates if this field should be marked as keyword-only when generating
+ the ``__init__()`` method as generated by the dataclass process.
+
+ .. versionadded:: 2.0.0b4
- When new items are added to this proxied collection, new instances of
- the class collected by the target collection will be created. For list
- and set collections, the target class constructor will be called with
- the 'value' for the new instance. For dict types, two arguments are
- passed: key and value.
+ :param info: optional, will be assigned to
+ :attr:`.AssociationProxy.info` if present.
- If you want to construct instances differently, supply a *creator*
- function that takes arguments as above and returns instances.
- For scalar relationships, creator() will be called if the target is None.
- If the target is present, set operations are proxied to setattr() on the
- associated object.
+ The following additional parameters involve injection of custom behaviors
+ within the :class:`.AssociationProxy` object and are for advanced use
+ only:
- If you have an associated object with multiple attributes, you may set
- up multiple association proxies mapping to different attributes. See
- the unit tests for examples, and for examples of how creator() functions
- can be used to construct the scalar relationship on-demand in this
- situation.
+ :param getset_factory: Optional. Proxied attribute access is
+ automatically handled by routines that get and set values based on
+ the `attr` argument for this proxy.
+
+ If you would like to customize this behavior, you may supply a
+ `getset_factory` callable that produces a tuple of `getter` and
+ `setter` functions. The factory is called with two arguments, the
+ abstract type of the underlying collection and this proxy instance.
+
+ :param proxy_factory: Optional. The type of collection to emulate is
+ determined by sniffing the target collection. If your collection
+ type can't be determined by duck typing or you'd like to use a
+ different collection implementation, you may supply a factory
+ function to produce those collections. Only applicable to
+ non-scalar relationships.
+
+ :param proxy_bulk_set: Optional, use with proxy_factory.
- :param \*\*kw: Passes along any other keyword arguments to
- :class:`.AssociationProxy`.
"""
- return AssociationProxy(target_collection, attr, **kw)
+ return AssociationProxy(
+ target_collection,
+ attr,
+ creator=creator,
+ getset_factory=getset_factory,
+ proxy_factory=proxy_factory,
+ proxy_bulk_set=proxy_bulk_set,
+ info=info,
+ cascade_scalar_deletes=cascade_scalar_deletes,
+ attribute_options=_AttributeOptions(
+ init, repr, default, default_factory, kw_only
+ ),
+ )
class AssociationProxyExtensionType(InspectionAttrExtensionType):
@@ -247,6 +338,7 @@ _SelfAssociationProxy = TypeVar(
class AssociationProxy(
interfaces.InspectionAttrInfo,
ORMDescriptor[_T],
+ _DCAttributeOptions,
_AssociationProxyProtocol[_T],
):
"""A descriptor that presents a read/write view of an object attribute."""
@@ -258,73 +350,22 @@ class AssociationProxy(
self,
target_collection: str,
attr: str,
+ *,
creator: Optional[_CreatorProtocol] = None,
getset_factory: Optional[_GetSetFactoryProtocol] = None,
proxy_factory: Optional[_ProxyFactoryProtocol] = None,
proxy_bulk_set: Optional[_ProxyBulkSetProtocol] = None,
info: Optional[_InfoType] = None,
cascade_scalar_deletes: bool = False,
+ attribute_options: Optional[_AttributeOptions] = None,
):
"""Construct a new :class:`.AssociationProxy`.
- The :func:`.association_proxy` function is provided as the usual
- entrypoint here, though :class:`.AssociationProxy` can be instantiated
- and/or subclassed directly.
-
- :param target_collection: Name of the collection we'll proxy to,
- usually created with :func:`_orm.relationship`.
-
- :param attr: Attribute on the collected instances we'll proxy
- for. For example, given a target collection of [obj1, obj2], a
- list created by this proxy property would look like
- [getattr(obj1, attr), getattr(obj2, attr)]
-
- :param creator: Optional. When new items are added to this proxied
- collection, new instances of the class collected by the target
- collection will be created. For list and set collections, the
- target class constructor will be called with the 'value' for the
- new instance. For dict types, two arguments are passed:
- key and value.
-
- If you want to construct instances differently, supply a 'creator'
- function that takes arguments as above and returns instances.
-
- :param cascade_scalar_deletes: when True, indicates that setting
- the proxied value to ``None``, or deleting it via ``del``, should
- also remove the source object. Only applies to scalar attributes.
- Normally, removing the proxied target will not remove the proxy
- source, as this object may have other state that is still to be
- kept.
-
- .. versionadded:: 1.3
-
- .. seealso::
+ The :class:`.AssociationProxy` object is typically constructed using
+ the :func:`.association_proxy` constructor function. See the
+ description of :func:`.association_proxy` for a description of all
+ parameters.
- :ref:`cascade_scalar_deletes` - complete usage example
-
- :param getset_factory: Optional. Proxied attribute access is
- automatically handled by routines that get and set values based on
- the `attr` argument for this proxy.
-
- If you would like to customize this behavior, you may supply a
- `getset_factory` callable that produces a tuple of `getter` and
- `setter` functions. The factory is called with two arguments, the
- abstract type of the underlying collection and this proxy instance.
-
- :param proxy_factory: Optional. The type of collection to emulate is
- determined by sniffing the target collection. If your collection
- type can't be determined by duck typing or you'd like to use a
- different collection implementation, you may supply a factory
- function to produce those collections. Only applicable to
- non-scalar relationships.
-
- :param proxy_bulk_set: Optional, use with proxy_factory. See
- the _set() method for details.
-
- :param info: optional, will be assigned to
- :attr:`.AssociationProxy.info` if present.
-
- .. versionadded:: 1.0.9
"""
self.target_collection = target_collection
@@ -343,6 +384,16 @@ class AssociationProxy(
if info:
self.info = info # type: ignore
+ if (
+ attribute_options
+ and attribute_options != _DEFAULT_ATTRIBUTE_OPTIONS
+ ):
+ self._has_dataclass_arguments = True
+ self._attribute_options = attribute_options
+ else:
+ self._has_dataclass_arguments = False
+ self._attribute_options = _DEFAULT_ATTRIBUTE_OPTIONS
+
@overload
def __get__(
self: _SelfAssociationProxy, instance: Any, owner: Literal[None]