SQLAlchemy 2.0.36__cp313-cp313-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 (273) hide show
  1. SQLAlchemy-2.0.36.dist-info/LICENSE +19 -0
  2. SQLAlchemy-2.0.36.dist-info/METADATA +243 -0
  3. SQLAlchemy-2.0.36.dist-info/RECORD +273 -0
  4. SQLAlchemy-2.0.36.dist-info/WHEEL +5 -0
  5. SQLAlchemy-2.0.36.dist-info/top_level.txt +1 -0
  6. sqlalchemy/__init__.py +294 -0
  7. sqlalchemy/connectors/__init__.py +18 -0
  8. sqlalchemy/connectors/aioodbc.py +174 -0
  9. sqlalchemy/connectors/asyncio.py +213 -0
  10. sqlalchemy/connectors/pyodbc.py +249 -0
  11. sqlalchemy/cyextension/__init__.py +6 -0
  12. sqlalchemy/cyextension/collections.cp313-win_amd64.pyd +0 -0
  13. sqlalchemy/cyextension/collections.pyx +409 -0
  14. sqlalchemy/cyextension/immutabledict.cp313-win_amd64.pyd +0 -0
  15. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  16. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  17. sqlalchemy/cyextension/processors.cp313-win_amd64.pyd +0 -0
  18. sqlalchemy/cyextension/processors.pyx +68 -0
  19. sqlalchemy/cyextension/resultproxy.cp313-win_amd64.pyd +0 -0
  20. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  21. sqlalchemy/cyextension/util.cp313-win_amd64.pyd +0 -0
  22. sqlalchemy/cyextension/util.pyx +91 -0
  23. sqlalchemy/dialects/__init__.py +61 -0
  24. sqlalchemy/dialects/_typing.py +25 -0
  25. sqlalchemy/dialects/mssql/__init__.py +88 -0
  26. sqlalchemy/dialects/mssql/aioodbc.py +64 -0
  27. sqlalchemy/dialects/mssql/base.py +4010 -0
  28. sqlalchemy/dialects/mssql/information_schema.py +254 -0
  29. sqlalchemy/dialects/mssql/json.py +133 -0
  30. sqlalchemy/dialects/mssql/provision.py +162 -0
  31. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  32. sqlalchemy/dialects/mssql/pyodbc.py +745 -0
  33. sqlalchemy/dialects/mysql/__init__.py +101 -0
  34. sqlalchemy/dialects/mysql/aiomysql.py +333 -0
  35. sqlalchemy/dialects/mysql/asyncmy.py +337 -0
  36. sqlalchemy/dialects/mysql/base.py +3494 -0
  37. sqlalchemy/dialects/mysql/cymysql.py +84 -0
  38. sqlalchemy/dialects/mysql/dml.py +219 -0
  39. sqlalchemy/dialects/mysql/enumerated.py +244 -0
  40. sqlalchemy/dialects/mysql/expression.py +141 -0
  41. sqlalchemy/dialects/mysql/json.py +81 -0
  42. sqlalchemy/dialects/mysql/mariadb.py +32 -0
  43. sqlalchemy/dialects/mysql/mariadbconnector.py +277 -0
  44. sqlalchemy/dialects/mysql/mysqlconnector.py +180 -0
  45. sqlalchemy/dialects/mysql/mysqldb.py +303 -0
  46. sqlalchemy/dialects/mysql/provision.py +110 -0
  47. sqlalchemy/dialects/mysql/pymysql.py +137 -0
  48. sqlalchemy/dialects/mysql/pyodbc.py +138 -0
  49. sqlalchemy/dialects/mysql/reflection.py +677 -0
  50. sqlalchemy/dialects/mysql/reserved_words.py +571 -0
  51. sqlalchemy/dialects/mysql/types.py +774 -0
  52. sqlalchemy/dialects/oracle/__init__.py +67 -0
  53. sqlalchemy/dialects/oracle/base.py +3271 -0
  54. sqlalchemy/dialects/oracle/cx_oracle.py +1483 -0
  55. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  56. sqlalchemy/dialects/oracle/oracledb.py +431 -0
  57. sqlalchemy/dialects/oracle/provision.py +220 -0
  58. sqlalchemy/dialects/oracle/types.py +287 -0
  59. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  60. sqlalchemy/dialects/postgresql/_psycopg_common.py +187 -0
  61. sqlalchemy/dialects/postgresql/array.py +425 -0
  62. sqlalchemy/dialects/postgresql/asyncpg.py +1274 -0
  63. sqlalchemy/dialects/postgresql/base.py +5008 -0
  64. sqlalchemy/dialects/postgresql/dml.py +310 -0
  65. sqlalchemy/dialects/postgresql/ext.py +496 -0
  66. sqlalchemy/dialects/postgresql/hstore.py +397 -0
  67. sqlalchemy/dialects/postgresql/json.py +333 -0
  68. sqlalchemy/dialects/postgresql/named_types.py +509 -0
  69. sqlalchemy/dialects/postgresql/operators.py +129 -0
  70. sqlalchemy/dialects/postgresql/pg8000.py +662 -0
  71. sqlalchemy/dialects/postgresql/pg_catalog.py +300 -0
  72. sqlalchemy/dialects/postgresql/provision.py +175 -0
  73. sqlalchemy/dialects/postgresql/psycopg.py +772 -0
  74. sqlalchemy/dialects/postgresql/psycopg2.py +886 -0
  75. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  76. sqlalchemy/dialects/postgresql/ranges.py +1029 -0
  77. sqlalchemy/dialects/postgresql/types.py +303 -0
  78. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  79. sqlalchemy/dialects/sqlite/aiosqlite.py +396 -0
  80. sqlalchemy/dialects/sqlite/base.py +2805 -0
  81. sqlalchemy/dialects/sqlite/dml.py +240 -0
  82. sqlalchemy/dialects/sqlite/json.py +92 -0
  83. sqlalchemy/dialects/sqlite/provision.py +198 -0
  84. sqlalchemy/dialects/sqlite/pysqlcipher.py +155 -0
  85. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  86. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  87. sqlalchemy/engine/__init__.py +62 -0
  88. sqlalchemy/engine/_py_processors.py +136 -0
  89. sqlalchemy/engine/_py_row.py +128 -0
  90. sqlalchemy/engine/_py_util.py +74 -0
  91. sqlalchemy/engine/base.py +3375 -0
  92. sqlalchemy/engine/characteristics.py +155 -0
  93. sqlalchemy/engine/create.py +875 -0
  94. sqlalchemy/engine/cursor.py +2181 -0
  95. sqlalchemy/engine/default.py +2365 -0
  96. sqlalchemy/engine/events.py +951 -0
  97. sqlalchemy/engine/interfaces.py +3403 -0
  98. sqlalchemy/engine/mock.py +131 -0
  99. sqlalchemy/engine/processors.py +61 -0
  100. sqlalchemy/engine/reflection.py +2098 -0
  101. sqlalchemy/engine/result.py +2382 -0
  102. sqlalchemy/engine/row.py +401 -0
  103. sqlalchemy/engine/strategies.py +19 -0
  104. sqlalchemy/engine/url.py +910 -0
  105. sqlalchemy/engine/util.py +167 -0
  106. sqlalchemy/event/__init__.py +25 -0
  107. sqlalchemy/event/api.py +225 -0
  108. sqlalchemy/event/attr.py +655 -0
  109. sqlalchemy/event/base.py +470 -0
  110. sqlalchemy/event/legacy.py +246 -0
  111. sqlalchemy/event/registry.py +386 -0
  112. sqlalchemy/events.py +17 -0
  113. sqlalchemy/exc.py +830 -0
  114. sqlalchemy/ext/__init__.py +11 -0
  115. sqlalchemy/ext/associationproxy.py +2013 -0
  116. sqlalchemy/ext/asyncio/__init__.py +25 -0
  117. sqlalchemy/ext/asyncio/base.py +279 -0
  118. sqlalchemy/ext/asyncio/engine.py +1466 -0
  119. sqlalchemy/ext/asyncio/exc.py +21 -0
  120. sqlalchemy/ext/asyncio/result.py +961 -0
  121. sqlalchemy/ext/asyncio/scoping.py +1614 -0
  122. sqlalchemy/ext/asyncio/session.py +1936 -0
  123. sqlalchemy/ext/automap.py +1691 -0
  124. sqlalchemy/ext/baked.py +574 -0
  125. sqlalchemy/ext/compiler.py +570 -0
  126. sqlalchemy/ext/declarative/__init__.py +65 -0
  127. sqlalchemy/ext/declarative/extensions.py +548 -0
  128. sqlalchemy/ext/horizontal_shard.py +481 -0
  129. sqlalchemy/ext/hybrid.py +1514 -0
  130. sqlalchemy/ext/indexable.py +341 -0
  131. sqlalchemy/ext/instrumentation.py +450 -0
  132. sqlalchemy/ext/mutable.py +1073 -0
  133. sqlalchemy/ext/mypy/__init__.py +6 -0
  134. sqlalchemy/ext/mypy/apply.py +320 -0
  135. sqlalchemy/ext/mypy/decl_class.py +515 -0
  136. sqlalchemy/ext/mypy/infer.py +590 -0
  137. sqlalchemy/ext/mypy/names.py +335 -0
  138. sqlalchemy/ext/mypy/plugin.py +303 -0
  139. sqlalchemy/ext/mypy/util.py +357 -0
  140. sqlalchemy/ext/orderinglist.py +416 -0
  141. sqlalchemy/ext/serializer.py +181 -0
  142. sqlalchemy/future/__init__.py +16 -0
  143. sqlalchemy/future/engine.py +15 -0
  144. sqlalchemy/inspection.py +174 -0
  145. sqlalchemy/log.py +288 -0
  146. sqlalchemy/orm/__init__.py +170 -0
  147. sqlalchemy/orm/_orm_constructors.py +2571 -0
  148. sqlalchemy/orm/_typing.py +179 -0
  149. sqlalchemy/orm/attributes.py +2835 -0
  150. sqlalchemy/orm/base.py +973 -0
  151. sqlalchemy/orm/bulk_persistence.py +2123 -0
  152. sqlalchemy/orm/clsregistry.py +571 -0
  153. sqlalchemy/orm/collections.py +1620 -0
  154. sqlalchemy/orm/context.py +3268 -0
  155. sqlalchemy/orm/decl_api.py +1883 -0
  156. sqlalchemy/orm/decl_base.py +2190 -0
  157. sqlalchemy/orm/dependency.py +1304 -0
  158. sqlalchemy/orm/descriptor_props.py +1076 -0
  159. sqlalchemy/orm/dynamic.py +300 -0
  160. sqlalchemy/orm/evaluator.py +379 -0
  161. sqlalchemy/orm/events.py +3261 -0
  162. sqlalchemy/orm/exc.py +228 -0
  163. sqlalchemy/orm/identity.py +302 -0
  164. sqlalchemy/orm/instrumentation.py +754 -0
  165. sqlalchemy/orm/interfaces.py +1474 -0
  166. sqlalchemy/orm/loading.py +1682 -0
  167. sqlalchemy/orm/mapped_collection.py +557 -0
  168. sqlalchemy/orm/mapper.py +4432 -0
  169. sqlalchemy/orm/path_registry.py +811 -0
  170. sqlalchemy/orm/persistence.py +1782 -0
  171. sqlalchemy/orm/properties.py +886 -0
  172. sqlalchemy/orm/query.py +3396 -0
  173. sqlalchemy/orm/relationships.py +3500 -0
  174. sqlalchemy/orm/scoping.py +2165 -0
  175. sqlalchemy/orm/session.py +5301 -0
  176. sqlalchemy/orm/state.py +1143 -0
  177. sqlalchemy/orm/state_changes.py +198 -0
  178. sqlalchemy/orm/strategies.py +3473 -0
  179. sqlalchemy/orm/strategy_options.py +2569 -0
  180. sqlalchemy/orm/sync.py +164 -0
  181. sqlalchemy/orm/unitofwork.py +796 -0
  182. sqlalchemy/orm/util.py +2424 -0
  183. sqlalchemy/orm/writeonly.py +678 -0
  184. sqlalchemy/pool/__init__.py +44 -0
  185. sqlalchemy/pool/base.py +1515 -0
  186. sqlalchemy/pool/events.py +370 -0
  187. sqlalchemy/pool/impl.py +581 -0
  188. sqlalchemy/py.typed +0 -0
  189. sqlalchemy/schema.py +70 -0
  190. sqlalchemy/sql/__init__.py +145 -0
  191. sqlalchemy/sql/_dml_constructors.py +140 -0
  192. sqlalchemy/sql/_elements_constructors.py +1850 -0
  193. sqlalchemy/sql/_orm_types.py +20 -0
  194. sqlalchemy/sql/_py_util.py +75 -0
  195. sqlalchemy/sql/_selectable_constructors.py +635 -0
  196. sqlalchemy/sql/_typing.py +460 -0
  197. sqlalchemy/sql/annotation.py +585 -0
  198. sqlalchemy/sql/base.py +2185 -0
  199. sqlalchemy/sql/cache_key.py +1057 -0
  200. sqlalchemy/sql/coercions.py +1405 -0
  201. sqlalchemy/sql/compiler.py +7818 -0
  202. sqlalchemy/sql/crud.py +1669 -0
  203. sqlalchemy/sql/ddl.py +1378 -0
  204. sqlalchemy/sql/default_comparator.py +552 -0
  205. sqlalchemy/sql/dml.py +1817 -0
  206. sqlalchemy/sql/elements.py +5499 -0
  207. sqlalchemy/sql/events.py +455 -0
  208. sqlalchemy/sql/expression.py +162 -0
  209. sqlalchemy/sql/functions.py +2055 -0
  210. sqlalchemy/sql/lambdas.py +1449 -0
  211. sqlalchemy/sql/naming.py +212 -0
  212. sqlalchemy/sql/operators.py +2579 -0
  213. sqlalchemy/sql/roles.py +323 -0
  214. sqlalchemy/sql/schema.py +6158 -0
  215. sqlalchemy/sql/selectable.py +7004 -0
  216. sqlalchemy/sql/sqltypes.py +3827 -0
  217. sqlalchemy/sql/traversals.py +1024 -0
  218. sqlalchemy/sql/type_api.py +2339 -0
  219. sqlalchemy/sql/util.py +1486 -0
  220. sqlalchemy/sql/visitors.py +1165 -0
  221. sqlalchemy/testing/__init__.py +96 -0
  222. sqlalchemy/testing/assertions.py +989 -0
  223. sqlalchemy/testing/assertsql.py +516 -0
  224. sqlalchemy/testing/asyncio.py +135 -0
  225. sqlalchemy/testing/config.py +427 -0
  226. sqlalchemy/testing/engines.py +472 -0
  227. sqlalchemy/testing/entities.py +117 -0
  228. sqlalchemy/testing/exclusions.py +435 -0
  229. sqlalchemy/testing/fixtures/__init__.py +28 -0
  230. sqlalchemy/testing/fixtures/base.py +366 -0
  231. sqlalchemy/testing/fixtures/mypy.py +312 -0
  232. sqlalchemy/testing/fixtures/orm.py +227 -0
  233. sqlalchemy/testing/fixtures/sql.py +503 -0
  234. sqlalchemy/testing/pickleable.py +155 -0
  235. sqlalchemy/testing/plugin/__init__.py +6 -0
  236. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  237. sqlalchemy/testing/plugin/plugin_base.py +779 -0
  238. sqlalchemy/testing/plugin/pytestplugin.py +868 -0
  239. sqlalchemy/testing/profiling.py +324 -0
  240. sqlalchemy/testing/provision.py +496 -0
  241. sqlalchemy/testing/requirements.py +1818 -0
  242. sqlalchemy/testing/schema.py +224 -0
  243. sqlalchemy/testing/suite/__init__.py +19 -0
  244. sqlalchemy/testing/suite/test_cte.py +211 -0
  245. sqlalchemy/testing/suite/test_ddl.py +389 -0
  246. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  247. sqlalchemy/testing/suite/test_dialect.py +740 -0
  248. sqlalchemy/testing/suite/test_insert.py +630 -0
  249. sqlalchemy/testing/suite/test_reflection.py +3225 -0
  250. sqlalchemy/testing/suite/test_results.py +502 -0
  251. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  252. sqlalchemy/testing/suite/test_select.py +1999 -0
  253. sqlalchemy/testing/suite/test_sequence.py +317 -0
  254. sqlalchemy/testing/suite/test_types.py +2141 -0
  255. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  256. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  257. sqlalchemy/testing/util.py +537 -0
  258. sqlalchemy/testing/warnings.py +52 -0
  259. sqlalchemy/types.py +76 -0
  260. sqlalchemy/util/__init__.py +160 -0
  261. sqlalchemy/util/_collections.py +715 -0
  262. sqlalchemy/util/_concurrency_py3k.py +288 -0
  263. sqlalchemy/util/_has_cy.py +40 -0
  264. sqlalchemy/util/_py_collections.py +541 -0
  265. sqlalchemy/util/compat.py +301 -0
  266. sqlalchemy/util/concurrency.py +108 -0
  267. sqlalchemy/util/deprecations.py +401 -0
  268. sqlalchemy/util/langhelpers.py +2218 -0
  269. sqlalchemy/util/preloaded.py +150 -0
  270. sqlalchemy/util/queue.py +322 -0
  271. sqlalchemy/util/tool_support.py +201 -0
  272. sqlalchemy/util/topological.py +120 -0
  273. sqlalchemy/util/typing.py +629 -0
