SQLAlchemy 2.1.0rc1__cp315-cp315-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. sqlalchemy/__init__.py +299 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +248 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +29 -0
  8. sqlalchemy/dialects/mssql/__init__.py +88 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4833 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +345 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +242 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +130 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +697 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +260 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +241 -0
  21. sqlalchemy/dialects/mysql/base.py +3896 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +107 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +314 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +291 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +318 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +188 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3847 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1736 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +157 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +898 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +366 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +170 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +232 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1318 -0
  50. sqlalchemy/dialects/postgresql/base.py +5935 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +599 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +422 -0
  55. sqlalchemy/dialects/postgresql/json.py +411 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +535 -0
  57. sqlalchemy/dialects/postgresql/operators.py +129 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +655 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +345 -0
  60. sqlalchemy/dialects/postgresql/provision.py +202 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +800 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +62 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +59 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +375 -0
  68. sqlalchemy/dialects/sqlite/base.py +3103 -0
  69. sqlalchemy/dialects/sqlite/dml.py +314 -0
  70. sqlalchemy/dialects/sqlite/json.py +134 -0
  71. sqlalchemy/dialects/sqlite/provision.py +237 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +166 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +959 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp315-win_amd64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp315-win_amd64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +711 -0
  80. sqlalchemy/engine/_row_cy.cp315-win_amd64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp315-win_amd64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3357 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2425 -0
  88. sqlalchemy/engine/default.py +2627 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3636 -0
  91. sqlalchemy/engine/mock.py +133 -0
  92. sqlalchemy/engine/processors.py +83 -0
  93. sqlalchemy/engine/reflection.py +2141 -0
  94. sqlalchemy/engine/result.py +2012 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +164 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +675 -0
  102. sqlalchemy/event/base.py +473 -0
  103. sqlalchemy/event/legacy.py +259 -0
  104. sqlalchemy/event/registry.py +391 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +939 -0
  107. sqlalchemy/ext/__init__.py +10 -0
  108. sqlalchemy/ext/associationproxy.py +2073 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2006 -0
  116. sqlalchemy/ext/automap.py +1702 -0
  117. sqlalchemy/ext/baked.py +558 -0
  118. sqlalchemy/ext/compiler.py +601 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +561 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +440 -0
  127. sqlalchemy/ext/serializer.py +184 -0
  128. sqlalchemy/future/__init__.py +17 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +188 -0
  131. sqlalchemy/log.py +279 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +180 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +991 -0
  137. sqlalchemy/orm/bulk_persistence.py +2168 -0
  138. sqlalchemy/orm/clsregistry.py +630 -0
  139. sqlalchemy/orm/collections.py +1569 -0
  140. sqlalchemy/orm/context.py +3475 -0
  141. sqlalchemy/orm/decl_api.py +2283 -0
  142. sqlalchemy/orm/decl_base.py +2320 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +306 -0
  146. sqlalchemy/orm/evaluator.py +378 -0
  147. sqlalchemy/orm/events.py +3387 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +749 -0
  151. sqlalchemy/orm/interfaces.py +1595 -0
  152. sqlalchemy/orm/loading.py +1712 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4465 -0
  155. sqlalchemy/orm/path_registry.py +907 -0
  156. sqlalchemy/orm/persistence.py +1790 -0
  157. sqlalchemy/orm/properties.py +972 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3608 -0
  160. sqlalchemy/orm/scoping.py +2233 -0
  161. sqlalchemy/orm/session.py +5468 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3552 -0
  165. sqlalchemy/orm/strategy_options.py +2648 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +797 -0
  168. sqlalchemy/orm/util.py +2461 -0
  169. sqlalchemy/orm/writeonly.py +701 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1540 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +583 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +75 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +402 -0
  178. sqlalchemy/sql/_cache_key_cy.cp315-win_amd64.pyd +0 -0
  179. sqlalchemy/sql/_cache_key_cy.py +363 -0
  180. sqlalchemy/sql/_dml_constructors.py +132 -0
  181. sqlalchemy/sql/_elements_constructors.py +2190 -0
  182. sqlalchemy/sql/_orm_types.py +19 -0
  183. sqlalchemy/sql/_selectable_constructors.py +840 -0
  184. sqlalchemy/sql/_typing.py +500 -0
  185. sqlalchemy/sql/_util_cy.cp315-win_amd64.pyd +0 -0
  186. sqlalchemy/sql/_util_cy.pxd +11 -0
  187. sqlalchemy/sql/_util_cy.py +127 -0
  188. sqlalchemy/sql/annotation.py +590 -0
  189. sqlalchemy/sql/base.py +2702 -0
  190. sqlalchemy/sql/cache_key.py +915 -0
  191. sqlalchemy/sql/coercions.py +1373 -0
  192. sqlalchemy/sql/compiler.py +8453 -0
  193. sqlalchemy/sql/crud.py +1816 -0
  194. sqlalchemy/sql/ddl.py +1962 -0
  195. sqlalchemy/sql/default_comparator.py +660 -0
  196. sqlalchemy/sql/dml.py +2018 -0
  197. sqlalchemy/sql/elements.py +6057 -0
  198. sqlalchemy/sql/events.py +458 -0
  199. sqlalchemy/sql/expression.py +171 -0
  200. sqlalchemy/sql/functions.py +2380 -0
  201. sqlalchemy/sql/lambdas.py +1442 -0
  202. sqlalchemy/sql/naming.py +204 -0
  203. sqlalchemy/sql/operators.py +2909 -0
  204. sqlalchemy/sql/roles.py +332 -0
  205. sqlalchemy/sql/schema.py +7075 -0
  206. sqlalchemy/sql/selectable.py +7634 -0
  207. sqlalchemy/sql/sqltypes.py +4130 -0
  208. sqlalchemy/sql/traversals.py +1041 -0
  209. sqlalchemy/sql/type_api.py +2450 -0
  210. sqlalchemy/sql/util.py +1496 -0
  211. sqlalchemy/sql/visitors.py +1153 -0
  212. sqlalchemy/testing/__init__.py +97 -0
  213. sqlalchemy/testing/assertions.py +1007 -0
  214. sqlalchemy/testing/assertsql.py +519 -0
  215. sqlalchemy/testing/asyncio.py +128 -0
  216. sqlalchemy/testing/cancellation.py +237 -0
  217. sqlalchemy/testing/config.py +440 -0
  218. sqlalchemy/testing/engines.py +482 -0
  219. sqlalchemy/testing/entities.py +117 -0
  220. sqlalchemy/testing/exclusions.py +501 -0
  221. sqlalchemy/testing/fixtures/__init__.py +30 -0
  222. sqlalchemy/testing/fixtures/base.py +426 -0
  223. sqlalchemy/testing/fixtures/mypy.py +247 -0
  224. sqlalchemy/testing/fixtures/orm.py +227 -0
  225. sqlalchemy/testing/fixtures/sql.py +538 -0
  226. sqlalchemy/testing/pickleable.py +155 -0
  227. sqlalchemy/testing/plugin/__init__.py +6 -0
  228. sqlalchemy/testing/plugin/bootstrap.py +50 -0
  229. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  230. sqlalchemy/testing/plugin/pytestplugin.py +896 -0
  231. sqlalchemy/testing/profiles_file.py +350 -0
  232. sqlalchemy/testing/profiling.py +294 -0
  233. sqlalchemy/testing/provision.py +633 -0
  234. sqlalchemy/testing/requirements.py +1971 -0
  235. sqlalchemy/testing/schema.py +198 -0
  236. sqlalchemy/testing/suite/__init__.py +19 -0
  237. sqlalchemy/testing/suite/test_cte.py +237 -0
  238. sqlalchemy/testing/suite/test_ddl.py +420 -0
  239. sqlalchemy/testing/suite/test_dialect.py +776 -0
  240. sqlalchemy/testing/suite/test_insert.py +630 -0
  241. sqlalchemy/testing/suite/test_reflection.py +3815 -0
  242. sqlalchemy/testing/suite/test_results.py +660 -0
  243. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  244. sqlalchemy/testing/suite/test_select.py +2112 -0
  245. sqlalchemy/testing/suite/test_sequence.py +317 -0
  246. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  247. sqlalchemy/testing/suite/test_types.py +2271 -0
  248. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  249. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  250. sqlalchemy/testing/util.py +575 -0
  251. sqlalchemy/testing/warnings.py +52 -0
  252. sqlalchemy/types.py +75 -0
  253. sqlalchemy/util/__init__.py +165 -0
  254. sqlalchemy/util/_collections.py +688 -0
  255. sqlalchemy/util/_collections_cy.cp315-win_amd64.pyd +0 -0
  256. sqlalchemy/util/_collections_cy.pxd +8 -0
  257. sqlalchemy/util/_collections_cy.py +516 -0
  258. sqlalchemy/util/_has_cython.py +48 -0
  259. sqlalchemy/util/_immutabledict_cy.cp315-win_amd64.pyd +0 -0
  260. sqlalchemy/util/_immutabledict_cy.py +240 -0
  261. sqlalchemy/util/compat.py +298 -0
  262. sqlalchemy/util/concurrency.py +272 -0
  263. sqlalchemy/util/cython.py +95 -0
  264. sqlalchemy/util/deprecations.py +401 -0
  265. sqlalchemy/util/langhelpers.py +2797 -0
  266. sqlalchemy/util/preloaded.py +153 -0
  267. sqlalchemy/util/queue.py +304 -0
  268. sqlalchemy/util/tool_support.py +202 -0
  269. sqlalchemy/util/topological.py +120 -0
  270. sqlalchemy/util/typing.py +709 -0
  271. sqlalchemy-2.1.0rc1.dist-info/METADATA +270 -0
  272. sqlalchemy-2.1.0rc1.dist-info/RECORD +276 -0
  273. sqlalchemy-2.1.0rc1.dist-info/WHEEL +5 -0
  274. sqlalchemy-2.1.0rc1.dist-info/licenses/AUTHORS +30 -0
  275. sqlalchemy-2.1.0rc1.dist-info/licenses/LICENSE +19 -0
  276. sqlalchemy-2.1.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2073 @@
