pyoq-sql 1.0.0__py3-none-any.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 (264) hide show
  1. pyoq/__init__.py +7 -0
  2. pyoq/__main__.py +5 -0
  3. pyoq/_native.pyi +5 -0
  4. pyoq/cli/__init__.py +5 -0
  5. pyoq/cli/commands.py +270 -0
  6. pyoq/cli/defaults.py +98 -0
  7. pyoq/cli/services.py +97 -0
  8. pyoq/config/__init__.py +19 -0
  9. pyoq/config/loader.py +204 -0
  10. pyoq/config/models.py +243 -0
  11. pyoq/descriptors.py +165 -0
  12. pyoq/diagnostics/__init__.py +68 -0
  13. pyoq/diagnostics/budget.py +136 -0
  14. pyoq/diagnostics/events.py +137 -0
  15. pyoq/diagnostics/fingerprint.py +267 -0
  16. pyoq/diagnostics/instrumented.py +237 -0
  17. pyoq/diagnostics/metrics.py +61 -0
  18. pyoq/diagnostics/observation.py +227 -0
  19. pyoq/diagnostics/scoped.py +103 -0
  20. pyoq/django/__init__.py +15 -0
  21. pyoq/django/apps.py +17 -0
  22. pyoq/django/execution.py +317 -0
  23. pyoq/django/generation.py +59 -0
  24. pyoq/django/management/__init__.py +0 -0
  25. pyoq/django/management/commands/__init__.py +0 -0
  26. pyoq/django/management/commands/makemigrations.py +53 -0
  27. pyoq/django/management/commands/pyoq_codegen.py +75 -0
  28. pyoq/django/parameters.py +101 -0
  29. pyoq/django/schema.py +379 -0
  30. pyoq/django/settings.py +87 -0
  31. pyoq/django/timeouts.py +105 -0
  32. pyoq/dsl/__init__.py +64 -0
  33. pyoq/dsl/aio/__init__.py +31 -0
  34. pyoq/dsl/aio/context.py +295 -0
  35. pyoq/dsl/aio/queries.py +335 -0
  36. pyoq/dsl/aio/writes.py +368 -0
  37. pyoq/dsl/context.py +326 -0
  38. pyoq/dsl/entry.py +37 -0
  39. pyoq/dsl/labels.py +36 -0
  40. pyoq/dsl/queries.py +339 -0
  41. pyoq/dsl/result.py +164 -0
  42. pyoq/dsl/writes.py +360 -0
  43. pyoq/errors.py +317 -0
  44. pyoq/fastapi/__init__.py +32 -0
  45. pyoq/fastapi/dependencies.py +167 -0
  46. pyoq/fastapi/lifespan.py +119 -0
  47. pyoq/fetching/__init__.py +55 -0
  48. pyoq/fetching/collections.py +136 -0
  49. pyoq/fetching/execution.py +587 -0
  50. pyoq/fetching/joined.py +79 -0
  51. pyoq/fetching/nesting.py +183 -0
  52. pyoq/fetching/plans.py +541 -0
  53. pyoq/fetching/select_in.py +149 -0
  54. pyoq/fetching/tables.py +110 -0
  55. pyoq/generation/__init__.py +54 -0
  56. pyoq/generation/cleanup.py +44 -0
  57. pyoq/generation/contracts.py +248 -0
  58. pyoq/generation/drift.py +169 -0
  59. pyoq/generation/lock.py +33 -0
  60. pyoq/generation/manifest.py +114 -0
  61. pyoq/generation/model.py +1001 -0
  62. pyoq/generation/pipeline.py +119 -0
  63. pyoq/generation/rendering/__init__.py +5 -0
  64. pyoq/generation/rendering/domains.py +51 -0
  65. pyoq/generation/rendering/enums.py +29 -0
  66. pyoq/generation/rendering/exports.py +70 -0
  67. pyoq/generation/rendering/imports.py +63 -0
  68. pyoq/generation/rendering/package.py +56 -0
  69. pyoq/generation/rendering/relations.py +133 -0
  70. pyoq/generation/rendering/routines.py +396 -0
  71. pyoq/generation/rendering/rows.py +79 -0
  72. pyoq/generation/rendering/source.py +121 -0
  73. pyoq/generation/rendering/tables.py +300 -0
  74. pyoq/generation/rendering/writes.py +514 -0
  75. pyoq/generation/validation.py +27 -0
  76. pyoq/generation/writer.py +184 -0
  77. pyoq/hydration/__init__.py +24 -0
  78. pyoq/hydration/engine.py +155 -0
  79. pyoq/hydration/identity.py +194 -0
  80. pyoq/hydration/plan.py +116 -0
  81. pyoq/migrations/__init__.py +9 -0
  82. pyoq/migrations/alembic.py +106 -0
  83. pyoq/migrations/hooks.py +75 -0
  84. pyoq/naming.py +261 -0
  85. pyoq/policies/__init__.py +47 -0
  86. pyoq/policies/bypass.py +122 -0
  87. pyoq/policies/governed.py +430 -0
  88. pyoq/policies/model.py +242 -0
  89. pyoq/policies/rewriting.py +263 -0
  90. pyoq/py.typed +1 -0
  91. pyoq/query/__init__.py +312 -0
  92. pyoq/query/aggregates.py +172 -0
  93. pyoq/query/arrays.py +65 -0
  94. pyoq/query/binding.py +52 -0
  95. pyoq/query/capabilities.py +317 -0
  96. pyoq/query/casts.py +73 -0
  97. pyoq/query/choices.py +185 -0
  98. pyoq/query/decoding.py +360 -0
  99. pyoq/query/documents.py +56 -0
  100. pyoq/query/execution/__init__.py +63 -0
  101. pyoq/query/execution/aio/__init__.py +31 -0
  102. pyoq/query/execution/aio/operations.py +228 -0
  103. pyoq/query/execution/aio/pooling.py +233 -0
  104. pyoq/query/execution/aio/streaming.py +161 -0
  105. pyoq/query/execution/aio/transactions.py +105 -0
  106. pyoq/query/execution/batch.py +96 -0
  107. pyoq/query/execution/binding_style.py +30 -0
  108. pyoq/query/execution/compilation.py +48 -0
  109. pyoq/query/execution/context.py +61 -0
  110. pyoq/query/execution/control.py +50 -0
  111. pyoq/query/execution/operations.py +224 -0
  112. pyoq/query/execution/planning.py +107 -0
  113. pyoq/query/execution/pooling.py +279 -0
  114. pyoq/query/execution/results.py +36 -0
  115. pyoq/query/execution/streaming.py +178 -0
  116. pyoq/query/execution/transactions.py +95 -0
  117. pyoq/query/expressions.py +1200 -0
  118. pyoq/query/fields.py +60 -0
  119. pyoq/query/mysql/__init__.py +59 -0
  120. pyoq/query/mysql/aio/__init__.py +38 -0
  121. pyoq/query/mysql/aio/commands.py +389 -0
  122. pyoq/query/mysql/aio/driver.py +196 -0
  123. pyoq/query/mysql/aio/executor.py +123 -0
  124. pyoq/query/mysql/aio/factory.py +26 -0
  125. pyoq/query/mysql/aio/operations.py +38 -0
  126. pyoq/query/mysql/aio/pool.py +53 -0
  127. pyoq/query/mysql/aio/transactions.py +313 -0
  128. pyoq/query/mysql/commands.py +354 -0
  129. pyoq/query/mysql/compiler.py +134 -0
  130. pyoq/query/mysql/context.py +20 -0
  131. pyoq/query/mysql/executor.py +126 -0
  132. pyoq/query/mysql/expressions.py +244 -0
  133. pyoq/query/mysql/factory.py +46 -0
  134. pyoq/query/mysql/health.py +66 -0
  135. pyoq/query/mysql/identifiers.py +9 -0
  136. pyoq/query/mysql/model.py +79 -0
  137. pyoq/query/mysql/operations.py +43 -0
  138. pyoq/query/mysql/parameters.py +69 -0
  139. pyoq/query/mysql/planning.py +20 -0
  140. pyoq/query/mysql/pool.py +67 -0
  141. pyoq/query/mysql/transactions.py +331 -0
  142. pyoq/query/mysql/writes.py +73 -0
  143. pyoq/query/nodes.py +750 -0
  144. pyoq/query/postgres/__init__.py +48 -0
  145. pyoq/query/postgres/aio/__init__.py +25 -0
  146. pyoq/query/postgres/aio/bulk.py +56 -0
  147. pyoq/query/postgres/aio/commands.py +264 -0
  148. pyoq/query/postgres/aio/executor.py +152 -0
  149. pyoq/query/postgres/aio/factory.py +26 -0
  150. pyoq/query/postgres/aio/operations.py +26 -0
  151. pyoq/query/postgres/aio/pool.py +40 -0
  152. pyoq/query/postgres/aio/transactions.py +295 -0
  153. pyoq/query/postgres/bulk.py +62 -0
  154. pyoq/query/postgres/commands.py +238 -0
  155. pyoq/query/postgres/compiler.py +114 -0
  156. pyoq/query/postgres/context.py +20 -0
  157. pyoq/query/postgres/executor.py +147 -0
  158. pyoq/query/postgres/expressions.py +311 -0
  159. pyoq/query/postgres/factory.py +24 -0
  160. pyoq/query/postgres/health.py +24 -0
  161. pyoq/query/postgres/identifiers.py +9 -0
  162. pyoq/query/postgres/model.py +81 -0
  163. pyoq/query/postgres/operations.py +25 -0
  164. pyoq/query/postgres/parameters.py +71 -0
  165. pyoq/query/postgres/planning.py +20 -0
  166. pyoq/query/postgres/pool.py +52 -0
  167. pyoq/query/postgres/transactions.py +295 -0
  168. pyoq/query/postgres/writes.py +37 -0
  169. pyoq/query/projections.py +105 -0
  170. pyoq/query/raw.py +90 -0
  171. pyoq/query/recursion.py +265 -0
  172. pyoq/query/rendering/__init__.py +1 -0
  173. pyoq/query/rendering/expressions.py +913 -0
  174. pyoq/query/rendering/identifiers.py +40 -0
  175. pyoq/query/rendering/projections.py +63 -0
  176. pyoq/query/rendering/queries.py +334 -0
  177. pyoq/query/rendering/sources.py +66 -0
  178. pyoq/query/rendering/writes.py +176 -0
  179. pyoq/query/results.py +459 -0
  180. pyoq/query/routines.py +196 -0
  181. pyoq/query/rows.py +156 -0
  182. pyoq/query/select.py +793 -0
  183. pyoq/query/select_nodes.py +277 -0
  184. pyoq/query/sources.py +236 -0
  185. pyoq/query/sqlite/__init__.py +43 -0
  186. pyoq/query/sqlite/commands.py +201 -0
  187. pyoq/query/sqlite/compiler.py +139 -0
  188. pyoq/query/sqlite/context.py +20 -0
  189. pyoq/query/sqlite/executor.py +119 -0
  190. pyoq/query/sqlite/expressions.py +224 -0
  191. pyoq/query/sqlite/factory.py +32 -0
  192. pyoq/query/sqlite/health.py +28 -0
  193. pyoq/query/sqlite/identifiers.py +9 -0
  194. pyoq/query/sqlite/model.py +73 -0
  195. pyoq/query/sqlite/operations.py +36 -0
  196. pyoq/query/sqlite/parameters.py +50 -0
  197. pyoq/query/sqlite/planning.py +20 -0
  198. pyoq/query/sqlite/pool.py +50 -0
  199. pyoq/query/sqlite/streaming.py +13 -0
  200. pyoq/query/sqlite/transactions.py +274 -0
  201. pyoq/query/sqlite/writes.py +35 -0
  202. pyoq/query/statements.py +27 -0
  203. pyoq/query/values.py +23 -0
  204. pyoq/query/vendor.py +162 -0
  205. pyoq/query/windows.py +424 -0
  206. pyoq/query/write_nodes.py +174 -0
  207. pyoq/query/writes.py +628 -0
  208. pyoq/relations/__init__.py +66 -0
  209. pyoq/relations/batching.py +219 -0
  210. pyoq/relations/derivation.py +111 -0
  211. pyoq/relations/fetching.py +355 -0
  212. pyoq/relations/graph.py +245 -0
  213. pyoq/relations/loading.py +74 -0
  214. pyoq/relations/model.py +75 -0
  215. pyoq/relations/planning.py +206 -0
  216. pyoq/runtime/__init__.py +9 -0
  217. pyoq/runtime/kernels.py +25 -0
  218. pyoq/runtime/python.py +43 -0
  219. pyoq/runtime/selection.py +73 -0
  220. pyoq/sanic/__init__.py +32 -0
  221. pyoq/sanic/scope.py +197 -0
  222. pyoq/sanic/workers.py +129 -0
  223. pyoq/schema/__init__.py +108 -0
  224. pyoq/schema/codec.py +711 -0
  225. pyoq/schema/models.py +604 -0
  226. pyoq/schema/mysql/__init__.py +16 -0
  227. pyoq/schema/mysql/connection.py +72 -0
  228. pyoq/schema/mysql/dsn.py +72 -0
  229. pyoq/schema/mysql/records.py +354 -0
  230. pyoq/schema/mysql/reflection.py +309 -0
  231. pyoq/schema/mysql/source.py +30 -0
  232. pyoq/schema/mysql/sql.py +128 -0
  233. pyoq/schema/mysql/types.py +105 -0
  234. pyoq/schema/postgres/__init__.py +13 -0
  235. pyoq/schema/postgres/connection.py +62 -0
  236. pyoq/schema/postgres/records.py +384 -0
  237. pyoq/schema/postgres/reflection.py +466 -0
  238. pyoq/schema/postgres/source.py +30 -0
  239. pyoq/schema/postgres/sql.py +246 -0
  240. pyoq/schema/postgres/types.py +98 -0
  241. pyoq/schema/registry.py +45 -0
  242. pyoq/schema/source.py +15 -0
  243. pyoq/schema/sqlite/__init__.py +6 -0
  244. pyoq/schema/sqlite/connection.py +54 -0
  245. pyoq/schema/sqlite/records.py +167 -0
  246. pyoq/schema/sqlite/reflection.py +393 -0
  247. pyoq/schema/sqlite/source.py +30 -0
  248. pyoq/schema/sqlite/sql.py +254 -0
  249. pyoq/schema/sqlite/types.py +74 -0
  250. pyoq/serving/__init__.py +19 -0
  251. pyoq/serving/databases.py +107 -0
  252. pyoq/snapshots/__init__.py +20 -0
  253. pyoq/snapshots/drift.py +312 -0
  254. pyoq/snapshots/files.py +96 -0
  255. pyoq/snapshots/routing.py +40 -0
  256. pyoq/snapshots/source.py +33 -0
  257. pyoq/tracing/__init__.py +5 -0
  258. pyoq/tracing/spans.py +89 -0
  259. pyoq/unset.py +14 -0
  260. pyoq_sql-1.0.0.dist-info/METADATA +3034 -0
  261. pyoq_sql-1.0.0.dist-info/RECORD +264 -0
  262. pyoq_sql-1.0.0.dist-info/WHEEL +4 -0
  263. pyoq_sql-1.0.0.dist-info/entry_points.txt +3 -0
  264. pyoq_sql-1.0.0.dist-info/licenses/LICENSE +373 -0
