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,701 @@
1
+ # orm/writeonly.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
+ """Write-only collection API.
9
+
10
+ This is an alternate mapped attribute style that only supports single-item
11
+ collection mutation operations. To read the collection, a select()
12
+ object must be executed each time.
13
+
14
+ .. versionadded:: 2.0
15
+
16
+
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from typing import Any
22
+ from typing import Collection
23
+ from typing import Dict
24
+ from typing import Generic
25
+ from typing import Iterable
26
+ from typing import Iterator
27
+ from typing import List
28
+ from typing import Literal
29
+ from typing import NoReturn
30
+ from typing import Optional
31
+ from typing import overload
32
+ from typing import Tuple
33
+ from typing import Type
34
+ from typing import TYPE_CHECKING
35
+ from typing import TypeVar
36
+ from typing import Union
37
+
38
+ from sqlalchemy.sql import bindparam
39
+ from . import attributes
40
+ from . import interfaces
41
+ from . import relationships
42
+ from . import strategies
43
+ from .base import ATTR_EMPTY
44
+ from .base import DONT_SET
45
+ from .base import NEVER_SET
46
+ from .base import object_mapper
47
+ from .base import PassiveFlag
48
+ from .base import RelationshipDirection
49
+ from .. import exc
50
+ from .. import inspect
51
+ from .. import log
52
+ from .. import util
53
+ from ..sql import delete
54
+ from ..sql import insert
55
+ from ..sql import select
56
+ from ..sql import update
57
+ from ..sql.dml import Delete
58
+ from ..sql.dml import Insert
59
+ from ..sql.dml import Update
60
+
61
+ if TYPE_CHECKING:
62
+ from . import QueryableAttribute
63
+ from ._typing import _InstanceDict
64
+ from .attributes import AttributeEventToken
65
+ from .base import LoaderCallableStatus
66
+ from .collections import _AdaptedCollectionProtocol
67
+ from .collections import CollectionAdapter
68
+ from .mapper import Mapper
69
+ from .relationships import _RelationshipOrderByArg
70
+ from .state import InstanceState
71
+ from .util import AliasedClass
72
+ from ..event import _Dispatch
73
+ from ..sql.selectable import FromClause
74
+ from ..sql.selectable import Select
75
+
76
+ _T = TypeVar("_T", bound=Any)
77
+
78
+
79
+ class WriteOnlyHistory(Generic[_T]):
80
+ """Overrides AttributeHistory to receive append/remove events directly."""
81
+
82
+ unchanged_items: util.OrderedIdentitySet
83
+ added_items: util.OrderedIdentitySet
84
+ deleted_items: util.OrderedIdentitySet
85
+ _reconcile_collection: bool
86
+
87
+ def __init__(
88
+ self,
89
+ attr: _WriteOnlyAttributeImpl,
90
+ state: InstanceState[_T],
91
+ passive: PassiveFlag,
92
+ apply_to: Optional[WriteOnlyHistory[_T]] = None,
93
+ ) -> None:
94
+ if apply_to:
95
+ if passive & PassiveFlag.SQL_OK:
96
+ raise exc.InvalidRequestError(
97
+ f"Attribute {attr} can't load the existing state from the "
98
+ "database for this operation; full iteration is not "
99
+ "permitted. If this is a delete operation, configure "
100
+ f"passive_deletes=True on the {attr} relationship in "
101
+ "order to resolve this error."
102
+ )
103
+
104
+ self.unchanged_items = apply_to.unchanged_items
105
+ self.added_items = apply_to.added_items
106
+ self.deleted_items = apply_to.deleted_items
107
+ self._reconcile_collection = apply_to._reconcile_collection
108
+ else:
109
+ self.deleted_items = util.OrderedIdentitySet()
110
+ self.added_items = util.OrderedIdentitySet()
111
+ self.unchanged_items = util.OrderedIdentitySet()
112
+ self._reconcile_collection = False
113
+
114
+ @property
115
+ def added_plus_unchanged(self) -> List[_T]:
116
+ return list(self.added_items.union(self.unchanged_items))
117
+
118
+ @property
119
+ def all_items(self) -> List[_T]:
120
+ return list(
121
+ self.added_items.union(self.unchanged_items).union(
122
+ self.deleted_items
123
+ )
124
+ )
125
+
126
+ def as_history(self) -> attributes.History:
127
+ if self._reconcile_collection:
128
+ added = self.added_items.difference(self.unchanged_items)
129
+ deleted = self.deleted_items.intersection(self.unchanged_items)
130
+ unchanged = self.unchanged_items.difference(deleted)
131
+ else:
132
+ added, unchanged, deleted = (
133
+ self.added_items,
134
+ self.unchanged_items,
135
+ self.deleted_items,
136
+ )
137
+ return attributes.History(list(added), list(unchanged), list(deleted))
138
+
139
+ def indexed(self, index: Union[int, slice]) -> Union[List[_T], _T]:
140
+ return list(self.added_items)[index]
141
+
142
+ def add_added(self, value: _T) -> None:
143
+ self.added_items.add(value)
144
+
145
+ def add_removed(self, value: _T) -> None:
146
+ if value in self.added_items:
147
+ self.added_items.remove(value)
148
+ else:
149
+ self.deleted_items.add(value)
150
+
151
+
152
+ class _WriteOnlyAttributeImpl(
153
+ attributes._HasCollectionAdapter, attributes._AttributeImpl
154
+ ):
155
+ uses_objects: bool = True
156
+ default_accepts_scalar_loader: bool = False
157
+ supports_population: bool = False
158
+ _supports_dynamic_iteration: bool = False
159
+ collection: bool = False
160
+ dynamic: bool = True
161
+ order_by: _RelationshipOrderByArg = ()
162
+ collection_history_cls: Type[WriteOnlyHistory[Any]] = WriteOnlyHistory
163
+
164
+ query_class: Type[WriteOnlyCollection[Any]]
165
+
166
+ def __init__(
167
+ self,
168
+ class_: Union[Type[Any], AliasedClass[Any]],
169
+ key: str,
170
+ dispatch: _Dispatch[QueryableAttribute[Any]],
171
+ target_mapper: Mapper[_T],
172
+ order_by: _RelationshipOrderByArg,
173
+ **kw: Any,
174
+ ):
175
+ super().__init__(class_, key, None, dispatch, **kw)
176
+ self.target_mapper = target_mapper
177
+ self.query_class = WriteOnlyCollection
178
+ if order_by:
179
+ self.order_by = tuple(order_by)
180
+
181
+ def get(
182
+ self,
183
+ state: InstanceState[Any],
184
+ dict_: _InstanceDict,
185
+ passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
186
+ ) -> Union[util.OrderedIdentitySet, WriteOnlyCollection[Any]]:
187
+ if not passive & PassiveFlag.SQL_OK:
188
+ return self._get_collection_history(
189
+ state, PassiveFlag.PASSIVE_NO_INITIALIZE
190
+ ).added_items
191
+ else:
192
+ return self.query_class(self, state)
193
+
194
+ @overload
195
+ def get_collection(
196
+ self,
197
+ state: InstanceState[Any],
198
+ dict_: _InstanceDict,
199
+ user_data: Literal[None] = ...,
200
+ passive: Literal[PassiveFlag.PASSIVE_OFF] = ...,
201
+ ) -> CollectionAdapter: ...
202
+
203
+ @overload
204
+ def get_collection(
205
+ self,
206
+ state: InstanceState[Any],
207
+ dict_: _InstanceDict,
208
+ user_data: _AdaptedCollectionProtocol = ...,
209
+ passive: PassiveFlag = ...,
210
+ ) -> CollectionAdapter: ...
211
+
212
+ @overload
213
+ def get_collection(
214
+ self,
215
+ state: InstanceState[Any],
216
+ dict_: _InstanceDict,
217
+ user_data: Optional[_AdaptedCollectionProtocol] = ...,
218
+ passive: PassiveFlag = ...,
219
+ ) -> Union[
220
+ Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
221
+ ]: ...
222
+
223
+ def get_collection(
224
+ self,
225
+ state: InstanceState[Any],
226
+ dict_: _InstanceDict,
227
+ user_data: Optional[_AdaptedCollectionProtocol] = None,
228
+ passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
229
+ ) -> Union[
230
+ Literal[LoaderCallableStatus.PASSIVE_NO_RESULT], CollectionAdapter
231
+ ]:
232
+ data: Collection[Any]
233
+ if not passive & PassiveFlag.SQL_OK:
234
+ data = self._get_collection_history(state, passive).added_items
235
+ else:
236
+ history = self._get_collection_history(state, passive)
237
+ data = history.added_plus_unchanged
238
+ return _DynamicCollectionAdapter(data) # type: ignore[return-value]
239
+
240
+ @util.memoized_property
241
+ def _append_token(self) -> attributes.AttributeEventToken:
242
+ return attributes.AttributeEventToken(self, attributes.OP_APPEND)
243
+
244
+ @util.memoized_property
245
+ def _remove_token(self) -> attributes.AttributeEventToken:
246
+ return attributes.AttributeEventToken(self, attributes.OP_REMOVE)
247
+
248
+ def fire_append_event(
249
+ self,
250
+ state: InstanceState[Any],
251
+ dict_: _InstanceDict,
252
+ value: Any,
253
+ initiator: Optional[AttributeEventToken],
254
+ collection_history: Optional[WriteOnlyHistory[Any]] = None,
255
+ ) -> None:
256
+ if collection_history is None:
257
+ collection_history = self._modified_event(state, dict_)
258
+
259
+ collection_history.add_added(value)
260
+
261
+ for fn in self.dispatch.append:
262
+ value = fn(state, value, initiator or self._append_token)
263
+
264
+ if self.trackparent and value is not None:
265
+ self.sethasparent(attributes.instance_state(value), state, True)
266
+
267
+ def fire_remove_event(
268
+ self,
269
+ state: InstanceState[Any],
270
+ dict_: _InstanceDict,
271
+ value: Any,
272
+ initiator: Optional[AttributeEventToken],
273
+ collection_history: Optional[WriteOnlyHistory[Any]] = None,
274
+ ) -> None:
275
+ if collection_history is None:
276
+ collection_history = self._modified_event(state, dict_)
277
+
278
+ collection_history.add_removed(value)
279
+
280
+ if self.trackparent and value is not None:
281
+ self.sethasparent(attributes.instance_state(value), state, False)
282
+
283
+ for fn in self.dispatch.remove:
284
+ fn(state, value, initiator or self._remove_token)
285
+
286
+ def _modified_event(
287
+ self, state: InstanceState[Any], dict_: _InstanceDict
288
+ ) -> WriteOnlyHistory[Any]:
289
+ if self.key not in state.committed_state:
290
+ state.committed_state[self.key] = self.collection_history_cls(
291
+ self, state, PassiveFlag.PASSIVE_NO_FETCH
292
+ )
293
+
294
+ state._modified_event(dict_, self, NEVER_SET)
295
+
296
+ # this is a hack to allow the entities.ComparableEntity fixture
297
+ # to work
298
+ dict_[self.key] = True
299
+ return state.committed_state[self.key] # type: ignore[no-any-return]
300
+
301
+ def set(
302
+ self,
303
+ state: InstanceState[Any],
304
+ dict_: _InstanceDict,
305
+ value: Any,
306
+ initiator: Optional[AttributeEventToken] = None,
307
+ passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
308
+ check_old: Any = None,
309
+ pop: bool = False,
310
+ _adapt: bool = True,
311
+ ) -> None:
312
+ if initiator and initiator.parent_token is self.parent_token:
313
+ return
314
+
315
+ if pop and value is None:
316
+ return
317
+
318
+ if value is DONT_SET:
319
+ # dataclasses default_factory for a write only collection
320
+ # sends DONT_SET; there's no collection to initialize so
321
+ # this is a no-op
322
+ return
323
+
324
+ iterable = value
325
+ new_values = list(iterable)
326
+ if state.has_identity:
327
+ if not self._supports_dynamic_iteration:
328
+ raise exc.InvalidRequestError(
329
+ f'Collection "{self}" does not support implicit '
330
+ "iteration; collection replacement operations "
331
+ "can't be used"
332
+ )
333
+ old_collection = util.IdentitySet(
334
+ self.get(state, dict_, passive=passive)
335
+ )
336
+
337
+ collection_history = self._modified_event(state, dict_)
338
+ if not state.has_identity:
339
+ old_collection = collection_history.added_items
340
+ else:
341
+ old_collection = old_collection.union(
342
+ collection_history.added_items
343
+ )
344
+
345
+ constants = old_collection.intersection(new_values)
346
+ additions = util.IdentitySet(new_values).difference(constants)
347
+ removals = old_collection.difference(constants)
348
+
349
+ for member in new_values:
350
+ if member in additions:
351
+ self.fire_append_event(
352
+ state,
353
+ dict_,
354
+ member,
355
+ None,
356
+ collection_history=collection_history,
357
+ )
358
+
359
+ for member in removals:
360
+ self.fire_remove_event(
361
+ state,
362
+ dict_,
363
+ member,
364
+ None,
365
+ collection_history=collection_history,
366
+ )
367
+
368
+ def delete(self, *args: Any, **kwargs: Any) -> NoReturn:
369
+ raise NotImplementedError()
370
+
371
+ def set_committed_value(
372
+ self, state: InstanceState[Any], dict_: _InstanceDict, value: Any
373
+ ) -> NoReturn:
374
+ raise NotImplementedError(
375
+ "Dynamic attributes don't support collection population."
376
+ )
377
+
378
+ def get_history(
379
+ self,
380
+ state: InstanceState[Any],
381
+ dict_: _InstanceDict,
382
+ passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
383
+ ) -> attributes.History:
384
+ c = self._get_collection_history(state, passive)
385
+ return c.as_history()
386
+
387
+ def get_all_pending(
388
+ self,
389
+ state: InstanceState[Any],
390
+ dict_: _InstanceDict,
391
+ passive: PassiveFlag = PassiveFlag.PASSIVE_NO_INITIALIZE,
392
+ ) -> List[Tuple[InstanceState[Any], Any]]:
393
+ c = self._get_collection_history(state, passive)
394
+ return [(attributes.instance_state(x), x) for x in c.all_items]
395
+
396
+ def _default_value(
397
+ self, state: InstanceState[Any], dict_: _InstanceDict
398
+ ) -> Any:
399
+ value = None
400
+ for fn in self.dispatch.init_scalar:
401
+ ret = fn(state, value, dict_)
402
+ if ret is not ATTR_EMPTY:
403
+ value = ret
404
+
405
+ return value
406
+
407
+ def _get_collection_history(
408
+ self, state: InstanceState[Any], passive: PassiveFlag
409
+ ) -> WriteOnlyHistory[Any]:
410
+ c: WriteOnlyHistory[Any]
411
+ if self.key in state.committed_state:
412
+ c = state.committed_state[self.key]
413
+ else:
414
+ c = self.collection_history_cls(
415
+ self, state, PassiveFlag.PASSIVE_NO_FETCH
416
+ )
417
+
418
+ if state.has_identity and (passive & PassiveFlag.INIT_OK):
419
+ return self.collection_history_cls(
420
+ self, state, passive, apply_to=c
421
+ )
422
+ else:
423
+ return c
424
+
425
+ def append(
426
+ self,
427
+ state: InstanceState[Any],
428
+ dict_: _InstanceDict,
429
+ value: Any,
430
+ initiator: Optional[AttributeEventToken],
431
+ passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
432
+ ) -> None:
433
+ if initiator is not self: # type: ignore[comparison-overlap]
434
+ self.fire_append_event(state, dict_, value, initiator)
435
+
436
+ def remove(
437
+ self,
438
+ state: InstanceState[Any],
439
+ dict_: _InstanceDict,
440
+ value: Any,
441
+ initiator: Optional[AttributeEventToken],
442
+ passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
443
+ ) -> None:
444
+ if initiator is not self: # type: ignore[comparison-overlap]
445
+ self.fire_remove_event(state, dict_, value, initiator)
446
+
447
+ def pop(
448
+ self,
449
+ state: InstanceState[Any],
450
+ dict_: _InstanceDict,
451
+ value: Any,
452
+ initiator: Optional[AttributeEventToken],
453
+ passive: PassiveFlag = PassiveFlag.PASSIVE_NO_FETCH,
454
+ ) -> None:
455
+ self.remove(state, dict_, value, initiator, passive=passive)
456
+
457
+
458
+ @log.class_logger
459
+ @relationships.RelationshipProperty.strategy_for(lazy="write_only")
460
+ class _WriteOnlyLoader(strategies._AbstractRelationshipLoader, log.Identified):
461
+ impl_class = _WriteOnlyAttributeImpl
462
+
463
+ def init_class_attribute(self, mapper: Mapper[Any]) -> None:
464
+ self.is_class_level = True
465
+ if not self.uselist or self.parent_property.direction not in (
466
+ interfaces.ONETOMANY,
467
+ interfaces.MANYTOMANY,
468
+ ):
469
+ raise exc.InvalidRequestError(
470
+ "On relationship %s, 'dynamic' loaders cannot be used with "
471
+ "many-to-one/one-to-one relationships and/or "
472
+ "uselist=False." % self.parent_property
473
+ )
474
+
475
+ strategies._register_attribute( # type: ignore[no-untyped-call]
476
+ self.parent_property,
477
+ mapper,
478
+ useobject=True,
479
+ impl_class=self.impl_class,
480
+ target_mapper=self.parent_property.mapper,
481
+ order_by=self.parent_property.order_by,
482
+ query_class=self.parent_property.query_class,
483
+ )
484
+
485
+
486
+ class _DynamicCollectionAdapter:
487
+ """simplified CollectionAdapter for internal API consistency"""
488
+
489
+ data: Collection[Any]
490
+
491
+ def __init__(self, data: Collection[Any]):
492
+ self.data = data
493
+
494
+ def __iter__(self) -> Iterator[Any]:
495
+ return iter(self.data)
496
+
497
+ def _reset_empty(self) -> None:
498
+ pass
499
+
500
+ def __len__(self) -> int:
501
+ return len(self.data)
502
+
503
+ def __bool__(self) -> bool:
504
+ return True
505
+
506
+
507
+ class _AbstractCollectionWriter(Generic[_T]):
508
+ """Virtual collection which includes append/remove methods that synchronize
509
+ into the attribute event system.
510
+
511
+ """
512
+
513
+ if not TYPE_CHECKING:
514
+ __slots__ = ()
515
+
516
+ instance: _T
517
+ _from_obj: Tuple[FromClause, ...]
518
+
519
+ def __init__(
520
+ self, attr: _WriteOnlyAttributeImpl, state: InstanceState[_T]
521
+ ):
522
+ instance = state.obj()
523
+ if TYPE_CHECKING:
524
+ assert instance
525
+ self.instance = instance
526
+ self.attr = attr
527
+
528
+ mapper = object_mapper(instance)
529
+ prop = mapper._props[self.attr.key]
530
+
531
+ if prop.secondary is not None:
532
+ # this is a hack right now. The Query only knows how to
533
+ # make subsequent joins() without a given left-hand side
534
+ # from self._from_obj[0]. We need to ensure prop.secondary
535
+ # is in the FROM. So we purposely put the mapper selectable
536
+ # in _from_obj[0] to ensure a user-defined join() later on
537
+ # doesn't fail, and secondary is then in _from_obj[1].
538
+
539
+ # note also, we are using the official ORM-annotated selectable
540
+ # from __clause_element__(), see #7868
541
+
542
+ # _no_filter_by annotation is to prevent this table from being
543
+ # considered by filter_by() as part of #8601
544
+ self._from_obj = (
545
+ prop.mapper.__clause_element__(),
546
+ prop.secondary._annotate({"_no_filter_by": True}),
547
+ )
548
+ else:
549
+ self._from_obj = ()
550
+
551
+ self._where_criteria = (
552
+ prop._with_parent(instance, alias_secondary=False),
553
+ )
554
+
555
+ if self.attr.order_by:
556
+ self._order_by_clauses = self.attr.order_by
557
+ else:
558
+ self._order_by_clauses = ()
559
+
560
+ def _add_all_impl(self, iterator: Iterable[_T]) -> None:
561
+ for item in iterator:
562
+ self.attr.append(
563
+ attributes.instance_state(self.instance),
564
+ attributes.instance_dict(self.instance),
565
+ item,
566
+ None,
567
+ )
568
+
569
+ def _remove_impl(self, item: _T) -> None:
570
+ self.attr.remove(
571
+ attributes.instance_state(self.instance),
572
+ attributes.instance_dict(self.instance),
573
+ item,
574
+ None,
575
+ )
576
+
577
+
578
+ class WriteOnlyCollection(_AbstractCollectionWriter[_T]):
579
+ """Write-only collection which can synchronize changes into the
580
+ attribute event system.
581
+
582
+ The :class:`.WriteOnlyCollection` is used in a mapping by
583
+ using the ``"write_only"`` lazy loading strategy with
584
+ :func:`_orm.relationship`. For background on this configuration,
585
+ see :ref:`write_only_relationship`.
586
+
587
+ .. versionadded:: 2.0
588
+
589
+ .. seealso::
590
+
591
+ :ref:`write_only_relationship`
592
+
593
+ """
594
+
595
+ __slots__ = (
596
+ "instance",
597
+ "attr",
598
+ "_where_criteria",
599
+ "_from_obj",
600
+ "_order_by_clauses",
601
+ )
602
+
603
+ def __iter__(self) -> NoReturn:
604
+ raise TypeError(
605
+ "WriteOnly collections don't support iteration in-place; "
606
+ "to query for collection items, use the select() method to "
607
+ "produce a SQL statement and execute it with session.scalars()."
608
+ )
609
+
610
+ def select(self) -> Select[_T]:
611
+ """Produce a :class:`_sql.Select` construct that represents the
612
+ rows within this instance-local :class:`_orm.WriteOnlyCollection`.
613
+
614
+ """
615
+ stmt = select(self.attr.target_mapper).where(*self._where_criteria)
616
+ if self._from_obj:
617
+ stmt = stmt.select_from(*self._from_obj)
618
+ if self._order_by_clauses:
619
+ stmt = stmt.order_by(*self._order_by_clauses)
620
+ return stmt
621
+
622
+ def insert(self) -> Insert:
623
+ """For one-to-many collections, produce a :class:`_dml.Insert` which
624
+ will insert new rows in terms of this this instance-local
625
+ :class:`_orm.WriteOnlyCollection`.
626
+
627
+ This construct is only supported for a :class:`_orm.Relationship`
628
+ that does **not** include the :paramref:`_orm.relationship.secondary`
629
+ parameter. For relationships that refer to a many-to-many table,
630
+ use ordinary bulk insert techniques to produce new objects, then
631
+ use :meth:`_orm.AbstractCollectionWriter.add_all` to associate them
632
+ with the collection.
633
+
634
+
635
+ """
636
+
637
+ state = inspect(self.instance)
638
+ mapper = state.mapper
639
+ prop = mapper._props[self.attr.key]
640
+
641
+ if prop.direction is not RelationshipDirection.ONETOMANY:
642
+ raise exc.InvalidRequestError(
643
+ "Write only bulk INSERT only supported for one-to-many "
644
+ "collections; for many-to-many, use a separate bulk "
645
+ "INSERT along with add_all()."
646
+ )
647
+
648
+ dict_: Dict[str, Any] = {}
649
+
650
+ for l, r in prop.synchronize_pairs:
651
+ fn = prop._get_attr_w_warn_on_none(
652
+ mapper,
653
+ state,
654
+ state.dict,
655
+ l,
656
+ )
657
+
658
+ dict_[r.key] = bindparam(None, callable_=fn)
659
+
660
+ return insert(self.attr.target_mapper).values(**dict_)
661
+
662
+ def update(self) -> Update:
663
+ """Produce a :class:`_dml.Update` which will refer to rows in terms
664
+ of this instance-local :class:`_orm.WriteOnlyCollection`.
665
+
666
+ """
667
+ return update(self.attr.target_mapper).where(*self._where_criteria)
668
+
669
+ def delete(self) -> Delete:
670
+ """Produce a :class:`_dml.Delete` which will refer to rows in terms
671
+ of this instance-local :class:`_orm.WriteOnlyCollection`.
672
+
673
+ """
674
+ return delete(self.attr.target_mapper).where(*self._where_criteria)
675
+
676
+ def add_all(self, iterator: Iterable[_T]) -> None:
677
+ """Add an iterable of items to this :class:`_orm.WriteOnlyCollection`.
678
+
679
+ The given items will be persisted to the database in terms of
680
+ the parent instance's collection on the next flush.
681
+
682
+ """
683
+ self._add_all_impl(iterator)
684
+
685
+ def add(self, item: _T) -> None:
686
+ """Add an item to this :class:`_orm.WriteOnlyCollection`.
687
+
688
+ The given item will be persisted to the database in terms of
689
+ the parent instance's collection on the next flush.
690
+
691
+ """
692
+ self._add_all_impl([item])
693
+
694
+ def remove(self, item: _T) -> None:
695
+ """Remove an item from this :class:`_orm.WriteOnlyCollection`.
696
+
697
+ The given item will be removed from the parent instance's collection on
698
+ the next flush.
699
+
700
+ """
701
+ self._remove_impl(item)