pyoq-sql 1.0.2__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 (267) hide show
  1. pyoq/__init__.py +10 -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 +31 -0
  9. pyoq/config/connection.py +161 -0
  10. pyoq/config/loader.py +289 -0
  11. pyoq/config/models.py +245 -0
  12. pyoq/config/values.py +142 -0
  13. pyoq/descriptors.py +165 -0
  14. pyoq/diagnostics/__init__.py +68 -0
  15. pyoq/diagnostics/budget.py +136 -0
  16. pyoq/diagnostics/events.py +137 -0
  17. pyoq/diagnostics/fingerprint.py +267 -0
  18. pyoq/diagnostics/instrumented.py +237 -0
  19. pyoq/diagnostics/metrics.py +61 -0
  20. pyoq/diagnostics/observation.py +227 -0
  21. pyoq/diagnostics/scoped.py +103 -0
  22. pyoq/django/__init__.py +15 -0
  23. pyoq/django/apps.py +17 -0
  24. pyoq/django/execution.py +317 -0
  25. pyoq/django/generation.py +59 -0
  26. pyoq/django/management/__init__.py +0 -0
  27. pyoq/django/management/commands/__init__.py +0 -0
  28. pyoq/django/management/commands/makemigrations.py +53 -0
  29. pyoq/django/management/commands/pyoq_codegen.py +75 -0
  30. pyoq/django/parameters.py +101 -0
  31. pyoq/django/schema.py +379 -0
  32. pyoq/django/settings.py +87 -0
  33. pyoq/django/timeouts.py +105 -0
  34. pyoq/dsl/__init__.py +64 -0
  35. pyoq/dsl/aio/__init__.py +31 -0
  36. pyoq/dsl/aio/context.py +295 -0
  37. pyoq/dsl/aio/queries.py +335 -0
  38. pyoq/dsl/aio/writes.py +368 -0
  39. pyoq/dsl/context.py +326 -0
  40. pyoq/dsl/entry.py +37 -0
  41. pyoq/dsl/labels.py +36 -0
  42. pyoq/dsl/queries.py +339 -0
  43. pyoq/dsl/result.py +164 -0
  44. pyoq/dsl/writes.py +360 -0
  45. pyoq/errors.py +317 -0
  46. pyoq/fastapi/__init__.py +32 -0
  47. pyoq/fastapi/dependencies.py +167 -0
  48. pyoq/fastapi/lifespan.py +119 -0
  49. pyoq/fetching/__init__.py +55 -0
  50. pyoq/fetching/collections.py +136 -0
  51. pyoq/fetching/execution.py +587 -0
  52. pyoq/fetching/joined.py +79 -0
  53. pyoq/fetching/nesting.py +183 -0
  54. pyoq/fetching/plans.py +541 -0
  55. pyoq/fetching/select_in.py +149 -0
  56. pyoq/fetching/tables.py +110 -0
  57. pyoq/generation/__init__.py +54 -0
  58. pyoq/generation/cleanup.py +44 -0
  59. pyoq/generation/contracts.py +248 -0
  60. pyoq/generation/drift.py +169 -0
  61. pyoq/generation/lock.py +33 -0
  62. pyoq/generation/manifest.py +114 -0
  63. pyoq/generation/model.py +1001 -0
  64. pyoq/generation/pipeline.py +119 -0
  65. pyoq/generation/rendering/__init__.py +5 -0
  66. pyoq/generation/rendering/domains.py +51 -0
  67. pyoq/generation/rendering/enums.py +29 -0
  68. pyoq/generation/rendering/exports.py +70 -0
  69. pyoq/generation/rendering/imports.py +63 -0
  70. pyoq/generation/rendering/package.py +56 -0
  71. pyoq/generation/rendering/relations.py +133 -0
  72. pyoq/generation/rendering/routines.py +396 -0
  73. pyoq/generation/rendering/rows.py +79 -0
  74. pyoq/generation/rendering/source.py +121 -0
  75. pyoq/generation/rendering/tables.py +300 -0
  76. pyoq/generation/rendering/writes.py +514 -0
  77. pyoq/generation/validation.py +27 -0
  78. pyoq/generation/writer.py +184 -0
  79. pyoq/hydration/__init__.py +24 -0
  80. pyoq/hydration/engine.py +155 -0
  81. pyoq/hydration/identity.py +194 -0
  82. pyoq/hydration/plan.py +116 -0
  83. pyoq/migrations/__init__.py +9 -0
  84. pyoq/migrations/alembic.py +106 -0
  85. pyoq/migrations/hooks.py +75 -0
  86. pyoq/naming.py +261 -0
  87. pyoq/policies/__init__.py +47 -0
  88. pyoq/policies/bypass.py +122 -0
  89. pyoq/policies/governed.py +430 -0
  90. pyoq/policies/model.py +242 -0
  91. pyoq/policies/rewriting.py +263 -0
  92. pyoq/py.typed +1 -0
  93. pyoq/query/__init__.py +312 -0
  94. pyoq/query/aggregates.py +172 -0
  95. pyoq/query/arrays.py +65 -0
  96. pyoq/query/binding.py +52 -0
  97. pyoq/query/capabilities.py +317 -0
  98. pyoq/query/casts.py +73 -0
  99. pyoq/query/choices.py +185 -0
  100. pyoq/query/decoding.py +360 -0
  101. pyoq/query/documents.py +56 -0
  102. pyoq/query/execution/__init__.py +63 -0
  103. pyoq/query/execution/aio/__init__.py +31 -0
  104. pyoq/query/execution/aio/operations.py +228 -0
  105. pyoq/query/execution/aio/pooling.py +233 -0
  106. pyoq/query/execution/aio/streaming.py +161 -0
  107. pyoq/query/execution/aio/transactions.py +105 -0
  108. pyoq/query/execution/batch.py +96 -0
  109. pyoq/query/execution/binding_style.py +30 -0
  110. pyoq/query/execution/compilation.py +48 -0
  111. pyoq/query/execution/context.py +61 -0
  112. pyoq/query/execution/control.py +50 -0
  113. pyoq/query/execution/operations.py +224 -0
  114. pyoq/query/execution/planning.py +107 -0
  115. pyoq/query/execution/pooling.py +279 -0
  116. pyoq/query/execution/results.py +36 -0
  117. pyoq/query/execution/streaming.py +178 -0
  118. pyoq/query/execution/transactions.py +95 -0
  119. pyoq/query/expressions.py +1200 -0
  120. pyoq/query/fields.py +60 -0
  121. pyoq/query/mysql/__init__.py +59 -0
  122. pyoq/query/mysql/aio/__init__.py +38 -0
  123. pyoq/query/mysql/aio/commands.py +389 -0
  124. pyoq/query/mysql/aio/driver.py +196 -0
  125. pyoq/query/mysql/aio/executor.py +123 -0
  126. pyoq/query/mysql/aio/factory.py +26 -0
  127. pyoq/query/mysql/aio/operations.py +38 -0
  128. pyoq/query/mysql/aio/pool.py +53 -0
  129. pyoq/query/mysql/aio/transactions.py +313 -0
  130. pyoq/query/mysql/commands.py +354 -0
  131. pyoq/query/mysql/compiler.py +134 -0
  132. pyoq/query/mysql/context.py +20 -0
  133. pyoq/query/mysql/executor.py +126 -0
  134. pyoq/query/mysql/expressions.py +244 -0
  135. pyoq/query/mysql/factory.py +46 -0
  136. pyoq/query/mysql/health.py +66 -0
  137. pyoq/query/mysql/identifiers.py +9 -0
  138. pyoq/query/mysql/model.py +79 -0
  139. pyoq/query/mysql/operations.py +43 -0
  140. pyoq/query/mysql/parameters.py +69 -0
  141. pyoq/query/mysql/planning.py +20 -0
  142. pyoq/query/mysql/pool.py +67 -0
  143. pyoq/query/mysql/transactions.py +331 -0
  144. pyoq/query/mysql/writes.py +73 -0
  145. pyoq/query/nodes.py +750 -0
  146. pyoq/query/postgres/__init__.py +48 -0
  147. pyoq/query/postgres/aio/__init__.py +25 -0
  148. pyoq/query/postgres/aio/bulk.py +56 -0
  149. pyoq/query/postgres/aio/commands.py +264 -0
  150. pyoq/query/postgres/aio/executor.py +152 -0
  151. pyoq/query/postgres/aio/factory.py +26 -0
  152. pyoq/query/postgres/aio/operations.py +26 -0
  153. pyoq/query/postgres/aio/pool.py +40 -0
  154. pyoq/query/postgres/aio/transactions.py +295 -0
  155. pyoq/query/postgres/bulk.py +62 -0
  156. pyoq/query/postgres/commands.py +238 -0
  157. pyoq/query/postgres/compiler.py +114 -0
  158. pyoq/query/postgres/context.py +20 -0
  159. pyoq/query/postgres/executor.py +147 -0
  160. pyoq/query/postgres/expressions.py +311 -0
  161. pyoq/query/postgres/factory.py +24 -0
  162. pyoq/query/postgres/health.py +24 -0
  163. pyoq/query/postgres/identifiers.py +9 -0
  164. pyoq/query/postgres/model.py +81 -0
  165. pyoq/query/postgres/operations.py +25 -0
  166. pyoq/query/postgres/parameters.py +71 -0
  167. pyoq/query/postgres/planning.py +20 -0
  168. pyoq/query/postgres/pool.py +52 -0
  169. pyoq/query/postgres/transactions.py +295 -0
  170. pyoq/query/postgres/writes.py +37 -0
  171. pyoq/query/projections.py +105 -0
  172. pyoq/query/raw.py +90 -0
  173. pyoq/query/recursion.py +265 -0
  174. pyoq/query/rendering/__init__.py +1 -0
  175. pyoq/query/rendering/expressions.py +913 -0
  176. pyoq/query/rendering/identifiers.py +40 -0
  177. pyoq/query/rendering/projections.py +63 -0
  178. pyoq/query/rendering/queries.py +334 -0
  179. pyoq/query/rendering/sources.py +66 -0
  180. pyoq/query/rendering/writes.py +176 -0
  181. pyoq/query/results.py +459 -0
  182. pyoq/query/routines.py +196 -0
  183. pyoq/query/rows.py +156 -0
  184. pyoq/query/select.py +793 -0
  185. pyoq/query/select_nodes.py +277 -0
  186. pyoq/query/sources.py +236 -0
  187. pyoq/query/sqlite/__init__.py +43 -0
  188. pyoq/query/sqlite/commands.py +201 -0
  189. pyoq/query/sqlite/compiler.py +139 -0
  190. pyoq/query/sqlite/context.py +20 -0
  191. pyoq/query/sqlite/executor.py +119 -0
  192. pyoq/query/sqlite/expressions.py +224 -0
  193. pyoq/query/sqlite/factory.py +32 -0
  194. pyoq/query/sqlite/health.py +28 -0
  195. pyoq/query/sqlite/identifiers.py +9 -0
  196. pyoq/query/sqlite/model.py +73 -0
  197. pyoq/query/sqlite/operations.py +36 -0
  198. pyoq/query/sqlite/parameters.py +50 -0
  199. pyoq/query/sqlite/planning.py +20 -0
  200. pyoq/query/sqlite/pool.py +50 -0
  201. pyoq/query/sqlite/streaming.py +13 -0
  202. pyoq/query/sqlite/transactions.py +274 -0
  203. pyoq/query/sqlite/writes.py +35 -0
  204. pyoq/query/statements.py +27 -0
  205. pyoq/query/values.py +23 -0
  206. pyoq/query/vendor.py +162 -0
  207. pyoq/query/windows.py +424 -0
  208. pyoq/query/write_nodes.py +174 -0
  209. pyoq/query/writes.py +628 -0
  210. pyoq/relations/__init__.py +66 -0
  211. pyoq/relations/batching.py +219 -0
  212. pyoq/relations/derivation.py +111 -0
  213. pyoq/relations/fetching.py +355 -0
  214. pyoq/relations/graph.py +245 -0
  215. pyoq/relations/loading.py +74 -0
  216. pyoq/relations/model.py +75 -0
  217. pyoq/relations/planning.py +206 -0
  218. pyoq/runtime/__init__.py +9 -0
  219. pyoq/runtime/kernels.py +25 -0
  220. pyoq/runtime/python.py +43 -0
  221. pyoq/runtime/selection.py +73 -0
  222. pyoq/sanic/__init__.py +32 -0
  223. pyoq/sanic/scope.py +197 -0
  224. pyoq/sanic/workers.py +129 -0
  225. pyoq/schema/__init__.py +108 -0
  226. pyoq/schema/codec.py +711 -0
  227. pyoq/schema/models.py +604 -0
  228. pyoq/schema/mysql/__init__.py +16 -0
  229. pyoq/schema/mysql/connection.py +73 -0
  230. pyoq/schema/mysql/dsn.py +72 -0
  231. pyoq/schema/mysql/records.py +354 -0
  232. pyoq/schema/mysql/reflection.py +309 -0
  233. pyoq/schema/mysql/source.py +30 -0
  234. pyoq/schema/mysql/sql.py +128 -0
  235. pyoq/schema/mysql/types.py +105 -0
  236. pyoq/schema/postgres/__init__.py +13 -0
  237. pyoq/schema/postgres/connection.py +63 -0
  238. pyoq/schema/postgres/records.py +384 -0
  239. pyoq/schema/postgres/reflection.py +466 -0
  240. pyoq/schema/postgres/source.py +30 -0
  241. pyoq/schema/postgres/sql.py +246 -0
  242. pyoq/schema/postgres/types.py +98 -0
  243. pyoq/schema/registry.py +45 -0
  244. pyoq/schema/source.py +15 -0
  245. pyoq/schema/sqlite/__init__.py +6 -0
  246. pyoq/schema/sqlite/connection.py +54 -0
  247. pyoq/schema/sqlite/records.py +167 -0
  248. pyoq/schema/sqlite/reflection.py +393 -0
  249. pyoq/schema/sqlite/source.py +30 -0
  250. pyoq/schema/sqlite/sql.py +254 -0
  251. pyoq/schema/sqlite/types.py +74 -0
  252. pyoq/serving/__init__.py +23 -0
  253. pyoq/serving/databases.py +107 -0
  254. pyoq/serving/opening.py +331 -0
  255. pyoq/snapshots/__init__.py +20 -0
  256. pyoq/snapshots/drift.py +312 -0
  257. pyoq/snapshots/files.py +96 -0
  258. pyoq/snapshots/routing.py +40 -0
  259. pyoq/snapshots/source.py +33 -0
  260. pyoq/tracing/__init__.py +5 -0
  261. pyoq/tracing/spans.py +89 -0
  262. pyoq/unset.py +14 -0
  263. pyoq_sql-1.0.2.dist-info/METADATA +3050 -0
  264. pyoq_sql-1.0.2.dist-info/RECORD +267 -0
  265. pyoq_sql-1.0.2.dist-info/WHEEL +4 -0
  266. pyoq_sql-1.0.2.dist-info/entry_points.txt +3 -0
  267. pyoq_sql-1.0.2.dist-info/licenses/LICENSE +373 -0
