snake-orm 0.1.0b1__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 (198) hide show
  1. snake_orm-0.1.0b1.dist-info/METADATA +366 -0
  2. snake_orm-0.1.0b1.dist-info/RECORD +198 -0
  3. snake_orm-0.1.0b1.dist-info/WHEEL +4 -0
  4. snake_orm-0.1.0b1.dist-info/entry_points.txt +2 -0
  5. snake_orm-0.1.0b1.dist-info/licenses/LICENSE +21 -0
  6. snakeorm/__init__.py +214 -0
  7. snakeorm/advisor.py +109 -0
  8. snakeorm/cli/__init__.py +3 -0
  9. snakeorm/cli/app.py +992 -0
  10. snakeorm/cli/discovery.py +179 -0
  11. snakeorm/cli/hooks.py +86 -0
  12. snakeorm/compiler/__init__.py +3 -0
  13. snakeorm/compiler/compiler.py +330 -0
  14. snakeorm/connection.py +228 -0
  15. snakeorm/contrib/__init__.py +17 -0
  16. snakeorm/contrib/asgi.py +192 -0
  17. snakeorm/contrib/config.py +264 -0
  18. snakeorm/contrib/deliver.py +256 -0
  19. snakeorm/contrib/django.py +245 -0
  20. snakeorm/contrib/sidecar.py +44 -0
  21. snakeorm/contrib/wsgi.py +176 -0
  22. snakeorm/core/__init__.py +1 -0
  23. snakeorm/core/config.py +173 -0
  24. snakeorm/core/converters.py +172 -0
  25. snakeorm/core/exceptions.py +175 -0
  26. snakeorm/core/placement.py +15 -0
  27. snakeorm/core/sentinels.py +37 -0
  28. snakeorm/core/signals.py +114 -0
  29. snakeorm/debug/__init__.py +42 -0
  30. snakeorm/debug/assets/css/panel.css +616 -0
  31. snakeorm/debug/assets/css/snake_orm_app.css +293 -0
  32. snakeorm/debug/assets/img/icon.svg +5 -0
  33. snakeorm/debug/assets/js/history.js +425 -0
  34. snakeorm/debug/assets/js/language.js +283 -0
  35. snakeorm/debug/assets/js/panel.js +241 -0
  36. snakeorm/debug/assets/js/snake_orm_app.js +82 -0
  37. snakeorm/debug/assets/pages/cli.html +28 -0
  38. snakeorm/debug/assets/pages/config.html +34 -0
  39. snakeorm/debug/assets/pages/database_first.html +29 -0
  40. snakeorm/debug/assets/pages/help.html +21 -0
  41. snakeorm/debug/assets/pages/history.html +27 -0
  42. snakeorm/debug/capture.py +234 -0
  43. snakeorm/debug/channel.py +131 -0
  44. snakeorm/debug/collector.py +144 -0
  45. snakeorm/debug/config.py +149 -0
  46. snakeorm/debug/html.py +578 -0
  47. snakeorm/debug/matrix.py +60 -0
  48. snakeorm/debug/origin.py +42 -0
  49. snakeorm/debug/otel/__init__.py +68 -0
  50. snakeorm/debug/otel/context.py +43 -0
  51. snakeorm/debug/otel/exporter.py +277 -0
  52. snakeorm/debug/otel/payload.py +97 -0
  53. snakeorm/debug/otel/report.py +29 -0
  54. snakeorm/debug/otel/spans.py +393 -0
  55. snakeorm/debug/otel/summary.py +59 -0
  56. snakeorm/debug/record.py +54 -0
  57. snakeorm/debug/report.py +311 -0
  58. snakeorm/debug/testing.py +23 -0
  59. snakeorm/decorators/__init__.py +13 -0
  60. snakeorm/decorators/abstract.py +26 -0
  61. snakeorm/decorators/db_first.py +104 -0
  62. snakeorm/decorators/function.py +28 -0
  63. snakeorm/decorators/model.py +268 -0
  64. snakeorm/decorators/polymorphic.py +140 -0
  65. snakeorm/decorators/result.py +167 -0
  66. snakeorm/decorators/row.py +90 -0
  67. snakeorm/decorators/trigger.py +47 -0
  68. snakeorm/decorators/view.py +149 -0
  69. snakeorm/dialects/__init__.py +6 -0
  70. snakeorm/dialects/base.py +373 -0
  71. snakeorm/dialects/capabilities.py +571 -0
  72. snakeorm/dialects/literals.py +46 -0
  73. snakeorm/dialects/matrix.py +384 -0
  74. snakeorm/dialects/mysql.py +519 -0
  75. snakeorm/dialects/postgres.py +504 -0
  76. snakeorm/dialects/sqlite.py +417 -0
  77. snakeorm/drivers/__init__.py +18 -0
  78. snakeorm/drivers/asyncbase.py +73 -0
  79. snakeorm/drivers/asyncdecorators.py +244 -0
  80. snakeorm/drivers/asyncpool.py +228 -0
  81. snakeorm/drivers/asyncpsycopg.py +124 -0
  82. snakeorm/drivers/asyncpymysql.py +50 -0
  83. snakeorm/drivers/asyncsqlite.py +30 -0
  84. snakeorm/drivers/base.py +76 -0
  85. snakeorm/drivers/failures.py +181 -0
  86. snakeorm/drivers/logging.py +167 -0
  87. snakeorm/drivers/pool.py +352 -0
  88. snakeorm/drivers/psycopg.py +188 -0
  89. snakeorm/drivers/pymysql.py +176 -0
  90. snakeorm/drivers/savepoints.py +46 -0
  91. snakeorm/drivers/sqlite.py +170 -0
  92. snakeorm/drivers/threaded.py +143 -0
  93. snakeorm/drivers/timeout.py +87 -0
  94. snakeorm/dto/__init__.py +55 -0
  95. snakeorm/dto/read.py +231 -0
  96. snakeorm/dto/region.py +326 -0
  97. snakeorm/dto/resolve.py +354 -0
  98. snakeorm/dto/spec.py +201 -0
  99. snakeorm/expressions/__init__.py +88 -0
  100. snakeorm/expressions/conditional.py +178 -0
  101. snakeorm/expressions/expression.py +810 -0
  102. snakeorm/expressions/functions.py +71 -0
  103. snakeorm/expressions/keys.py +299 -0
  104. snakeorm/expressions/paths.py +43 -0
  105. snakeorm/expressions/scalar.py +293 -0
  106. snakeorm/expressions/window.py +248 -0
  107. snakeorm/fields/__init__.py +68 -0
  108. snakeorm/fields/check.py +79 -0
  109. snakeorm/fields/column.py +316 -0
  110. snakeorm/fields/enum.py +64 -0
  111. snakeorm/fields/index.py +46 -0
  112. snakeorm/fields/relationship.py +738 -0
  113. snakeorm/fields/typed.py +736 -0
  114. snakeorm/helpers/__init__.py +1 -0
  115. snakeorm/helpers/annotations.py +21 -0
  116. snakeorm/helpers/inheritance.py +41 -0
  117. snakeorm/helpers/pyliteral.py +52 -0
  118. snakeorm/helpers/pytype.py +50 -0
  119. snakeorm/introspection/__init__.py +13 -0
  120. snakeorm/introspection/base.py +30 -0
  121. snakeorm/introspection/drift.py +78 -0
  122. snakeorm/introspection/models.py +633 -0
  123. snakeorm/introspection/mysql.py +367 -0
  124. snakeorm/introspection/postgres.py +397 -0
  125. snakeorm/introspection/sqlite.py +217 -0
  126. snakeorm/introspection/unsupported.py +94 -0
  127. snakeorm/linker/__init__.py +3 -0
  128. snakeorm/linker/linker.py +521 -0
  129. snakeorm/metadata/__init__.py +41 -0
  130. snakeorm/metadata/check.py +37 -0
  131. snakeorm/metadata/column.py +201 -0
  132. snakeorm/metadata/enum_storage.py +33 -0
  133. snakeorm/metadata/fk_action.py +19 -0
  134. snakeorm/metadata/foreign_key.py +26 -0
  135. snakeorm/metadata/index.py +43 -0
  136. snakeorm/metadata/index_method.py +19 -0
  137. snakeorm/metadata/int_size.py +20 -0
  138. snakeorm/metadata/json_storage.py +21 -0
  139. snakeorm/metadata/polymorphic.py +30 -0
  140. snakeorm/metadata/primary_key.py +23 -0
  141. snakeorm/metadata/relationship.py +60 -0
  142. snakeorm/metadata/relationship_kind.py +43 -0
  143. snakeorm/metadata/routine.py +26 -0
  144. snakeorm/metadata/server_default.py +20 -0
  145. snakeorm/metadata/table.py +97 -0
  146. snakeorm/metadata/table_kind.py +23 -0
  147. snakeorm/metadata/trigger.py +62 -0
  148. snakeorm/metadata/type_params.py +348 -0
  149. snakeorm/migration/__init__.py +88 -0
  150. snakeorm/migration/asyncrunner.py +168 -0
  151. snakeorm/migration/autodetect.py +96 -0
  152. snakeorm/migration/ddl.py +1172 -0
  153. snakeorm/migration/diff.py +701 -0
  154. snakeorm/migration/loader.py +77 -0
  155. snakeorm/migration/operations.py +952 -0
  156. snakeorm/migration/planner.py +78 -0
  157. snakeorm/migration/realize.py +420 -0
  158. snakeorm/migration/renames.py +153 -0
  159. snakeorm/migration/render.py +1095 -0
  160. snakeorm/migration/runner.py +314 -0
  161. snakeorm/migration/squash.py +60 -0
  162. snakeorm/migration/state.py +107 -0
  163. snakeorm/model.py +162 -0
  164. snakeorm/py.typed +0 -0
  165. snakeorm/query/__init__.py +9 -0
  166. snakeorm/query/compound.py +288 -0
  167. snakeorm/query/join_kind.py +19 -0
  168. snakeorm/query/joined.py +143 -0
  169. snakeorm/query/query.py +1001 -0
  170. snakeorm/query/recursive.py +244 -0
  171. snakeorm/registry/__init__.py +5 -0
  172. snakeorm/registry/by_module.py +163 -0
  173. snakeorm/registry/registry.py +166 -0
  174. snakeorm/session/__init__.py +8 -0
  175. snakeorm/session/asyncsession.py +638 -0
  176. snakeorm/session/coercion.py +306 -0
  177. snakeorm/session/factory.py +29 -0
  178. snakeorm/session/guards.py +269 -0
  179. snakeorm/session/isolation.py +23 -0
  180. snakeorm/session/mapper.py +213 -0
  181. snakeorm/session/planning.py +815 -0
  182. snakeorm/session/retry.py +96 -0
  183. snakeorm/session/session.py +871 -0
  184. snakeorm/session/shared.py +355 -0
  185. snakeorm/sql/__init__.py +18 -0
  186. snakeorm/sql/adapt.py +104 -0
  187. snakeorm/sql/aggregate.py +232 -0
  188. snakeorm/sql/condition.py +359 -0
  189. snakeorm/sql/delete.py +46 -0
  190. snakeorm/sql/insert.py +149 -0
  191. snakeorm/sql/joins.py +158 -0
  192. snakeorm/sql/pk_subquery.py +54 -0
  193. snakeorm/sql/refs.py +19 -0
  194. snakeorm/sql/resolver.py +28 -0
  195. snakeorm/sql/select.py +175 -0
  196. snakeorm/sql/update.py +82 -0
  197. snakeorm/sql/value.py +552 -0
  198. snakeorm/times.py +276 -0