pyoq/dsl/aio/writes.py ADDED
@@ -0,0 +1,368 @@
1
+ """The asynchronous counterpart of a write bound to its database."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Generic, TypeVar, TypeVarTuple, cast, overload
6
+
7
+ from pyoq.dsl.labels import labels_of
8
+ from pyoq.dsl.result import Result, Row
9
+ from pyoq.query.write_nodes import WriteNode
10
+ from pyoq.query.writes import (
11
+ ConflictUpdate,
12
+ DeleteStatement,
13
+ InsertStatement,
14
+ MutationStatement,
15
+ ReturningStatement,
16
+ UpdateStatement,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Callable, Iterable
21
+
22
+ from pyoq.query.execution.aio import AsyncQueryOperations
23
+ from pyoq.query.expressions import Expression
24
+ from pyoq.query.writes import ColumnReference
25
+
26
+ Values = TypeVarTuple("Values")
27
+ Mutation = TypeVar("Mutation", bound=MutationStatement[WriteNode])
28
+ ResultRow = TypeVar("ResultRow", bound=tuple[object, ...])
29
+ Mapped = TypeVar("Mapped")
30
+ ScalarValue = TypeVar("ScalarValue")
31
+ Value1 = TypeVar("Value1")
32
+ Value2 = TypeVar("Value2")
33
+ Value3 = TypeVar("Value3")
34
+ Value4 = TypeVar("Value4")
35
+ Value5 = TypeVar("Value5")
36
+ Value6 = TypeVar("Value6")
37
+ Value7 = TypeVar("Value7")
38
+ Value8 = TypeVar("Value8")
39
+ AssignedValue = TypeVar("AssignedValue")
40
+
41
+
42
+ class AsyncBoundReturning(Generic[ResultRow]):
43
+ """Rows a write handed back, read the way a query's rows are read."""
44
+
45
+ __slots__ = ("_operations", "_statement")
46
+ _operations: AsyncQueryOperations
47
+ _statement: ReturningStatement[ResultRow]
48
+
49
+ def __init__(
50
+ self,
51
+ operations: AsyncQueryOperations,
52
+ statement: ReturningStatement[ResultRow],
53
+ ) -> None:
54
+ object.__setattr__(self, "_operations", operations)
55
+ object.__setattr__(self, "_statement", statement)
56
+
57
+ def __setattr__(self, name: str, value: object) -> None:
58
+ message = f"{type(self).__name__} is immutable"
59
+ raise AttributeError(message, name, value)
60
+
61
+ @property
62
+ def statement(self) -> ReturningStatement[ResultRow]:
63
+ return self._statement
64
+
65
+ async def fetch_all(self) -> Result[ResultRow]:
66
+ rows = tuple(await self._operations.many(self._statement))
67
+ return Result(rows, labels_of(self._statement.node.returning))
68
+
69
+ async def fetch_one(self) -> Row[ResultRow]:
70
+ """Exactly one row, because that is what the caller said to expect."""
71
+ return Row(
72
+ await self._operations.one(self._statement),
73
+ labels_of(self._statement.node.returning),
74
+ )
75
+
76
+ async def fetch_optional(self) -> Row[ResultRow] | None:
77
+ row = await self._operations.one_or_none(self._statement)
78
+ return (
79
+ None if row is None else Row(row, labels_of(self._statement.node.returning))
80
+ )
81
+
82
+ async def fetch_optional_into(
83
+ self,
84
+ factory: Callable[..., Mapped],
85
+ /,
86
+ ) -> Mapped | None:
87
+ """One row built by the caller's own type, or nothing.
88
+
89
+ The row and the many-row result both take a factory inline.
90
+ A row that may be missing had to be tested for first, which
91
+ is the one shape that could not be asked for in the chain.
92
+ """
93
+ row = await self.fetch_optional()
94
+ return None if row is None else row.into(factory)
95
+
96
+ async def fetch_value(
97
+ self: AsyncBoundReturning[tuple[ScalarValue]],
98
+ /,
99
+ ) -> ScalarValue:
100
+ return await self._operations.scalar(self._statement)
101
+
102
+
103
+ class AsyncBoundMutation(Generic[Mutation]):
104
+ """What every bound write can do, whatever it is writing."""
105
+
106
+ __slots__ = ("_operations", "_statement")
107
+ _operations: AsyncQueryOperations
108
+ _statement: Mutation
109
+
110
+ def __init__(
111
+ self,
112
+ operations: AsyncQueryOperations,
113
+ statement: Mutation,
114
+ ) -> None:
115
+ object.__setattr__(self, "_operations", operations)
116
+ object.__setattr__(self, "_statement", statement)
117
+
118
+ def __setattr__(self, name: str, value: object) -> None:
119
+ message = f"{type(self).__name__} is immutable"
120
+ raise AttributeError(message, name, value)
121
+
122
+ @property
123
+ def statement(self) -> Mutation:
124
+ """The statement on its own, which cannot reach a database."""
125
+ return self._statement
126
+
127
+ async def execute(self) -> int:
128
+ """How many rows the database says it wrote."""
129
+ written = await self._operations.execute(self._statement)
130
+ return written.rows_affected
131
+
132
+ def returning_all(self) -> AsyncBoundReturning[tuple[object, ...]]:
133
+ """Every column the table declares, without listing them."""
134
+ return AsyncBoundReturning(self._operations, self._statement.returning_all())
135
+
136
+ @overload
137
+ def returning(
138
+ self,
139
+ value1: Expression[Value1],
140
+ /,
141
+ ) -> AsyncBoundReturning[tuple[Value1]]: ...
142
+
143
+ @overload
144
+ def returning(
145
+ self,
146
+ value1: Expression[Value1],
147
+ value2: Expression[Value2],
148
+ /,
149
+ ) -> AsyncBoundReturning[tuple[Value1, Value2]]: ...
150
+
151
+ @overload
152
+ def returning(
153
+ self,
154
+ value1: Expression[Value1],
155
+ value2: Expression[Value2],
156
+ value3: Expression[Value3],
157
+ /,
158
+ ) -> AsyncBoundReturning[tuple[Value1, Value2, Value3]]: ...
159
+
160
+ @overload
161
+ def returning(
162
+ self,
163
+ value1: Expression[Value1],
164
+ value2: Expression[Value2],
165
+ value3: Expression[Value3],
166
+ value4: Expression[Value4],
167
+ /,
168
+ ) -> AsyncBoundReturning[tuple[Value1, Value2, Value3, Value4]]: ...
169
+
170
+ @overload
171
+ def returning(
172
+ self,
173
+ value1: Expression[Value1],
174
+ value2: Expression[Value2],
175
+ value3: Expression[Value3],
176
+ value4: Expression[Value4],
177
+ value5: Expression[Value5],
178
+ /,
179
+ ) -> AsyncBoundReturning[tuple[Value1, Value2, Value3, Value4, Value5]]: ...
180
+
181
+ @overload
182
+ def returning(
183
+ self,
184
+ value1: Expression[Value1],
185
+ value2: Expression[Value2],
186
+ value3: Expression[Value3],
187
+ value4: Expression[Value4],
188
+ value5: Expression[Value5],
189
+ value6: Expression[Value6],
190
+ /,
191
+ ) -> AsyncBoundReturning[tuple[Value1, Value2, Value3, Value4, Value5, Value6]]: ...
192
+
193
+ @overload
194
+ def returning(
195
+ self,
196
+ value1: Expression[Value1],
197
+ value2: Expression[Value2],
198
+ value3: Expression[Value3],
199
+ value4: Expression[Value4],
200
+ value5: Expression[Value5],
201
+ value6: Expression[Value6],
202
+ value7: Expression[Value7],
203
+ /,
204
+ ) -> AsyncBoundReturning[
205
+ tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7]
206
+ ]: ...
207
+
208
+ @overload
209
+ def returning(
210
+ self,
211
+ value1: Expression[Value1],
212
+ value2: Expression[Value2],
213
+ value3: Expression[Value3],
214
+ value4: Expression[Value4],
215
+ value5: Expression[Value5],
216
+ value6: Expression[Value6],
217
+ value7: Expression[Value7],
218
+ value8: Expression[Value8],
219
+ /,
220
+ ) -> AsyncBoundReturning[
221
+ tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8]
222
+ ]: ...
223
+
224
+ @overload
225
+ def returning(
226
+ self,
227
+ value1: object,
228
+ value2: object,
229
+ value3: object,
230
+ value4: object,
231
+ value5: object,
232
+ value6: object,
233
+ value7: object,
234
+ value8: object,
235
+ /,
236
+ *values: object,
237
+ ) -> AsyncBoundReturning[tuple[object, ...]]:
238
+ """Wider than the overloads above name, where the types stop.
239
+
240
+ A statement is still a statement past eight returned columns,
241
+ and matching no overload would take the whole chain down with
242
+ it rather than just the tuple.
243
+ """
244
+
245
+ def returning( # type: ignore[misc]
246
+ self,
247
+ *values: object,
248
+ ) -> AsyncBoundReturning[tuple[object, ...]]:
249
+ # The builder declares typed overloads for callers; this is the one
250
+ # implementation behind them, reached variadically.
251
+ build = cast(
252
+ "Callable[..., ReturningStatement[tuple[object, ...]]]",
253
+ self._statement.returning,
254
+ )
255
+ return AsyncBoundReturning(self._operations, build(*values))
256
+
257
+
258
+ class AsyncBoundInsert(
259
+ AsyncBoundMutation[InsertStatement[*Values]],
260
+ Generic[*Values],
261
+ ):
262
+ """An INSERT and the database it will run against."""
263
+
264
+ __slots__ = ()
265
+
266
+ def values(self, *values: *Values) -> AsyncBoundInsert[*Values]:
267
+ return AsyncBoundInsert(self._operations, self._statement.values(*values))
268
+
269
+ def values_many(self, rows: Iterable[object], /) -> AsyncBoundInsert[*Values]:
270
+ return AsyncBoundInsert(self._operations, self._statement.values_many(rows))
271
+
272
+ def on_conflict_do_nothing(
273
+ self,
274
+ *columns: ColumnReference,
275
+ ) -> AsyncBoundInsert[*Values]:
276
+ return AsyncBoundInsert(
277
+ self._operations,
278
+ self._statement.on_conflict_do_nothing(*columns),
279
+ )
280
+
281
+ def on_conflict_do_update(
282
+ self,
283
+ *columns: ColumnReference,
284
+ ) -> AsyncBoundConflictUpdate[*Values]:
285
+ return AsyncBoundConflictUpdate(
286
+ self._operations,
287
+ self._statement.on_conflict_do_update(*columns),
288
+ )
289
+
290
+
291
+ class AsyncBoundConflictUpdate(
292
+ AsyncBoundMutation[ConflictUpdate[*Values]],
293
+ Generic[*Values],
294
+ ):
295
+ """The SET and WHERE that belong to a conflict clause."""
296
+
297
+ __slots__ = ()
298
+
299
+ def set(
300
+ self,
301
+ column: Expression[AssignedValue],
302
+ value: AssignedValue | Expression[AssignedValue],
303
+ /,
304
+ ) -> AsyncBoundConflictUpdate[*Values]:
305
+ return AsyncBoundConflictUpdate(
306
+ self._operations, self._statement.set(column, value)
307
+ )
308
+
309
+ def where(
310
+ self,
311
+ condition: Expression[bool],
312
+ /,
313
+ ) -> AsyncBoundConflictUpdate[*Values]:
314
+ return AsyncBoundConflictUpdate(
315
+ self._operations, self._statement.where(condition)
316
+ )
317
+
318
+
319
+ class AsyncBoundUpdate(AsyncBoundMutation[UpdateStatement]):
320
+ """An UPDATE, which has to say which rows it means."""
321
+
322
+ __slots__ = ()
323
+
324
+ def set(
325
+ self,
326
+ column: Expression[AssignedValue],
327
+ value: AssignedValue | Expression[AssignedValue],
328
+ /,
329
+ ) -> AsyncBoundUpdate:
330
+ return AsyncBoundUpdate(self._operations, self._statement.set(column, value))
331
+
332
+ def set_values(
333
+ self,
334
+ values: object,
335
+ /,
336
+ *columns: ColumnReference,
337
+ ) -> AsyncBoundUpdate:
338
+ return AsyncBoundUpdate(
339
+ self._operations, self._statement.set_values(values, *columns)
340
+ )
341
+
342
+ def where(self, condition: Expression[bool], /) -> AsyncBoundUpdate:
343
+ return AsyncBoundUpdate(self._operations, self._statement.where(condition))
344
+
345
+ def all_rows(self) -> AsyncBoundUpdate:
346
+ return AsyncBoundUpdate(self._operations, self._statement.all_rows())
347
+
348
+
349
+ class AsyncBoundDelete(AsyncBoundMutation[DeleteStatement]):
350
+ """A DELETE, which has to say which rows it means."""
351
+
352
+ __slots__ = ()
353
+
354
+ def where(self, condition: Expression[bool], /) -> AsyncBoundDelete:
355
+ return AsyncBoundDelete(self._operations, self._statement.where(condition))
356
+
357
+ def all_rows(self) -> AsyncBoundDelete:
358
+ return AsyncBoundDelete(self._operations, self._statement.all_rows())
359
+
360
+
361
+ __all__ = (
362
+ "AsyncBoundConflictUpdate",
363
+ "AsyncBoundDelete",
364
+ "AsyncBoundInsert",
365
+ "AsyncBoundMutation",
366
+ "AsyncBoundReturning",
367
+ "AsyncBoundUpdate",
368
+ )
pyoq/dsl/context.py ADDED
@@ -0,0 +1,326 @@
1
+ """The entry point that ties a query to a database.
2
+
3
+ PyOQ's builders describe a statement and never reach a database, which is what
4
+ makes them safe to share, cache, and test. A context does not change that: it
5
+ holds the operations to run against and hands back builders that carry it, so a
6
+ chain can end in a fetch instead of being passed to an executor.
7
+
8
+ The unattached builders are still there and still cannot execute. Anything built
9
+ with them runs through `fetch` or `execute` here.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import TYPE_CHECKING, TypeVar, TypeVarTuple, cast, overload
15
+
16
+ from pyoq.dsl.labels import labels_of
17
+ from pyoq.dsl.queries import BoundSelect
18
+ from pyoq.dsl.result import Result
19
+ from pyoq.dsl.writes import BoundDelete, BoundInsert, BoundUpdate
20
+ from pyoq.query.select import SelectQuery, select, select_from
21
+ from pyoq.query.select_nodes import SelectNode
22
+ from pyoq.query.writes import (
23
+ InsertStatement,
24
+ ReturningStatement,
25
+ delete_from,
26
+ insert_into,
27
+ update,
28
+ )
29
+
30
+ if TYPE_CHECKING:
31
+ from collections.abc import Callable
32
+
33
+ from pyoq.query.execution import QueryOperations
34
+ from pyoq.query.expressions import Expression
35
+ from pyoq.query.projections import Projection
36
+ from pyoq.query.routines import Call
37
+ from pyoq.query.select import Query
38
+ from pyoq.query.sources import ColumnedTable, TableLike
39
+
40
+ ResultRow = TypeVar("ResultRow", bound=tuple[object, ...])
41
+ Values = TypeVarTuple("Values")
42
+ Value1 = TypeVar("Value1")
43
+ Value2 = TypeVar("Value2")
44
+ Value3 = TypeVar("Value3")
45
+ Value4 = TypeVar("Value4")
46
+ Value5 = TypeVar("Value5")
47
+ Value6 = TypeVar("Value6")
48
+ Value7 = TypeVar("Value7")
49
+ Value8 = TypeVar("Value8")
50
+
51
+
52
+ class DslContext:
53
+ """A database to build against."""
54
+
55
+ __slots__ = ("_operations",)
56
+ _operations: QueryOperations
57
+
58
+ def __init__(self, operations: QueryOperations) -> None:
59
+ object.__setattr__(self, "_operations", operations)
60
+
61
+ def __setattr__(self, name: str, value: object) -> None:
62
+ message = f"{type(self).__name__} is immutable"
63
+ raise AttributeError(message, name, value)
64
+
65
+ @property
66
+ def operations(self) -> QueryOperations:
67
+ return self._operations
68
+
69
+ @overload
70
+ def select(
71
+ self,
72
+ value1: Expression[Value1] | Projection[Value1],
73
+ /,
74
+ ) -> BoundSelect[tuple[Value1]]: ...
75
+
76
+ @overload
77
+ def select(
78
+ self,
79
+ value1: Expression[Value1] | Projection[Value1],
80
+ value2: Expression[Value2] | Projection[Value2],
81
+ /,
82
+ ) -> BoundSelect[tuple[Value1, Value2]]: ...
83
+
84
+ @overload
85
+ def select(
86
+ self,
87
+ value1: Expression[Value1] | Projection[Value1],
88
+ value2: Expression[Value2] | Projection[Value2],
89
+ value3: Expression[Value3] | Projection[Value3],
90
+ /,
91
+ ) -> BoundSelect[tuple[Value1, Value2, Value3]]: ...
92
+
93
+ @overload
94
+ def select(
95
+ self,
96
+ value1: Expression[Value1] | Projection[Value1],
97
+ value2: Expression[Value2] | Projection[Value2],
98
+ value3: Expression[Value3] | Projection[Value3],
99
+ value4: Expression[Value4] | Projection[Value4],
100
+ /,
101
+ ) -> BoundSelect[tuple[Value1, Value2, Value3, Value4]]: ...
102
+
103
+ @overload
104
+ def select(
105
+ self,
106
+ value1: Expression[Value1] | Projection[Value1],
107
+ value2: Expression[Value2] | Projection[Value2],
108
+ value3: Expression[Value3] | Projection[Value3],
109
+ value4: Expression[Value4] | Projection[Value4],
110
+ value5: Expression[Value5] | Projection[Value5],
111
+ /,
112
+ ) -> BoundSelect[tuple[Value1, Value2, Value3, Value4, Value5]]: ...
113
+
114
+ @overload
115
+ def select(
116
+ self,
117
+ value1: Expression[Value1] | Projection[Value1],
118
+ value2: Expression[Value2] | Projection[Value2],
119
+ value3: Expression[Value3] | Projection[Value3],
120
+ value4: Expression[Value4] | Projection[Value4],
121
+ value5: Expression[Value5] | Projection[Value5],
122
+ value6: Expression[Value6] | Projection[Value6],
123
+ /,
124
+ ) -> BoundSelect[tuple[Value1, Value2, Value3, Value4, Value5, Value6]]: ...
125
+
126
+ @overload
127
+ def select(
128
+ self,
129
+ value1: Expression[Value1] | Projection[Value1],
130
+ value2: Expression[Value2] | Projection[Value2],
131
+ value3: Expression[Value3] | Projection[Value3],
132
+ value4: Expression[Value4] | Projection[Value4],
133
+ value5: Expression[Value5] | Projection[Value5],
134
+ value6: Expression[Value6] | Projection[Value6],
135
+ value7: Expression[Value7] | Projection[Value7],
136
+ /,
137
+ ) -> BoundSelect[tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7]]: ...
138
+
139
+ @overload
140
+ def select(
141
+ self,
142
+ value1: Expression[Value1] | Projection[Value1],
143
+ value2: Expression[Value2] | Projection[Value2],
144
+ value3: Expression[Value3] | Projection[Value3],
145
+ value4: Expression[Value4] | Projection[Value4],
146
+ value5: Expression[Value5] | Projection[Value5],
147
+ value6: Expression[Value6] | Projection[Value6],
148
+ value7: Expression[Value7] | Projection[Value7],
149
+ value8: Expression[Value8] | Projection[Value8],
150
+ /,
151
+ ) -> BoundSelect[
152
+ tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8]
153
+ ]: ...
154
+
155
+ @overload
156
+ def select(
157
+ self,
158
+ value1: object,
159
+ value2: object,
160
+ value3: object,
161
+ value4: object,
162
+ value5: object,
163
+ value6: object,
164
+ value7: object,
165
+ value8: object,
166
+ /,
167
+ *values: object,
168
+ ) -> BoundSelect[tuple[object, ...]]:
169
+ """Wider than the overloads above name, where the element types stop.
170
+
171
+ A query is still a query past eight projections, and matching no
172
+ overload would take the whole chain down with it rather than just the
173
+ tuple. Past here a projection is checked when it is turned into one,
174
+ and the row is `object` per column, which is what is still known.
175
+ """
176
+
177
+ def select( # type: ignore[misc]
178
+ self,
179
+ *values: object,
180
+ ) -> BoundSelect[tuple[object, ...]]:
181
+ # `select` declares typed overloads for callers; this is the single
182
+ # implementation behind them, reached variadically. The overloads above
183
+ # are what a caller sees, and they carry the types across.
184
+ build = cast("Callable[..., SelectQuery[tuple[object, ...]]]", select)
185
+ return BoundSelect(self._operations, build(*values))
186
+
187
+ def select_from(self, table: ColumnedTable, /) -> BoundSelect[tuple[object, ...]]:
188
+ """Every column the table declares, without naming them one by one.
189
+
190
+ The columns are written into the SQL rather than left as a star, so
191
+ adding one later cannot silently change what a caller reads back.
192
+ Values arrive in declared order, which is the order the generated row
193
+ type takes its arguments, so `.into(TableRow)` builds it.
194
+ """
195
+ return BoundSelect(self._operations, select_from(table))
196
+
197
+ @overload
198
+ @overload
199
+ def insert_into(
200
+ self,
201
+ table: TableLike,
202
+ /,
203
+ column1: Expression[Value1],
204
+ ) -> BoundInsert[Value1]: ...
205
+
206
+ @overload
207
+ def insert_into(
208
+ self,
209
+ table: TableLike,
210
+ /,
211
+ column1: Expression[Value1],
212
+ column2: Expression[Value2],
213
+ ) -> BoundInsert[Value1, Value2]: ...
214
+
215
+ @overload
216
+ def insert_into(
217
+ self,
218
+ table: TableLike,
219
+ /,
220
+ column1: Expression[Value1],
221
+ column2: Expression[Value2],
222
+ column3: Expression[Value3],
223
+ ) -> BoundInsert[Value1, Value2, Value3]: ...
224
+
225
+ @overload
226
+ def insert_into(
227
+ self,
228
+ table: TableLike,
229
+ /,
230
+ column1: Expression[Value1],
231
+ column2: Expression[Value2],
232
+ column3: Expression[Value3],
233
+ column4: Expression[Value4],
234
+ ) -> BoundInsert[Value1, Value2, Value3, Value4]: ...
235
+
236
+ @overload
237
+ def insert_into(
238
+ self,
239
+ table: TableLike,
240
+ /,
241
+ column1: Expression[Value1],
242
+ column2: Expression[Value2],
243
+ column3: Expression[Value3],
244
+ column4: Expression[Value4],
245
+ column5: Expression[Value5],
246
+ ) -> BoundInsert[Value1, Value2, Value3, Value4, Value5]: ...
247
+
248
+ @overload
249
+ def insert_into(
250
+ self,
251
+ table: TableLike,
252
+ /,
253
+ column1: Expression[Value1],
254
+ column2: Expression[Value2],
255
+ column3: Expression[Value3],
256
+ column4: Expression[Value4],
257
+ column5: Expression[Value5],
258
+ column6: Expression[Value6],
259
+ ) -> BoundInsert[Value1, Value2, Value3, Value4, Value5, Value6]: ...
260
+
261
+ @overload
262
+ def insert_into(
263
+ self,
264
+ table: TableLike,
265
+ /,
266
+ column1: Expression[Value1],
267
+ column2: Expression[Value2],
268
+ column3: Expression[Value3],
269
+ column4: Expression[Value4],
270
+ column5: Expression[Value5],
271
+ column6: Expression[Value6],
272
+ column7: Expression[Value7],
273
+ ) -> BoundInsert[Value1, Value2, Value3, Value4, Value5, Value6, Value7]: ...
274
+
275
+ @overload
276
+ def insert_into(
277
+ self,
278
+ table: TableLike,
279
+ /,
280
+ column1: Expression[Value1],
281
+ column2: Expression[Value2],
282
+ column3: Expression[Value3],
283
+ column4: Expression[Value4],
284
+ column5: Expression[Value5],
285
+ column6: Expression[Value6],
286
+ column7: Expression[Value7],
287
+ column8: Expression[Value8],
288
+ ) -> BoundInsert[
289
+ Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8
290
+ ]: ...
291
+
292
+ def insert_into( # type: ignore[misc]
293
+ self,
294
+ table: TableLike,
295
+ /,
296
+ *columns: object,
297
+ ) -> BoundInsert[*tuple[object, ...]]:
298
+ build = cast("Callable[..., InsertStatement[*tuple[object, ...]]]", insert_into)
299
+ return BoundInsert(self._operations, build(table, *columns))
300
+
301
+ def update(self, table: TableLike, /) -> BoundUpdate:
302
+ return BoundUpdate(self._operations, update(table))
303
+
304
+ def delete_from(self, table: TableLike, /) -> BoundDelete:
305
+ return BoundDelete(self._operations, delete_from(table))
306
+
307
+ def fetch(
308
+ self,
309
+ query: Query[ResultRow] | ReturningStatement[ResultRow] | Call[ResultRow],
310
+ /,
311
+ ) -> Result[ResultRow]:
312
+ """Run something built the ordinary way, and read it the same way."""
313
+ rows = tuple(self._operations.many(query))
314
+ return Result(rows, _labels(query))
315
+
316
+
317
+ def _labels(
318
+ query: Query[ResultRow] | ReturningStatement[ResultRow] | Call[ResultRow],
319
+ ) -> tuple[str | None, ...]:
320
+ if isinstance(query, ReturningStatement):
321
+ return labels_of(query.node.returning)
322
+ node = query.node
323
+ return labels_of(node.projections) if isinstance(node, SelectNode) else ()
324
+
325
+
326
+ __all__ = ("DslContext",)