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
@@ -0,0 +1,1001 @@
1
+ """Canonical Python generation model derived from a schema snapshot."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from pyoq.config import DatabaseDialect
8
+ from pyoq.naming import (
9
+ NameRequest,
10
+ NamingPolicy,
11
+ NamingResult,
12
+ NamingScope,
13
+ PythonNameStyle,
14
+ )
15
+ from pyoq.relations import (
16
+ RelationDirection,
17
+ RelationGraph,
18
+ TypedRelation,
19
+ build_relation_graph,
20
+ )
21
+ from pyoq.schema import (
22
+ Catalog,
23
+ Column,
24
+ Domain,
25
+ EnumType,
26
+ Identifier,
27
+ Key,
28
+ KeyKind,
29
+ ObjectReference,
30
+ ParameterMode,
31
+ Routine,
32
+ RoutineKind,
33
+ RoutineParameter,
34
+ Schema,
35
+ SchemaSnapshot,
36
+ SqlType,
37
+ Table,
38
+ TypeKind,
39
+ )
40
+
41
+ _FIELD_RESERVED_NAMES = (
42
+ "build",
43
+ "builder",
44
+ "update_builder",
45
+ )
46
+
47
+
48
+ @dataclass(frozen=True, slots=True)
49
+ class GeneratedColumn:
50
+ source: Column
51
+ field_name: str
52
+ constant_name: str
53
+ annotation: str
54
+
55
+ @property
56
+ def writable(self) -> bool:
57
+ return self.source.generated is None
58
+
59
+ @property
60
+ def required(self) -> bool:
61
+ return (
62
+ self.writable and not self.source.nullable and self.source.default is None
63
+ )
64
+
65
+ @property
66
+ def value_annotation(self) -> str:
67
+ if self.source.nullable:
68
+ return f"{self.annotation} | None"
69
+ return self.annotation
70
+
71
+
72
+ @dataclass(frozen=True, slots=True)
73
+ class GeneratedKey:
74
+ source: Key
75
+ type_name: str
76
+ constant_name: str
77
+ columns: tuple[GeneratedColumn, ...]
78
+
79
+
80
+ @dataclass(frozen=True, slots=True)
81
+ class GeneratedRelation:
82
+ source: TypedRelation
83
+ constant_name: str
84
+ target_row_name: str
85
+
86
+
87
+ @dataclass(frozen=True, slots=True)
88
+ class GeneratedTable:
89
+ source: Table
90
+ schema_name: str | None
91
+ catalog_name: str | None
92
+ class_name: str
93
+ constant_name: str
94
+ row_name: str
95
+ insert_name: str
96
+ update_name: str
97
+ builder_name: str
98
+ complete_builder_name: str
99
+ update_builder_name: str
100
+ columns: tuple[GeneratedColumn, ...]
101
+ keys: tuple[GeneratedKey, ...]
102
+ relations: tuple[GeneratedRelation, ...]
103
+
104
+ @property
105
+ def writable_columns(self) -> tuple[GeneratedColumn, ...]:
106
+ return tuple(column for column in self.columns if column.writable)
107
+
108
+ @property
109
+ def required_columns(self) -> tuple[GeneratedColumn, ...]:
110
+ return tuple(column for column in self.columns if column.required)
111
+
112
+
113
+ @dataclass(frozen=True, slots=True)
114
+ class GeneratedEnum:
115
+ source: EnumType
116
+ schema_name: str | None
117
+ catalog_name: str | None
118
+ class_name: str
119
+ members: tuple[tuple[str, str], ...]
120
+
121
+
122
+ @dataclass(frozen=True, slots=True)
123
+ class GeneratedDomain:
124
+ """A domain as a type of its own, distinct from the type it is written over.
125
+
126
+ Two domains over one base are two types, so each becomes a name a checker
127
+ can tell apart. The value is still the base value, so nothing is converted
128
+ on the way in or out.
129
+ """
130
+
131
+ source: Domain
132
+ schema_name: str | None
133
+ catalog_name: str | None
134
+ type_name: str
135
+ base_annotation: str
136
+
137
+
138
+ @dataclass(frozen=True, slots=True)
139
+ class GeneratedRoutineParameter:
140
+ source: RoutineParameter
141
+ field_name: str
142
+ annotation: str
143
+
144
+
145
+ @dataclass(frozen=True, slots=True)
146
+ class GeneratedRoutine:
147
+ """A routine as a call a caller can make, with the database's own signature.
148
+
149
+ The parameters and the result come from the catalog rather than from a
150
+ caller's assertion, so arity, order, and types are the database's facts.
151
+ """
152
+
153
+ source: Routine
154
+ schema_name: str | None
155
+ catalog_name: str | None
156
+ function_name: str
157
+ parameters: tuple[GeneratedRoutineParameter, ...]
158
+ returns_annotation: str | None
159
+ dialect: DatabaseDialect
160
+
161
+ @property
162
+ def given_parameters(self) -> tuple[GeneratedRoutineParameter, ...]:
163
+ """Values a caller supplies, excluding values the routine only writes."""
164
+ return tuple(
165
+ parameter
166
+ for parameter in self.parameters
167
+ if parameter.source.mode is not ParameterMode.OUT
168
+ )
169
+
170
+ @property
171
+ def sql_parameters(self) -> tuple[GeneratedRoutineParameter, ...]:
172
+ """Parameters represented in SQL for this dialect and routine kind."""
173
+ if self.source.kind is RoutineKind.FUNCTION:
174
+ return self.given_parameters
175
+ return self.parameters
176
+
177
+ @property
178
+ def unsupported_reason(self) -> str | None:
179
+ """Why the generated adapter cannot safely invoke this routine."""
180
+ has_outputs = any(
181
+ parameter.source.mode is not ParameterMode.IN
182
+ for parameter in self.parameters
183
+ )
184
+ if self.dialect is DatabaseDialect.MYSQL and has_outputs:
185
+ return "MySQL OUT and INOUT routines require an output adapter"
186
+ if self.source.kind is RoutineKind.PROCEDURE and _default_precedes_output(
187
+ self.parameters
188
+ ):
189
+ return "Procedure defaults followed by OUT parameters are not supported"
190
+ return None
191
+
192
+ @property
193
+ def outputs(self) -> tuple[GeneratedRoutineParameter, ...]:
194
+ """The parameters the procedure answers through rather than reads."""
195
+ return tuple(
196
+ parameter
197
+ for parameter in self.parameters
198
+ if parameter.source.mode is not ParameterMode.IN
199
+ )
200
+
201
+
202
+ @dataclass(frozen=True, slots=True)
203
+ class GeneratedOverload:
204
+ """Routines a database lets share one name, called under that name.
205
+
206
+ Python resolves an overload from what a call gives it, and the only thing
207
+ it can tell two calls apart by at runtime is how many values they carry.
208
+ A group is written only where every routine in it takes a different
209
+ number, so the call that arrives names exactly one of them.
210
+ """
211
+
212
+ function_name: str
213
+ routines: tuple[GeneratedRoutine, ...]
214
+
215
+
216
+ def _default_precedes_output(
217
+ parameters: tuple[GeneratedRoutineParameter, ...],
218
+ ) -> bool:
219
+ default_seen = False
220
+ for parameter in parameters:
221
+ default_seen = default_seen or parameter.source.has_default
222
+ if default_seen and parameter.source.mode is ParameterMode.OUT:
223
+ return True
224
+ return False
225
+
226
+
227
+ @dataclass(frozen=True, slots=True)
228
+ class GeneratedPackage:
229
+ tables: tuple[GeneratedTable, ...]
230
+ enums: tuple[GeneratedEnum, ...]
231
+ domains: tuple[GeneratedDomain, ...] = ()
232
+ routines: tuple[GeneratedRoutine, ...] = ()
233
+ overloads: tuple[GeneratedOverload, ...] = ()
234
+
235
+
236
+ @dataclass(frozen=True, slots=True)
237
+ class _TableSource:
238
+ key: str
239
+ catalog: Catalog
240
+ schema: Schema
241
+ table: Table
242
+
243
+
244
+ @dataclass(frozen=True, slots=True)
245
+ class _RoutineSource:
246
+ key: str
247
+ catalog: Catalog
248
+ schema: Schema
249
+ routine: Routine
250
+ shared_name: bool = False
251
+ """Whether another routine of this schema is written under the same name."""
252
+
253
+ @property
254
+ def group(self) -> tuple[str | None, str | None, str]:
255
+ """What makes two routines overloads of each other rather than two names."""
256
+ return (
257
+ _identifier_value(self.catalog.name),
258
+ _identifier_value(self.schema.name),
259
+ self.routine.name.value,
260
+ )
261
+
262
+
263
+ @dataclass(frozen=True, slots=True)
264
+ class _DomainSource:
265
+ key: str
266
+ catalog: Catalog
267
+ schema: Schema
268
+ domain: Domain
269
+
270
+
271
+ @dataclass(frozen=True, slots=True)
272
+ class _EnumSource:
273
+ key: str
274
+ catalog: Catalog
275
+ schema: Schema
276
+ enum: EnumType
277
+
278
+
279
+ def build_codegen_package(
280
+ snapshot: SchemaSnapshot,
281
+ naming_policy: NamingPolicy | None = None,
282
+ ) -> GeneratedPackage:
283
+ tables = _table_sources(snapshot)
284
+ enums = _enum_sources(snapshot)
285
+ domains = _domain_sources(snapshot)
286
+ routines = _routine_sources(snapshot)
287
+ graph = build_relation_graph(snapshot)
288
+ scopes = _naming_scopes(tables, enums, domains, routines, graph)
289
+ names = (naming_policy or NamingPolicy()).resolve(scopes)
290
+ generated_enums = _generated_enums(enums, names)
291
+ enum_names = _enum_name_map(generated_enums)
292
+ generated_domains = _generated_domains(domains, names, enum_names)
293
+ domain_names = _domain_name_map(generated_domains)
294
+ generated_routines = _generated_routines(
295
+ routines, names, enum_names, domain_names, snapshot.dialect
296
+ )
297
+ table_row_names = {
298
+ (
299
+ _identifier_value(table.catalog.name),
300
+ _identifier_value(table.schema.name),
301
+ table.table.name.value,
302
+ ): f"{names.get('tables', table.key)}Row"
303
+ for table in tables
304
+ }
305
+ generated_tables = tuple(
306
+ _generated_table(table, names, enum_names, domain_names, table_row_names, graph)
307
+ for table in tables
308
+ )
309
+ return GeneratedPackage(
310
+ generated_tables,
311
+ generated_enums,
312
+ generated_domains,
313
+ generated_routines,
314
+ _generated_overloads(routines, generated_routines, names),
315
+ )
316
+
317
+
318
+ def _table_sources(snapshot: SchemaSnapshot) -> tuple[_TableSource, ...]:
319
+ sources: list[_TableSource] = []
320
+ for catalog_index, catalog in enumerate(snapshot.catalogs):
321
+ for schema_index, schema in enumerate(catalog.schemas):
322
+ for table_index, table in enumerate(schema.tables):
323
+ key = _qualified_key(
324
+ catalog.name,
325
+ schema.name,
326
+ table.name,
327
+ (catalog_index, schema_index, table_index),
328
+ )
329
+ sources.append(_TableSource(key, catalog, schema, table))
330
+ return tuple(sorted(sources, key=lambda source: source.key))
331
+
332
+
333
+ def _enum_sources(snapshot: SchemaSnapshot) -> tuple[_EnumSource, ...]:
334
+ sources: list[_EnumSource] = []
335
+ for catalog_index, catalog in enumerate(snapshot.catalogs):
336
+ for schema_index, schema in enumerate(catalog.schemas):
337
+ for enum_index, enum in enumerate(schema.enums):
338
+ key = _qualified_key(
339
+ catalog.name,
340
+ schema.name,
341
+ enum.name,
342
+ (catalog_index, schema_index, enum_index),
343
+ )
344
+ sources.append(_EnumSource(key, catalog, schema, enum))
345
+ return tuple(sorted(sources, key=lambda source: source.key))
346
+
347
+
348
+ def _qualified_key(
349
+ catalog: Identifier | None,
350
+ schema: Identifier | None,
351
+ name: Identifier,
352
+ indexes: tuple[int, int, int],
353
+ ) -> str:
354
+ qualified = tuple(
355
+ identifier.value if identifier is not None else ""
356
+ for identifier in (catalog, schema, name)
357
+ )
358
+ return "/".join((*qualified, *(str(index) for index in indexes)))
359
+
360
+
361
+ def _naming_scopes(
362
+ tables: tuple[_TableSource, ...],
363
+ enums: tuple[_EnumSource, ...],
364
+ domains: tuple[_DomainSource, ...],
365
+ routines: tuple[_RoutineSource, ...],
366
+ graph: RelationGraph,
367
+ ) -> tuple[NamingScope, ...]:
368
+ scopes: list[NamingScope] = [
369
+ NamingScope(
370
+ "tables",
371
+ PythonNameStyle.PASCAL_CASE,
372
+ tuple(NameRequest(table.key, table.table.name) for table in tables),
373
+ ),
374
+ NamingScope(
375
+ "table-constants",
376
+ PythonNameStyle.UPPER_SNAKE_CASE,
377
+ tuple(NameRequest(table.key, table.table.name) for table in tables),
378
+ ),
379
+ NamingScope(
380
+ "enums",
381
+ PythonNameStyle.PASCAL_CASE,
382
+ tuple(NameRequest(enum.key, enum.enum.name) for enum in enums),
383
+ ),
384
+ NamingScope(
385
+ "domains",
386
+ PythonNameStyle.PASCAL_CASE,
387
+ tuple(NameRequest(domain.key, domain.domain.name) for domain in domains),
388
+ ),
389
+ NamingScope(
390
+ "routines",
391
+ PythonNameStyle.SNAKE_CASE,
392
+ _routine_name_requests(routines),
393
+ ),
394
+ *(
395
+ NamingScope(
396
+ f"routine-parameters:{routine.key}",
397
+ PythonNameStyle.SNAKE_CASE,
398
+ tuple(
399
+ NameRequest(str(index), parameter.name or Identifier(f"a{index}"))
400
+ for index, parameter in enumerate(routine.routine.parameters)
401
+ ),
402
+ )
403
+ for routine in routines
404
+ ),
405
+ ]
406
+ for table in tables:
407
+ scopes.extend(_table_scopes(table, _table_relations(table, graph)))
408
+ for enum in enums:
409
+ scopes.append(
410
+ NamingScope(
411
+ f"enum-members:{enum.key}",
412
+ PythonNameStyle.UPPER_SNAKE_CASE,
413
+ tuple(
414
+ NameRequest(str(index), Identifier(value))
415
+ for index, value in enumerate(enum.enum.values)
416
+ ),
417
+ )
418
+ )
419
+ return tuple(scopes)
420
+
421
+
422
+ def _table_relations(
423
+ table: _TableSource,
424
+ graph: RelationGraph,
425
+ ) -> tuple[TypedRelation, ...]:
426
+ """Both directions of every key that touches this table."""
427
+ return graph.relations_from(
428
+ ObjectReference(table.table.name, table.schema.name, table.catalog.name)
429
+ )
430
+
431
+
432
+ def _table_scopes(
433
+ table: _TableSource,
434
+ relations: tuple[TypedRelation, ...],
435
+ ) -> tuple[NamingScope, ...]:
436
+ column_requests = tuple(
437
+ NameRequest(str(index), column.name)
438
+ for index, column in enumerate(table.table.columns)
439
+ )
440
+ key_requests = tuple(
441
+ NameRequest(str(index), _key_type_identifier(key))
442
+ for index, key in enumerate(table.table.keys)
443
+ )
444
+ key_constant_requests = tuple(
445
+ NameRequest(str(index), _key_constant_identifier(key, index))
446
+ for index, key in enumerate(table.table.keys)
447
+ )
448
+ relation_requests = tuple(
449
+ NameRequest(str(index), _relation_identifier(relation))
450
+ for index, relation in enumerate(relations)
451
+ )
452
+ return (
453
+ NamingScope(
454
+ f"fields:{table.key}",
455
+ PythonNameStyle.SNAKE_CASE,
456
+ column_requests,
457
+ _FIELD_RESERVED_NAMES,
458
+ ),
459
+ NamingScope(
460
+ f"columns:{table.key}",
461
+ PythonNameStyle.UPPER_SNAKE_CASE,
462
+ column_requests,
463
+ ),
464
+ NamingScope(
465
+ f"key-types:{table.key}",
466
+ PythonNameStyle.PASCAL_CASE,
467
+ key_requests,
468
+ ),
469
+ NamingScope(
470
+ f"key-constants:{table.key}",
471
+ PythonNameStyle.UPPER_SNAKE_CASE,
472
+ key_constant_requests,
473
+ ),
474
+ NamingScope(
475
+ f"relations:{table.key}",
476
+ PythonNameStyle.UPPER_SNAKE_CASE,
477
+ relation_requests,
478
+ ),
479
+ )
480
+
481
+
482
+ def _key_constant_identifier(key: Key, index: int) -> Identifier:
483
+ if key.name is not None:
484
+ return key.name
485
+ columns = "_".join(column.value for column in key.columns)
486
+ prefix = "primary" if key.kind is KeyKind.PRIMARY else "unique"
487
+ return Identifier(f"{prefix}_{columns}_{index}")
488
+
489
+
490
+ def _key_type_identifier(key: Key) -> Identifier:
491
+ if key.kind is KeyKind.PRIMARY:
492
+ return Identifier("primary_key")
493
+ columns = "_".join(column.value for column in key.columns)
494
+ return Identifier(f"{columns}_unique_key")
495
+
496
+
497
+ def _relation_identifier(relation: TypedRelation) -> Identifier:
498
+ """Name a relation after the key it follows, and say which way.
499
+
500
+ Both directions of one key land on one table when the key points at that
501
+ table itself, so the direction is always part of the name rather than only
502
+ when it happens to be needed.
503
+ """
504
+ base = _relation_base_identifier(relation)
505
+ if relation.direction is RelationDirection.FORWARD:
506
+ return base
507
+ return Identifier(f"{base.value}_reverse")
508
+
509
+
510
+ def _relation_base_identifier(relation: TypedRelation) -> Identifier:
511
+ """Name the key by the table across it and the columns that carry it.
512
+
513
+ Nothing here depends on where the relation sits in a list, so adding a key
514
+ to some other table cannot rename the ones already generated.
515
+ """
516
+ if relation.constraint is not None:
517
+ return relation.constraint
518
+ columns = "_".join(column.value for column in _referencing_columns(relation))
519
+ return Identifier(f"{relation.target.table.name.value}_{columns}")
520
+
521
+
522
+ def _referencing_columns(relation: TypedRelation) -> tuple[Identifier, ...]:
523
+ """The columns that carry the key, which always sit on the child side."""
524
+ if relation.direction is RelationDirection.FORWARD:
525
+ return relation.source.columns
526
+ return relation.target.columns
527
+
528
+
529
+ def _generated_enums(
530
+ sources: tuple[_EnumSource, ...],
531
+ names: NamingResult,
532
+ ) -> tuple[GeneratedEnum, ...]:
533
+ return tuple(
534
+ GeneratedEnum(
535
+ source=source.enum,
536
+ schema_name=_identifier_value(source.schema.name),
537
+ catalog_name=_identifier_value(source.catalog.name),
538
+ class_name=names.get("enums", source.key),
539
+ members=tuple(
540
+ (
541
+ names.get(f"enum-members:{source.key}", str(index)),
542
+ value,
543
+ )
544
+ for index, value in enumerate(source.enum.values)
545
+ ),
546
+ )
547
+ for source in sources
548
+ )
549
+
550
+
551
+ def _domain_sources(snapshot: SchemaSnapshot) -> tuple[_DomainSource, ...]:
552
+ sources: list[_DomainSource] = []
553
+ for catalog_index, catalog in enumerate(snapshot.catalogs):
554
+ for schema_index, schema in enumerate(catalog.schemas):
555
+ for domain_index, domain in enumerate(schema.domains):
556
+ key = _qualified_key(
557
+ catalog.name,
558
+ schema.name,
559
+ domain.name,
560
+ (catalog_index, schema_index, domain_index),
561
+ )
562
+ sources.append(_DomainSource(key, catalog, schema, domain))
563
+ return tuple(sorted(sources, key=lambda source: source.key))
564
+
565
+
566
+ def _generated_domains(
567
+ sources: tuple[_DomainSource, ...],
568
+ names: NamingResult,
569
+ enum_names: dict[tuple[str | None, str | None, str], str],
570
+ ) -> tuple[GeneratedDomain, ...]:
571
+ return tuple(
572
+ GeneratedDomain(
573
+ source=source.domain,
574
+ schema_name=_identifier_value(source.schema.name),
575
+ catalog_name=_identifier_value(source.catalog.name),
576
+ type_name=names.get("domains", source.key),
577
+ base_annotation=_python_annotation(
578
+ source.domain.data_type,
579
+ enum_names,
580
+ default_catalog=_identifier_value(source.catalog.name),
581
+ default_schema=_identifier_value(source.schema.name),
582
+ ),
583
+ )
584
+ for source in sources
585
+ )
586
+
587
+
588
+ def _routine_sources(snapshot: SchemaSnapshot) -> tuple[_RoutineSource, ...]:
589
+ placed: list[tuple[tuple[int, int, int], Catalog, Schema, Routine]] = [
590
+ ((catalog_index, schema_index, routine_index), catalog, schema, routine)
591
+ for catalog_index, catalog in enumerate(snapshot.catalogs)
592
+ for schema_index, schema in enumerate(catalog.schemas)
593
+ for routine_index, routine in enumerate(schema.routines)
594
+ ]
595
+ shared = _shared_routine_names(placed)
596
+ sources: list[_RoutineSource] = []
597
+ for position, catalog, schema, routine in placed:
598
+ group = (
599
+ _identifier_value(catalog.name),
600
+ _identifier_value(schema.name),
601
+ routine.name.value,
602
+ )
603
+ is_shared = group in shared
604
+ sources.append(
605
+ _RoutineSource(
606
+ _qualified_key(
607
+ catalog.name,
608
+ schema.name,
609
+ _overloaded(routine, is_shared),
610
+ position,
611
+ ),
612
+ catalog,
613
+ schema,
614
+ routine,
615
+ is_shared,
616
+ )
617
+ )
618
+ return tuple(sorted(sources, key=lambda source: source.key))
619
+
620
+
621
+ def _routine_name_requests(
622
+ routines: tuple[_RoutineSource, ...],
623
+ ) -> tuple[NameRequest, ...]:
624
+ """Every routine, and the shared names the calls between them are written under.
625
+
626
+ They are asked for together so that no shared name can collide with a
627
+ routine that happens to be spelled the same way.
628
+ """
629
+ requests = [
630
+ NameRequest(routine.key, _overloaded(routine.routine, routine.shared_name))
631
+ for routine in routines
632
+ ]
633
+ shared = {
634
+ routine.group: routine.routine.name
635
+ for routine in routines
636
+ if routine.shared_name
637
+ }
638
+ requests.extend(
639
+ NameRequest(_overload_key(group), name) for group, name in shared.items()
640
+ )
641
+ return tuple(requests)
642
+
643
+
644
+ def _generated_overloads(
645
+ sources: tuple[_RoutineSource, ...],
646
+ generated: tuple[GeneratedRoutine, ...],
647
+ names: NamingResult,
648
+ ) -> tuple[GeneratedOverload, ...]:
649
+ """One call per shared name, where the calls can be told apart.
650
+
651
+ Two routines of the same name and the same number of parameters differ
652
+ only by the types of them, and nothing a Python call carries at runtime
653
+ separates those reliably, so no call is written for that name and each
654
+ routine keeps the name of its own.
655
+ """
656
+ grouped: dict[tuple[str | None, str | None, str], list[GeneratedRoutine]] = {}
657
+ for source, routine in zip(sources, generated, strict=True):
658
+ if source.shared_name:
659
+ grouped.setdefault(source.group, []).append(routine)
660
+ overloads: list[GeneratedOverload] = []
661
+ for group, members in grouped.items():
662
+ ordered = sorted(members, key=lambda item: len(item.given_parameters))
663
+ if not _tellable_apart(ordered):
664
+ continue
665
+ overloads.append(
666
+ GeneratedOverload(
667
+ names.get("routines", _overload_key(group)), tuple(ordered)
668
+ )
669
+ )
670
+ return tuple(sorted(overloads, key=lambda item: item.function_name))
671
+
672
+
673
+ def _tellable_apart(ordered: list[GeneratedRoutine]) -> bool:
674
+ """Whether one call written under the shared name can reach each of them.
675
+
676
+ Each routine must take a different number of values, so the call names
677
+ one, and the routines must agree on the type of every position they
678
+ share, so the one signature that carries them out states each once.
679
+ """
680
+ arities = [len(routine.given_parameters) for routine in ordered]
681
+ if len(set(arities)) != len(arities):
682
+ return False
683
+ widest = ordered[-1].given_parameters
684
+ return all(
685
+ parameter.annotation == widest[position].annotation
686
+ for routine in ordered
687
+ for position, parameter in enumerate(routine.given_parameters)
688
+ )
689
+
690
+
691
+ def _overload_key(group: tuple[str | None, str | None, str]) -> str:
692
+ """The naming key the shared name itself is written under."""
693
+ catalog, schema, name = group
694
+ return f"overload:{catalog}.{schema}.{name}"
695
+
696
+
697
+ def _shared_routine_names(
698
+ placed: list[tuple[tuple[int, int, int], Catalog, Schema, Routine]],
699
+ ) -> frozenset[tuple[str | None, str | None, str]]:
700
+ """Names more than one routine of a schema is written under."""
701
+ seen: dict[tuple[str | None, str | None, str], int] = {}
702
+ for _, catalog, schema, routine in placed:
703
+ group = (
704
+ _identifier_value(catalog.name),
705
+ _identifier_value(schema.name),
706
+ routine.name.value,
707
+ )
708
+ seen[group] = seen.get(group, 0) + 1
709
+ return frozenset(group for group, count in seen.items() if count > 1)
710
+
711
+
712
+ def _overloaded(routine: Routine, shared: bool) -> Identifier:
713
+ """A name that separates two routines the database lets share one.
714
+
715
+ A shared name is never taken by one of the routines that share it, so the
716
+ call that chooses between them can be written under the name itself.
717
+ """
718
+ if routine.overload is not None:
719
+ return Identifier(f"{routine.name.value} {routine.overload}")
720
+ if shared:
721
+ return Identifier(f"{routine.name.value} 1")
722
+ return routine.name
723
+
724
+
725
+ def _generated_routines(
726
+ sources: tuple[_RoutineSource, ...],
727
+ names: NamingResult,
728
+ enum_names: dict[tuple[str | None, str | None, str], str],
729
+ domain_names: dict[tuple[str | None, str | None, str], str],
730
+ dialect: DatabaseDialect,
731
+ ) -> tuple[GeneratedRoutine, ...]:
732
+ return tuple(
733
+ GeneratedRoutine(
734
+ source=source.routine,
735
+ schema_name=_identifier_value(source.schema.name),
736
+ catalog_name=_identifier_value(source.catalog.name),
737
+ function_name=names.get("routines", source.key),
738
+ parameters=tuple(
739
+ _generated_parameter(
740
+ source, parameter, index, names, enum_names, domain_names
741
+ )
742
+ for index, parameter in enumerate(source.routine.parameters)
743
+ ),
744
+ returns_annotation=(
745
+ None
746
+ if source.routine.returns is None
747
+ else _routine_annotation(
748
+ source, source.routine.returns, enum_names, domain_names
749
+ )
750
+ ),
751
+ dialect=dialect,
752
+ )
753
+ for source in sources
754
+ )
755
+
756
+
757
+ def _generated_parameter(
758
+ source: _RoutineSource,
759
+ parameter: RoutineParameter,
760
+ index: int,
761
+ names: NamingResult,
762
+ enum_names: dict[tuple[str | None, str | None, str], str],
763
+ domain_names: dict[tuple[str | None, str | None, str], str],
764
+ ) -> GeneratedRoutineParameter:
765
+ written = _routine_annotation(source, parameter.data_type, enum_names, domain_names)
766
+ return GeneratedRoutineParameter(
767
+ source=parameter,
768
+ field_name=names.get(f"routine-parameters:{source.key}", str(index)),
769
+ annotation=f"{written} | None" if parameter.nullable else written,
770
+ )
771
+
772
+
773
+ def _routine_annotation(
774
+ source: _RoutineSource,
775
+ data_type: SqlType,
776
+ enum_names: dict[tuple[str | None, str | None, str], str],
777
+ domain_names: dict[tuple[str | None, str | None, str], str],
778
+ ) -> str:
779
+ return _python_annotation(
780
+ data_type,
781
+ enum_names,
782
+ default_catalog=_identifier_value(source.catalog.name),
783
+ default_schema=_identifier_value(source.schema.name),
784
+ domain_names=domain_names,
785
+ )
786
+
787
+
788
+ def _domain_name_map(
789
+ domains: tuple[GeneratedDomain, ...],
790
+ ) -> dict[tuple[str | None, str | None, str], str]:
791
+ return {
792
+ (
793
+ domain.catalog_name,
794
+ domain.schema_name,
795
+ domain.source.name.value,
796
+ ): domain.type_name
797
+ for domain in domains
798
+ }
799
+
800
+
801
+ def _enum_name_map(
802
+ enums: tuple[GeneratedEnum, ...],
803
+ ) -> dict[tuple[str | None, str | None, str], str]:
804
+ return {
805
+ (enum.catalog_name, enum.schema_name, enum.source.name.value): enum.class_name
806
+ for enum in enums
807
+ }
808
+
809
+
810
+ def _generated_table(
811
+ source: _TableSource,
812
+ names: NamingResult,
813
+ enum_names: dict[tuple[str | None, str | None, str], str],
814
+ domain_names: dict[tuple[str | None, str | None, str], str],
815
+ table_row_names: dict[tuple[str | None, str | None, str], str],
816
+ graph: RelationGraph,
817
+ ) -> GeneratedTable:
818
+ class_name = names.get("tables", source.key)
819
+ columns = tuple(
820
+ _generated_column(source, column, index, names, enum_names, domain_names)
821
+ for index, column in enumerate(source.table.columns)
822
+ )
823
+ column_map = {column.source.name: column for column in columns}
824
+ keys = tuple(
825
+ _generated_key(source, key, index, names, column_map, class_name)
826
+ for index, key in enumerate(source.table.keys)
827
+ )
828
+ relations = tuple(
829
+ GeneratedRelation(
830
+ relation,
831
+ (
832
+ f"{names.get('table-constants', source.key)}_"
833
+ f"{names.get(f'relations:{source.key}', str(index))}"
834
+ ),
835
+ _target_row_name(relation.target.table, table_row_names, source),
836
+ )
837
+ for index, relation in enumerate(_table_relations(source, graph))
838
+ )
839
+ return GeneratedTable(
840
+ source=source.table,
841
+ schema_name=_identifier_value(source.schema.name),
842
+ catalog_name=_identifier_value(source.catalog.name),
843
+ class_name=class_name,
844
+ constant_name=names.get("table-constants", source.key),
845
+ row_name=f"{class_name}Row",
846
+ insert_name=f"{class_name}Insert",
847
+ update_name=f"{class_name}Update",
848
+ builder_name=f"{class_name}Builder",
849
+ complete_builder_name=f"_{class_name}CompleteBuilder",
850
+ update_builder_name=f"{class_name}UpdateBuilder",
851
+ columns=columns,
852
+ keys=keys,
853
+ relations=relations,
854
+ )
855
+
856
+
857
+ def _generated_column(
858
+ table: _TableSource,
859
+ column: Column,
860
+ index: int,
861
+ names: NamingResult,
862
+ enum_names: dict[tuple[str | None, str | None, str], str],
863
+ domain_names: dict[tuple[str | None, str | None, str], str],
864
+ ) -> GeneratedColumn:
865
+ return GeneratedColumn(
866
+ source=column,
867
+ field_name=names.get(f"fields:{table.key}", str(index)),
868
+ constant_name=names.get(f"columns:{table.key}", str(index)),
869
+ annotation=_python_annotation(
870
+ column.data_type,
871
+ enum_names,
872
+ default_catalog=_identifier_value(table.catalog.name),
873
+ default_schema=_identifier_value(table.schema.name),
874
+ domain_names=domain_names,
875
+ ),
876
+ )
877
+
878
+
879
+ def _generated_key(
880
+ table: _TableSource,
881
+ key: Key,
882
+ index: int,
883
+ names: NamingResult,
884
+ columns: dict[Identifier, GeneratedColumn],
885
+ table_name: str,
886
+ ) -> GeneratedKey:
887
+ suffix = names.get(f"key-types:{table.key}", str(index))
888
+ return GeneratedKey(
889
+ source=key,
890
+ type_name=f"{table_name}{suffix}",
891
+ constant_name=names.get(f"key-constants:{table.key}", str(index)),
892
+ columns=tuple(columns[column] for column in key.columns),
893
+ )
894
+
895
+
896
+ def _python_annotation(
897
+ sql_type: SqlType,
898
+ enum_names: dict[tuple[str | None, str | None, str], str],
899
+ *,
900
+ default_catalog: str | None,
901
+ default_schema: str | None,
902
+ domain_names: dict[tuple[str | None, str | None, str], str] | None = None,
903
+ ) -> str:
904
+ scalar_annotations = {
905
+ TypeKind.BOOLEAN: "bool",
906
+ TypeKind.SMALL_INTEGER: "int",
907
+ TypeKind.INTEGER: "int",
908
+ TypeKind.BIG_INTEGER: "int",
909
+ TypeKind.DECIMAL: "Decimal",
910
+ TypeKind.REAL: "float",
911
+ TypeKind.DOUBLE: "float",
912
+ TypeKind.STRING: "str",
913
+ TypeKind.BINARY: "bytes",
914
+ TypeKind.DATE: "date",
915
+ TypeKind.TIME: "time",
916
+ TypeKind.DATETIME: "datetime",
917
+ TypeKind.INTERVAL: "timedelta",
918
+ TypeKind.UUID: "UUID",
919
+ TypeKind.JSON: "JsonValue",
920
+ TypeKind.OTHER: "object",
921
+ }
922
+ if sql_type.kind is TypeKind.DOMAIN and sql_type.element_type is not None:
923
+ named = _named_reference(
924
+ sql_type.named_type,
925
+ domain_names,
926
+ default_catalog=default_catalog,
927
+ default_schema=default_schema,
928
+ )
929
+ if named is not None:
930
+ return named
931
+ return _python_annotation(
932
+ sql_type.element_type,
933
+ enum_names,
934
+ default_catalog=default_catalog,
935
+ default_schema=default_schema,
936
+ )
937
+ if sql_type.kind is TypeKind.ARRAY and sql_type.element_type is not None:
938
+ element = _python_annotation(
939
+ sql_type.element_type,
940
+ enum_names,
941
+ default_catalog=default_catalog,
942
+ default_schema=default_schema,
943
+ )
944
+ return f"tuple[{element}, ...]"
945
+ if sql_type.kind is TypeKind.ENUM and sql_type.named_type is not None:
946
+ named = _named_reference(
947
+ sql_type.named_type,
948
+ enum_names,
949
+ default_catalog=default_catalog,
950
+ default_schema=default_schema,
951
+ )
952
+ return named if named is not None else "str"
953
+ return scalar_annotations[sql_type.kind]
954
+
955
+
956
+ def _named_reference(
957
+ reference: ObjectReference | None,
958
+ names: dict[tuple[str | None, str | None, str], str] | None,
959
+ *,
960
+ default_catalog: str | None,
961
+ default_schema: str | None,
962
+ ) -> str | None:
963
+ """What a generated artifact of this named type is called, if one exists."""
964
+ if reference is None or not names:
965
+ return None
966
+ key = (
967
+ _identifier_value(reference.catalog) or default_catalog,
968
+ _identifier_value(reference.schema) or default_schema,
969
+ reference.name.value,
970
+ )
971
+ return names.get(key)
972
+
973
+
974
+ def _target_row_name(
975
+ reference: ObjectReference,
976
+ table_row_names: dict[tuple[str | None, str | None, str], str],
977
+ source: _TableSource,
978
+ ) -> str:
979
+ catalog_name = reference.catalog or source.catalog.name
980
+ schema_name = reference.schema or source.schema.name
981
+ prefix = (
982
+ _identifier_value(catalog_name),
983
+ _identifier_value(schema_name),
984
+ reference.name.value,
985
+ )
986
+ return table_row_names.get(prefix, "object")
987
+
988
+
989
+ def _identifier_value(identifier: Identifier | None) -> str | None:
990
+ return identifier.value if identifier is not None else None
991
+
992
+
993
+ __all__ = (
994
+ "GeneratedColumn",
995
+ "GeneratedEnum",
996
+ "GeneratedKey",
997
+ "GeneratedPackage",
998
+ "GeneratedRelation",
999
+ "GeneratedTable",
1000
+ "build_codegen_package",
1001
+ )