@@ -0,0 +1,178 @@
1
+ """Conditionals inside the SQL: `CASE WHEN`, `COALESCE`, `NULLIF`.
2
+
3
+ They are VALUES, not conditions: a `CASE` returns a datum (it gets compared, projected, aggregated),
4
+ so it inherits from `SnakeValue`. The TYPE is pinned down by the constructors:
5
+ `snake_case((cond, 1), default=0)` is `SnakeValue[int]`, and projecting it types the tuple with no `Any`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from typing import Any, TypeVar, overload
12
+
13
+ from snakeorm.core.exceptions import SnakeEmitError
14
+ from snakeorm.expressions.expression import SnakeCondition, SnakeValue
15
+
16
+ T = TypeVar("T")
17
+
18
+ # One branch of a CASE: if the condition holds, the value (a column or a literal).
19
+ SnakeCaseBranch = tuple[SnakeCondition, "SnakeValue[T] | T"]
20
+
21
+
22
+ @dataclass(frozen=True, slots=True, eq=False)
23
+ class SnakeCase(SnakeValue[T]):
24
+ """`CASE WHEN cond THEN value ... [ELSE default] END`.
25
+
26
+ The branches are evaluated in order and the first match wins (the order is meaningful). Without
27
+ a `default`, no `ELSE` is emitted (in SQL that already means NULL).
28
+ """
29
+
30
+ branches: tuple[SnakeCaseBranch[T], ...]
31
+ default: SnakeValue[T] | T | None = None
32
+ has_default: bool = False
33
+
34
+ def paths(self) -> tuple[tuple[str, ...], ...]:
35
+ """Paths of the columns it mentions (conditions, results and default), for the JOIN planner."""
36
+ from snakeorm.expressions.paths import condition_paths
37
+
38
+ collected: list[tuple[str, ...]] = []
39
+ for condition, result in self.branches:
40
+ collected.extend(condition_paths(condition))
41
+ if isinstance(result, SnakeValue):
42
+ collected.extend(result.paths())
43
+ if isinstance(self.default, SnakeValue):
44
+ collected.extend(self.default.paths())
45
+ return tuple(collected)
46
+
47
+
48
+ @dataclass(frozen=True, slots=True, eq=False)
49
+ class SnakeCoalesce(SnakeValue[T]):
50
+ """`COALESCE(a, b, ...)`: the first argument that is not NULL."""
51
+
52
+ arguments: tuple[SnakeValue[T] | T, ...]
53
+
54
+ def paths(self) -> tuple[tuple[str, ...], ...]:
55
+ """Paths of the arguments that are columns."""
56
+ return tuple(
57
+ path
58
+ for argument in self.arguments
59
+ if isinstance(argument, SnakeValue)
60
+ for path in argument.paths()
61
+ )
62
+
63
+
64
+ @dataclass(frozen=True, slots=True, eq=False)
65
+ class SnakeNullIf(SnakeValue[T]):
66
+ """`NULLIF(value, sentinel)`: NULL when the two are equal, and the value otherwise.
67
+
68
+ `T` is the type of the RESULT —which carries the `| None` this node introduces— and not that of
69
+ the operands, so the fields are stored untyped. Pinning them to `T` would demand a
70
+ `SnakeValue[int | None]` where the caller has a `SnakeValue[int]`, and invariance refuses it.
71
+ Same shape as `SnakeCoalesce`, which stores its arguments the same way and for the same reason:
72
+ the constructor in this module is what pins the types down, which is where they belong.
73
+ """
74
+
75
+ value: SnakeValue[Any]
76
+ sentinel: SnakeValue[Any] | Any
77
+
78
+ def paths(self) -> tuple[tuple[str, ...], ...]:
79
+ """Paths of the value and, if it is one, of the sentinel."""
80
+ collected = list(self.value.paths())
81
+ if isinstance(self.sentinel, SnakeValue):
82
+ collected.extend(self.sentinel.paths())
83
+ return tuple(collected)
84
+
85
+
86
+ _NO_DEFAULT = object()
87
+
88
+
89
+ # The overloads split the `SnakeValue[T] | T` union: together it is AMBIGUOUS (when passing
90
+ # `SnakeValue[str]`, `T` could be `str` or `SnakeValue[str]`, and mypy resolves it to `Never`). Split
91
+ # apart, each one pins `T` down.
92
+ @overload
93
+ def snake_case(*branches: SnakeCaseBranch[T]) -> SnakeCase[T]: ...
94
+ @overload
95
+ def snake_case(
96
+ *branches: SnakeCaseBranch[T], default: SnakeValue[T]
97
+ ) -> SnakeCase[T]: ...
98
+ @overload
99
+ def snake_case(*branches: SnakeCaseBranch[T], default: T) -> SnakeCase[T]: ...
100
+ def snake_case(
101
+ *branches: SnakeCaseBranch[T], default: SnakeValue[T] | T | object = _NO_DEFAULT
102
+ ) -> SnakeCase[T]:
103
+ """Builds a `CASE WHEN`. The branches are evaluated in order; the first match wins.
104
+
105
+ snake_case((User.age < 18, "minor"), (User.age < 65, "adult"), default="retired")
106
+
107
+ Without a `default` it returns NULL when none of them match (SQL semantics).
108
+ """
109
+ if not branches:
110
+ raise SnakeEmitError(
111
+ "A CASE needs at least one branch (condition, value): with none there is no valid SQL "
112
+ "to emit, only a stray ELSE."
113
+ )
114
+ if default is _NO_DEFAULT:
115
+ return SnakeCase(branches=branches)
116
+ return SnakeCase(
117
+ branches=branches,
118
+ default=default, # type: ignore[arg-type]
119
+ has_default=True,
120
+ )
121
+
122
+
123
+ @overload
124
+ def snake_coalesce(first: SnakeValue[T], *rest: SnakeValue[T]) -> SnakeCoalesce[T]: ...
125
+
126
+
127
+ @overload
128
+ def snake_coalesce(first: SnakeValue[T | None], fallback: T, /) -> SnakeCoalesce[T]: ...
129
+
130
+
131
+ @overload
132
+ def snake_coalesce(
133
+ first: SnakeValue[T], *rest: SnakeValue[T] | T
134
+ ) -> SnakeCoalesce[T]: ...
135
+
136
+
137
+ def snake_coalesce(
138
+ first: SnakeValue[Any], *rest: SnakeValue[Any] | Any
139
+ ) -> SnakeCoalesce[Any]:
140
+ """Builds a `COALESCE(...)`: the first non-NULL argument.
141
+
142
+ The FIRST argument must be an expression: a literal there would never be NULL (it would always
143
+ return that literal) and it also anchors the type `T`.
144
+
145
+ THE MIDDLE OVERLOAD IS THE POINT: a `COALESCE` whose fallback is a LITERAL cannot be NULL, so it
146
+ drops the `| None`. `COALESCE(SUM(x), 0)` is an `int`, and that is the entire reason anybody
147
+ writes it — the value stops being nullable IN THE ENGINE, so it has to stop being nullable in the
148
+ type. Without it the declarator that exists to remove a `None` handed one back, and the caller
149
+ had to `cast()` in a project whose rule is zero `Any`.
150
+
151
+ The first overload is what keeps that from becoming a lie: with every argument an expression,
152
+ nothing guarantees a value and the nullability survives. Order matters — it has to be tried
153
+ before the literal one, or an expression fallback would be read as the literal.
154
+ """
155
+ arguments: tuple[SnakeValue[Any] | Any, ...] = (first, *rest)
156
+ if len(arguments) < 2:
157
+ raise SnakeEmitError(
158
+ "COALESCE needs at least two arguments: with only one it chooses nothing, it is the "
159
+ "identity, and with zero there is nothing to choose from."
160
+ )
161
+ return SnakeCoalesce(arguments=arguments)
162
+
163
+
164
+ def snake_nullif(
165
+ value: SnakeValue[T], sentinel: SnakeValue[T] | T
166
+ ) -> SnakeNullIf[T | None]:
167
+ """Builds a `NULLIF(value, sentinel)`: turns a sentinel (the empty string) into NULL.
168
+
169
+ THE RESULT IS NULLABLE, and saying so is the whole point. This is the exact mirror of
170
+ `snake_coalesce`, which REMOVES a `None` and declares it: this one PUTS one in. It used to
171
+ return `SnakeNullIf[T]` — the same `T` it was given — so the declarator whose entire job is to
172
+ introduce a NULL was the one place the type did not mention it.
173
+
174
+ That is not a corner case. Guarding a division against zero is `x / snake_nullif(y, 0)`, and the
175
+ NULL it produces IS the guard working. Typing that `int` told the caller the result could not be
176
+ `None` in a project that asks people to trust the checker over the engine.
177
+ """
178
+ return SnakeNullIf(value=value, sentinel=sentinel)