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
@@ -0,0 +1,295 @@
1
+ """The asynchronous counterpart of a context to build against."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, TypeVar, cast, overload
6
+
7
+ from pyoq.dsl.aio.queries import AsyncBoundSelect
8
+ from pyoq.dsl.aio.writes import AsyncBoundDelete, AsyncBoundInsert, AsyncBoundUpdate
9
+ from pyoq.dsl.labels import labels_of
10
+ from pyoq.dsl.result import Result
11
+ from pyoq.query.select import SelectQuery, select, select_from
12
+ from pyoq.query.select_nodes import SelectNode
13
+ from pyoq.query.writes import (
14
+ InsertStatement,
15
+ ReturningStatement,
16
+ delete_from,
17
+ insert_into,
18
+ update,
19
+ )
20
+
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Callable
23
+
24
+ from pyoq.query.execution.aio import AsyncQueryOperations
25
+ from pyoq.query.expressions import Expression
26
+ from pyoq.query.projections import Projection
27
+ from pyoq.query.routines import Call
28
+ from pyoq.query.select import Query
29
+ from pyoq.query.sources import ColumnedTable, TableLike
30
+
31
+ ResultRow = TypeVar("ResultRow", bound=tuple[object, ...])
32
+ Value1 = TypeVar("Value1")
33
+ Value2 = TypeVar("Value2")
34
+ Value3 = TypeVar("Value3")
35
+ Value4 = TypeVar("Value4")
36
+ Value5 = TypeVar("Value5")
37
+ Value6 = TypeVar("Value6")
38
+ Value7 = TypeVar("Value7")
39
+ Value8 = TypeVar("Value8")
40
+
41
+
42
+ class AsyncDslContext:
43
+ """A database to build against, whose fetches are awaited."""
44
+
45
+ __slots__ = ("_operations",)
46
+ _operations: AsyncQueryOperations
47
+
48
+ def __init__(self, operations: AsyncQueryOperations) -> None:
49
+ object.__setattr__(self, "_operations", operations)
50
+
51
+ def __setattr__(self, name: str, value: object) -> None:
52
+ message = f"{type(self).__name__} is immutable"
53
+ raise AttributeError(message, name, value)
54
+
55
+ @property
56
+ def operations(self) -> AsyncQueryOperations:
57
+ return self._operations
58
+
59
+ @overload
60
+ def select(
61
+ self,
62
+ value1: Expression[Value1] | Projection[Value1],
63
+ /,
64
+ ) -> AsyncBoundSelect[tuple[Value1]]: ...
65
+
66
+ @overload
67
+ def select(
68
+ self,
69
+ value1: Expression[Value1] | Projection[Value1],
70
+ value2: Expression[Value2] | Projection[Value2],
71
+ /,
72
+ ) -> AsyncBoundSelect[tuple[Value1, Value2]]: ...
73
+
74
+ @overload
75
+ def select(
76
+ self,
77
+ value1: Expression[Value1] | Projection[Value1],
78
+ value2: Expression[Value2] | Projection[Value2],
79
+ value3: Expression[Value3] | Projection[Value3],
80
+ /,
81
+ ) -> AsyncBoundSelect[tuple[Value1, Value2, Value3]]: ...
82
+
83
+ @overload
84
+ def select(
85
+ self,
86
+ value1: Expression[Value1] | Projection[Value1],
87
+ value2: Expression[Value2] | Projection[Value2],
88
+ value3: Expression[Value3] | Projection[Value3],
89
+ value4: Expression[Value4] | Projection[Value4],
90
+ /,
91
+ ) -> AsyncBoundSelect[tuple[Value1, Value2, Value3, Value4]]: ...
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
+ value5: Expression[Value5] | Projection[Value5],
101
+ /,
102
+ ) -> AsyncBoundSelect[tuple[Value1, Value2, Value3, Value4, Value5]]: ...
103
+
104
+ @overload
105
+ def select(
106
+ self,
107
+ value1: Expression[Value1] | Projection[Value1],
108
+ value2: Expression[Value2] | Projection[Value2],
109
+ value3: Expression[Value3] | Projection[Value3],
110
+ value4: Expression[Value4] | Projection[Value4],
111
+ value5: Expression[Value5] | Projection[Value5],
112
+ value6: Expression[Value6] | Projection[Value6],
113
+ /,
114
+ ) -> AsyncBoundSelect[tuple[Value1, Value2, Value3, Value4, Value5, Value6]]: ...
115
+
116
+ @overload
117
+ def select(
118
+ self,
119
+ value1: Expression[Value1] | Projection[Value1],
120
+ value2: Expression[Value2] | Projection[Value2],
121
+ value3: Expression[Value3] | Projection[Value3],
122
+ value4: Expression[Value4] | Projection[Value4],
123
+ value5: Expression[Value5] | Projection[Value5],
124
+ value6: Expression[Value6] | Projection[Value6],
125
+ value7: Expression[Value7] | Projection[Value7],
126
+ /,
127
+ ) -> AsyncBoundSelect[
128
+ tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7]
129
+ ]: ...
130
+
131
+ @overload
132
+ def select(
133
+ self,
134
+ value1: Expression[Value1] | Projection[Value1],
135
+ value2: Expression[Value2] | Projection[Value2],
136
+ value3: Expression[Value3] | Projection[Value3],
137
+ value4: Expression[Value4] | Projection[Value4],
138
+ value5: Expression[Value5] | Projection[Value5],
139
+ value6: Expression[Value6] | Projection[Value6],
140
+ value7: Expression[Value7] | Projection[Value7],
141
+ value8: Expression[Value8] | Projection[Value8],
142
+ /,
143
+ ) -> AsyncBoundSelect[
144
+ tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8]
145
+ ]: ...
146
+
147
+ def select( # type: ignore[misc]
148
+ self,
149
+ *values: object,
150
+ ) -> AsyncBoundSelect[tuple[object, ...]]:
151
+ build = cast("Callable[..., SelectQuery[tuple[object, ...]]]", select)
152
+ return AsyncBoundSelect(self._operations, build(*values))
153
+
154
+ def select_from(
155
+ self, table: ColumnedTable, /
156
+ ) -> AsyncBoundSelect[tuple[object, ...]]:
157
+ """Every column the table declares, without naming them one by one.
158
+
159
+ The columns are written into the SQL rather than left as a star, so
160
+ adding one later cannot silently change what a caller reads back.
161
+ Values arrive in declared order, which is the order the generated row
162
+ type takes its arguments, so `.into(TableRow)` builds it.
163
+ """
164
+ return AsyncBoundSelect(self._operations, select_from(table))
165
+
166
+ @overload
167
+ @overload
168
+ def insert_into(
169
+ self,
170
+ table: TableLike,
171
+ /,
172
+ column1: Expression[Value1],
173
+ ) -> AsyncBoundInsert[Value1]: ...
174
+
175
+ @overload
176
+ def insert_into(
177
+ self,
178
+ table: TableLike,
179
+ /,
180
+ column1: Expression[Value1],
181
+ column2: Expression[Value2],
182
+ ) -> AsyncBoundInsert[Value1, Value2]: ...
183
+
184
+ @overload
185
+ def insert_into(
186
+ self,
187
+ table: TableLike,
188
+ /,
189
+ column1: Expression[Value1],
190
+ column2: Expression[Value2],
191
+ column3: Expression[Value3],
192
+ ) -> AsyncBoundInsert[Value1, Value2, Value3]: ...
193
+
194
+ @overload
195
+ def insert_into(
196
+ self,
197
+ table: TableLike,
198
+ /,
199
+ column1: Expression[Value1],
200
+ column2: Expression[Value2],
201
+ column3: Expression[Value3],
202
+ column4: Expression[Value4],
203
+ ) -> AsyncBoundInsert[Value1, Value2, Value3, Value4]: ...
204
+
205
+ @overload
206
+ def insert_into(
207
+ self,
208
+ table: TableLike,
209
+ /,
210
+ column1: Expression[Value1],
211
+ column2: Expression[Value2],
212
+ column3: Expression[Value3],
213
+ column4: Expression[Value4],
214
+ column5: Expression[Value5],
215
+ ) -> AsyncBoundInsert[Value1, Value2, Value3, Value4, Value5]: ...
216
+
217
+ @overload
218
+ def insert_into(
219
+ self,
220
+ table: TableLike,
221
+ /,
222
+ column1: Expression[Value1],
223
+ column2: Expression[Value2],
224
+ column3: Expression[Value3],
225
+ column4: Expression[Value4],
226
+ column5: Expression[Value5],
227
+ column6: Expression[Value6],
228
+ ) -> AsyncBoundInsert[Value1, Value2, Value3, Value4, Value5, Value6]: ...
229
+
230
+ @overload
231
+ def insert_into(
232
+ self,
233
+ table: TableLike,
234
+ /,
235
+ column1: Expression[Value1],
236
+ column2: Expression[Value2],
237
+ column3: Expression[Value3],
238
+ column4: Expression[Value4],
239
+ column5: Expression[Value5],
240
+ column6: Expression[Value6],
241
+ column7: Expression[Value7],
242
+ ) -> AsyncBoundInsert[Value1, Value2, Value3, Value4, Value5, Value6, Value7]: ...
243
+
244
+ @overload
245
+ def insert_into(
246
+ self,
247
+ table: TableLike,
248
+ /,
249
+ column1: Expression[Value1],
250
+ column2: Expression[Value2],
251
+ column3: Expression[Value3],
252
+ column4: Expression[Value4],
253
+ column5: Expression[Value5],
254
+ column6: Expression[Value6],
255
+ column7: Expression[Value7],
256
+ column8: Expression[Value8],
257
+ ) -> AsyncBoundInsert[
258
+ Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8
259
+ ]: ...
260
+
261
+ def insert_into( # type: ignore[misc]
262
+ self,
263
+ table: TableLike,
264
+ /,
265
+ *columns: object,
266
+ ) -> AsyncBoundInsert[*tuple[object, ...]]:
267
+ build = cast("Callable[..., InsertStatement[*tuple[object, ...]]]", insert_into)
268
+ return AsyncBoundInsert(self._operations, build(table, *columns))
269
+
270
+ def update(self, table: TableLike, /) -> AsyncBoundUpdate:
271
+ return AsyncBoundUpdate(self._operations, update(table))
272
+
273
+ def delete_from(self, table: TableLike, /) -> AsyncBoundDelete:
274
+ return AsyncBoundDelete(self._operations, delete_from(table))
275
+
276
+ async def fetch(
277
+ self,
278
+ query: Query[ResultRow] | ReturningStatement[ResultRow] | Call[ResultRow],
279
+ /,
280
+ ) -> Result[ResultRow]:
281
+ """Run something built the ordinary way, and read it the same way."""
282
+ rows = tuple(await self._operations.many(query))
283
+ return Result(rows, _labels(query))
284
+
285
+
286
+ def _labels(
287
+ query: Query[ResultRow] | ReturningStatement[ResultRow] | Call[ResultRow],
288
+ ) -> tuple[str | None, ...]:
289
+ if isinstance(query, ReturningStatement):
290
+ return labels_of(query.node.returning)
291
+ node = query.node
292
+ return labels_of(node.projections) if isinstance(node, SelectNode) else ()
293
+
294
+
295
+ __all__ = ("AsyncDslContext",)
@@ -0,0 +1,335 @@
1
+ """The asynchronous counterpart of a query bound to its database.
2
+
3
+ Only the ending differs. A chain is built the same way and describes the same
4
+ statement; the fetch at the end is awaited because the database is.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Callable
10
+ from typing import TYPE_CHECKING, Generic, TypeVar
11
+
12
+ from pyoq.dsl.labels import labels_of
13
+ from pyoq.dsl.result import Result, Row
14
+ from pyoq.query.select import JoinStep, Query, SelectQuery, SemiJoinStep
15
+ from pyoq.query.select_nodes import SelectNode, SetNode
16
+
17
+ if TYPE_CHECKING:
18
+ from pyoq.descriptors import RelationshipDescriptor
19
+ from pyoq.query.execution.aio import AsyncQueryOperations
20
+ from pyoq.query.expressions import Expression
21
+ from pyoq.query.sources import CommonTable, SourceBase, TableLike
22
+ from pyoq.query.writes import ColumnReference
23
+
24
+ ResultRow = TypeVar("ResultRow", bound=tuple[object, ...])
25
+ Mapped = TypeVar("Mapped")
26
+ ScalarValue = TypeVar("ScalarValue")
27
+
28
+
29
+ class AsyncBoundQuery(Generic[ResultRow]):
30
+ """A query and the database it will run against."""
31
+
32
+ __slots__ = ("_operations", "_query")
33
+ _operations: AsyncQueryOperations
34
+ _query: Query[ResultRow]
35
+
36
+ def __init__(
37
+ self,
38
+ operations: AsyncQueryOperations,
39
+ query: Query[ResultRow],
40
+ ) -> None:
41
+ object.__setattr__(self, "_operations", operations)
42
+ object.__setattr__(self, "_query", query)
43
+
44
+ def __setattr__(self, name: str, value: object) -> None:
45
+ message = f"{type(self).__name__} is immutable"
46
+ raise AttributeError(message, name, value)
47
+
48
+ @property
49
+ def query(self) -> Query[ResultRow]:
50
+ """The query on its own, which cannot reach a database."""
51
+ return self._query
52
+
53
+ def order_by(self, *terms: object) -> AsyncBoundQuery[ResultRow]:
54
+ return self._with(self._query.order_by(*terms))
55
+
56
+ def limit(self, value: int, /) -> AsyncBoundQuery[ResultRow]:
57
+ return self._with(self._query.limit(value))
58
+
59
+ def offset(self, value: int, /) -> AsyncBoundQuery[ResultRow]:
60
+ return self._with(self._query.offset(value))
61
+
62
+ def union(self, other: Query[ResultRow], /) -> AsyncBoundQuery[ResultRow]:
63
+ return self._with(self._query.union(other))
64
+
65
+ def union_all(self, other: Query[ResultRow], /) -> AsyncBoundQuery[ResultRow]:
66
+ return self._with(self._query.union_all(other))
67
+
68
+ def intersect(self, other: Query[ResultRow], /) -> AsyncBoundQuery[ResultRow]:
69
+ return self._with(self._query.intersect(other))
70
+
71
+ def except_(self, other: Query[ResultRow], /) -> AsyncBoundQuery[ResultRow]:
72
+ return self._with(self._query.except_(other))
73
+
74
+ async def fetch_all(self) -> Result[ResultRow]:
75
+ rows = tuple(await self._operations.many(self._query))
76
+ return Result(rows, _labels(self._query))
77
+
78
+ async def fetch_one(self) -> Row[ResultRow]:
79
+ """Exactly one row, because that is what the caller said to expect."""
80
+ return Row(await self._operations.one(self._query), _labels(self._query))
81
+
82
+ async def fetch_optional(self) -> Row[ResultRow] | None:
83
+ row = await self._operations.one_or_none(self._query)
84
+ return None if row is None else Row(row, _labels(self._query))
85
+
86
+ async def fetch_optional_into(
87
+ self,
88
+ factory: Callable[..., Mapped],
89
+ /,
90
+ ) -> Mapped | None:
91
+ """One row built by the caller's own type, or nothing.
92
+
93
+ The row and the many-row result both take a factory inline.
94
+ A row that may be missing had to be tested for first, which
95
+ is the one shape that could not be asked for in the chain.
96
+ """
97
+ row = await self.fetch_optional()
98
+ return None if row is None else row.into(factory)
99
+
100
+ async def fetch_value(
101
+ self: AsyncBoundQuery[tuple[ScalarValue]],
102
+ /,
103
+ ) -> ScalarValue:
104
+ """The single column of the single row.
105
+
106
+ Only a query that selected one column can answer this, which is why the
107
+ annotation says so rather than a check at run time.
108
+ """
109
+ return await self._operations.scalar(self._query)
110
+
111
+ def _with(self, query: Query[ResultRow]) -> AsyncBoundQuery[ResultRow]:
112
+ return AsyncBoundQuery(self._operations, query)
113
+
114
+
115
+ class AsyncBoundSelect(AsyncBoundQuery[ResultRow], Generic[ResultRow]):
116
+ """A SELECT that knows its database, with the builder's own shape kept."""
117
+
118
+ __slots__ = ()
119
+
120
+ @property
121
+ def query(self) -> SelectQuery[ResultRow]:
122
+ return _as_select(self._query)
123
+
124
+ def distinct(self, enabled: bool = True, /) -> AsyncBoundSelect[ResultRow]:
125
+ return self._select(self.query.distinct(enabled))
126
+
127
+ def from_(
128
+ self,
129
+ source: SourceBase | TableLike,
130
+ /,
131
+ ) -> AsyncBoundSelect[ResultRow]:
132
+ return self._select(self.query.from_(source))
133
+
134
+ def join(self, source: SourceBase | TableLike, /) -> AsyncBoundJoinStep[ResultRow]:
135
+ """An inner join, qualified by the clause that follows it."""
136
+ return AsyncBoundJoinStep(self._select, self.query.join(source))
137
+
138
+ def inner_join(
139
+ self,
140
+ source: SourceBase | TableLike,
141
+ /,
142
+ ) -> AsyncBoundJoinStep[ResultRow]:
143
+ return AsyncBoundJoinStep(self._select, self.query.inner_join(source))
144
+
145
+ def left_join(
146
+ self,
147
+ source: SourceBase | TableLike,
148
+ /,
149
+ ) -> AsyncBoundJoinStep[ResultRow]:
150
+ return AsyncBoundJoinStep(self._select, self.query.left_join(source))
151
+
152
+ def right_join(
153
+ self,
154
+ source: SourceBase | TableLike,
155
+ /,
156
+ ) -> AsyncBoundJoinStep[ResultRow]:
157
+ return AsyncBoundJoinStep(self._select, self.query.right_join(source))
158
+
159
+ def full_join(
160
+ self,
161
+ source: SourceBase | TableLike,
162
+ /,
163
+ ) -> AsyncBoundJoinStep[ResultRow]:
164
+ return AsyncBoundJoinStep(self._select, self.query.full_join(source))
165
+
166
+ def semi_join(
167
+ self,
168
+ source: SourceBase | TableLike,
169
+ /,
170
+ ) -> AsyncBoundSemiJoinStep[ResultRow]:
171
+ """Rows that have a match, without bringing the match back."""
172
+ return AsyncBoundSemiJoinStep(self._select, self.query.semi_join(source))
173
+
174
+ def anti_join(
175
+ self,
176
+ source: SourceBase | TableLike,
177
+ /,
178
+ ) -> AsyncBoundSemiJoinStep[ResultRow]:
179
+ """Rows that have no match."""
180
+ return AsyncBoundSemiJoinStep(self._select, self.query.anti_join(source))
181
+
182
+ def cross_join(
183
+ self,
184
+ source: SourceBase | TableLike,
185
+ /,
186
+ ) -> AsyncBoundSelect[ResultRow]:
187
+ return self._select(self.query.cross_join(source))
188
+
189
+ def natural_join(
190
+ self,
191
+ source: SourceBase | TableLike,
192
+ /,
193
+ ) -> AsyncBoundSelect[ResultRow]:
194
+ return self._select(self.query.natural_join(source))
195
+
196
+ def natural_left_join(
197
+ self,
198
+ source: SourceBase | TableLike,
199
+ /,
200
+ ) -> AsyncBoundSelect[ResultRow]:
201
+ return self._select(self.query.natural_left_join(source))
202
+
203
+ def natural_right_join(
204
+ self,
205
+ source: SourceBase | TableLike,
206
+ /,
207
+ ) -> AsyncBoundSelect[ResultRow]:
208
+ return self._select(self.query.natural_right_join(source))
209
+
210
+ def natural_full_join(
211
+ self,
212
+ source: SourceBase | TableLike,
213
+ /,
214
+ ) -> AsyncBoundSelect[ResultRow]:
215
+ return self._select(self.query.natural_full_join(source))
216
+
217
+ def where(
218
+ self,
219
+ condition: Expression[bool],
220
+ /,
221
+ ) -> AsyncBoundSelect[ResultRow]:
222
+ return self._select(self.query.where(condition))
223
+
224
+ def group_by(self, *expressions: object) -> AsyncBoundSelect[ResultRow]:
225
+ return self._select(self.query.group_by(*expressions))
226
+
227
+ def having(
228
+ self,
229
+ condition: Expression[bool],
230
+ /,
231
+ ) -> AsyncBoundSelect[ResultRow]:
232
+ return self._select(self.query.having(condition))
233
+
234
+ def with_(
235
+ self,
236
+ *common_tables: CommonTable[object],
237
+ ) -> AsyncBoundSelect[ResultRow]:
238
+ return self._select(self.query.with_(*common_tables))
239
+
240
+ def order_by(self, *terms: object) -> AsyncBoundSelect[ResultRow]:
241
+ return self._select(self.query.order_by(*terms))
242
+
243
+ def limit(self, value: int, /) -> AsyncBoundSelect[ResultRow]:
244
+ return self._select(self.query.limit(value))
245
+
246
+ def offset(self, value: int, /) -> AsyncBoundSelect[ResultRow]:
247
+ return self._select(self.query.offset(value))
248
+
249
+ def _select(
250
+ self,
251
+ query: SelectQuery[ResultRow],
252
+ ) -> AsyncBoundSelect[ResultRow]:
253
+ return AsyncBoundSelect(self._operations, query)
254
+
255
+
256
+ def _as_select(query: Query[ResultRow]) -> SelectQuery[ResultRow]:
257
+ if not isinstance(query, SelectQuery):
258
+ message = "the query is no longer a SELECT"
259
+ raise TypeError(message)
260
+ return query
261
+
262
+
263
+ def _labels(query: Query[ResultRow]) -> tuple[str | None, ...]:
264
+ """A set operation takes its names from the first query in it."""
265
+ select = _first_select(query.node)
266
+ return () if select is None else labels_of(select.projections)
267
+
268
+
269
+ def _first_select(node: object) -> SelectNode | None:
270
+ while isinstance(node, SetNode):
271
+ node = node.left
272
+ return node if isinstance(node, SelectNode) else None
273
+
274
+
275
+ class AsyncBoundJoinStep(Generic[ResultRow]):
276
+ """A bound join waiting to be told how the two tables line up."""
277
+
278
+ __slots__ = ("_bind", "_step")
279
+ _bind: Callable[[SelectQuery[ResultRow]], AsyncBoundSelect[ResultRow]]
280
+ _step: JoinStep[ResultRow]
281
+
282
+ def __init__(
283
+ self,
284
+ bind: Callable[[SelectQuery[ResultRow]], AsyncBoundSelect[ResultRow]],
285
+ step: JoinStep[ResultRow],
286
+ ) -> None:
287
+ object.__setattr__(self, "_bind", bind)
288
+ object.__setattr__(self, "_step", step)
289
+
290
+ def __setattr__(self, name: str, value: object) -> None:
291
+ message = "join steps are immutable"
292
+ raise AttributeError(message, name, value)
293
+
294
+ def on(self, condition: Expression[bool], /) -> AsyncBoundSelect[ResultRow]:
295
+ """The predicate the rows are matched by."""
296
+ return self._bind(self._step.on(condition))
297
+
298
+ def using(self, *columns: str | ColumnReference) -> AsyncBoundSelect[ResultRow]:
299
+ """Matched on columns both tables name the same way."""
300
+ return self._bind(self._step.using(*columns))
301
+
302
+ def on_key(
303
+ self,
304
+ relation: RelationshipDescriptor[object, object],
305
+ /,
306
+ ) -> AsyncBoundSelect[ResultRow]:
307
+ """Matched on the foreign key the schema already declares."""
308
+ return self._bind(self._step.on_key(relation))
309
+
310
+
311
+ class AsyncBoundSemiJoinStep(Generic[ResultRow]):
312
+ """A bound semi or anti join waiting for its predicate."""
313
+
314
+ __slots__ = ("_bind", "_step")
315
+ _bind: Callable[[SelectQuery[ResultRow]], AsyncBoundSelect[ResultRow]]
316
+ _step: SemiJoinStep[ResultRow]
317
+
318
+ def __init__(
319
+ self,
320
+ bind: Callable[[SelectQuery[ResultRow]], AsyncBoundSelect[ResultRow]],
321
+ step: SemiJoinStep[ResultRow],
322
+ ) -> None:
323
+ object.__setattr__(self, "_bind", bind)
324
+ object.__setattr__(self, "_step", step)
325
+
326
+ def __setattr__(self, name: str, value: object) -> None:
327
+ message = "join steps are immutable"
328
+ raise AttributeError(message, name, value)
329
+
330
+ def on(self, condition: Expression[bool], /) -> AsyncBoundSelect[ResultRow]:
331
+ """The predicate a row is matched by."""
332
+ return self._bind(self._step.on(condition))
333
+
334
+
335
+ __all__ = ("AsyncBoundQuery", "AsyncBoundSelect")