SQLAlchemy 2.1.0b2__cp313-cp313t-win_arm64.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 (270) hide show
  1. sqlalchemy/__init__.py +298 -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 +250 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +30 -0
  8. sqlalchemy/dialects/mssql/__init__.py +89 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4166 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +220 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +698 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +226 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +214 -0
  21. sqlalchemy/dialects/mysql/base.py +3877 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +106 -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 +330 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +296 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +312 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +157 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +156 -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 +3977 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1601 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +158 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +909 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +368 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +171 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +229 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1323 -0
  50. sqlalchemy/dialects/postgresql/base.py +5789 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +593 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +423 -0
  55. sqlalchemy/dialects/postgresql/json.py +408 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +521 -0
  57. sqlalchemy/dialects/postgresql/operators.py +130 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +670 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +344 -0
  60. sqlalchemy/dialects/postgresql/provision.py +184 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +799 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +321 -0
  68. sqlalchemy/dialects/sqlite/base.py +3063 -0
  69. sqlalchemy/dialects/sqlite/dml.py +279 -0
  70. sqlalchemy/dialects/sqlite/json.py +100 -0
  71. sqlalchemy/dialects/sqlite/provision.py +229 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +161 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +754 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp313t-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp313t-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +633 -0
  80. sqlalchemy/engine/_row_cy.cp313t-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp313t-win_arm64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3354 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2421 -0
  88. sqlalchemy/engine/default.py +2402 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3495 -0
  91. sqlalchemy/engine/mock.py +134 -0
  92. sqlalchemy/engine/processors.py +82 -0
  93. sqlalchemy/engine/reflection.py +2100 -0
  94. sqlalchemy/engine/result.py +1966 -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 +156 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +674 -0
  102. sqlalchemy/event/base.py +472 -0
  103. sqlalchemy/event/legacy.py +258 -0
  104. sqlalchemy/event/registry.py +390 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +922 -0
  107. sqlalchemy/ext/__init__.py +11 -0
  108. sqlalchemy/ext/associationproxy.py +2072 -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 +2007 -0
  116. sqlalchemy/ext/automap.py +1701 -0
  117. sqlalchemy/ext/baked.py +559 -0
  118. sqlalchemy/ext/compiler.py +600 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +560 -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 +439 -0
  127. sqlalchemy/ext/serializer.py +185 -0
  128. sqlalchemy/future/__init__.py +16 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +174 -0
  131. sqlalchemy/log.py +283 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +179 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +976 -0
  137. sqlalchemy/orm/bulk_persistence.py +2152 -0
  138. sqlalchemy/orm/clsregistry.py +582 -0
  139. sqlalchemy/orm/collections.py +1568 -0
  140. sqlalchemy/orm/context.py +3471 -0
  141. sqlalchemy/orm/decl_api.py +2280 -0
  142. sqlalchemy/orm/decl_base.py +2309 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +307 -0
  146. sqlalchemy/orm/evaluator.py +379 -0
  147. sqlalchemy/orm/events.py +3386 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +746 -0
  151. sqlalchemy/orm/interfaces.py +1589 -0
  152. sqlalchemy/orm/loading.py +1684 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4411 -0
  155. sqlalchemy/orm/path_registry.py +829 -0
  156. sqlalchemy/orm/persistence.py +1789 -0
  157. sqlalchemy/orm/properties.py +973 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3570 -0
  160. sqlalchemy/orm/scoping.py +2232 -0
  161. sqlalchemy/orm/session.py +5403 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3492 -0
  165. sqlalchemy/orm/strategy_options.py +2562 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +798 -0
  168. sqlalchemy/orm/util.py +2438 -0
  169. sqlalchemy/orm/writeonly.py +694 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1522 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +582 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +74 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +397 -0
  178. sqlalchemy/sql/_dml_constructors.py +132 -0
  179. sqlalchemy/sql/_elements_constructors.py +2164 -0
  180. sqlalchemy/sql/_orm_types.py +20 -0
  181. sqlalchemy/sql/_selectable_constructors.py +840 -0
  182. sqlalchemy/sql/_typing.py +487 -0
  183. sqlalchemy/sql/_util_cy.cp313t-win_arm64.pyd +0 -0
  184. sqlalchemy/sql/_util_cy.py +127 -0
  185. sqlalchemy/sql/annotation.py +590 -0
  186. sqlalchemy/sql/base.py +2699 -0
  187. sqlalchemy/sql/cache_key.py +1066 -0
  188. sqlalchemy/sql/coercions.py +1373 -0
  189. sqlalchemy/sql/compiler.py +8327 -0
  190. sqlalchemy/sql/crud.py +1815 -0
  191. sqlalchemy/sql/ddl.py +1928 -0
  192. sqlalchemy/sql/default_comparator.py +654 -0
  193. sqlalchemy/sql/dml.py +1977 -0
  194. sqlalchemy/sql/elements.py +6033 -0
  195. sqlalchemy/sql/events.py +458 -0
  196. sqlalchemy/sql/expression.py +172 -0
  197. sqlalchemy/sql/functions.py +2305 -0
  198. sqlalchemy/sql/lambdas.py +1443 -0
  199. sqlalchemy/sql/naming.py +209 -0
  200. sqlalchemy/sql/operators.py +2897 -0
  201. sqlalchemy/sql/roles.py +332 -0
  202. sqlalchemy/sql/schema.py +6703 -0
  203. sqlalchemy/sql/selectable.py +7553 -0
  204. sqlalchemy/sql/sqltypes.py +4093 -0
  205. sqlalchemy/sql/traversals.py +1042 -0
  206. sqlalchemy/sql/type_api.py +2446 -0
  207. sqlalchemy/sql/util.py +1495 -0
  208. sqlalchemy/sql/visitors.py +1157 -0
  209. sqlalchemy/testing/__init__.py +96 -0
  210. sqlalchemy/testing/assertions.py +1007 -0
  211. sqlalchemy/testing/assertsql.py +519 -0
  212. sqlalchemy/testing/asyncio.py +128 -0
  213. sqlalchemy/testing/config.py +440 -0
  214. sqlalchemy/testing/engines.py +483 -0
  215. sqlalchemy/testing/entities.py +117 -0
  216. sqlalchemy/testing/exclusions.py +476 -0
  217. sqlalchemy/testing/fixtures/__init__.py +30 -0
  218. sqlalchemy/testing/fixtures/base.py +384 -0
  219. sqlalchemy/testing/fixtures/mypy.py +247 -0
  220. sqlalchemy/testing/fixtures/orm.py +227 -0
  221. sqlalchemy/testing/fixtures/sql.py +538 -0
  222. sqlalchemy/testing/pickleable.py +155 -0
  223. sqlalchemy/testing/plugin/__init__.py +6 -0
  224. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  225. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  226. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  227. sqlalchemy/testing/profiling.py +329 -0
  228. sqlalchemy/testing/provision.py +613 -0
  229. sqlalchemy/testing/requirements.py +1978 -0
  230. sqlalchemy/testing/schema.py +198 -0
  231. sqlalchemy/testing/suite/__init__.py +19 -0
  232. sqlalchemy/testing/suite/test_cte.py +237 -0
  233. sqlalchemy/testing/suite/test_ddl.py +420 -0
  234. sqlalchemy/testing/suite/test_dialect.py +776 -0
  235. sqlalchemy/testing/suite/test_insert.py +630 -0
  236. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  237. sqlalchemy/testing/suite/test_results.py +660 -0
  238. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  239. sqlalchemy/testing/suite/test_select.py +2112 -0
  240. sqlalchemy/testing/suite/test_sequence.py +317 -0
  241. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  242. sqlalchemy/testing/suite/test_types.py +2271 -0
  243. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  244. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  245. sqlalchemy/testing/util.py +535 -0
  246. sqlalchemy/testing/warnings.py +52 -0
  247. sqlalchemy/types.py +76 -0
  248. sqlalchemy/util/__init__.py +158 -0
  249. sqlalchemy/util/_collections.py +688 -0
  250. sqlalchemy/util/_collections_cy.cp313t-win_arm64.pyd +0 -0
  251. sqlalchemy/util/_collections_cy.pxd +8 -0
  252. sqlalchemy/util/_collections_cy.py +516 -0
  253. sqlalchemy/util/_has_cython.py +46 -0
  254. sqlalchemy/util/_immutabledict_cy.cp313t-win_arm64.pyd +0 -0
  255. sqlalchemy/util/_immutabledict_cy.py +240 -0
  256. sqlalchemy/util/compat.py +299 -0
  257. sqlalchemy/util/concurrency.py +322 -0
  258. sqlalchemy/util/cython.py +79 -0
  259. sqlalchemy/util/deprecations.py +401 -0
  260. sqlalchemy/util/langhelpers.py +2320 -0
  261. sqlalchemy/util/preloaded.py +152 -0
  262. sqlalchemy/util/queue.py +304 -0
  263. sqlalchemy/util/tool_support.py +201 -0
  264. sqlalchemy/util/topological.py +120 -0
  265. sqlalchemy/util/typing.py +711 -0
  266. sqlalchemy-2.1.0b2.dist-info/METADATA +269 -0
  267. sqlalchemy-2.1.0b2.dist-info/RECORD +270 -0
  268. sqlalchemy-2.1.0b2.dist-info/WHEEL +5 -0
  269. sqlalchemy-2.1.0b2.dist-info/licenses/LICENSE +19 -0
  270. sqlalchemy-2.1.0b2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,481 @@