1
+ # ext/associationproxy.py
2
+ # Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+
8
+ """Contain the ``AssociationProxy`` class.
9
+
10
+ The ``AssociationProxy`` is a Python property object which provides
11
+ transparent proxied access to the endpoint of an association object.
12
+
13
+ See the example ``examples/association/proxied_association.py``.
14
+
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import operator
20
+ import typing
21
+ from typing import AbstractSet
22
+ from typing import Any
23
+ from typing import Callable
24
+ from typing import cast
25
+ from typing import Collection
26
+ from typing import Dict
27
+ from typing import Generic
28
+ from typing import ItemsView
29
+ from typing import Iterable
30
+ from typing import Iterator
31
+ from typing import KeysView
32
+ from typing import List
33
+ from typing import Literal
34
+ from typing import Mapping
35
+ from typing import MutableMapping
36
+ from typing import MutableSequence
37
+ from typing import MutableSet
38
+ from typing import NoReturn
39
+ from typing import Optional
40
+ from typing import overload
41
+ from typing import Protocol
42
+ from typing import Set
43
+ from typing import SupportsIndex
44
+ from typing import Tuple
45
+ from typing import Type
46
+ from typing import TypeVar
47
+ from typing import Union
48
+ from typing import ValuesView
49
+
50
+ from .. import ColumnElement
51
+ from .. import exc
52
+ from .. import inspect
53
+ from .. import orm
54
+ from .. import util
55
+ from ..orm import collections
56
+ from ..orm import InspectionAttrExtensionType
57
+ from ..orm import interfaces
58
+ from ..orm import ORMDescriptor
59
+ from ..orm.base import SQLORMOperations
60
+ from ..orm.interfaces import _AttributeOptions
61
+ from ..orm.interfaces import _DCAttributeOptions
62
+ from ..orm.interfaces import _DEFAULT_ATTRIBUTE_OPTIONS
63
+ from ..sql import operators
64
+ from ..sql import or_
65
+ from ..sql.base import _NoArg
66
+ from ..util.typing import Self
67
+ from ..util.typing import SupportsKeysAndGetItem
68
+
69
+ if typing.TYPE_CHECKING:
70
+ from ..orm.interfaces import MapperProperty
71
+ from ..orm.interfaces import PropComparator
72
+ from ..orm.mapper import Mapper
73
+ from ..orm.util import AliasedInsp
74
+ from ..sql._typing import _ColumnExpressionArgument
75
+ from ..sql._typing import _InfoType
76
+
77
+
78
+ _T = TypeVar("_T", bound=Any)
79
+ _T_co = TypeVar("_T_co", bound=Any, covariant=True)
80
+ _T_con = TypeVar("_T_con", bound=Any, contravariant=True)
81
+ _S = TypeVar("_S", bound=Any)
82
+ _KT = TypeVar("_KT", bound=Any)
83
+ _VT = TypeVar("_VT", bound=Any)
84
+
85
+
86
+ def association_proxy(
87
+ target_collection: str,
88
+ attr: str,
89
+ *,
90
+ creator: Optional[_CreatorProtocol] = None,
91
+ getset_factory: Optional[_GetSetFactoryProtocol] = None,
92
+ proxy_factory: Optional[_ProxyFactoryProtocol] = None,
93
+ proxy_bulk_set: Optional[_ProxyBulkSetProtocol] = None,
94
+ info: Optional[_InfoType] = None,
95
+ cascade_scalar_deletes: bool = False,
96
+ create_on_none_assignment: bool = False,
97
+ init: Union[_NoArg, bool] = _NoArg.NO_ARG,
98
+ repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002
99
+ default: Optional[Any] = _NoArg.NO_ARG,
100
+ default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG,
101
+ compare: Union[_NoArg, bool] = _NoArg.NO_ARG,
102
+ kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG,
103
+ hash: Union[_NoArg, bool, None] = _NoArg.NO_ARG, # noqa: A002
104
+ dataclass_metadata: Union[_NoArg, Mapping[Any, Any], None] = _NoArg.NO_ARG,
105
+ ) -> AssociationProxy[Any]:
106
+ r"""Return a Python property implementing a view of a target
107
+ attribute which references an attribute on members of the
108
+ target.
109
+
110
+ The returned value is an instance of :class:`.AssociationProxy`.
111
+
112
+ Implements a Python property representing a relationship as a collection
113
+ of simpler values, or a scalar value. The proxied property will mimic
114
+ the collection type of the target (list, dict or set), or, in the case of
115
+ a one to one relationship, a simple scalar value.
116
+
117
+ :param target_collection: Name of the attribute that is the immediate
118
+ target. This attribute is typically mapped by
119
+ :func:`~sqlalchemy.orm.relationship` to link to a target collection, but
120
+ can also be a many-to-one or non-scalar relationship.
121
+
122
+ :param attr: Attribute on the associated instance or instances that
123
+ are available on instances of the target object.
124
+
125
+ :param creator: optional.
126
+
127
+ Defines custom behavior when new items are added to the proxied
128
+ collection.
129
+
130
+ By default, adding new items to the collection will trigger a
131
+ construction of an instance of the target object, passing the given
132
+ item as a positional argument to the target constructor. For cases
133
+ where this isn't sufficient, :paramref:`.association_proxy.creator`
134
+ can supply a callable that will construct the object in the
135
+ appropriate way, given the item that was passed.
136
+
137
+ For list- and set- oriented collections, a single argument is
138
+ passed to the callable. For dictionary oriented collections, two
139
+ arguments are passed, corresponding to the key and value.
140
+
141
+ The :paramref:`.association_proxy.creator` callable is also invoked
142
+ for scalar (i.e. many-to-one, one-to-one) relationships. If the
143
+ current value of the target relationship attribute is ``None``, the
144
+ callable is used to construct a new object. If an object value already
145
+ exists, the given attribute value is populated onto that object.
146
+
147
+ .. seealso::
148
+
149
+ :ref:`associationproxy_creator`
150
+
151
+ :param cascade_scalar_deletes: when True, indicates that setting
152
+ the proxied value to ``None``, or deleting it via ``del``, should
153
+ also remove the source object. Only applies to scalar attributes.
154
+ Normally, removing the proxied target will not remove the proxy
155
+ source, as this object may have other state that is still to be
156
+ kept.
157
+
158
+ .. seealso::
159
+
160
+ :ref:`cascade_scalar_deletes` - complete usage example
161
+
162
+ :param create_on_none_assignment: when True, indicates that setting
163
+ the proxied value to ``None`` should **create** the source object
164
+ if it does not exist, using the creator. Only applies to scalar
165
+ attributes. This is mutually exclusive
166
+ vs. the :paramref:`.association_proxy.cascade_scalar_deletes`.
167
+
168
+ .. versionadded:: 2.0.18
169
+
170
+ :param init: Specific to :ref:`orm_declarative_native_dataclasses`,
171
+ specifies if the mapped attribute should be part of the ``__init__()``
172
+ method as generated by the dataclass process.
173
+
174
+ .. versionadded:: 2.0.0b4
175
+
176
+ :param repr: Specific to :ref:`orm_declarative_native_dataclasses`,
177
+ specifies if the attribute established by this :class:`.AssociationProxy`
178
+ should be part of the ``__repr__()`` method as generated by the dataclass
179
+ process.
180
+
181
+ .. versionadded:: 2.0.0b4
182
+
183
+ :param default_factory: Specific to
184
+ :ref:`orm_declarative_native_dataclasses`, specifies a default-value
185
+ generation function that will take place as part of the ``__init__()``
186
+ method as generated by the dataclass process.
187
+
188
+ .. versionadded:: 2.0.0b4
189
+
190
+ :param compare: Specific to
191
+ :ref:`orm_declarative_native_dataclasses`, indicates if this field
192
+ should be included in comparison operations when generating the
193
+ ``__eq__()`` and ``__ne__()`` methods for the mapped class.
194
+
195
+ .. versionadded:: 2.0.0b4
196
+
197
+ :param kw_only: Specific to :ref:`orm_declarative_native_dataclasses`,
198
+ indicates if this field should be marked as keyword-only when generating
199
+ the ``__init__()`` method as generated by the dataclass process.
200
+
201
+ .. versionadded:: 2.0.0b4
202
+
203
+ :param hash: Specific to
204
+ :ref:`orm_declarative_native_dataclasses`, controls if this field
205
+ is included when generating the ``__hash__()`` method for the mapped
206
+ class.
207
+
208
+ .. versionadded:: 2.0.36
209
+
210
+ :param dataclass_metadata: Specific to
211
+ :ref:`orm_declarative_native_dataclasses`, supplies metadata
212
+ to be attached to the generated dataclass field.
213
+
214
+ .. versionadded:: 2.0.42
215
+
216
+ :param info: optional, will be assigned to
217
+ :attr:`.AssociationProxy.info` if present.
218
+
219
+
220
+ The following additional parameters involve injection of custom behaviors
221
+ within the :class:`.AssociationProxy` object and are for advanced use
222
+ only:
223
+
224
+ :param getset_factory: Optional. Proxied attribute access is
225
+ automatically handled by routines that get and set values based on
226
+ the `attr` argument for this proxy.
227
+
228
+ If you would like to customize this behavior, you may supply a
229
+ `getset_factory` callable that produces a tuple of `getter` and
230
+ `setter` functions. The factory is called with two arguments, the
231
+ abstract type of the underlying collection and this proxy instance.
232
+
233
+ :param proxy_factory: Optional. The type of collection to emulate is
234
+ determined by sniffing the target collection. If your collection
235
+ type can't be determined by duck typing or you'd like to use a
236
+ different collection implementation, you may supply a factory
237
+ function to produce those collections. Only applicable to
238
+ non-scalar relationships.
239
+
240
+ :param proxy_bulk_set: Optional, use with proxy_factory.
241
+
242
+
243
+ """
244
+ return AssociationProxy(
245
+ target_collection,
246
+ attr,
247
+ creator=creator,
248
+ getset_factory=getset_factory,
249
+ proxy_factory=proxy_factory,
250
+ proxy_bulk_set=proxy_bulk_set,
251
+ info=info,
252
+ cascade_scalar_deletes=cascade_scalar_deletes,
253
+ create_on_none_assignment=create_on_none_assignment,
254
+ attribute_options=_AttributeOptions(
255
+ init,
256
+ repr,
257
+ default,
258
+ default_factory,
259
+ compare,
260
+ kw_only,
261
+ hash,
262
+ dataclass_metadata,
263
+ ),
264
+ )
265
+
266
+
267
+ class AssociationProxyExtensionType(InspectionAttrExtensionType):
268
+ ASSOCIATION_PROXY = "ASSOCIATION_PROXY"
269
+ """Symbol indicating an :class:`.InspectionAttr` that's
270
+ of type :class:`.AssociationProxy`.
271
+
272
+ Is assigned to the :attr:`.InspectionAttr.extension_type`
273
+ attribute.
274
+
275
+ """
276
+
277
+
278
+ class _GetterProtocol(Protocol[_T_co]):
279
+ def __call__(self, instance: Any) -> _T_co: ...
280
+
281
+
282
+ # mypy 0.990 we are no longer allowed to make this Protocol[_T_con]
283
+ class _SetterProtocol(Protocol): ...
284
+
285
+
286
+ class _PlainSetterProtocol(_SetterProtocol, Protocol[_T_con]):
287
+ def __call__(self, instance: Any, value: _T_con) -> None: ...
288
+
289
+
290
+ class _DictSetterProtocol(_SetterProtocol, Protocol[_T_con]):
291
+ def __call__(self, instance: Any, key: Any, value: _T_con) -> None: ...
292
+
293
+
294
+ # mypy 0.990 we are no longer allowed to make this Protocol[_T_con]
295
+ class _CreatorProtocol(Protocol): ...
296
+
297
+
298
+ class _PlainCreatorProtocol(_CreatorProtocol, Protocol[_T_con]):
299
+ def __call__(self, value: _T_con) -> Any: ...
300
+
301
+
302
+ class _KeyCreatorProtocol(_CreatorProtocol, Protocol[_T_con]):
303
+ def __call__(self, key: Any, value: Optional[_T_con]) -> Any: ...
304
+
305
+
306
+ class _LazyCollectionProtocol(Protocol[_T]):
307
+ def __call__(
308
+ self,
309
+ ) -> Union[
310
+ MutableSet[_T], MutableMapping[Any, _T], MutableSequence[_T]
311
+ ]: ...
312
+
313
+
314
+ class _GetSetFactoryProtocol(Protocol):
315
+ def __call__(
316
+ self,
317
+ collection_class: Optional[Type[Any]],
318
+ assoc_instance: AssociationProxyInstance[Any],
319
+ ) -> Tuple[_GetterProtocol[Any], _SetterProtocol]: ...
320
+
321
+
322
+ class _ProxyFactoryProtocol(Protocol):
323
+ def __call__(
324
+ self,
325
+ lazy_collection: _LazyCollectionProtocol[Any],
326
+ creator: _CreatorProtocol,
327
+ value_attr: str,
328
+ parent: AssociationProxyInstance[Any],
329
+ ) -> Any: ...
330
+
331
+
332
+ class _ProxyBulkSetProtocol(Protocol):
333
+ def __call__(
334
+ self, proxy: _AssociationCollection[Any], collection: Iterable[Any]
335
+ ) -> None: ...
336
+
337
+
338
+ class _AssociationProxyProtocol(Protocol[_T]):
339
+ """describes the interface of :class:`.AssociationProxy`
340
+ without including descriptor methods in the interface."""
341
+
342
+ creator: Optional[_CreatorProtocol]
343
+ key: str
344
+ target_collection: str
345
+ value_attr: str
346
+ cascade_scalar_deletes: bool
347
+ create_on_none_assignment: bool
348
+ getset_factory: Optional[_GetSetFactoryProtocol]
349
+ proxy_factory: Optional[_ProxyFactoryProtocol]
350
+ proxy_bulk_set: Optional[_ProxyBulkSetProtocol]
351
+
352
+ @util.ro_memoized_property
353
+ def info(self) -> _InfoType: ...
354
+
355
+ def for_class(
356
+ self, class_: Type[Any], obj: Optional[object] = None
357
+ ) -> AssociationProxyInstance[_T]: ...
358
+
359
+ def _default_getset(
360
+ self, collection_class: Any
361
+ ) -> Tuple[_GetterProtocol[Any], _SetterProtocol]: ...
362
+
363
+
364
+ class AssociationProxy(
365
+ interfaces.InspectionAttrInfo,
366
+ ORMDescriptor[_T],
367
+ _DCAttributeOptions,
368
+ _AssociationProxyProtocol[_T],
369
+ ):
370
+ """A descriptor that presents a read/write view of an object attribute."""
371
+
372
+ is_attribute = True
373
+ extension_type = AssociationProxyExtensionType.ASSOCIATION_PROXY
374
+
375
+ def __init__(
376
+ self,
377
+ target_collection: str,
378
+ attr: str,
379
+ *,
380
+ creator: Optional[_CreatorProtocol] = None,
381
+ getset_factory: Optional[_GetSetFactoryProtocol] = None,
382
+ proxy_factory: Optional[_ProxyFactoryProtocol] = None,
383
+ proxy_bulk_set: Optional[_ProxyBulkSetProtocol] = None,
384
+ info: Optional[_InfoType] = None,
385
+ cascade_scalar_deletes: bool = False,
386
+ create_on_none_assignment: bool = False,
387
+ attribute_options: Optional[_AttributeOptions] = None,
388
+ ):
389
+ """Construct a new :class:`.AssociationProxy`.
390
+
391
+ The :class:`.AssociationProxy` object is typically constructed using
392
+ the :func:`.association_proxy` constructor function. See the
393
+ description of :func:`.association_proxy` for a description of all
394
+ parameters.
395
+
396
+
397
+ """
398
+ self.target_collection = target_collection
399
+ self.value_attr = attr
400
+ self.creator = creator
401
+ self.getset_factory = getset_factory
402
+ self.proxy_factory = proxy_factory
403
+ self.proxy_bulk_set = proxy_bulk_set
404
+
405
+ if cascade_scalar_deletes and create_on_none_assignment:
406
+ raise exc.ArgumentError(
407
+ "The cascade_scalar_deletes and create_on_none_assignment "
408
+ "parameters are mutually exclusive."
409
+ )
410
+ self.cascade_scalar_deletes = cascade_scalar_deletes
411
+ self.create_on_none_assignment = create_on_none_assignment
412
+
413
+ self.key = "_%s_%s_%s" % (
414
+ type(self).__name__,
415
+ target_collection,
416
+ id(self),
417
+ )
418
+ if info:
419
+ self.info = info # type: ignore[misc]
420
+
421
+ if (
422
+ attribute_options
423
+ and attribute_options != _DEFAULT_ATTRIBUTE_OPTIONS
424
+ ):
425
+ self._has_dataclass_arguments = True
426
+ self._attribute_options = attribute_options
427
+ else:
428
+ self._has_dataclass_arguments = False
429
+ self._attribute_options = _DEFAULT_ATTRIBUTE_OPTIONS
430
+
431
+ @overload
432
+ def __get__(
433
+ self, instance: Literal[None], owner: Literal[None]
434
+ ) -> Self: ...
435
+
436
+ @overload
437
+ def __get__(
438
+ self, instance: Literal[None], owner: Any
439
+ ) -> AssociationProxyInstance[_T]: ...
440
+
441
+ @overload
442
+ def __get__(self, instance: object, owner: Any) -> _T: ...
443
+
444
+ def __get__(
445
+ self, instance: object, owner: Any
446
+ ) -> Union[AssociationProxyInstance[_T], _T, AssociationProxy[_T]]:
447
+ if owner is None:
448
+ return self
449
+ inst = self._as_instance(owner, instance)
450
+ if inst:
451
+ return inst.get(instance)
452
+
453
+ assert instance is None
454
+
455
+ return self
456
+
457
+ def __set__(self, instance: object, values: _T) -> None:
458
+ class_ = type(instance)
459
+ self._as_instance(class_, instance).set(instance, values)
460
+
461
+ def __delete__(self, instance: object) -> None:
462
+ class_ = type(instance)
463
+ self._as_instance(class_, instance).delete(instance)
464
+
465
+ def for_class(
466
+ self, class_: Type[Any], obj: Optional[object] = None
467
+ ) -> AssociationProxyInstance[_T]:
468
+ r"""Return the internal state local to a specific mapped class.
469
+
470
+ E.g., given a class ``User``::
471
+
472
+ class User(Base):
473
+ # ...
474
+
475
+ keywords = association_proxy("kws", "keyword")
476
+
477
+ If we access this :class:`.AssociationProxy` from
478
+ :attr:`_orm.Mapper.all_orm_descriptors`, and we want to view the
479
+ target class for this proxy as mapped by ``User``::
480
+
481
+ inspect(User).all_orm_descriptors["keywords"].for_class(User).target_class
482
+
483
+ This returns an instance of :class:`.AssociationProxyInstance` that
484
+ is specific to the ``User`` class. The :class:`.AssociationProxy`
485
+ object remains agnostic of its parent class.
486
+
487
+ :param class\_: the class that we are returning state for.
488
+
489
+ :param obj: optional, an instance of the class that is required
490
+ if the attribute refers to a polymorphic target, e.g. where we have
491
+ to look at the type of the actual destination object to get the
492
+ complete path.
493
+
494
+ """
495
+ return self._as_instance(class_, obj)
496
+
497
+ def _as_instance(
498
+ self, class_: Any, obj: Any
499
+ ) -> AssociationProxyInstance[_T]:
500
+ try:
501
+ inst = class_.__dict__[self.key + "_inst"]
502
+ except KeyError:
503
+ inst = None
504
+
505
+ # avoid exception context
506
+ if inst is None:
507
+ owner = self._calc_owner(class_)
508
+ if owner is not None:
509
+ inst = AssociationProxyInstance.for_proxy(self, owner, obj)
510
+ setattr(class_, self.key + "_inst", inst)
511
+ else:
512
+ inst = None
513
+
514
+ if inst is not None and not inst._is_canonical:
515
+ # the AssociationProxyInstance can't be generalized
516
+ # since the proxied attribute is not on the targeted
517
+ # class, only on subclasses of it, which might be
518
+ # different. only return for the specific
519
+ # object's current value
520
+ return inst._non_canonical_get_for_object(obj) # type: ignore[no-any-return] # noqa: E501
521
+ else:
522
+ return inst # type: ignore[no-any-return] # TODO
523
+
524
+ def _calc_owner(self, target_cls: Any) -> Any:
525
+ # we might be getting invoked for a subclass
526
+ # that is not mapped yet, in some declarative situations.
527
+ # save until we are mapped
528
+ try:
529
+ insp = inspect(target_cls)
530
+ except exc.NoInspectionAvailable:
531
+ # can't find a mapper, don't set owner. if we are a not-yet-mapped
532
+ # subclass, we can also scan through __mro__ to find a mapped
533
+ # class, but instead just wait for us to be called again against a
534
+ # mapped class normally.
535
+ return None
536
+ else:
537
+ return insp.mapper.class_manager.class_
538
+
539
+ def _default_getset(
540
+ self, collection_class: Any
541
+ ) -> Tuple[_GetterProtocol[Any], _SetterProtocol]:
542
+ attr = self.value_attr
543
+ _getter = operator.attrgetter(attr)
544
+
545
+ def getter(instance: Any) -> Optional[Any]:
546
+ return _getter(instance) if instance is not None else None
547
+
548
+ if collection_class is dict:
549
+
550
+ def dict_setter(instance: Any, k: Any, value: Any) -> None:
551
+ setattr(instance, attr, value)
552
+
553
+ return getter, dict_setter
554
+
555
+ else:
556
+
557
+ def plain_setter(o: Any, v: Any) -> None:
558
+ setattr(o, attr, v)
559
+
560
+ return getter, plain_setter
561
+
562
+ def __repr__(self) -> str:
563
+ return "AssociationProxy(%r, %r)" % (
564
+ self.target_collection,
565
+ self.value_attr,
566
+ )
567
+
568
+
569
+ # the pep-673 Self type does not work in Mypy for a "hybrid"
570
+ # style method that returns type or Self, so for one specific case
571
+ # we still need to use the pre-pep-673 workaround.
572
+ _Self = TypeVar("_Self", bound="AssociationProxyInstance[Any]")
573
+
574
+
575
+ class AssociationProxyInstance(SQLORMOperations[_T]):
576
+ """A per-class object that serves class- and object-specific results.
577
+
578
+ This is used by :class:`.AssociationProxy` when it is invoked
579
+ in terms of a specific class or instance of a class, i.e. when it is
580
+ used as a regular Python descriptor.
581
+
582
+ When referring to the :class:`.AssociationProxy` as a normal Python
583
+ descriptor, the :class:`.AssociationProxyInstance` is the object that
584
+ actually serves the information. Under normal circumstances, its presence
585
+ is transparent::
586
+
587
+ >>> User.keywords.scalar
588
+ False
589
+
590
+ In the special case that the :class:`.AssociationProxy` object is being
591
+ accessed directly, in order to get an explicit handle to the
592
+ :class:`.AssociationProxyInstance`, use the
593
+ :meth:`.AssociationProxy.for_class` method::
594
+
595
+ proxy_state = inspect(User).all_orm_descriptors["keywords"].for_class(User)
596
+
597
+ # view if proxy object is scalar or not
598
+ >>> proxy_state.scalar
599
+ False
600
+
601
+ """ # noqa
602
+
603
+ collection_class: Optional[Type[Any]]
604
+ parent: _AssociationProxyProtocol[_T]
605
+
606
+ def __init__(
607
+ self,
608
+ parent: _AssociationProxyProtocol[_T],
609
+ owning_class: Type[Any],
610
+ target_class: Type[Any],
611
+ value_attr: str,
612
+ ):
613
+ self.parent = parent
614
+ self.key = parent.key
615
+ self.owning_class = owning_class
616
+ self.target_collection = parent.target_collection
617
+ self.collection_class = None
618
+ self.target_class = target_class
619
+ self.value_attr = value_attr
620
+
621
+ target_class: Type[Any]
622
+ """The intermediary class handled by this
623
+ :class:`.AssociationProxyInstance`.
624
+
625
+ Intercepted append/set/assignment events will result
626
+ in the generation of new instances of this class.
627
+
628
+ """
629
+
630
+ @classmethod
631
+ def for_proxy(
632
+ cls,
633
+ parent: AssociationProxy[_T],
634
+ owning_class: Type[Any],
635
+ parent_instance: Any,
636
+ ) -> AssociationProxyInstance[_T]:
637
+ target_collection = parent.target_collection
638
+ value_attr = parent.value_attr
639
+ prop = cast(
640
+ "orm.RelationshipProperty[_T]",
641
+ orm.class_mapper(owning_class).get_property(target_collection),
642
+ )
643
+
644
+ # this was never asserted before but this should be made clear.
645
+ if not isinstance(prop, orm.RelationshipProperty):
646
+ raise NotImplementedError(
647
+ "association proxy to a non-relationship "
648
+ "intermediary is not supported"
649
+ ) from None
650
+
651
+ target_class = prop.mapper.class_
652
+
653
+ try:
654
+ target_assoc = cast(
655
+ "AssociationProxyInstance[_T]",
656
+ cls._cls_unwrap_target_assoc_proxy(target_class, value_attr),
657
+ )
658
+ except AttributeError:
659
+ # the proxied attribute doesn't exist on the target class;
660
+ # return an "ambiguous" instance that will work on a per-object
661
+ # basis
662
+ return AmbiguousAssociationProxyInstance(
663
+ parent, owning_class, target_class, value_attr
664
+ )
665
+ except Exception as err:
666
+ raise exc.InvalidRequestError(
667
+ f"Association proxy received an unexpected error when "
668
+ f"trying to retrieve attribute "
669
+ f'"{target_class.__name__}.{parent.value_attr}" from '
670
+ f'class "{target_class.__name__}": {err}'
671
+ ) from err
672
+ else:
673
+ return cls._construct_for_assoc(
674
+ target_assoc, parent, owning_class, target_class, value_attr
675
+ )
676
+
677
+ @classmethod
678
+ def _construct_for_assoc(
679
+ cls,
680
+ target_assoc: Optional[AssociationProxyInstance[_T]],
681
+ parent: _AssociationProxyProtocol[_T],
682
+ owning_class: Type[Any],
683
+ target_class: Type[Any],
684
+ value_attr: str,
685
+ ) -> AssociationProxyInstance[_T]:
686
+ if target_assoc is not None:
687
+ return ObjectAssociationProxyInstance(
688
+ parent, owning_class, target_class, value_attr
689
+ )
690
+
691
+ attr = getattr(target_class, value_attr)
692
+ if not hasattr(attr, "_is_internal_proxy"):
693
+ return AmbiguousAssociationProxyInstance(
694
+ parent, owning_class, target_class, value_attr
695
+ )
696
+ is_object = attr._impl_uses_objects
697
+ if is_object:
698
+ return ObjectAssociationProxyInstance(
699
+ parent, owning_class, target_class, value_attr
700
+ )
701
+ else:
702
+ return ColumnAssociationProxyInstance(
703
+ parent, owning_class, target_class, value_attr
704
+ )
705
+
706
+ def _get_property(self) -> MapperProperty[Any]:
707
+ return orm.class_mapper(self.owning_class).get_property(
708
+ self.target_collection
709
+ )
710
+
711
+ @property
712
+ def _comparator(self) -> PropComparator[Any]:
713
+ return getattr( # type: ignore[no-any-return]
714
+ self.owning_class, self.target_collection
715
+ ).comparator
716
+
717
+ def __clause_element__(self) -> NoReturn:
718
+ raise NotImplementedError(
719
+ "The association proxy can't be used as a plain column "
720
+ "expression; it only works inside of a comparison expression"
721
+ )
722
+
723
+ @classmethod
724
+ def _cls_unwrap_target_assoc_proxy(
725
+ cls, target_class: Any, value_attr: str
726
+ ) -> Optional[AssociationProxyInstance[_T]]:
727
+ attr = getattr(target_class, value_attr)
728
+ assert not isinstance(attr, AssociationProxy)
729
+ if isinstance(attr, AssociationProxyInstance):
730
+ return attr
731
+ return None
732
+
733
+ @util.memoized_property
734
+ def _unwrap_target_assoc_proxy(
735
+ self,
736
+ ) -> Optional[AssociationProxyInstance[_T]]:
737
+ return self._cls_unwrap_target_assoc_proxy(
738
+ self.target_class, self.value_attr
739
+ )
740
+
741
+ @property
742
+ def remote_attr(self) -> SQLORMOperations[_T]:
743
+ """The 'remote' class attribute referenced by this
744
+ :class:`.AssociationProxyInstance`.
745
+
746
+ .. seealso::
747
+
748
+ :attr:`.AssociationProxyInstance.attr`
749
+
750
+ :attr:`.AssociationProxyInstance.local_attr`
751
+
752
+ """
753
+ return cast(
754
+ "SQLORMOperations[_T]", getattr(self.target_class, self.value_attr)
755
+ )
756
+
757
+ @property
758
+ def local_attr(self) -> SQLORMOperations[Any]:
759
+ """The 'local' class attribute referenced by this
760
+ :class:`.AssociationProxyInstance`.
761
+
762
+ .. seealso::
763
+
764
+ :attr:`.AssociationProxyInstance.attr`
765
+
766
+ :attr:`.AssociationProxyInstance.remote_attr`
767
+
768
+ """
769
+ return cast(
770
+ "SQLORMOperations[Any]",
771
+ getattr(self.owning_class, self.target_collection),
772
+ )
773
+
774
+ @property
775
+ def attr(self) -> Tuple[SQLORMOperations[Any], SQLORMOperations[_T]]:
776
+ """Return a tuple of ``(local_attr, remote_attr)``.
777
+
778
+ This attribute was originally intended to facilitate using the
779
+ :meth:`_query.Query.join` method to join across the two relationships
780
+ at once, however this makes use of a deprecated calling style.
781
+
782
+ To use :meth:`_sql.select.join` or :meth:`_orm.Query.join` with
783
+ an association proxy, the current method is to make use of the
784
+ :attr:`.AssociationProxyInstance.local_attr` and
785
+ :attr:`.AssociationProxyInstance.remote_attr` attributes separately::
786
+
787
+ stmt = (
788
+ select(Parent)
789
+ .join(Parent.proxied.local_attr)
790
+ .join(Parent.proxied.remote_attr)
791
+ )
792
+
793
+ A future release may seek to provide a more succinct join pattern
794
+ for association proxy attributes.
795
+
796
+ .. seealso::
797
+
798
+ :attr:`.AssociationProxyInstance.local_attr`
799
+
800
+ :attr:`.AssociationProxyInstance.remote_attr`
801
+
802
+ """
803
+ return (self.local_attr, self.remote_attr)
804
+
805
+ @util.memoized_property
806
+ def scalar(self) -> bool:
807
+ """Return ``True`` if this :class:`.AssociationProxyInstance`
808
+ proxies a scalar relationship on the local side."""
809
+
810
+ scalar = not self._get_property().uselist
811
+ if scalar:
812
+ self._initialize_scalar_accessors()
813
+ return scalar
814
+
815
+ @util.memoized_property
816
+ def _value_is_scalar(self) -> bool:
817
+ return (
818
+ not self._get_property()
819
+ .mapper.get_property(self.value_attr)
820
+ .uselist
821
+ )
822
+
823
+ @property
824
+ def _target_is_object(self) -> bool:
825
+ raise NotImplementedError()
826
+
827
+ _scalar_get: _GetterProtocol[_T]
828
+ _scalar_set: _PlainSetterProtocol[_T]
829
+
830
+ def _initialize_scalar_accessors(self) -> None:
831
+ if self.parent.getset_factory:
832
+ get, set_ = self.parent.getset_factory(None, self)
833
+ else:
834
+ get, set_ = self.parent._default_getset(None)
835
+ self._scalar_get, self._scalar_set = get, cast(
836
+ "_PlainSetterProtocol[_T]", set_
837
+ )
838
+
839
+ def _default_getset(
840
+ self, collection_class: Any
841
+ ) -> Tuple[_GetterProtocol[Any], _SetterProtocol]:
842
+ attr = self.value_attr
843
+ _getter = operator.attrgetter(attr)
844
+
845
+ def getter(instance: Any) -> Optional[_T]:
846
+ return _getter(instance) if instance is not None else None
847
+
848
+ if collection_class is dict:
849
+
850
+ def dict_setter(instance: Any, k: Any, value: _T) -> None:
851
+ setattr(instance, attr, value)
852
+
853
+ return getter, dict_setter
854
+ else:
855
+
856
+ def plain_setter(o: Any, v: _T) -> None:
857
+ setattr(o, attr, v)
858
+
859
+ return getter, plain_setter
860
+
861
+ @util.ro_non_memoized_property
862
+ def info(self) -> _InfoType:
863
+ return self.parent.info
864
+
865
+ @overload
866
+ def get(self: _Self, obj: Literal[None]) -> _Self: ...
867
+
868
+ @overload
869
+ def get(self, obj: Any) -> _T: ...
870
+
871
+ def get(
872
+ self, obj: Any
873
+ ) -> Union[Optional[_T], AssociationProxyInstance[_T]]:
874
+ if obj is None:
875
+ return self
876
+
877
+ proxy: _T
878
+
879
+ if self.scalar:
880
+ target = getattr(obj, self.target_collection)
881
+ return self._scalar_get(target)
882
+ else:
883
+ try:
884
+ # If the owning instance is reborn (orm session resurrect,
885
+ # etc.), refresh the proxy cache.
886
+ creator_id, self_id, proxy = cast(
887
+ "Tuple[int, int, _T]", getattr(obj, self.key)
888
+ )
889
+ except AttributeError:
890
+ pass
891
+ else:
892
+ if id(obj) == creator_id and id(self) == self_id:
893
+ assert self.collection_class is not None
894
+ return proxy
895
+
896
+ self.collection_class, proxy = self._new(
897
+ _lazy_collection(obj, self.target_collection)
898
+ )
899
+ setattr(obj, self.key, (id(obj), id(self), proxy))
900
+ return proxy
901
+
902
+ def set(self, obj: Any, values: _T) -> None:
903
+ if self.scalar:
904
+ creator = cast(
905
+ "_PlainCreatorProtocol[_T]",
906
+ (
907
+ self.parent.creator
908
+ if self.parent.creator
909
+ else self.target_class
910
+ ),
911
+ )
912
+ target = getattr(obj, self.target_collection)
913
+ if target is None:
914
+ if (
915
+ values is None
916
+ and not self.parent.create_on_none_assignment
917
+ ):
918
+ return
919
+ setattr(obj, self.target_collection, creator(values))
920
+ else:
921
+ self._scalar_set(target, values)
922
+ if values is None and self.parent.cascade_scalar_deletes:
923
+ setattr(obj, self.target_collection, None)
924
+ else:
925
+ proxy = self.get(obj)
926
+ assert self.collection_class is not None
927
+ if proxy is not values:
928
+ proxy._bulk_replace(self, values)
929
+
930
+ def delete(self, obj: Any) -> None:
931
+ if self.owning_class is None:
932
+ self._calc_owner(obj, None)
933
+
934
+ if self.scalar:
935
+ target = getattr(obj, self.target_collection)
936
+ if target is not None:
937
+ delattr(target, self.value_attr)
938
+ delattr(obj, self.target_collection)
939
+
940
+ def _new(
941
+ self, lazy_collection: _LazyCollectionProtocol[_T]
942
+ ) -> Tuple[Type[Any], _T]:
943
+ creator = (
944
+ self.parent.creator
945
+ if self.parent.creator is not None
946
+ else cast("_CreatorProtocol", self.target_class)
947
+ )
948
+ collection_class = util.duck_type_collection(lazy_collection())
949
+
950
+ if collection_class is None:
951
+ raise exc.InvalidRequestError(
952
+ f"lazy collection factory did not return a "
953
+ f"valid collection type, got {collection_class}"
954
+ )
955
+ if self.parent.proxy_factory:
956
+ return (
957
+ collection_class,
958
+ self.parent.proxy_factory(
959
+ lazy_collection, creator, self.value_attr, self
960
+ ),
961
+ )
962
+
963
+ if self.parent.getset_factory:
964
+ getter, setter = self.parent.getset_factory(collection_class, self)
965
+ else:
966
+ getter, setter = self.parent._default_getset(collection_class)
967
+
968
+ if collection_class is list:
969
+ return (
970
+ collection_class,
971
+ cast(
972
+ _T,
973
+ _AssociationList(
974
+ lazy_collection, creator, getter, setter, self
975
+ ),
976
+ ),
977
+ )
978
+ elif collection_class is dict:
979
+ return (
980
+ collection_class,
981
+ cast(
982
+ _T,
983
+ _AssociationDict(
984
+ lazy_collection, creator, getter, setter, self
985
+ ),
986
+ ),
987
+ )
988
+ elif collection_class is set:
989
+ return (
990
+ collection_class,
991
+ cast(
992
+ _T,
993
+ _AssociationSet(
994
+ lazy_collection, creator, getter, setter, self
995
+ ),
996
+ ),
997
+ )
998
+ else:
999
+ raise exc.ArgumentError(
1000
+ "could not guess which interface to use for "
1001
+ 'collection_class "%s" backing "%s"; specify a '
1002
+ "proxy_factory and proxy_bulk_set manually"
1003
+ % (self.collection_class, self.target_collection)
1004
+ )
1005
+
1006
+ def _set(
1007
+ self, proxy: _AssociationCollection[Any], values: Iterable[Any]
1008
+ ) -> None:
1009
+ if self.parent.proxy_bulk_set:
1010
+ self.parent.proxy_bulk_set(proxy, values)
1011
+ elif self.collection_class is list:
1012
+ cast("_AssociationList[Any]", proxy).extend(values)
1013
+ elif self.collection_class is dict:
1014
+ cast("_AssociationDict[Any, Any]", proxy).update(values)
1015
+ elif self.collection_class is set:
1016
+ cast("_AssociationSet[Any]", proxy).update(values)
1017
+ else:
1018
+ raise exc.ArgumentError(
1019
+ "no proxy_bulk_set supplied for custom "
1020
+ "collection_class implementation"
1021
+ )
1022
+
1023
+ def _inflate(self, proxy: _AssociationCollection[Any]) -> None:
1024
+ creator = (
1025
+ self.parent.creator
1026
+ and self.parent.creator
1027
+ or cast(_CreatorProtocol, self.target_class)
1028
+ )
1029
+
1030
+ if self.parent.getset_factory:
1031
+ getter, setter = self.parent.getset_factory(
1032
+ self.collection_class, self
1033
+ )
1034
+ else:
1035
+ getter, setter = self.parent._default_getset(self.collection_class)
1036
+
1037
+ proxy.creator = creator
1038
+ proxy.getter = getter
1039
+ proxy.setter = setter
1040
+
1041
+ def _criterion_exists(
1042
+ self,
1043
+ criterion: Optional[_ColumnExpressionArgument[bool]] = None,
1044
+ **kwargs: Any,
1045
+ ) -> ColumnElement[bool]:
1046
+ is_has = kwargs.pop("is_has", None)
1047
+
1048
+ target_assoc = self._unwrap_target_assoc_proxy
1049
+ if target_assoc is not None:
1050
+ inner = target_assoc._criterion_exists(
1051
+ criterion=criterion, **kwargs
1052
+ )
1053
+ return self._comparator._criterion_exists(inner)
1054
+
1055
+ if self._target_is_object:
1056
+ attr = getattr(self.target_class, self.value_attr)
1057
+ value_expr = attr.comparator._criterion_exists(criterion, **kwargs)
1058
+ else:
1059
+ if kwargs:
1060
+ raise exc.ArgumentError(
1061
+ "Can't apply keyword arguments to column-targeted "
1062
+ "association proxy; use =="
1063
+ )
1064
+ elif is_has and criterion is not None:
1065
+ raise exc.ArgumentError(
1066
+ "Non-empty has() not allowed for "
1067
+ "column-targeted association proxy; use =="
1068
+ )
1069
+
1070
+ value_expr = criterion
1071
+
1072
+ return self._comparator._criterion_exists(value_expr)
1073
+
1074
+ def any(
1075
+ self,
1076
+ criterion: Optional[_ColumnExpressionArgument[bool]] = None,
1077
+ **kwargs: Any,
1078
+ ) -> ColumnElement[bool]:
1079
+ """Produce a proxied 'any' expression using EXISTS.
1080
+
1081
+ This expression will be a composed product
1082
+ using the :meth:`.Relationship.Comparator.any`
1083
+ and/or :meth:`.Relationship.Comparator.has`
1084
+ operators of the underlying proxied attributes.
1085
+
1086
+ """
1087
+ if self._unwrap_target_assoc_proxy is None and (
1088
+ self.scalar
1089
+ and (not self._target_is_object or self._value_is_scalar)
1090
+ ):
1091
+ raise exc.InvalidRequestError(
1092
+ "'any()' not implemented for scalar attributes. Use has()."
1093
+ )
1094
+ return self._criterion_exists(
1095
+ criterion=criterion, is_has=False, **kwargs
1096
+ )
1097
+
1098
+ def has(
1099
+ self,
1100
+ criterion: Optional[_ColumnExpressionArgument[bool]] = None,
1101
+ **kwargs: Any,
1102
+ ) -> ColumnElement[bool]:
1103
+ """Produce a proxied 'has' expression using EXISTS.
1104
+
1105
+ This expression will be a composed product
1106
+ using the :meth:`.Relationship.Comparator.any`
1107
+ and/or :meth:`.Relationship.Comparator.has`
1108
+ operators of the underlying proxied attributes.
1109
+
1110
+ """
1111
+ if self._unwrap_target_assoc_proxy is None and (
1112
+ not self.scalar
1113
+ or (self._target_is_object and not self._value_is_scalar)
1114
+ ):
1115
+ raise exc.InvalidRequestError(
1116
+ "'has()' not implemented for collections. Use any()."
1117
+ )
1118
+ return self._criterion_exists(
1119
+ criterion=criterion, is_has=True, **kwargs
1120
+ )
1121
+
1122
+ def __repr__(self) -> str:
1123
+ return "%s(%r)" % (self.__class__.__name__, self.parent)
1124
+
1125
+
1126
+ class AmbiguousAssociationProxyInstance(AssociationProxyInstance[_T]):
1127
+ """an :class:`.AssociationProxyInstance` where we cannot determine
1128
+ the type of target object.
1129
+ """
1130
+
1131
+ _is_canonical = False
1132
+
1133
+ def _ambiguous(self) -> NoReturn:
1134
+ raise AttributeError(
1135
+ "Association proxy %s.%s refers to an attribute '%s' that is not "
1136
+ "directly mapped on class %s; therefore this operation cannot "
1137
+ "proceed since we don't know what type of object is referred "
1138
+ "towards"
1139
+ % (
1140
+ self.owning_class.__name__,
1141
+ self.target_collection,
1142
+ self.value_attr,
1143
+ self.target_class,
1144
+ )
1145
+ )
1146
+
1147
+ def get(self, obj: Any) -> Any:
1148
+ if obj is None:
1149
+ return self
1150
+ else:
1151
+ return super().get(obj)
1152
+
1153
+ def __eq__(self, obj: object) -> NoReturn:
1154
+ self._ambiguous()
1155
+
1156
+ def __ne__(self, obj: object) -> NoReturn:
1157
+ self._ambiguous()
1158
+
1159
+ def any(
1160
+ self,
1161
+ criterion: Optional[_ColumnExpressionArgument[bool]] = None,
1162
+ **kwargs: Any,
1163
+ ) -> NoReturn:
1164
+ self._ambiguous()
1165
+
1166
+ def has(
1167
+ self,
1168
+ criterion: Optional[_ColumnExpressionArgument[bool]] = None,
1169
+ **kwargs: Any,
1170
+ ) -> NoReturn:
1171
+ self._ambiguous()
1172
+
1173
+ @util.memoized_property
1174
+ def _lookup_cache(self) -> Dict[Type[Any], AssociationProxyInstance[_T]]:
1175
+ # mapping of <subclass>->AssociationProxyInstance.
1176
+ # e.g. proxy is A-> A.b -> B -> B.b_attr, but B.b_attr doesn't exist;
1177
+ # only B1(B) and B2(B) have "b_attr", keys in here would be B1, B2
1178
+ return {}
1179
+
1180
+ def _non_canonical_get_for_object(
1181
+ self, parent_instance: Any
1182
+ ) -> AssociationProxyInstance[_T]:
1183
+ if parent_instance is not None:
1184
+ actual_obj = getattr(parent_instance, self.target_collection)
1185
+ if actual_obj is not None:
1186
+ try:
1187
+ insp = inspect(actual_obj)
1188
+ except exc.NoInspectionAvailable:
1189
+ pass
1190
+ else:
1191
+ mapper = insp.mapper
1192
+ instance_class = mapper.class_
1193
+ if instance_class not in self._lookup_cache:
1194
+ self._populate_cache(instance_class, mapper)
1195
+
1196
+ try:
1197
+ return self._lookup_cache[instance_class]
1198
+ except KeyError:
1199
+ pass
1200
+
1201
+ # no object or ambiguous object given, so return "self", which
1202
+ # is a proxy with generally only instance-level functionality
1203
+ return self
1204
+
1205
+ def _populate_cache(
1206
+ self, instance_class: Any, mapper: Mapper[Any]
1207
+ ) -> None:
1208
+ prop = orm.class_mapper(self.owning_class).get_property(
1209
+ self.target_collection
1210
+ )
1211
+
1212
+ if mapper.isa(prop.mapper):
1213
+ target_class = instance_class
1214
+ try:
1215
+ target_assoc = self._cls_unwrap_target_assoc_proxy(
1216
+ target_class, self.value_attr
1217
+ )
1218
+ except AttributeError:
1219
+ pass
1220
+ else:
1221
+ self._lookup_cache[instance_class] = self._construct_for_assoc(
1222
+ cast("AssociationProxyInstance[_T]", target_assoc),
1223
+ self.parent,
1224
+ self.owning_class,
1225
+ target_class,
1226
+ self.value_attr,
1227
+ )
1228
+
1229
+
1230
+ class ObjectAssociationProxyInstance(AssociationProxyInstance[_T]):
1231
+ """an :class:`.AssociationProxyInstance` that has an object as a target."""
1232
+
1233
+ _target_is_object: bool = True
1234
+ _is_canonical = True
1235
+
1236
+ def adapt_to_entity(
1237
+ self, aliased_insp: AliasedInsp[Any]
1238
+ ) -> AliasedAssociationProxyInstance[_T]:
1239
+ return AliasedAssociationProxyInstance(self, aliased_insp)
1240
+
1241
+ def contains(self, other: Any, **kw: Any) -> ColumnElement[bool]:
1242
+ """Produce a proxied 'contains' expression using EXISTS.
1243
+
1244
+ This expression will be a composed product
1245
+ using the :meth:`.Relationship.Comparator.any`,
1246
+ :meth:`.Relationship.Comparator.has`,
1247
+ and/or :meth:`.Relationship.Comparator.contains`
1248
+ operators of the underlying proxied attributes.
1249
+ """
1250
+
1251
+ target_assoc = self._unwrap_target_assoc_proxy
1252
+ if target_assoc is not None:
1253
+ return self._comparator._criterion_exists(
1254
+ target_assoc.contains(other)
1255
+ if not target_assoc.scalar
1256
+ else target_assoc == other
1257
+ )
1258
+ elif (
1259
+ self._target_is_object
1260
+ and self.scalar
1261
+ and not self._value_is_scalar
1262
+ ):
1263
+ return self._comparator.has(
1264
+ getattr(self.target_class, self.value_attr).contains(other)
1265
+ )
1266
+ elif self._target_is_object and self.scalar and self._value_is_scalar:
1267
+ raise exc.InvalidRequestError(
1268
+ "contains() doesn't apply to a scalar object endpoint; use =="
1269
+ )
1270
+ else:
1271
+ return self._comparator._criterion_exists(
1272
+ **{self.value_attr: other}
1273
+ )
1274
+
1275
+ def __eq__(self, obj: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
1276
+ # note the has() here will fail for collections; eq_()
1277
+ # is only allowed with a scalar.
1278
+ if obj is None:
1279
+ return or_(
1280
+ self._comparator.has(**{self.value_attr: obj}),
1281
+ self._comparator == None,
1282
+ )
1283
+ else:
1284
+ return self._comparator.has(**{self.value_attr: obj})
1285
+
1286
+ def __ne__(self, obj: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
1287
+ # note the has() here will fail for collections; eq_()
1288
+ # is only allowed with a scalar.
1289
+ return self._comparator.has(
1290
+ getattr(self.target_class, self.value_attr) != obj
1291
+ )
1292
+
1293
+
1294
+ class AliasedAssociationProxyInstance(ObjectAssociationProxyInstance[_T]):
1295
+ def __init__(
1296
+ self,
1297
+ parent_instance: ObjectAssociationProxyInstance[_T],
1298
+ aliased_insp: AliasedInsp[Any],
1299
+ ) -> None:
1300
+ self.parent = parent_instance.parent
1301
+ self.owning_class = parent_instance.owning_class
1302
+ self.aliased_insp = aliased_insp
1303
+ self.target_collection = parent_instance.target_collection
1304
+ self.collection_class = None
1305
+ self.target_class = parent_instance.target_class
1306
+ self.value_attr = parent_instance.value_attr
1307
+
1308
+ @property
1309
+ def _comparator(self) -> PropComparator[Any]:
1310
+ return getattr( # type: ignore[no-any-return]
1311
+ self.aliased_insp.entity, self.target_collection
1312
+ ).comparator
1313
+
1314
+ @property
1315
+ def local_attr(self) -> SQLORMOperations[Any]:
1316
+ """The 'local' class attribute referenced by this
1317
+ :class:`.AssociationProxyInstance`.
1318
+
1319
+ .. seealso::
1320
+
1321
+ :attr:`.AssociationProxyInstance.attr`
1322
+
1323
+ :attr:`.AssociationProxyInstance.remote_attr`
1324
+
1325
+ """
1326
+ return cast(
1327
+ "SQLORMOperations[Any]",
1328
+ getattr(self.aliased_insp.entity, self.target_collection),
1329
+ )
1330
+
1331
+
1332
+ class ColumnAssociationProxyInstance(AssociationProxyInstance[_T]):
1333
+ """an :class:`.AssociationProxyInstance` that has a database column as a
1334
+ target.
1335
+ """
1336
+
1337
+ _target_is_object: bool = False
1338
+ _is_canonical = True
1339
+
1340
+ def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
1341
+ # special case "is None" to check for no related row as well
1342
+ expr = self._criterion_exists(
1343
+ self.remote_attr.operate(operators.eq, other)
1344
+ )
1345
+ if other is None:
1346
+ return or_(expr, self._comparator == None)
1347
+ else:
1348
+ return expr
1349
+
1350
+ def operate(
1351
+ self, op: operators.OperatorType, *other: Any, **kwargs: Any
1352
+ ) -> ColumnElement[Any]:
1353
+ return self._criterion_exists(
1354
+ self.remote_attr.operate(op, *other, **kwargs)
1355
+ )
1356
+
1357
+
1358
+ class _lazy_collection(_LazyCollectionProtocol[_T]):
1359
+ def __init__(self, obj: Any, target: str):
1360
+ self.parent = obj
1361
+ self.target = target
1362
+
1363
+ def __call__(
1364
+ self,
1365
+ ) -> Union[MutableSet[_T], MutableMapping[Any, _T], MutableSequence[_T]]:
1366
+ return getattr(self.parent, self.target) # type: ignore[no-any-return]
1367
+
1368
+ def __getstate__(self) -> Any:
1369
+ return {"obj": self.parent, "target": self.target}
1370
+
1371
+ def __setstate__(self, state: Any) -> None:
1372
+ self.parent = state["obj"]
1373
+ self.target = state["target"]
1374
+
1375
+
1376
+ _IT = TypeVar("_IT", bound="Any")
1377
+ """instance type - this is the type of object inside a collection.
1378
+
1379
+ this is not the same as the _T of AssociationProxy and
1380
+ AssociationProxyInstance itself, which will often refer to the
1381
+ collection[_IT] type.
1382
+
1383
+ """
1384
+
1385
+
1386
+ class _AssociationCollection(Generic[_IT]):
1387
+ getter: _GetterProtocol[_IT]
1388
+ """A function. Given an associated object, return the 'value'."""
1389
+
1390
+ creator: _CreatorProtocol
1391
+ """
1392
+ A function that creates new target entities. Given one parameter:
1393
+ value. This assertion is assumed::
1394
+
1395
+ obj = creator(somevalue)
1396
+ assert getter(obj) == somevalue
1397
+ """
1398
+
1399
+ parent: AssociationProxyInstance[_IT]
1400
+ setter: _SetterProtocol
1401
+ """A function. Given an associated object and a value, store that
1402
+ value on the object.
1403
+ """
1404
+
1405
+ lazy_collection: _LazyCollectionProtocol[_IT]
1406
+ """A callable returning a list-based collection of entities (usually an
1407
+ object attribute managed by a SQLAlchemy relationship())"""
1408
+
1409
+ def __init__(
1410
+ self,
1411
+ lazy_collection: _LazyCollectionProtocol[_IT],
1412
+ creator: _CreatorProtocol,
1413
+ getter: _GetterProtocol[_IT],
1414
+ setter: _SetterProtocol,
1415
+ parent: AssociationProxyInstance[_IT],
1416
+ ):
1417
+ """Constructs an _AssociationCollection.
1418
+
1419
+ This will always be a subclass of either _AssociationList,
1420
+ _AssociationSet, or _AssociationDict.
1421
+
1422
+ """
1423
+ self.lazy_collection = lazy_collection
1424
+ self.creator = creator
1425
+ self.getter = getter
1426
+ self.setter = setter
1427
+ self.parent = parent
1428
+
1429
+ if typing.TYPE_CHECKING:
1430
+ col: Collection[_IT]
1431
+ else:
1432
+ col = property(lambda self: self.lazy_collection())
1433
+
1434
+ def __len__(self) -> int:
1435
+ return len(self.col)
1436
+
1437
+ def __bool__(self) -> bool:
1438
+ return bool(self.col)
1439
+
1440
+ def __getstate__(self) -> Any:
1441
+ return {"parent": self.parent, "lazy_collection": self.lazy_collection}
1442
+
1443
+ def __setstate__(self, state: Any) -> None:
1444
+ self.parent = state["parent"]
1445
+ self.lazy_collection = state["lazy_collection"]
1446
+ self.parent._inflate(self)
1447
+
1448
+ def clear(self) -> None:
1449
+ raise NotImplementedError()
1450
+
1451
+
1452
+ class _AssociationSingleItem(_AssociationCollection[_T]):
1453
+ setter: _PlainSetterProtocol[_T]
1454
+ creator: _PlainCreatorProtocol[_T]
1455
+
1456
+ def _create(self, value: _T) -> Any:
1457
+ return self.creator(value)
1458
+
1459
+ def _get(self, object_: Any) -> _T:
1460
+ return self.getter(object_)
1461
+
1462
+ def _bulk_replace(
1463
+ self, assoc_proxy: AssociationProxyInstance[Any], values: Iterable[_IT]
1464
+ ) -> None:
1465
+ self.clear()
1466
+ assoc_proxy._set(self, values)
1467
+
1468
+
1469
+ class _AssociationList(_AssociationSingleItem[_T], MutableSequence[_T]):
1470
+ """Generic, converting, list-to-list proxy."""
1471
+
1472
+ col: MutableSequence[_T]
1473
+
1474
+ def _set(self, object_: Any, value: _T) -> None:
1475
+ self.setter(object_, value)
1476
+
1477
+ @overload
1478
+ def __getitem__(self, index: int) -> _T: ...
1479
+
1480
+ @overload
1481
+ def __getitem__(self, index: slice) -> MutableSequence[_T]: ...
1482
+
1483
+ def __getitem__(
1484
+ self, index: Union[int, slice]
1485
+ ) -> Union[_T, MutableSequence[_T]]:
1486
+ if not isinstance(index, slice):
1487
+ return self._get(self.col[index])
1488
+ else:
1489
+ return [self._get(member) for member in self.col[index]]
1490
+
1491
+ @overload
1492
+ def __setitem__(self, index: int, value: _T) -> None: ...
1493
+
1494
+ @overload
1495
+ def __setitem__(self, index: slice, value: Iterable[_T]) -> None: ...
1496
+
1497
+ def __setitem__(
1498
+ self, index: Union[int, slice], value: Union[_T, Iterable[_T]]
1499
+ ) -> None:
1500
+ if not isinstance(index, slice):
1501
+ self._set(self.col[index], cast("_T", value))
1502
+ else:
1503
+ if index.stop is None:
1504
+ stop = len(self)
1505
+ elif index.stop < 0:
1506
+ stop = len(self) + index.stop
1507
+ else:
1508
+ stop = index.stop
1509
+ step = index.step or 1
1510
+
1511
+ start = index.start or 0
1512
+ rng = list(range(index.start or 0, stop, step))
1513
+
1514
+ sized_value = list(value)
1515
+
1516
+ if step == 1:
1517
+ for i in rng:
1518
+ del self[start]
1519
+ i = start
1520
+ for item in sized_value:
1521
+ self.insert(i, item)
1522
+ i += 1
1523
+ else:
1524
+ if len(sized_value) != len(rng):
1525
+ raise ValueError(
1526
+ "attempt to assign sequence of size %s to "
1527
+ "extended slice of size %s"
1528
+ % (len(sized_value), len(rng))
1529
+ )
1530
+ for i, item in zip(rng, value):
1531
+ self._set(self.col[i], item)
1532
+
1533
+ @overload
1534
+ def __delitem__(self, index: int) -> None: ...
1535
+
1536
+ @overload
1537
+ def __delitem__(self, index: slice) -> None: ...
1538
+
1539
+ def __delitem__(self, index: Union[slice, int]) -> None:
1540
+ del self.col[index]
1541
+
1542
+ def __contains__(self, value: object) -> bool:
1543
+ for member in self.col:
1544
+ # testlib.pragma exempt:__eq__
1545
+ if self._get(member) == value:
1546
+ return True
1547
+ return False
1548
+
1549
+ def __iter__(self) -> Iterator[_T]:
1550
+ """Iterate over proxied values.
1551
+
1552
+ For the actual domain objects, iterate over .col instead or
1553
+ just use the underlying collection directly from its property
1554
+ on the parent.
1555
+ """
1556
+
1557
+ for member in self.col:
1558
+ yield self._get(member)
1559
+ return
1560
+
1561
+ def append(self, value: _T) -> None:
1562
+ col = self.col
1563
+ item = self._create(value)
1564
+ col.append(item)
1565
+
1566
+ def count(self, value: Any) -> int:
1567
+ count = 0
1568
+ for v in self:
1569
+ if v == value:
1570
+ count += 1
1571
+ return count
1572
+
1573
+ def extend(self, values: Iterable[_T]) -> None:
1574
+ for v in values:
1575
+ self.append(v)
1576
+
1577
+ def insert(self, index: int, value: _T) -> None:
1578
+ self.col[index:index] = [self._create(value)]
1579
+
1580
+ def pop(self, index: int = -1) -> _T:
1581
+ return self.getter(self.col.pop(index))
1582
+
1583
+ def remove(self, value: _T) -> None:
1584
+ for i, val in enumerate(self):
1585
+ if val == value:
1586
+ del self.col[i]
1587
+ return
1588
+ raise ValueError("value not in list")
1589
+
1590
+ def reverse(self) -> NoReturn:
1591
+ """Not supported, use reversed(mylist)"""
1592
+
1593
+ raise NotImplementedError()
1594
+
1595
+ def sort(self) -> NoReturn:
1596
+ """Not supported, use sorted(mylist)"""
1597
+
1598
+ raise NotImplementedError()
1599
+
1600
+ def clear(self) -> None:
1601
+ del self.col[0 : len(self.col)]
1602
+
1603
+ def __eq__(self, other: object) -> bool:
1604
+ return list(self) == other
1605
+
1606
+ def __ne__(self, other: object) -> bool:
1607
+ return list(self) != other
1608
+
1609
+ def __lt__(self, other: List[_T]) -> bool:
1610
+ return list(self) < other
1611
+
1612
+ def __le__(self, other: List[_T]) -> bool:
1613
+ return list(self) <= other
1614
+
1615
+ def __gt__(self, other: List[_T]) -> bool:
1616
+ return list(self) > other
1617
+
1618
+ def __ge__(self, other: List[_T]) -> bool:
1619
+ return list(self) >= other
1620
+
1621
+ def __add__(self, other: List[_T]) -> List[_T]:
1622
+ try:
1623
+ other = list(other)
1624
+ except TypeError:
1625
+ return NotImplemented
1626
+ return list(self) + other
1627
+
1628
+ def __radd__(self, other: List[_T]) -> List[_T]:
1629
+ try:
1630
+ other = list(other)
1631
+ except TypeError:
1632
+ return NotImplemented
1633
+ return other + list(self)
1634
+
1635
+ def __mul__(self, n: SupportsIndex) -> List[_T]:
1636
+ if not isinstance(n, int):
1637
+ return NotImplemented
1638
+ return list(self) * n
1639
+
1640
+ def __rmul__(self, n: SupportsIndex) -> List[_T]:
1641
+ if not isinstance(n, int):
1642
+ return NotImplemented
1643
+ return n * list(self)
1644
+
1645
+ def __iadd__(self, iterable: Iterable[_T]) -> Self:
1646
+ self.extend(iterable)
1647
+ return self
1648
+
1649
+ def __imul__(self, n: SupportsIndex) -> Self:
1650
+ # unlike a regular list *=, proxied __imul__ will generate unique
1651
+ # backing objects for each copy. *= on proxied lists is a bit of
1652
+ # a stretch anyhow, and this interpretation of the __imul__ contract
1653
+ # is more plausibly useful than copying the backing objects.
1654
+ if not isinstance(n, int):
1655
+ raise NotImplementedError()
1656
+ if n == 0:
1657
+ self.clear()
1658
+ elif n > 1:
1659
+ self.extend(list(self) * (n - 1))
1660
+ return self
1661
+
1662
+ if typing.TYPE_CHECKING:
1663
+ # TODO: no idea how to do this without separate "stub"
1664
+ def index(
1665
+ self, value: Any, start: int = ..., stop: int = ...
1666
+ ) -> int: ...
1667
+
1668
+ else:
1669
+
1670
+ def index(self, value: Any, *arg) -> int:
1671
+ ls = list(self)
1672
+ return ls.index(value, *arg)
1673
+
1674
+ def copy(self) -> List[_T]:
1675
+ return list(self)
1676
+
1677
+ def __repr__(self) -> str:
1678
+ return repr(list(self))
1679
+
1680
+ def __hash__(self) -> NoReturn:
1681
+ raise TypeError("%s objects are unhashable" % type(self).__name__)
1682
+
1683
+ if not typing.TYPE_CHECKING:
1684
+ for func_name, func in list(locals().items()):
1685
+ if (
1686
+ callable(func)
1687
+ and func.__name__ == func_name
1688
+ and not func.__doc__
1689
+ and hasattr(list, func_name)
1690
+ ):
1691
+ func.__doc__ = getattr(list, func_name).__doc__
1692
+ del func_name, func
1693
+
1694
+
1695
+ class _AssociationDict(_AssociationCollection[_VT], MutableMapping[_KT, _VT]):
1696
+ """Generic, converting, dict-to-dict proxy."""
1697
+
1698
+ setter: _DictSetterProtocol[_VT]
1699
+ creator: _KeyCreatorProtocol[_VT]
1700
+ col: MutableMapping[_KT, Optional[_VT]]
1701
+
1702
+ def _create(self, key: _KT, value: Optional[_VT]) -> Any:
1703
+ return self.creator(key, value)
1704
+
1705
+ def _get(self, object_: Any) -> _VT:
1706
+ return self.getter(object_)
1707
+
1708
+ def _set(self, object_: Any, key: _KT, value: _VT) -> None:
1709
+ return self.setter(object_, key, value)
1710
+
1711
+ def __getitem__(self, key: _KT) -> _VT:
1712
+ return self._get(self.col[key])
1713
+
1714
+ def __setitem__(self, key: _KT, value: _VT) -> None:
1715
+ if key in self.col:
1716
+ self._set(self.col[key], key, value)
1717
+ else:
1718
+ self.col[key] = self._create(key, value)
1719
+
1720
+ def __delitem__(self, key: _KT) -> None:
1721
+ del self.col[key]
1722
+
1723
+ def __contains__(self, key: object) -> bool:
1724
+ return key in self.col
1725
+
1726
+ def __iter__(self) -> Iterator[_KT]:
1727
+ return iter(self.col.keys())
1728
+
1729
+ def clear(self) -> None:
1730
+ self.col.clear()
1731
+
1732
+ def __eq__(self, other: object) -> bool:
1733
+ return dict(self) == other
1734
+
1735
+ def __ne__(self, other: object) -> bool:
1736
+ return dict(self) != other
1737
+
1738
+ def __repr__(self) -> str:
1739
+ return repr(dict(self))
1740
+
1741
+ @overload
1742
+ def get(self, __key: _KT, /) -> Optional[_VT]: ...
1743
+
1744
+ @overload
1745
+ def get(
1746
+ self, __key: _KT, /, default: Union[_VT, _T]
1747
+ ) -> Union[_VT, _T]: ...
1748
+
1749
+ def get(
1750
+ self, __key: _KT, /, default: Optional[Union[_VT, _T]] = None
1751
+ ) -> Union[_VT, _T, None]:
1752
+ try:
1753
+ return self[__key]
1754
+ except KeyError:
1755
+ return default
1756
+
1757
+ def setdefault(self, key: _KT, default: Optional[_VT] = None) -> _VT:
1758
+ # TODO: again, no idea how to create an actual MutableMapping.
1759
+ # default must allow None, return type can't include None,
1760
+ # the stub explicitly allows for default of None with a cryptic message
1761
+ # "This overload should be allowed only if the value type is
1762
+ # compatible with None.".
1763
+ if key not in self.col:
1764
+ self.col[key] = self._create(key, default)
1765
+ return default # type: ignore[return-value]
1766
+ else:
1767
+ return self[key]
1768
+
1769
+ def keys(self) -> KeysView[_KT]:
1770
+ return self.col.keys()
1771
+
1772
+ def items(self) -> ItemsView[_KT, _VT]:
1773
+ return ItemsView(self)
1774
+
1775
+ def values(self) -> ValuesView[_VT]:
1776
+ return ValuesView(self)
1777
+
1778
+ @overload
1779
+ def pop(self, __key: _KT, /) -> _VT: ...
1780
+
1781
+ @overload
1782
+ def pop(
1783
+ self, __key: _KT, /, default: Union[_VT, _T] = ...
1784
+ ) -> Union[_VT, _T]: ...
1785
+
1786
+ def pop(self, __key: _KT, /, *arg: Any, **kw: Any) -> Union[_VT, _T]:
1787
+ member = self.col.pop(__key, *arg, **kw)
1788
+ return self._get(member)
1789
+
1790
+ def popitem(self) -> Tuple[_KT, _VT]:
1791
+ item = self.col.popitem()
1792
+ return (item[0], self._get(item[1]))
1793
+
1794
+ @overload
1795
+ def update(
1796
+ self, __m: SupportsKeysAndGetItem[_KT, _VT], **kwargs: _VT
1797
+ ) -> None: ...
1798
+
1799
+ @overload
1800
+ def update(
1801
+ self, __m: Iterable[tuple[_KT, _VT]], **kwargs: _VT
1802
+ ) -> None: ...
1803
+
1804
+ @overload
1805
+ def update(self, **kwargs: _VT) -> None: ...
1806
+
1807
+ def update(self, *a: Any, **kw: Any) -> None:
1808
+ up: Dict[_KT, _VT] = {}
1809
+ up.update(*a, **kw)
1810
+
1811
+ for key, value in up.items():
1812
+ self[key] = value
1813
+
1814
+ def _bulk_replace(
1815
+ self,
1816
+ assoc_proxy: AssociationProxyInstance[Any],
1817
+ values: Mapping[_KT, _VT],
1818
+ ) -> None:
1819
+ existing = set(self)
1820
+ constants = existing.intersection(values or ())
1821
+ additions = set(values or ()).difference(constants)
1822
+ removals = existing.difference(constants)
1823
+
1824
+ for key, member in values.items() or ():
1825
+ if key in additions:
1826
+ self[key] = member
1827
+ elif key in constants:
1828
+ self[key] = member
1829
+
1830
+ for key in removals:
1831
+ del self[key]
1832
+
1833
+ def copy(self) -> Dict[_KT, _VT]:
1834
+ return dict(self.items())
1835
+
1836
+ def __hash__(self) -> NoReturn:
1837
+ raise TypeError("%s objects are unhashable" % type(self).__name__)
1838
+
1839
+ if not typing.TYPE_CHECKING:
1840
+ for func_name, func in list(locals().items()):
1841
+ if (
1842
+ callable(func)
1843
+ and func.__name__ == func_name
1844
+ and not func.__doc__
1845
+ and hasattr(dict, func_name)
1846
+ ):
1847
+ func.__doc__ = getattr(dict, func_name).__doc__
1848
+ del func_name, func
1849
+
1850
+
1851
+ class _AssociationSet(_AssociationSingleItem[_T], MutableSet[_T]):
1852
+ """Generic, converting, set-to-set proxy."""
1853
+
1854
+ col: MutableSet[_T]
1855
+
1856
+ def __len__(self) -> int:
1857
+ return len(self.col)
1858
+
1859
+ def __bool__(self) -> bool:
1860
+ if self.col:
1861
+ return True
1862
+ else:
1863
+ return False
1864
+
1865
+ def __contains__(self, __o: object) -> bool:
1866
+ for member in self.col:
1867
+ if self._get(member) == __o:
1868
+ return True
1869
+ return False
1870
+
1871
+ def __iter__(self) -> Iterator[_T]:
1872
+ """Iterate over proxied values.
1873
+
1874
+ For the actual domain objects, iterate over .col instead or just use
1875
+ the underlying collection directly from its property on the parent.
1876
+
1877
+ """
1878
+ for member in self.col:
1879
+ yield self._get(member)
1880
+ return
1881
+
1882
+ def add(self, __element: _T, /) -> None:
1883
+ if __element not in self:
1884
+ self.col.add(self._create(__element))
1885
+
1886
+ # for discard and remove, choosing a more expensive check strategy rather
1887
+ # than call self.creator()
1888
+ def discard(self, __element: _T, /) -> None:
1889
+ for member in self.col:
1890
+ if self._get(member) == __element:
1891
+ self.col.discard(member)
1892
+ break
1893
+
1894
+ def remove(self, __element: _T, /) -> None:
1895
+ for member in self.col:
1896
+ if self._get(member) == __element:
1897
+ self.col.discard(member)
1898
+ return
1899
+ raise KeyError(__element)
1900
+
1901
+ def pop(self) -> _T:
1902
+ if not self.col:
1903
+ raise KeyError("pop from an empty set")
1904
+ member = self.col.pop()
1905
+ return self._get(member)
1906
+
1907
+ def update(self, *s: Iterable[_T]) -> None:
1908
+ for iterable in s:
1909
+ for value in iterable:
1910
+ self.add(value)
1911
+
1912
+ def _bulk_replace(self, assoc_proxy: Any, values: Iterable[_T]) -> None:
1913
+ existing = set(self)
1914
+ constants = existing.intersection(values or ())
1915
+ additions = set(values or ()).difference(constants)
1916
+ removals = existing.difference(constants)
1917
+
1918
+ appender = self.add
1919
+ remover = self.remove
1920
+
1921
+ for member in values or ():
1922
+ if member in additions:
1923
+ appender(member)
1924
+ elif member in constants:
1925
+ appender(member)
1926
+
1927
+ for member in removals:
1928
+ remover(member)
1929
+
1930
+ def __ior__( # type: ignore[override]
1931
+ self, other: AbstractSet[_S]
1932
+ ) -> MutableSet[Union[_T, _S]]:
1933
+ if not collections._set_binops_check_strict(self, other):
1934
+ return NotImplemented
1935
+ for value in other:
1936
+ self.add(value)
1937
+ return self
1938
+
1939
+ def _set(self) -> Set[_T]:
1940
+ return set(iter(self))
1941
+
1942
+ def union(self, *s: Iterable[_S]) -> MutableSet[Union[_T, _S]]:
1943
+ return set(self).union(*s)
1944
+
1945
+ def __or__(self, __s: AbstractSet[_S]) -> MutableSet[Union[_T, _S]]:
1946
+ if not collections._set_binops_check_strict(self, __s):
1947
+ return NotImplemented
1948
+ return self.union(__s)
1949
+
1950
+ def difference(self, *s: Iterable[Any]) -> MutableSet[_T]:
1951
+ return set(self).difference(*s)
1952
+
1953
+ def __sub__(self, s: AbstractSet[Any]) -> MutableSet[_T]:
1954
+ if not collections._set_binops_check_strict(self, s):
1955
+ return NotImplemented
1956
+ return self.difference(s)
1957
+
1958
+ def difference_update(self, *s: Iterable[Any]) -> None:
1959
+ for other in s:
1960
+ for value in other:
1961
+ self.discard(value)
1962
+
1963
+ def __isub__(self, s: AbstractSet[Any]) -> Self:
1964
+ if not collections._set_binops_check_strict(self, s):
1965
+ return NotImplemented
1966
+ for value in s:
1967
+ self.discard(value)
1968
+ return self
1969
+
1970
+ def intersection(self, *s: Iterable[Any]) -> MutableSet[_T]:
1971
+ return set(self).intersection(*s)
1972
+
1973
+ def __and__(self, s: AbstractSet[Any]) -> MutableSet[_T]:
1974
+ if not collections._set_binops_check_strict(self, s):
1975
+ return NotImplemented
1976
+ return self.intersection(s)
1977
+
1978
+ def intersection_update(self, *s: Iterable[Any]) -> None:
1979
+ for other in s:
1980
+ want, have = self.intersection(other), set(self)
1981
+
1982
+ remove, add = have - want, want - have
1983
+
1984
+ for value in remove:
1985
+ self.remove(value)
1986
+ for value in add:
1987
+ self.add(value)
1988
+
1989
+ def __iand__(self, s: AbstractSet[Any]) -> Self:
1990
+ if not collections._set_binops_check_strict(self, s):
1991
+ return NotImplemented
1992
+ want = self.intersection(s)
1993
+ have: Set[_T] = set(self)
1994
+
1995
+ remove, add = have - want, want - have
1996
+
1997
+ for value in remove:
1998
+ self.remove(value)
1999
+ for value in add:
2000
+ self.add(value)
2001
+ return self
2002
+
2003
+ def symmetric_difference(self, __s: Iterable[_T]) -> MutableSet[_T]:
2004
+ return set(self).symmetric_difference(__s)
2005
+
2006
+ def __xor__(self, s: AbstractSet[_S]) -> MutableSet[Union[_T, _S]]:
2007
+ if not collections._set_binops_check_strict(self, s):
2008
+ return NotImplemented
2009
+ return self.symmetric_difference(s)
2010
+
2011
+ def symmetric_difference_update(self, other: Iterable[Any]) -> None:
2012
+ want, have = self.symmetric_difference(other), set(self)
2013
+
2014
+ remove, add = have - want, want - have
2015
+
2016
+ for value in remove:
2017
+ self.remove(value)
2018
+ for value in add:
2019
+ self.add(value)
2020
+
2021
+ def __ixor__(self, other: AbstractSet[_S]) -> MutableSet[Union[_T, _S]]: # type: ignore[override] # noqa: E501
2022
+ if not collections._set_binops_check_strict(self, other):
2023
+ return NotImplemented
2024
+
2025
+ self.symmetric_difference_update(other)
2026
+ return self
2027
+
2028
+ def issubset(self, __s: Iterable[Any]) -> bool:
2029
+ return set(self).issubset(__s)
2030
+
2031
+ def issuperset(self, __s: Iterable[Any]) -> bool:
2032
+ return set(self).issuperset(__s)
2033
+
2034
+ def clear(self) -> None:
2035
+ self.col.clear()
2036
+
2037
+ def copy(self) -> AbstractSet[_T]:
2038
+ return set(self)
2039
+
2040
+ def __eq__(self, other: object) -> bool:
2041
+ return set(self) == other
2042
+
2043
+ def __ne__(self, other: object) -> bool:
2044
+ return set(self) != other
2045
+
2046
+ def __lt__(self, other: AbstractSet[Any]) -> bool:
2047
+ return set(self) < other
2048
+
2049
+ def __le__(self, other: AbstractSet[Any]) -> bool:
2050
+ return set(self) <= other
2051
+
2052
+ def __gt__(self, other: AbstractSet[Any]) -> bool:
2053
+ return set(self) > other
2054
+
2055
+ def __ge__(self, other: AbstractSet[Any]) -> bool:
2056
+ return set(self) >= other
2057
+
2058
+ def __repr__(self) -> str:
2059
+ return repr(set(self))
2060
+
2061
+ def __hash__(self) -> NoReturn:
2062
+ raise TypeError("%s objects are unhashable" % type(self).__name__)
2063
+
2064
+ if not typing.TYPE_CHECKING:
2065
+ for func_name, func in list(locals().items()):
2066
+ if (
2067
+ callable(func)
2068
+ and func.__name__ == func_name
2069
+ and not func.__doc__
2070
+ and hasattr(set, func_name)
2071
+ ):
2072
+ func.__doc__ = getattr(set, func_name).__doc__
2073
+ del func_name, func