pyoq/dsl/writes.py ADDED
@@ -0,0 +1,360 @@
1
+ """Writes that know which database they belong to.
2
+
3
+ The same split as the queries: a statement is still described by the builder
4
+ that cannot reach a database, and these carry one alongside the operations it
5
+ will run against.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING, Generic, TypeVar, TypeVarTuple, cast, overload
11
+
12
+ from pyoq.dsl.labels import labels_of
13
+ from pyoq.dsl.result import Result, Row
14
+ from pyoq.query.write_nodes import WriteNode
15
+ from pyoq.query.writes import (
16
+ ConflictUpdate,
17
+ DeleteStatement,
18
+ InsertStatement,
19
+ MutationStatement,
20
+ ReturningStatement,
21
+ UpdateStatement,
22
+ )
23
+
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Callable, Iterable
26
+
27
+ from pyoq.query.execution import QueryOperations
28
+ from pyoq.query.expressions import Expression
29
+ from pyoq.query.writes import ColumnReference
30
+
31
+ Values = TypeVarTuple("Values")
32
+ Mutation = TypeVar("Mutation", bound=MutationStatement[WriteNode])
33
+ ResultRow = TypeVar("ResultRow", bound=tuple[object, ...])
34
+ Mapped = TypeVar("Mapped")
35
+ ScalarValue = TypeVar("ScalarValue")
36
+ Value1 = TypeVar("Value1")
37
+ Value2 = TypeVar("Value2")
38
+ Value3 = TypeVar("Value3")
39
+ Value4 = TypeVar("Value4")
40
+ Value5 = TypeVar("Value5")
41
+ Value6 = TypeVar("Value6")
42
+ Value7 = TypeVar("Value7")
43
+ Value8 = TypeVar("Value8")
44
+ AssignedValue = TypeVar("AssignedValue")
45
+
46
+
47
+ class BoundReturning(Generic[ResultRow]):
48
+ """Rows a write handed back, read the way a query's rows are read."""
49
+
50
+ __slots__ = ("_operations", "_statement")
51
+ _operations: QueryOperations
52
+ _statement: ReturningStatement[ResultRow]
53
+
54
+ def __init__(
55
+ self,
56
+ operations: QueryOperations,
57
+ statement: ReturningStatement[ResultRow],
58
+ ) -> None:
59
+ object.__setattr__(self, "_operations", operations)
60
+ object.__setattr__(self, "_statement", statement)
61
+
62
+ def __setattr__(self, name: str, value: object) -> None:
63
+ message = f"{type(self).__name__} is immutable"
64
+ raise AttributeError(message, name, value)
65
+
66
+ @property
67
+ def statement(self) -> ReturningStatement[ResultRow]:
68
+ """The statement on its own, which cannot reach a database."""
69
+ return self._statement
70
+
71
+ def fetch_all(self) -> Result[ResultRow]:
72
+ rows = tuple(self._operations.many(self._statement))
73
+ return Result(rows, labels_of(self._statement.node.returning))
74
+
75
+ def fetch_one(self) -> Row[ResultRow]:
76
+ """Exactly one row, because that is what the caller said to expect."""
77
+ return Row(
78
+ self._operations.one(self._statement),
79
+ labels_of(self._statement.node.returning),
80
+ )
81
+
82
+ def fetch_optional(self) -> Row[ResultRow] | None:
83
+ row = self._operations.one_or_none(self._statement)
84
+ return (
85
+ None if row is None else Row(row, labels_of(self._statement.node.returning))
86
+ )
87
+
88
+ def fetch_optional_into(
89
+ self,
90
+ factory: Callable[..., Mapped],
91
+ /,
92
+ ) -> Mapped | None:
93
+ """One row built by the caller's own type, or nothing.
94
+
95
+ The row and the many-row result both take a factory inline.
96
+ A row that may be missing had to be tested for first, which
97
+ is the one shape that could not be asked for in the chain.
98
+ """
99
+ row = self.fetch_optional()
100
+ return None if row is None else row.into(factory)
101
+
102
+ def fetch_value(self: BoundReturning[tuple[ScalarValue]], /) -> ScalarValue:
103
+ return self._operations.scalar(self._statement)
104
+
105
+
106
+ class BoundMutation(Generic[Mutation]):
107
+ """What every bound write can do, whatever it is writing."""
108
+
109
+ __slots__ = ("_operations", "_statement")
110
+ _operations: QueryOperations
111
+ _statement: Mutation
112
+
113
+ def __init__(self, operations: QueryOperations, statement: Mutation) -> None:
114
+ object.__setattr__(self, "_operations", operations)
115
+ object.__setattr__(self, "_statement", statement)
116
+
117
+ def __setattr__(self, name: str, value: object) -> None:
118
+ message = f"{type(self).__name__} is immutable"
119
+ raise AttributeError(message, name, value)
120
+
121
+ @property
122
+ def statement(self) -> Mutation:
123
+ """The statement on its own, which cannot reach a database."""
124
+ return self._statement
125
+
126
+ def execute(self) -> int:
127
+ """How many rows the database says it wrote."""
128
+ return self._operations.execute(self._statement).rows_affected
129
+
130
+ def returning_all(self) -> BoundReturning[tuple[object, ...]]:
131
+ """Every column the table declares, without listing them."""
132
+ return BoundReturning(self._operations, self._statement.returning_all())
133
+
134
+ @overload
135
+ def returning(
136
+ self,
137
+ value1: Expression[Value1],
138
+ /,
139
+ ) -> BoundReturning[tuple[Value1]]: ...
140
+
141
+ @overload
142
+ def returning(
143
+ self,
144
+ value1: Expression[Value1],
145
+ value2: Expression[Value2],
146
+ /,
147
+ ) -> BoundReturning[tuple[Value1, Value2]]: ...
148
+
149
+ @overload
150
+ def returning(
151
+ self,
152
+ value1: Expression[Value1],
153
+ value2: Expression[Value2],
154
+ value3: Expression[Value3],
155
+ /,
156
+ ) -> BoundReturning[tuple[Value1, Value2, Value3]]: ...
157
+
158
+ @overload
159
+ def returning(
160
+ self,
161
+ value1: Expression[Value1],
162
+ value2: Expression[Value2],
163
+ value3: Expression[Value3],
164
+ value4: Expression[Value4],
165
+ /,
166
+ ) -> BoundReturning[tuple[Value1, Value2, Value3, Value4]]: ...
167
+
168
+ @overload
169
+ def returning(
170
+ self,
171
+ value1: Expression[Value1],
172
+ value2: Expression[Value2],
173
+ value3: Expression[Value3],
174
+ value4: Expression[Value4],
175
+ value5: Expression[Value5],
176
+ /,
177
+ ) -> BoundReturning[tuple[Value1, Value2, Value3, Value4, Value5]]: ...
178
+
179
+ @overload
180
+ def returning(
181
+ self,
182
+ value1: Expression[Value1],
183
+ value2: Expression[Value2],
184
+ value3: Expression[Value3],
185
+ value4: Expression[Value4],
186
+ value5: Expression[Value5],
187
+ value6: Expression[Value6],
188
+ /,
189
+ ) -> BoundReturning[tuple[Value1, Value2, Value3, Value4, Value5, Value6]]: ...
190
+
191
+ @overload
192
+ def returning(
193
+ self,
194
+ value1: Expression[Value1],
195
+ value2: Expression[Value2],
196
+ value3: Expression[Value3],
197
+ value4: Expression[Value4],
198
+ value5: Expression[Value5],
199
+ value6: Expression[Value6],
200
+ value7: Expression[Value7],
201
+ /,
202
+ ) -> BoundReturning[
203
+ tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7]
204
+ ]: ...
205
+
206
+ @overload
207
+ def returning(
208
+ self,
209
+ value1: Expression[Value1],
210
+ value2: Expression[Value2],
211
+ value3: Expression[Value3],
212
+ value4: Expression[Value4],
213
+ value5: Expression[Value5],
214
+ value6: Expression[Value6],
215
+ value7: Expression[Value7],
216
+ value8: Expression[Value8],
217
+ /,
218
+ ) -> BoundReturning[
219
+ tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8]
220
+ ]: ...
221
+
222
+ @overload
223
+ def returning(
224
+ self,
225
+ value1: object,
226
+ value2: object,
227
+ value3: object,
228
+ value4: object,
229
+ value5: object,
230
+ value6: object,
231
+ value7: object,
232
+ value8: object,
233
+ /,
234
+ *values: object,
235
+ ) -> BoundReturning[tuple[object, ...]]:
236
+ """Wider than the overloads above name, where the types stop.
237
+
238
+ A statement is still a statement past eight returned columns,
239
+ and matching no overload would take the whole chain down with
240
+ it rather than just the tuple.
241
+ """
242
+
243
+ def returning( # type: ignore[misc]
244
+ self,
245
+ *values: object,
246
+ ) -> BoundReturning[tuple[object, ...]]:
247
+ # The builder declares typed overloads for callers; this is the one
248
+ # implementation behind them, reached variadically. The overloads above
249
+ # are what a caller sees, and they carry the types across.
250
+ build = cast(
251
+ "Callable[..., ReturningStatement[tuple[object, ...]]]",
252
+ self._statement.returning,
253
+ )
254
+ return BoundReturning(self._operations, build(*values))
255
+
256
+
257
+ class BoundInsert(BoundMutation[InsertStatement[*Values]], Generic[*Values]):
258
+ """An INSERT and the database it will run against."""
259
+
260
+ __slots__ = ()
261
+
262
+ def values(self, *values: *Values) -> BoundInsert[*Values]:
263
+ return BoundInsert(self._operations, self._statement.values(*values))
264
+
265
+ def values_many(self, rows: Iterable[object], /) -> BoundInsert[*Values]:
266
+ return BoundInsert(self._operations, self._statement.values_many(rows))
267
+
268
+ def on_conflict_do_nothing(
269
+ self,
270
+ *columns: ColumnReference,
271
+ ) -> BoundInsert[*Values]:
272
+ return BoundInsert(
273
+ self._operations,
274
+ self._statement.on_conflict_do_nothing(*columns),
275
+ )
276
+
277
+ def on_conflict_do_update(
278
+ self,
279
+ *columns: ColumnReference,
280
+ ) -> BoundConflictUpdate[*Values]:
281
+ return BoundConflictUpdate(
282
+ self._operations,
283
+ self._statement.on_conflict_do_update(*columns),
284
+ )
285
+
286
+
287
+ class BoundConflictUpdate(
288
+ BoundMutation[ConflictUpdate[*Values]],
289
+ Generic[*Values],
290
+ ):
291
+ """The SET and WHERE that belong to a conflict clause."""
292
+
293
+ __slots__ = ()
294
+
295
+ def set(
296
+ self,
297
+ column: Expression[AssignedValue],
298
+ value: AssignedValue | Expression[AssignedValue],
299
+ /,
300
+ ) -> BoundConflictUpdate[*Values]:
301
+ return BoundConflictUpdate(self._operations, self._statement.set(column, value))
302
+
303
+ def where(
304
+ self,
305
+ condition: Expression[bool],
306
+ /,
307
+ ) -> BoundConflictUpdate[*Values]:
308
+ return BoundConflictUpdate(self._operations, self._statement.where(condition))
309
+
310
+
311
+ class BoundUpdate(BoundMutation[UpdateStatement]):
312
+ """An UPDATE, which has to say which rows it means."""
313
+
314
+ __slots__ = ()
315
+
316
+ def set(
317
+ self,
318
+ column: Expression[AssignedValue],
319
+ value: AssignedValue | Expression[AssignedValue],
320
+ /,
321
+ ) -> BoundUpdate:
322
+ return BoundUpdate(self._operations, self._statement.set(column, value))
323
+
324
+ def set_values(
325
+ self,
326
+ values: object,
327
+ /,
328
+ *columns: ColumnReference,
329
+ ) -> BoundUpdate:
330
+ return BoundUpdate(
331
+ self._operations, self._statement.set_values(values, *columns)
332
+ )
333
+
334
+ def where(self, condition: Expression[bool], /) -> BoundUpdate:
335
+ return BoundUpdate(self._operations, self._statement.where(condition))
336
+
337
+ def all_rows(self) -> BoundUpdate:
338
+ return BoundUpdate(self._operations, self._statement.all_rows())
339
+
340
+
341
+ class BoundDelete(BoundMutation[DeleteStatement]):
342
+ """A DELETE, which has to say which rows it means."""
343
+
344
+ __slots__ = ()
345
+
346
+ def where(self, condition: Expression[bool], /) -> BoundDelete:
347
+ return BoundDelete(self._operations, self._statement.where(condition))
348
+
349
+ def all_rows(self) -> BoundDelete:
350
+ return BoundDelete(self._operations, self._statement.all_rows())
351
+
352
+
353
+ __all__ = (
354
+ "BoundConflictUpdate",
355
+ "BoundDelete",
356
+ "BoundInsert",
357
+ "BoundMutation",
358
+ "BoundReturning",
359
+ "BoundUpdate",
360
+ )
pyoq/errors.py ADDED
@@ -0,0 +1,317 @@
1
+ """Public exception hierarchy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from pyoq.naming import NameCollision
9
+
10
+
11
+ class PyOQError(Exception):
12
+ """Base class for package errors."""
13
+
14
+
15
+ class ConfigurationError(PyOQError):
16
+ """Base class for configuration failures."""
17
+
18
+
19
+ class ConfigurationFileError(ConfigurationError):
20
+ """Raised when a configuration file cannot be read or parsed."""
21
+
22
+
23
+ class ConfigurationValidationError(ConfigurationError, ValueError):
24
+ """Raised when configuration values violate the public contract."""
25
+
26
+
27
+ class UnknownConfigurationSettingError(ConfigurationValidationError):
28
+ """Raised when a configuration table contains unsupported settings."""
29
+
30
+
31
+ class PathContainmentError(ConfigurationValidationError):
32
+ """Raised when a configured path escapes its project root."""
33
+
34
+
35
+ class SecretResolutionError(ConfigurationError, LookupError):
36
+ """Raised when a referenced secret is unavailable."""
37
+
38
+
39
+ class RuntimeSelectionError(PyOQError, ValueError):
40
+ """Raised when runtime selection is invalid."""
41
+
42
+
43
+ class NativeRuntimeUnavailableError(PyOQError, ImportError):
44
+ """Raised when native execution is required but unavailable."""
45
+
46
+
47
+ class CommandError(PyOQError):
48
+ """Base class for command execution failures."""
49
+
50
+
51
+ class OperationUnavailableError(CommandError):
52
+ """Raised when a command's service is not implemented."""
53
+
54
+
55
+ class SchemaError(PyOQError):
56
+ """Base class for schema model failures."""
57
+
58
+
59
+ class SchemaValidationError(SchemaError, ValueError):
60
+ """Raised when schema metadata violates model invariants."""
61
+
62
+
63
+ class SchemaDriftError(SchemaError):
64
+ """Raised when a recorded schema no longer matches the database."""
65
+
66
+
67
+ class SchemaSerializationError(SchemaError, ValueError):
68
+ """Raised when serialized schema metadata is invalid."""
69
+
70
+
71
+ class SchemaInspectionError(SchemaError, CommandError):
72
+ """Raised when database metadata cannot be inspected safely."""
73
+
74
+
75
+ class HydrationError(PyOQError):
76
+ """Base class for row hydration failures."""
77
+
78
+
79
+ class RowIdentityError(HydrationError, ValueError):
80
+ """Raised when a result row cannot be identified."""
81
+
82
+
83
+ class CollectionDecodeError(HydrationError, ValueError):
84
+ """Raised when a nested collection does not hold what it promised."""
85
+
86
+
87
+ class RelationError(PyOQError):
88
+ """Base class for relation graph failures."""
89
+
90
+
91
+ class RelationResolutionError(RelationError, LookupError):
92
+ """Raised when a relation names a table the graph does not hold."""
93
+
94
+
95
+ class RelationNotLoadedError(RelationError):
96
+ """Raised when a relation that was never fetched is read."""
97
+
98
+
99
+ class FetchPlanError(RelationError, ValueError):
100
+ """Raised when a fetch plan does not describe a possible fetch."""
101
+
102
+
103
+ class NamingError(PyOQError):
104
+ """Base class for generated-name failures."""
105
+
106
+
107
+ class NamingValidationError(NamingError, ValueError):
108
+ """Raised when a naming request violates the public contract."""
109
+
110
+
111
+ class NamingCollisionError(NamingError):
112
+ """Raised with every generated-name collision in a naming batch."""
113
+
114
+ collisions: tuple[NameCollision, ...]
115
+
116
+ def __init__(
117
+ self,
118
+ message: str,
119
+ collisions: tuple[NameCollision, ...],
120
+ ) -> None:
121
+ super().__init__(message)
122
+ self.collisions = collisions
123
+
124
+
125
+ class GenerationError(CommandError):
126
+ """Base class for generation pipeline failures."""
127
+
128
+
129
+ class GenerationValidationError(GenerationError, ValueError):
130
+ """Raised when planned generated output is invalid."""
131
+
132
+
133
+ class GenerationManifestError(GenerationError, ValueError):
134
+ """Raised when an ownership manifest is malformed or incompatible."""
135
+
136
+
137
+ class GenerationOwnershipError(GenerationError):
138
+ """Raised when generation would modify a file it does not own."""
139
+
140
+
141
+ class GenerationLockError(GenerationError):
142
+ """Raised when another generation operation owns the project lock."""
143
+
144
+
145
+ class GenerationDriftError(GenerationError):
146
+ """Raised when checked generated output differs from its plan."""
147
+
148
+
149
+ class GenerationWriteError(GenerationError):
150
+ """Raised when an atomic package replacement cannot complete safely."""
151
+
152
+
153
+ class ExpressionError(PyOQError):
154
+ """Base class for query expression failures."""
155
+
156
+
157
+ class ExpressionTypeError(ExpressionError, TypeError):
158
+ """Raised when expression operands are not compatible."""
159
+
160
+
161
+ class RawExpressionError(ExpressionError, ValueError):
162
+ """Raised when an explicit raw expression template is invalid."""
163
+
164
+
165
+ class QueryError(PyOQError):
166
+ """Base class for query model failures."""
167
+
168
+
169
+ class QueryStateError(QueryError):
170
+ """Raised when a clause conflicts with the current query state."""
171
+
172
+
173
+ class QueryBudgetExceededError(QueryError):
174
+ """Raised when a scope asks a database to do more than it was allowed."""
175
+
176
+
177
+ class PolicyViolationError(PyOQError):
178
+ """A statement asked for something its policies do not allow."""
179
+
180
+
181
+ class QueryValidationError(QueryError, ValueError):
182
+ """Raised when query structure violates a public invariant."""
183
+
184
+
185
+ class CompilationError(QueryError):
186
+ """Base class for SQL compilation failures."""
187
+
188
+
189
+ class UnsupportedQueryError(CompilationError):
190
+ """Raised when a query requires an unavailable dialect capability."""
191
+
192
+
193
+ class ParameterLimitError(CompilationError):
194
+ """Raised when a compiled statement exceeds its parameter budget."""
195
+
196
+
197
+ class ExecutionError(QueryError):
198
+ """Base class for query execution failures."""
199
+
200
+
201
+ class QueryExecutionError(ExecutionError):
202
+ """Raised when a database driver cannot execute a query."""
203
+
204
+
205
+ class QueryTimeoutError(ExecutionError, TimeoutError):
206
+ """Raised when query execution exceeds its configured duration."""
207
+
208
+
209
+ class QueryCancelledError(ExecutionError):
210
+ """Raised when cooperative query cancellation is requested."""
211
+
212
+
213
+ class ParameterBindingError(ExecutionError, TypeError):
214
+ """Raised when a value cannot be represented by a database driver."""
215
+
216
+
217
+ class ResultCardinalityError(ExecutionError):
218
+ """Base class for result cardinality failures."""
219
+
220
+
221
+ class NoResultError(ResultCardinalityError):
222
+ """Raised when exactly one row was required but none was returned."""
223
+
224
+
225
+ class MultipleResultsError(ResultCardinalityError):
226
+ """Raised when at most one row was required but several were returned."""
227
+
228
+
229
+ class ConnectionPoolError(ExecutionError):
230
+ """Base class for connection pool failures."""
231
+
232
+
233
+ class ConnectionPoolClosedError(ConnectionPoolError):
234
+ """Raised when checkout is attempted after pool closure."""
235
+
236
+
237
+ class ConnectionPoolTimeoutError(ConnectionPoolError, TimeoutError):
238
+ """Raised when a connection cannot be checked out within its budget."""
239
+
240
+
241
+ class TransactionError(ExecutionError):
242
+ """Base class for transaction failures."""
243
+
244
+
245
+ class TransactionStateError(TransactionError):
246
+ """Raised when a transaction operation violates ownership or state."""
247
+
248
+
249
+ class StreamError(ExecutionError):
250
+ """Base class for streaming failures."""
251
+
252
+
253
+ class StreamStateError(StreamError):
254
+ """Raised when a row stream violates its lifecycle or ownership contract."""
255
+
256
+
257
+ __all__ = (
258
+ "CollectionDecodeError",
259
+ "CommandError",
260
+ "CompilationError",
261
+ "ConfigurationError",
262
+ "ConfigurationFileError",
263
+ "ConfigurationValidationError",
264
+ "ConnectionPoolClosedError",
265
+ "ConnectionPoolError",
266
+ "ConnectionPoolTimeoutError",
267
+ "ExecutionError",
268
+ "ExpressionError",
269
+ "ExpressionTypeError",
270
+ "FetchPlanError",
271
+ "GenerationDriftError",
272
+ "GenerationError",
273
+ "GenerationLockError",
274
+ "GenerationManifestError",
275
+ "GenerationOwnershipError",
276
+ "GenerationValidationError",
277
+ "GenerationWriteError",
278
+ "HydrationError",
279
+ "MultipleResultsError",
280
+ "NamingCollisionError",
281
+ "NamingError",
282
+ "NamingValidationError",
283
+ "NativeRuntimeUnavailableError",
284
+ "NoResultError",
285
+ "OperationUnavailableError",
286
+ "ParameterBindingError",
287
+ "ParameterLimitError",
288
+ "PathContainmentError",
289
+ "PolicyViolationError",
290
+ "PyOQError",
291
+ "QueryBudgetExceededError",
292
+ "QueryCancelledError",
293
+ "QueryError",
294
+ "QueryExecutionError",
295
+ "QueryStateError",
296
+ "QueryTimeoutError",
297
+ "QueryValidationError",
298
+ "RawExpressionError",
299
+ "RelationError",
300
+ "RelationNotLoadedError",
301
+ "RelationResolutionError",
302
+ "ResultCardinalityError",
303
+ "RowIdentityError",
304
+ "RuntimeSelectionError",
305
+ "SchemaDriftError",
306
+ "SchemaError",
307
+ "SchemaInspectionError",
308
+ "SchemaSerializationError",
309
+ "SchemaValidationError",
310
+ "SecretResolutionError",
311
+ "StreamError",
312
+ "StreamStateError",
313
+ "TransactionError",
314
+ "TransactionStateError",
315
+ "UnknownConfigurationSettingError",
316
+ "UnsupportedQueryError",
317
+ )
@@ -0,0 +1,32 @@
1
+ """Running PyOQ inside a FastAPI application."""
2
+
3
+ from pyoq.fastapi.dependencies import (
4
+ async_database,
5
+ async_scoped,
6
+ async_transaction,
7
+ database,
8
+ scoped,
9
+ transaction,
10
+ )
11
+ from pyoq.fastapi.lifespan import database_lifespan, databases_of
12
+ from pyoq.serving import (
13
+ DEFAULT,
14
+ AsyncDatabase,
15
+ Databases,
16
+ SyncDatabase,
17
+ )
18
+
19
+ __all__ = (
20
+ "DEFAULT",
21
+ "AsyncDatabase",
22
+ "Databases",
23
+ "SyncDatabase",
24
+ "async_database",
25
+ "async_scoped",
26
+ "async_transaction",
27
+ "database",
28
+ "database_lifespan",
29
+ "databases_of",
30
+ "scoped",
31
+ "transaction",
32
+ )