1
+ # ext/horizontal_shard.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
+ """Horizontal sharding support.
9
+
10
+ Defines a rudimental 'horizontal sharding' system which allows a Session to
11
+ distribute queries and persistence operations across multiple databases.
12
+
13
+ For a usage example, see the :ref:`examples_sharding` example included in
14
+ the source distribution.
15
+
16
+ .. deepalchemy:: The horizontal sharding extension is an advanced feature,
17
+ involving a complex statement -> database interaction as well as
18
+ use of semi-public APIs for non-trivial cases. Simpler approaches to
19
+ referring to multiple database "shards", most commonly using a distinct
20
+ :class:`_orm.Session` per "shard", should always be considered first
21
+ before using this more complex and less-production-tested system.
22
+
23
+
24
+
25
+ """
26
+ from __future__ import annotations
27
+
28
+ from typing import Any
29
+ from typing import Callable
30
+ from typing import Dict
31
+ from typing import Iterable
32
+ from typing import Optional
33
+ from typing import Protocol
34
+ from typing import Tuple
35
+ from typing import Type
36
+ from typing import TYPE_CHECKING
37
+ from typing import TypeVar
38
+ from typing import Union
39
+
40
+ from .. import event
41
+ from .. import exc
42
+ from .. import inspect
43
+ from .. import util
44
+ from ..orm import PassiveFlag
45
+ from ..orm._typing import OrmExecuteOptionsParameter
46
+ from ..orm.interfaces import ORMOption
47
+ from ..orm.mapper import Mapper
48
+ from ..orm.query import Query
49
+ from ..orm.session import _BindArguments
50
+ from ..orm.session import _PKIdentityArgument
51
+ from ..orm.session import Session
52
+ from ..util.typing import Self
53
+ from ..util.typing import TupleAny
54
+ from ..util.typing import TypeVarTuple
55
+ from ..util.typing import Unpack
56
+
57
+
58
+ if TYPE_CHECKING:
59
+ from ..engine.base import Connection
60
+ from ..engine.base import Engine
61
+ from ..engine.base import OptionEngine
62
+ from ..engine.result import Result
63
+ from ..orm import LoaderCallableStatus
64
+ from ..orm._typing import _O
65
+ from ..orm.bulk_persistence import _BulkUDCompileState
66
+ from ..orm.context import QueryContext
67
+ from ..orm.session import _EntityBindKey
68
+ from ..orm.session import _SessionBind
69
+ from ..orm.session import ORMExecuteState
70
+ from ..orm.state import InstanceState
71
+ from ..sql import Executable
72
+ from ..sql.elements import ClauseElement
73
+
74
+ __all__ = ["ShardedSession", "ShardedQuery"]
75
+
76
+ _T = TypeVar("_T", bound=Any)
77
+ _Ts = TypeVarTuple("_Ts")
78
+
79
+
80
+ ShardIdentifier = str
81
+
82
+
83
+ class ShardChooser(Protocol):
84
+ def __call__(
85
+ self,
86
+ mapper: Optional[Mapper[_T]],
87
+ instance: Any,
88
+ clause: Optional[ClauseElement],
89
+ ) -> Any: ...
90
+
91
+
92
+ class IdentityChooser(Protocol):
93
+ def __call__(
94
+ self,
95
+ mapper: Mapper[_T],
96
+ primary_key: _PKIdentityArgument,
97
+ *,
98
+ lazy_loaded_from: Optional[InstanceState[Any]],
99
+ execution_options: OrmExecuteOptionsParameter,
100
+ bind_arguments: _BindArguments,
101
+ **kw: Any,
102
+ ) -> Any: ...
103
+
104
+
105
+ class ShardedQuery(Query[_T]):
106
+ """Query class used with :class:`.ShardedSession`.
107
+
108
+ .. legacy:: The :class:`.ShardedQuery` is a subclass of the legacy
109
+ :class:`.Query` class. The :class:`.ShardedSession` now supports
110
+ 2.0 style execution via the :meth:`.ShardedSession.execute` method.
111
+
112
+ """
113
+
114
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
115
+ super().__init__(*args, **kwargs)
116
+ assert isinstance(self.session, ShardedSession)
117
+
118
+ self.identity_chooser = self.session.identity_chooser
119
+ self.execute_chooser = self.session.execute_chooser
120
+ self._shard_id = None
121
+
122
+ def set_shard(self, shard_id: ShardIdentifier) -> Self:
123
+ """Return a new query, limited to a single shard ID.
124
+
125
+ All subsequent operations with the returned query will
126
+ be against the single shard regardless of other state.
127
+
128
+ The shard_id can be passed for a 2.0 style execution to the
129
+ bind_arguments dictionary of :meth:`.Session.execute`::
130
+
131
+ results = session.execute(stmt, bind_arguments={"shard_id": "my_shard"})
132
+
133
+ """ # noqa: E501
134
+ return self.execution_options(_sa_shard_id=shard_id)
135
+
136
+
137
+ class ShardedSession(Session):
138
+ shard_chooser: ShardChooser
139
+ identity_chooser: IdentityChooser
140
+ execute_chooser: Callable[[ORMExecuteState], Iterable[Any]]
141
+
142
+ def __init__(
143
+ self,
144
+ shard_chooser: ShardChooser,
145
+ identity_chooser: Optional[IdentityChooser] = None,
146
+ execute_chooser: Optional[
147
+ Callable[[ORMExecuteState], Iterable[Any]]
148
+ ] = None,
149
+ shards: Optional[Dict[str, Any]] = None,
150
+ query_cls: Type[Query[_T]] = ShardedQuery,
151
+ *,
152
+ id_chooser: Optional[
153
+ Callable[[Query[_T], Iterable[_T]], Iterable[Any]]
154
+ ] = None,
155
+ query_chooser: Optional[Callable[[Executable], Iterable[Any]]] = None,
156
+ **kwargs: Any,
157
+ ) -> None:
158
+ """Construct a ShardedSession.
159
+
160
+ :param shard_chooser: A callable which, passed a Mapper, a mapped
161
+ instance, and possibly a SQL clause, returns a shard ID. This id
162
+ may be based off of the attributes present within the object, or on
163
+ some round-robin scheme. If the scheme is based on a selection, it
164
+ should set whatever state on the instance to mark it in the future as
165
+ participating in that shard.
166
+
167
+ :param identity_chooser: A callable, passed a Mapper and primary key
168
+ argument, which should return a list of shard ids where this
169
+ primary key might reside.
170
+
171
+ .. versionchanged:: 2.0 The ``identity_chooser`` parameter
172
+ supersedes the ``id_chooser`` parameter.
173
+
174
+ :param execute_chooser: For a given :class:`.ORMExecuteState`,
175
+ returns the list of shard_ids
176
+ where the query should be issued. Results from all shards returned
177
+ will be combined together into a single listing.
178
+
179
+ .. versionchanged:: 1.4 The ``execute_chooser`` parameter
180
+ supersedes the ``query_chooser`` parameter.
181
+
182
+ :param shards: A dictionary of string shard names
183
+ to :class:`~sqlalchemy.engine.Engine` objects.
184
+
185
+ """
186
+ super().__init__(query_cls=query_cls, **kwargs)
187
+
188
+ event.listen(
189
+ self, "do_orm_execute", execute_and_instances, retval=True
190
+ )
191
+ self.shard_chooser = shard_chooser
192
+
193
+ if id_chooser:
194
+ _id_chooser = id_chooser
195
+ util.warn_deprecated(
196
+ "The ``id_chooser`` parameter is deprecated; "
197
+ "please use ``identity_chooser``.",
198
+ "2.0",
199
+ )
200
+
201
+ def _legacy_identity_chooser(
202
+ mapper: Mapper[_T],
203
+ primary_key: _PKIdentityArgument,
204
+ *,
205
+ lazy_loaded_from: Optional[InstanceState[Any]],
206
+ execution_options: OrmExecuteOptionsParameter,
207
+ bind_arguments: _BindArguments,
208
+ **kw: Any,
209
+ ) -> Any:
210
+ q = self.query(mapper)
211
+ if lazy_loaded_from:
212
+ q = q._set_lazyload_from(lazy_loaded_from)
213
+ return _id_chooser(q, primary_key)
214
+
215
+ self.identity_chooser = _legacy_identity_chooser
216
+ elif identity_chooser:
217
+ self.identity_chooser = identity_chooser
218
+ else:
219
+ raise exc.ArgumentError(
220
+ "identity_chooser or id_chooser is required"
221
+ )
222
+
223
+ if query_chooser:
224
+ _query_chooser = query_chooser
225
+ util.warn_deprecated(
226
+ "The ``query_chooser`` parameter is deprecated; "
227
+ "please use ``execute_chooser``.",
228
+ "1.4",
229
+ )
230
+ if execute_chooser:
231
+ raise exc.ArgumentError(
232
+ "Can't pass query_chooser and execute_chooser "
233
+ "at the same time."
234
+ )
235
+
236
+ def _default_execute_chooser(
237
+ orm_context: ORMExecuteState,
238
+ ) -> Iterable[Any]:
239
+ return _query_chooser(orm_context.statement)
240
+
241
+ if execute_chooser is None:
242
+ execute_chooser = _default_execute_chooser
243
+
244
+ if execute_chooser is None:
245
+ raise exc.ArgumentError(
246
+ "execute_chooser or query_chooser is required"
247
+ )
248
+ self.execute_chooser = execute_chooser
249
+ self.__shards: Dict[ShardIdentifier, _SessionBind] = {}
250
+ if shards is not None:
251
+ for k in shards:
252
+ self.bind_shard(k, shards[k])
253
+
254
+ def _identity_lookup(
255
+ self,
256
+ mapper: Mapper[_O],
257
+ primary_key_identity: Union[Any, Tuple[Any, ...]],
258
+ identity_token: Optional[Any] = None,
259
+ passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
260
+ lazy_loaded_from: Optional[InstanceState[Any]] = None,
261
+ execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
262
+ bind_arguments: Optional[_BindArguments] = None,
263
+ **kw: Any,
264
+ ) -> Union[Optional[_O], LoaderCallableStatus]:
265
+ """override the default :meth:`.Session._identity_lookup` method so
266
+ that we search for a given non-token primary key identity across all
267
+ possible identity tokens (e.g. shard ids).
268
+
269
+ .. versionchanged:: 1.4 Moved :meth:`.Session._identity_lookup` from
270
+ the :class:`_query.Query` object to the :class:`.Session`.
271
+
272
+ """
273
+
274
+ if identity_token is not None:
275
+ obj = super()._identity_lookup(
276
+ mapper,
277
+ primary_key_identity,
278
+ identity_token=identity_token,
279
+ **kw,
280
+ )
281
+
282
+ return obj
283
+ else:
284
+ for shard_id in self.identity_chooser(
285
+ mapper,
286
+ primary_key_identity,
287
+ lazy_loaded_from=lazy_loaded_from,
288
+ execution_options=execution_options,
289
+ bind_arguments=dict(bind_arguments) if bind_arguments else {},
290
+ ):
291
+ obj2 = super()._identity_lookup(
292
+ mapper,
293
+ primary_key_identity,
294
+ identity_token=shard_id,
295
+ lazy_loaded_from=lazy_loaded_from,
296
+ **kw,
297
+ )
298
+ if obj2 is not None:
299
+ return obj2
300
+
301
+ return None
302
+
303
+ def _choose_shard_and_assign(
304
+ self,
305
+ mapper: Optional[_EntityBindKey[_O]],
306
+ instance: Any,
307
+ **kw: Any,
308
+ ) -> Any:
309
+ if instance is not None:
310
+ state = inspect(instance)
311
+ if state.key:
312
+ token = state.key[2]
313
+ assert token is not None
314
+ return token
315
+ elif state.identity_token:
316
+ return state.identity_token
317
+
318
+ assert isinstance(mapper, Mapper)
319
+ shard_id = self.shard_chooser(mapper, instance, **kw)
320
+ if instance is not None:
321
+ state.identity_token = shard_id
322
+ return shard_id
323
+
324
+ def connection_callable(
325
+ self,
326
+ mapper: Optional[Mapper[_T]] = None,
327
+ instance: Optional[Any] = None,
328
+ shard_id: Optional[ShardIdentifier] = None,
329
+ **kw: Any,
330
+ ) -> Connection:
331
+ """Provide a :class:`_engine.Connection` to use in the unit of work
332
+ flush process.
333
+
334
+ """
335
+
336
+ if shard_id is None:
337
+ shard_id = self._choose_shard_and_assign(mapper, instance)
338
+
339
+ if self.in_transaction():
340
+ trans = self.get_transaction()
341
+ assert trans is not None
342
+ return trans.connection(mapper, shard_id=shard_id)
343
+ else:
344
+ bind = self.get_bind(
345
+ mapper=mapper, shard_id=shard_id, instance=instance
346
+ )
347
+
348
+ if isinstance(bind, Engine):
349
+ return bind.connect(**kw)
350
+ else:
351
+ assert isinstance(bind, Connection)
352
+ return bind
353
+
354
+ def get_bind(
355
+ self,
356
+ mapper: Optional[_EntityBindKey[_O]] = None,
357
+ *,
358
+ shard_id: Optional[ShardIdentifier] = None,
359
+ instance: Optional[Any] = None,
360
+ clause: Optional[ClauseElement] = None,
361
+ **kw: Any,
362
+ ) -> _SessionBind:
363
+ if shard_id is None:
364
+ shard_id = self._choose_shard_and_assign(
365
+ mapper, instance=instance, clause=clause
366
+ )
367
+ assert shard_id is not None
368
+ return self.__shards[shard_id]
369
+
370
+ def bind_shard(
371
+ self, shard_id: ShardIdentifier, bind: Union[Engine, OptionEngine]
372
+ ) -> None:
373
+ self.__shards[shard_id] = bind
374
+
375
+
376
+ class set_shard_id(ORMOption):
377
+ """a loader option for statements to apply a specific shard id to the
378
+ primary query as well as for additional relationship and column
379
+ loaders.
380
+
381
+ The :class:`_horizontal.set_shard_id` option may be applied using
382
+ the :meth:`_sql.Executable.options` method of any executable statement::
383
+
384
+ stmt = (
385
+ select(MyObject)
386
+ .where(MyObject.name == "some name")
387
+ .options(set_shard_id("shard1"))
388
+ )
389
+
390
+ Above, the statement when invoked will limit to the "shard1" shard
391
+ identifier for the primary query as well as for all relationship and
392
+ column loading strategies, including eager loaders such as
393
+ :func:`_orm.selectinload`, deferred column loaders like :func:`_orm.defer`,
394
+ and the lazy relationship loader :func:`_orm.lazyload`.
395
+
396
+ In this way, the :class:`_horizontal.set_shard_id` option has much wider
397
+ scope than using the "shard_id" argument within the
398
+ :paramref:`_orm.Session.execute.bind_arguments` dictionary.
399
+
400
+
401
+ .. versionadded:: 2.0.0
402
+
403
+ """
404
+
405
+ __slots__ = ("shard_id", "propagate_to_loaders")
406
+
407
+ def __init__(
408
+ self, shard_id: ShardIdentifier, propagate_to_loaders: bool = True
409
+ ):
410
+ """Construct a :class:`_horizontal.set_shard_id` option.
411
+
412
+ :param shard_id: shard identifier
413
+ :param propagate_to_loaders: if left at its default of ``True``, the
414
+ shard option will take place for lazy loaders such as
415
+ :func:`_orm.lazyload` and :func:`_orm.defer`; if False, the option
416
+ will not be propagated to loaded objects. Note that :func:`_orm.defer`
417
+ always limits to the shard_id of the parent row in any case, so the
418
+ parameter only has a net effect on the behavior of the
419
+ :func:`_orm.lazyload` strategy.
420
+
421
+ """
422
+ self.shard_id = shard_id
423
+ self.propagate_to_loaders = propagate_to_loaders
424
+
425
+
426
+ def execute_and_instances(
427
+ orm_context: ORMExecuteState,
428
+ ) -> Result[Unpack[TupleAny]]:
429
+ active_options: Union[
430
+ None,
431
+ QueryContext.default_load_options,
432
+ Type[QueryContext.default_load_options],
433
+ _BulkUDCompileState.default_update_options,
434
+ Type[_BulkUDCompileState.default_update_options],
435
+ ]
436
+
437
+ if orm_context.is_select:
438
+ active_options = orm_context.load_options
439
+
440
+ elif orm_context.is_update or orm_context.is_delete:
441
+ active_options = orm_context.update_delete_options
442
+ else:
443
+ active_options = None
444
+
445
+ session = orm_context.session
446
+ assert isinstance(session, ShardedSession)
447
+
448
+ def iter_for_shard(
449
+ shard_id: ShardIdentifier,
450
+ ) -> Result[Unpack[TupleAny]]:
451
+ bind_arguments = dict(orm_context.bind_arguments)
452
+ bind_arguments["shard_id"] = shard_id
453
+
454
+ orm_context.update_execution_options(identity_token=shard_id)
455
+ return orm_context.invoke_statement(bind_arguments=bind_arguments)
456
+
457
+ for orm_opt in orm_context._non_compile_orm_options:
458
+ # TODO: if we had an ORMOption that gets applied at ORM statement
459
+ # execution time, that would allow this to be more generalized.
460
+ # for now just iterate and look for our options
461
+ if isinstance(orm_opt, set_shard_id):
462
+ shard_id = orm_opt.shard_id
463
+ break
464
+ else:
465
+ if active_options and active_options._identity_token is not None:
466
+ shard_id = active_options._identity_token
467
+ elif "_sa_shard_id" in orm_context.execution_options:
468
+ shard_id = orm_context.execution_options["_sa_shard_id"]
469
+ elif "shard_id" in orm_context.bind_arguments:
470
+ shard_id = orm_context.bind_arguments["shard_id"]
471
+ else:
472
+ shard_id = None
473
+
474
+ if shard_id is not None:
475
+ return iter_for_shard(shard_id)
476
+ else:
477
+ partial = []
478
+ for shard_id in session.execute_chooser(orm_context):
479
+ result_ = iter_for_shard(shard_id)
480
+ partial.append(result_)
481
+ return partial[0].merge(*partial[1:])