@@ -0,0 +1,240 @@
1
+ # dialects/sqlite/dml.py
2
+ # Copyright (C) 2005-2024 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
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from .._typing import _OnConflictIndexElementsT
12
+ from .._typing import _OnConflictIndexWhereT
13
+ from .._typing import _OnConflictSetT
14
+ from .._typing import _OnConflictWhereT
15
+ from ... import util
16
+ from ...sql import coercions
17
+ from ...sql import roles
18
+ from ...sql._typing import _DMLTableArgument
19
+ from ...sql.base import _exclusive_against
20
+ from ...sql.base import _generative
21
+ from ...sql.base import ColumnCollection
22
+ from ...sql.base import ReadOnlyColumnCollection
23
+ from ...sql.dml import Insert as StandardInsert
24
+ from ...sql.elements import ClauseElement
25
+ from ...sql.elements import KeyedColumnElement
26
+ from ...sql.expression import alias
27
+ from ...util.typing import Self
28
+
29
+ __all__ = ("Insert", "insert")
30
+
31
+
32
+ def insert(table: _DMLTableArgument) -> Insert:
33
+ """Construct a sqlite-specific variant :class:`_sqlite.Insert`
34
+ construct.
35
+
36
+ .. container:: inherited_member
37
+
38
+ The :func:`sqlalchemy.dialects.sqlite.insert` function creates
39
+ a :class:`sqlalchemy.dialects.sqlite.Insert`. This class is based
40
+ on the dialect-agnostic :class:`_sql.Insert` construct which may
41
+ be constructed using the :func:`_sql.insert` function in
42
+ SQLAlchemy Core.
43
+
44
+ The :class:`_sqlite.Insert` construct includes additional methods
45
+ :meth:`_sqlite.Insert.on_conflict_do_update`,
46
+ :meth:`_sqlite.Insert.on_conflict_do_nothing`.
47
+
48
+ """
49
+ return Insert(table)
50
+
51
+
52
+ class Insert(StandardInsert):
53
+ """SQLite-specific implementation of INSERT.
54
+
55
+ Adds methods for SQLite-specific syntaxes such as ON CONFLICT.
56
+
57
+ The :class:`_sqlite.Insert` object is created using the
58
+ :func:`sqlalchemy.dialects.sqlite.insert` function.
59
+
60
+ .. versionadded:: 1.4
61
+
62
+ .. seealso::
63
+
64
+ :ref:`sqlite_on_conflict_insert`
65
+
66
+ """
67
+
68
+ stringify_dialect = "sqlite"
69
+ inherit_cache = False
70
+
71
+ @util.memoized_property
72
+ def excluded(
73
+ self,
74
+ ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
75
+ """Provide the ``excluded`` namespace for an ON CONFLICT statement
76
+
77
+ SQLite's ON CONFLICT clause allows reference to the row that would
78
+ be inserted, known as ``excluded``. This attribute provides
79
+ all columns in this row to be referenceable.
80
+
81
+ .. tip:: The :attr:`_sqlite.Insert.excluded` attribute is an instance
82
+ of :class:`_expression.ColumnCollection`, which provides an
83
+ interface the same as that of the :attr:`_schema.Table.c`
84
+ collection described at :ref:`metadata_tables_and_columns`.
85
+ With this collection, ordinary names are accessible like attributes
86
+ (e.g. ``stmt.excluded.some_column``), but special names and
87
+ dictionary method names should be accessed using indexed access,
88
+ such as ``stmt.excluded["column name"]`` or
89
+ ``stmt.excluded["values"]``. See the docstring for
90
+ :class:`_expression.ColumnCollection` for further examples.
91
+
92
+ """
93
+ return alias(self.table, name="excluded").columns
94
+
95
+ _on_conflict_exclusive = _exclusive_against(
96
+ "_post_values_clause",
97
+ msgs={
98
+ "_post_values_clause": "This Insert construct already has "
99
+ "an ON CONFLICT clause established"
100
+ },
101
+ )
102
+
103
+ @_generative
104
+ @_on_conflict_exclusive
105
+ def on_conflict_do_update(
106
+ self,
107
+ index_elements: _OnConflictIndexElementsT = None,
108
+ index_where: _OnConflictIndexWhereT = None,
109
+ set_: _OnConflictSetT = None,
110
+ where: _OnConflictWhereT = None,
111
+ ) -> Self:
112
+ r"""
113
+ Specifies a DO UPDATE SET action for ON CONFLICT clause.
114
+
115
+ :param index_elements:
116
+ A sequence consisting of string column names, :class:`_schema.Column`
117
+ objects, or other column expression objects that will be used
118
+ to infer a target index or unique constraint.
119
+
120
+ :param index_where:
121
+ Additional WHERE criterion that can be used to infer a
122
+ conditional target index.
123
+
124
+ :param set\_:
125
+ A dictionary or other mapping object
126
+ where the keys are either names of columns in the target table,
127
+ or :class:`_schema.Column` objects or other ORM-mapped columns
128
+ matching that of the target table, and expressions or literals
129
+ as values, specifying the ``SET`` actions to take.
130
+
131
+ .. versionadded:: 1.4 The
132
+ :paramref:`_sqlite.Insert.on_conflict_do_update.set_`
133
+ parameter supports :class:`_schema.Column` objects from the target
134
+ :class:`_schema.Table` as keys.
135
+
136
+ .. warning:: This dictionary does **not** take into account
137
+ Python-specified default UPDATE values or generation functions,
138
+ e.g. those specified using :paramref:`_schema.Column.onupdate`.
139
+ These values will not be exercised for an ON CONFLICT style of
140
+ UPDATE, unless they are manually specified in the
141
+ :paramref:`.Insert.on_conflict_do_update.set_` dictionary.
142
+
143
+ :param where:
144
+ Optional argument. If present, can be a literal SQL
145
+ string or an acceptable expression for a ``WHERE`` clause
146
+ that restricts the rows affected by ``DO UPDATE SET``. Rows
147
+ not meeting the ``WHERE`` condition will not be updated
148
+ (effectively a ``DO NOTHING`` for those rows).
149
+
150
+ """
151
+
152
+ self._post_values_clause = OnConflictDoUpdate(
153
+ index_elements, index_where, set_, where
154
+ )
155
+ return self
156
+
157
+ @_generative
158
+ @_on_conflict_exclusive
159
+ def on_conflict_do_nothing(
160
+ self,
161
+ index_elements: _OnConflictIndexElementsT = None,
162
+ index_where: _OnConflictIndexWhereT = None,
163
+ ) -> Self:
164
+ """
165
+ Specifies a DO NOTHING action for ON CONFLICT clause.
166
+
167
+ :param index_elements:
168
+ A sequence consisting of string column names, :class:`_schema.Column`
169
+ objects, or other column expression objects that will be used
170
+ to infer a target index or unique constraint.
171
+
172
+ :param index_where:
173
+ Additional WHERE criterion that can be used to infer a
174
+ conditional target index.
175
+
176
+ """
177
+
178
+ self._post_values_clause = OnConflictDoNothing(
179
+ index_elements, index_where
180
+ )
181
+ return self
182
+
183
+
184
+ class OnConflictClause(ClauseElement):
185
+ stringify_dialect = "sqlite"
186
+
187
+ constraint_target: None
188
+ inferred_target_elements: _OnConflictIndexElementsT
189
+ inferred_target_whereclause: _OnConflictIndexWhereT
190
+
191
+ def __init__(
192
+ self,
193
+ index_elements: _OnConflictIndexElementsT = None,
194
+ index_where: _OnConflictIndexWhereT = None,
195
+ ):
196
+ if index_elements is not None:
197
+ self.constraint_target = None
198
+ self.inferred_target_elements = index_elements
199
+ self.inferred_target_whereclause = index_where
200
+ else:
201
+ self.constraint_target = self.inferred_target_elements = (
202
+ self.inferred_target_whereclause
203
+ ) = None
204
+
205
+
206
+ class OnConflictDoNothing(OnConflictClause):
207
+ __visit_name__ = "on_conflict_do_nothing"
208
+
209
+
210
+ class OnConflictDoUpdate(OnConflictClause):
211
+ __visit_name__ = "on_conflict_do_update"
212
+
213
+ def __init__(
214
+ self,
215
+ index_elements: _OnConflictIndexElementsT = None,
216
+ index_where: _OnConflictIndexWhereT = None,
217
+ set_: _OnConflictSetT = None,
218
+ where: _OnConflictWhereT = None,
219
+ ):
220
+ super().__init__(
221
+ index_elements=index_elements,
222
+ index_where=index_where,
223
+ )
224
+
225
+ if isinstance(set_, dict):
226
+ if not set_:
227
+ raise ValueError("set parameter dictionary must not be empty")
228
+ elif isinstance(set_, ColumnCollection):
229
+ set_ = dict(set_)
230
+ else:
231
+ raise ValueError(
232
+ "set parameter must be a non-empty dictionary "
233
+ "or a ColumnCollection such as the `.c.` collection "
234
+ "of a Table object"
235
+ )
236
+ self.update_values_to_set = [
237
+ (coercions.expect(roles.DMLColumnRole, key), value)
238
+ for key, value in set_.items()
239
+ ]
240
+ self.update_whereclause = where
@@ -0,0 +1,92 @@
1
+ # dialects/sqlite/json.py
2
+ # Copyright (C) 2005-2024 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
+ # mypy: ignore-errors
8
+
9
+ from ... import types as sqltypes
10
+
11
+
12
+ class JSON(sqltypes.JSON):
13
+ """SQLite JSON type.
14
+
15
+ SQLite supports JSON as of version 3.9 through its JSON1_ extension. Note
16
+ that JSON1_ is a
17
+ `loadable extension <https://www.sqlite.org/loadext.html>`_ and as such
18
+ may not be available, or may require run-time loading.
19
+
20
+ :class:`_sqlite.JSON` is used automatically whenever the base
21
+ :class:`_types.JSON` datatype is used against a SQLite backend.
22
+
23
+ .. seealso::
24
+
25
+ :class:`_types.JSON` - main documentation for the generic
26
+ cross-platform JSON datatype.
27
+
28
+ The :class:`_sqlite.JSON` type supports persistence of JSON values
29
+ as well as the core index operations provided by :class:`_types.JSON`
30
+ datatype, by adapting the operations to render the ``JSON_EXTRACT``
31
+ function wrapped in the ``JSON_QUOTE`` function at the database level.
32
+ Extracted values are quoted in order to ensure that the results are
33
+ always JSON string values.
34
+
35
+
36
+ .. versionadded:: 1.3
37
+
38
+
39
+ .. _JSON1: https://www.sqlite.org/json1.html
40
+
41
+ """
42
+
43
+
44
+ # Note: these objects currently match exactly those of MySQL, however since
45
+ # these are not generalizable to all JSON implementations, remain separately
46
+ # implemented for each dialect.
47
+ class _FormatTypeMixin:
48
+ def _format_value(self, value):
49
+ raise NotImplementedError()
50
+
51
+ def bind_processor(self, dialect):
52
+ super_proc = self.string_bind_processor(dialect)
53
+
54
+ def process(value):
55
+ value = self._format_value(value)
56
+ if super_proc:
57
+ value = super_proc(value)
58
+ return value
59
+
60
+ return process
61
+
62
+ def literal_processor(self, dialect):
63
+ super_proc = self.string_literal_processor(dialect)
64
+
65
+ def process(value):
66
+ value = self._format_value(value)
67
+ if super_proc:
68
+ value = super_proc(value)
69
+ return value
70
+
71
+ return process
72
+
73
+
74
+ class JSONIndexType(_FormatTypeMixin, sqltypes.JSON.JSONIndexType):
75
+ def _format_value(self, value):
76
+ if isinstance(value, int):
77
+ value = "$[%s]" % value
78
+ else:
79
+ value = '$."%s"' % value
80
+ return value
81
+
82
+
83
+ class JSONPathType(_FormatTypeMixin, sqltypes.JSON.JSONPathType):
84
+ def _format_value(self, value):
85
+ return "$%s" % (
86
+ "".join(
87
+ [
88
+ "[%s]" % elem if isinstance(elem, int) else '."%s"' % elem
89
+ for elem in value
90
+ ]
91
+ )
92
+ )
@@ -0,0 +1,198 @@
1
+ # dialects/sqlite/provision.py
2
+ # Copyright (C) 2005-2024 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
+ # mypy: ignore-errors
8
+
9
+ import os
10
+ import re
11
+
12
+ from ... import exc
13
+ from ...engine import url as sa_url
14
+ from ...testing.provision import create_db
15
+ from ...testing.provision import drop_db
16
+ from ...testing.provision import follower_url_from_main
17
+ from ...testing.provision import generate_driver_url
18
+ from ...testing.provision import log
19
+ from ...testing.provision import post_configure_engine
20
+ from ...testing.provision import run_reap_dbs
21
+ from ...testing.provision import stop_test_class_outside_fixtures
22
+ from ...testing.provision import temp_table_keyword_args
23
+ from ...testing.provision import upsert
24
+
25
+
26
+ # TODO: I can't get this to build dynamically with pytest-xdist procs
27
+ _drivernames = {
28
+ "pysqlite",
29
+ "aiosqlite",
30
+ "pysqlcipher",
31
+ "pysqlite_numeric",
32
+ "pysqlite_dollar",
33
+ }
34
+
35
+
36
+ def _format_url(url, driver, ident):
37
+ """given a sqlite url + desired driver + ident, make a canonical
38
+ URL out of it
39
+
40
+ """
41
+ url = sa_url.make_url(url)
42
+
43
+ if driver is None:
44
+ driver = url.get_driver_name()
45
+
46
+ filename = url.database
47
+
48
+ needs_enc = driver == "pysqlcipher"
49
+ name_token = None
50
+
51
+ if filename and filename != ":memory:":
52
+ assert "test_schema" not in filename
53
+ tokens = re.split(r"[_\.]", filename)
54
+
55
+ new_filename = f"{driver}"
56
+
57
+ for token in tokens:
58
+ if token in _drivernames:
59
+ if driver is None:
60
+ driver = token
61
+ continue
62
+ elif token in ("db", "enc"):
63
+ continue
64
+ elif name_token is None:
65
+ name_token = token.strip("_")
66
+
67
+ assert name_token, f"sqlite filename has no name token: {url.database}"
68
+
69
+ new_filename = f"{name_token}_{driver}"
70
+ if ident:
71
+ new_filename += f"_{ident}"
72
+ new_filename += ".db"
73
+ if needs_enc:
74
+ new_filename += ".enc"
75
+ url = url.set(database=new_filename)
76
+
77
+ if needs_enc:
78
+ url = url.set(password="test")
79
+
80
+ url = url.set(drivername="sqlite+%s" % (driver,))
81
+
82
+ return url
83
+
84
+
85
+ @generate_driver_url.for_db("sqlite")
86
+ def generate_driver_url(url, driver, query_str):
87
+ url = _format_url(url, driver, None)
88
+
89
+ try:
90
+ url.get_dialect()
91
+ except exc.NoSuchModuleError:
92
+ return None
93
+ else:
94
+ return url
95
+
96
+
97
+ @follower_url_from_main.for_db("sqlite")
98
+ def _sqlite_follower_url_from_main(url, ident):
99
+ return _format_url(url, None, ident)
100
+
101
+
102
+ @post_configure_engine.for_db("sqlite")
103
+ def _sqlite_post_configure_engine(url, engine, follower_ident):
104
+ from sqlalchemy import event
105
+
106
+ if follower_ident:
107
+ attach_path = f"{follower_ident}_{engine.driver}_test_schema.db"
108
+ else:
109
+ attach_path = f"{engine.driver}_test_schema.db"
110
+
111
+ @event.listens_for(engine, "connect")
112
+ def connect(dbapi_connection, connection_record):
113
+ # use file DBs in all cases, memory acts kind of strangely
114
+ # as an attached
115
+
116
+ # NOTE! this has to be done *per connection*. New sqlite connection,
117
+ # as we get with say, QueuePool, the attaches are gone.
118
+ # so schemes to delete those attached files have to be done at the
119
+ # filesystem level and not rely upon what attachments are in a
120
+ # particular SQLite connection
121
+ dbapi_connection.execute(
122
+ f'ATTACH DATABASE "{attach_path}" AS test_schema'
123
+ )
124
+
125
+ @event.listens_for(engine, "engine_disposed")
126
+ def dispose(engine):
127
+ """most databases should be dropped using
128
+ stop_test_class_outside_fixtures
129
+
130
+ however a few tests like AttachedDBTest might not get triggered on
131
+ that main hook
132
+
133
+ """
134
+
135
+ if os.path.exists(attach_path):
136
+ os.remove(attach_path)
137
+
138
+ filename = engine.url.database
139
+
140
+ if filename and filename != ":memory:" and os.path.exists(filename):
141
+ os.remove(filename)
142
+
143
+
144
+ @create_db.for_db("sqlite")
145
+ def _sqlite_create_db(cfg, eng, ident):
146
+ pass
147
+
148
+
149
+ @drop_db.for_db("sqlite")
150
+ def _sqlite_drop_db(cfg, eng, ident):
151
+ _drop_dbs_w_ident(eng.url.database, eng.driver, ident)
152
+
153
+
154
+ def _drop_dbs_w_ident(databasename, driver, ident):
155
+ for path in os.listdir("."):
156
+ fname, ext = os.path.split(path)
157
+ if ident in fname and ext in [".db", ".db.enc"]:
158
+ log.info("deleting SQLite database file: %s", path)
159
+ os.remove(path)
160
+
161
+
162
+ @stop_test_class_outside_fixtures.for_db("sqlite")
163
+ def stop_test_class_outside_fixtures(config, db, cls):
164
+ db.dispose()
165
+
166
+
167
+ @temp_table_keyword_args.for_db("sqlite")
168
+ def _sqlite_temp_table_keyword_args(cfg, eng):
169
+ return {"prefixes": ["TEMPORARY"]}
170
+
171
+
172
+ @run_reap_dbs.for_db("sqlite")
173
+ def _reap_sqlite_dbs(url, idents):
174
+ log.info("db reaper connecting to %r", url)
175
+ log.info("identifiers in file: %s", ", ".join(idents))
176
+ url = sa_url.make_url(url)
177
+ for ident in idents:
178
+ for drivername in _drivernames:
179
+ _drop_dbs_w_ident(url.database, drivername, ident)
180
+
181
+
182
+ @upsert.for_db("sqlite")
183
+ def _upsert(
184
+ cfg, table, returning, *, set_lambda=None, sort_by_parameter_order=False
185
+ ):
186
+ from sqlalchemy.dialects.sqlite import insert
187
+
188
+ stmt = insert(table)
189
+
190
+ if set_lambda:
191
+ stmt = stmt.on_conflict_do_update(set_=set_lambda(stmt.excluded))
192
+ else:
193
+ stmt = stmt.on_conflict_do_nothing()
194
+
195
+ stmt = stmt.returning(
196
+ *returning, sort_by_parameter_order=sort_by_parameter_order
197
+ )
198
+ return stmt
@@ -0,0 +1,155 @@
1
+ # dialects/sqlite/pysqlcipher.py
2
+ # Copyright (C) 2005-2024 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
+ # mypy: ignore-errors
8
+
9
+
10
+ """
11
+ .. dialect:: sqlite+pysqlcipher
12
+ :name: pysqlcipher
13
+ :dbapi: sqlcipher 3 or pysqlcipher
14
+ :connectstring: sqlite+pysqlcipher://:passphrase@/file_path[?kdf_iter=<iter>]
15
+
16
+ Dialect for support of DBAPIs that make use of the
17
+ `SQLCipher <https://www.zetetic.net/sqlcipher>`_ backend.
18
+
19
+
20
+ Driver
21
+ ------
22
+
23
+ Current dialect selection logic is:
24
+
25
+ * If the :paramref:`_sa.create_engine.module` parameter supplies a DBAPI module,
26
+ that module is used.
27
+ * Otherwise for Python 3, choose https://pypi.org/project/sqlcipher3/
28
+ * If not available, fall back to https://pypi.org/project/pysqlcipher3/
29
+ * For Python 2, https://pypi.org/project/pysqlcipher/ is used.
30
+
31
+ .. warning:: The ``pysqlcipher3`` and ``pysqlcipher`` DBAPI drivers are no
32
+ longer maintained; the ``sqlcipher3`` driver as of this writing appears
33
+ to be current. For future compatibility, any pysqlcipher-compatible DBAPI
34
+ may be used as follows::
35
+
36
+ import sqlcipher_compatible_driver
37
+
38
+ from sqlalchemy import create_engine
39
+
40
+ e = create_engine(
41
+ "sqlite+pysqlcipher://:password@/dbname.db",
42
+ module=sqlcipher_compatible_driver
43
+ )
44
+
45
+ These drivers make use of the SQLCipher engine. This system essentially
46
+ introduces new PRAGMA commands to SQLite which allows the setting of a
47
+ passphrase and other encryption parameters, allowing the database file to be
48
+ encrypted.
49
+
50
+
51
+ Connect Strings
52
+ ---------------
53
+
54
+ The format of the connect string is in every way the same as that
55
+ of the :mod:`~sqlalchemy.dialects.sqlite.pysqlite` driver, except that the
56
+ "password" field is now accepted, which should contain a passphrase::
57
+
58
+ e = create_engine('sqlite+pysqlcipher://:testing@/foo.db')
59
+
60
+ For an absolute file path, two leading slashes should be used for the
61
+ database name::
62
+
63
+ e = create_engine('sqlite+pysqlcipher://:testing@//path/to/foo.db')
64
+
65
+ A selection of additional encryption-related pragmas supported by SQLCipher
66
+ as documented at https://www.zetetic.net/sqlcipher/sqlcipher-api/ can be passed
67
+ in the query string, and will result in that PRAGMA being called for each
68
+ new connection. Currently, ``cipher``, ``kdf_iter``
69
+ ``cipher_page_size`` and ``cipher_use_hmac`` are supported::
70
+
71
+ e = create_engine('sqlite+pysqlcipher://:testing@/foo.db?cipher=aes-256-cfb&kdf_iter=64000')
72
+
73
+ .. warning:: Previous versions of sqlalchemy did not take into consideration
74
+ the encryption-related pragmas passed in the url string, that were silently
75
+ ignored. This may cause errors when opening files saved by a
76
+ previous sqlalchemy version if the encryption options do not match.
77
+
78
+
79
+ Pooling Behavior
80
+ ----------------
81
+
82
+ The driver makes a change to the default pool behavior of pysqlite
83
+ as described in :ref:`pysqlite_threading_pooling`. The pysqlcipher driver
84
+ has been observed to be significantly slower on connection than the
85
+ pysqlite driver, most likely due to the encryption overhead, so the
86
+ dialect here defaults to using the :class:`.SingletonThreadPool`
87
+ implementation,
88
+ instead of the :class:`.NullPool` pool used by pysqlite. As always, the pool
89
+ implementation is entirely configurable using the
90
+ :paramref:`_sa.create_engine.poolclass` parameter; the :class:`.
91
+ StaticPool` may
92
+ be more feasible for single-threaded use, or :class:`.NullPool` may be used
93
+ to prevent unencrypted connections from being held open for long periods of
94
+ time, at the expense of slower startup time for new connections.
95
+
96
+
97
+ """ # noqa
98
+
99
+ from .pysqlite import SQLiteDialect_pysqlite
100
+ from ... import pool
101
+
102
+
103
+ class SQLiteDialect_pysqlcipher(SQLiteDialect_pysqlite):
104
+ driver = "pysqlcipher"
105
+ supports_statement_cache = True
106
+
107
+ pragmas = ("kdf_iter", "cipher", "cipher_page_size", "cipher_use_hmac")
108
+
109
+ @classmethod
110
+ def import_dbapi(cls):
111
+ try:
112
+ import sqlcipher3 as sqlcipher
113
+ except ImportError:
114
+ pass
115
+ else:
116
+ return sqlcipher
117
+
118
+ from pysqlcipher3 import dbapi2 as sqlcipher
119
+
120
+ return sqlcipher
121
+
122
+ @classmethod
123
+ def get_pool_class(cls, url):
124
+ return pool.SingletonThreadPool
125
+
126
+ def on_connect_url(self, url):
127
+ super_on_connect = super().on_connect_url(url)
128
+
129
+ # pull the info we need from the URL early. Even though URL
130
+ # is immutable, we don't want any in-place changes to the URL
131
+ # to affect things
132
+ passphrase = url.password or ""
133
+ url_query = dict(url.query)
134
+
135
+ def on_connect(conn):
136
+ cursor = conn.cursor()
137
+ cursor.execute('pragma key="%s"' % passphrase)
138
+ for prag in self.pragmas:
139
+ value = url_query.get(prag, None)
140
+ if value is not None:
141
+ cursor.execute('pragma %s="%s"' % (prag, value))
142
+ cursor.close()
143
+
144
+ if super_on_connect:
145
+ super_on_connect(conn)
146
+
147
+ return on_connect
148
+
149
+ def create_connect_args(self, url):
150
+ plain_url = url._replace(password=None)
151
+ plain_url = plain_url.difference_update_query(self.pragmas)
152
+ return super().create_connect_args(plain_url)
153
+
154
+
155
+ dialect = SQLiteDialect_pysqlcipher