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
snakeorm/__init__.py ADDED
@@ -0,0 +1,214 @@
1
+ """SnakeORM — a dataclass-first, type-first ORM for Python 3.11+.
2
+
3
+ This module is the PUBLIC SURFACE: everything needed to declare a model, query it and execute it
4
+ is imported from here, without knowing the package's internal structure.
5
+
6
+ from snakeorm import SnakeColumn, snake_auto, snake_model, snake_str
7
+
8
+ @snake_model
9
+ class User:
10
+ id: SnakeColumn[int] = snake_auto()
11
+ email: SnakeColumn[str] = snake_str(unique=True)
12
+
13
+ What is NOT here, on purpose: the migration operations (`CreateTable`, `AddColumn`...) and the
14
+ structures of the compiled graph (`SnakeTableInfo`, `SnakeColumnInfo`...). They live in
15
+ `snakeorm.migration` and `snakeorm.metadata`, which is where the generated migration files import
16
+ them from. Duplicating them here would publish two paths to the same thing.
17
+ """
18
+
19
+ from importlib import metadata as _metadata
20
+
21
+ from snakeorm.decorators import SnakeResult as SnakeResult
22
+ from snakeorm.decorators import SnakeRow as SnakeRow
23
+ from snakeorm.decorators import snake_abstract as snake_abstract
24
+ from snakeorm.decorators import snake_db_first as snake_db_first
25
+ from snakeorm.decorators import snake_function as snake_function
26
+ from snakeorm.decorators import snake_model as snake_model
27
+ from snakeorm.decorators import snake_result as snake_result
28
+ from snakeorm.decorators import snake_row as snake_row
29
+ from snakeorm.decorators import snake_table as snake_table
30
+ from snakeorm.decorators import snake_trigger as snake_trigger
31
+ from snakeorm.decorators import snake_view as snake_view
32
+ from snakeorm.dialects import MySQLDialect as MySQLDialect
33
+ from snakeorm.dialects import PostgresDialect as PostgresDialect
34
+ from snakeorm.dialects import SnakeDialect as SnakeDialect
35
+ from snakeorm.dialects import SQLiteDialect as SQLiteDialect
36
+ from snakeorm.core.converters import register_converter as register_converter
37
+ from snakeorm.drivers import AsyncDriver as AsyncDriver
38
+ from snakeorm.drivers import AsyncLoggingDriver as AsyncLoggingDriver
39
+ from snakeorm.drivers import AsyncPsycopgDriver as AsyncPsycopgDriver
40
+ from snakeorm.drivers import AsyncPyMySQLDriver as AsyncPyMySQLDriver
41
+ from snakeorm.drivers import AsyncSQLiteDriver as AsyncSQLiteDriver
42
+ from snakeorm.drivers import AsyncSnakePool as AsyncSnakePool
43
+ from snakeorm.drivers import AsyncTimeoutDriver as AsyncTimeoutDriver
44
+ from snakeorm.drivers import LoggingDriver as LoggingDriver
45
+ from snakeorm.drivers import PsycopgDriver as PsycopgDriver
46
+ from snakeorm.drivers import PyMySQLDriver as PyMySQLDriver
47
+ from snakeorm.drivers import SnakeDriver as SnakeDriver
48
+ from snakeorm.drivers import SnakePool as SnakePool
49
+ from snakeorm.drivers import SQLiteDriver as SQLiteDriver
50
+ from snakeorm.drivers import TimeoutDriver as TimeoutDriver
51
+ from snakeorm.drivers import psycopg_pool as psycopg_pool
52
+ from snakeorm.core.exceptions import SnakeAggregateNotLoaded as SnakeAggregateNotLoaded
53
+ from snakeorm.core.exceptions import SnakeColumnNotLoaded as SnakeColumnNotLoaded
54
+ from snakeorm.core.exceptions import SnakeConfigError as SnakeConfigError
55
+ from snakeorm.core.exceptions import SnakeDialectError as SnakeDialectError
56
+ from snakeorm.core.exceptions import SnakeEmitError as SnakeEmitError
57
+ from snakeorm.core.exceptions import SnakeError as SnakeError
58
+ from snakeorm.core.exceptions import SnakeMigrationError as SnakeMigrationError
59
+ from snakeorm.core.exceptions import (
60
+ SnakeModelDefinitionError as SnakeModelDefinitionError,
61
+ )
62
+ from snakeorm.core.exceptions import SnakeModelError as SnakeModelError
63
+ from snakeorm.core.exceptions import SnakeNodeError as SnakeNodeError
64
+ from snakeorm.core.exceptions import SnakeCheckViolation as SnakeCheckViolation
65
+ from snakeorm.core.exceptions import (
66
+ SnakeForeignKeyViolation as SnakeForeignKeyViolation,
67
+ )
68
+ from snakeorm.core.exceptions import SnakeIntegrityError as SnakeIntegrityError
69
+ from snakeorm.core.exceptions import SnakeNotNullViolation as SnakeNotNullViolation
70
+ from snakeorm.core.exceptions import SnakePoolTimeout as SnakePoolTimeout
71
+ from snakeorm.core.exceptions import SnakeUniqueViolation as SnakeUniqueViolation
72
+ from snakeorm.core.exceptions import SnakeRegistryError as SnakeRegistryError
73
+ from snakeorm.core.exceptions import (
74
+ SnakeRelationshipNotLoaded as SnakeRelationshipNotLoaded,
75
+ )
76
+ from snakeorm.core.exceptions import SnakeUnknownColumn as SnakeUnknownColumn
77
+ from snakeorm.core.exceptions import (
78
+ SnakeUnknownRelationship as SnakeUnknownRelationship,
79
+ )
80
+ from snakeorm.core.exceptions import (
81
+ SnakeUnlinkedRelationship as SnakeUnlinkedRelationship,
82
+ )
83
+ from snakeorm.core.exceptions import SnakeUnsupportedFeature as SnakeUnsupportedFeature
84
+ from snakeorm.core.exceptions import SnakeValueError as SnakeValueError
85
+ from snakeorm.core.exceptions import SnakeWarning as SnakeWarning
86
+ from snakeorm.expressions import SnakeCast as SnakeCast
87
+ from snakeorm.expressions import SnakeCase as SnakeCase
88
+ from snakeorm.expressions import SnakeCoalesce as SnakeCoalesce
89
+ from snakeorm.expressions import SnakeCondition as SnakeCondition
90
+ from snakeorm.expressions import SnakeSubquery as SnakeSubquery
91
+ from snakeorm.expressions import SnakeExpr as SnakeExpr
92
+ from snakeorm.expressions import SnakeFunc as SnakeFunc
93
+ from snakeorm.expressions import SnakeNullIf as SnakeNullIf
94
+ from snakeorm.expressions import SnakeOrder as SnakeOrder
95
+ from snakeorm.expressions import SnakeValue as SnakeValue
96
+ from snakeorm.expressions import SnakeKey as SnakeKey
97
+ from snakeorm.expressions import SnakeKeys as SnakeKeys
98
+ from snakeorm.expressions import SnakeWindow as SnakeWindow
99
+ from snakeorm.expressions import snake_key as snake_key
100
+ from snakeorm.expressions import snake_keys as snake_keys
101
+ from snakeorm.expressions import avg as avg
102
+ from snakeorm.expressions import count as count
103
+ from snakeorm.expressions import dense_rank as dense_rank
104
+ from snakeorm.expressions import lag as lag
105
+ from snakeorm.expressions import lead as lead
106
+ from snakeorm.expressions import max_ as max_
107
+ from snakeorm.expressions import min_ as min_
108
+ from snakeorm.expressions import rank as rank
109
+ from snakeorm.expressions import row_number as row_number
110
+ from snakeorm.expressions import SnakeStringAgg as SnakeStringAgg
111
+ from snakeorm.expressions import string_agg as string_agg
112
+ from snakeorm.expressions import snake_case as snake_case
113
+ from snakeorm.expressions import snake_cast as snake_cast
114
+ from snakeorm.expressions import snake_substring as snake_substring
115
+ from snakeorm.expressions import snake_replace as snake_replace
116
+ from snakeorm.expressions import snake_ceil as snake_ceil
117
+ from snakeorm.expressions import snake_floor as snake_floor
118
+ from snakeorm.expressions import snake_sqrt as snake_sqrt
119
+ from snakeorm.expressions import snake_power as snake_power
120
+
121
+ # The unit a date is shifted by. It is exported because `snake_date_add` NAMES it in its
122
+ # signature: a caller who can reach the function and not its argument type cannot annotate what
123
+ # they are passing, which is the gap `test_public_api` exists to close.
124
+ from snakeorm.expressions import SnakeDatePart as SnakeDatePart
125
+ from snakeorm.expressions import snake_date_add as snake_date_add
126
+ from snakeorm.expressions import snake_date_sub as snake_date_sub
127
+ from snakeorm.expressions import SNAKE_CURRENT_ROW as SNAKE_CURRENT_ROW
128
+ from snakeorm.expressions import SnakeFrame as SnakeFrame
129
+ from snakeorm.expressions import SnakeFrameBound as SnakeFrameBound
130
+ from snakeorm.expressions import SnakeFrameMode as SnakeFrameMode
131
+ from snakeorm.expressions import snake_following as snake_following
132
+ from snakeorm.expressions import snake_preceding as snake_preceding
133
+ from snakeorm.expressions import snake_range as snake_range
134
+ from snakeorm.expressions import snake_rows as snake_rows
135
+ from snakeorm.expressions import snake_coalesce as snake_coalesce
136
+ from snakeorm.expressions import snake_nullif as snake_nullif
137
+ from snakeorm.expressions import sum_ as sum_
138
+ from snakeorm.fields import SnakeCollection as SnakeCollection
139
+ from snakeorm.fields import SnakeColumn as SnakeColumn
140
+ from snakeorm.fields import SnakeIndex as SnakeIndex
141
+ from snakeorm.fields import SnakePrefetch as SnakePrefetch
142
+ from snakeorm.fields import SnakePrefetchHop as SnakePrefetchHop
143
+ from snakeorm.fields import SnakeToMany as SnakeToMany
144
+ from snakeorm.fields import SnakeToOne as SnakeToOne
145
+ from snakeorm.fields import snake_auto as snake_auto
146
+ from snakeorm.fields import snake_check as snake_check
147
+ from snakeorm.fields import snake_checks as snake_checks
148
+ from snakeorm.fields import snake_column as snake_column
149
+ from snakeorm.fields import snake_datetime as snake_datetime
150
+ from snakeorm.fields import snake_datetimetz as snake_datetimetz
151
+ from snakeorm.fields import snake_decimal as snake_decimal
152
+ from snakeorm.fields import snake_discriminator as snake_discriminator
153
+ from snakeorm.fields import snake_enum as snake_enum
154
+ from snakeorm.fields import snake_indexes as snake_indexes
155
+ from snakeorm.fields import snake_int as snake_int
156
+ from snakeorm.fields import snake_json as snake_json
157
+ from snakeorm.fields import snake_str as snake_str
158
+ from snakeorm.fields import snake_float as snake_float
159
+ from snakeorm.fields import snake_time as snake_time
160
+ from snakeorm.fields import snake_timetz as snake_timetz
161
+ from snakeorm.fields import snake_to_many as snake_to_many
162
+ from snakeorm.fields import snake_to_many_through as snake_to_many_through
163
+ from snakeorm.fields import snake_to_one as snake_to_one
164
+ from snakeorm.linker import snake_link as snake_link
165
+ from snakeorm.metadata import SnakeCheckInfo as SnakeCheckInfo
166
+ from snakeorm.metadata import SnakeEnumStorage as SnakeEnumStorage
167
+ from snakeorm.metadata import SnakeFkAction as SnakeFkAction
168
+ from snakeorm.metadata import SnakeIntSize as SnakeIntSize
169
+ from snakeorm.metadata import SnakeIndexMethod as SnakeIndexMethod
170
+ from snakeorm.metadata import SnakeJsonStorage as SnakeJsonStorage
171
+ from snakeorm.metadata import SnakeRelationshipKind as SnakeRelationshipKind
172
+ from snakeorm.metadata import SnakeServerDefault as SnakeServerDefault
173
+ from snakeorm.metadata import SnakeThroughInfo as SnakeThroughInfo
174
+ from snakeorm.metadata import SnakeDateTimeParams as SnakeDateTimeParams
175
+ from snakeorm.metadata import SnakeDecimalParams as SnakeDecimalParams
176
+ from snakeorm.metadata import SnakeFloatParams as SnakeFloatParams
177
+ from snakeorm.metadata import SnakeIntParams as SnakeIntParams
178
+ from snakeorm.metadata import SnakeJsonParams as SnakeJsonParams
179
+ from snakeorm.metadata import SnakeStrParams as SnakeStrParams
180
+ from snakeorm.metadata import SnakeTimeParams as SnakeTimeParams
181
+ from snakeorm.metadata import SnakeTriggerEvent as SnakeTriggerEvent
182
+ from snakeorm.metadata import SnakeTriggerInfo as SnakeTriggerInfo
183
+ from snakeorm.metadata import SnakeTriggerTiming as SnakeTriggerTiming
184
+ from snakeorm.model import SnakeModel as SnakeModel
185
+ from snakeorm.model import SnakeView as SnakeView
186
+ from snakeorm.query import SnakeCompound as SnakeCompound
187
+ from snakeorm.query import SnakeJoin as SnakeJoin
188
+ from snakeorm.query import SnakeJoinedQuery as SnakeJoinedQuery
189
+ from snakeorm.query import SnakeQuery as SnakeQuery
190
+ from snakeorm.query import SnakeRecursive as SnakeRecursive
191
+ from snakeorm.query import SnakeSetOp as SnakeSetOp
192
+ from snakeorm.registry import registry as registry
193
+ from snakeorm.session import AsyncSession as AsyncSession
194
+ from snakeorm.session import SnakeIsolation as SnakeIsolation
195
+ from snakeorm.connection import SnakeBackend as SnakeBackend
196
+ from snakeorm.connection import SnakeConnectionConfig as SnakeConnectionConfig
197
+ from snakeorm.session import SnakeSession as SnakeSession
198
+ from snakeorm.times import SnakeUtc as SnakeUtc
199
+ from snakeorm.times import parse_utc as parse_utc
200
+ from snakeorm.times import to_utc as to_utc
201
+ from snakeorm.times import utc_from_zone as utc_from_zone
202
+ from snakeorm.times import utc_now as utc_now
203
+ from snakeorm.session import is_transient as is_transient
204
+ from snakeorm.session import snake_session as snake_session
205
+ from snakeorm.session import with_retry as with_retry
206
+ from snakeorm.core.signals import SnakeSignal as SnakeSignal
207
+ from snakeorm.core.signals import snake_on as snake_on
208
+
209
+ __version__ = _metadata.version("snake-orm")
210
+ """The installed version, READ from the metadata and not written down a second time.
211
+
212
+ Two copies of one number agree until the release somebody bumps one of them, and a bug report
213
+ against the wrong version sends everybody to read the wrong code.
214
+ """
snakeorm/advisor.py ADDED
@@ -0,0 +1,109 @@
1
+ """Index advisor: spots FILTER/FK columns with no index, from the SCHEMA (static) or from the
2
+ QUERIES actually emitted (dynamic). Cross-checking against the metadata avoids suggesting what is
3
+ already indexed (PK, unique or a declared index). FKs are the most filtered and joined columns of
4
+ all; without an index, correlated aggregates at scale crawl (a 64 s annotate that drops to 41 ms
5
+ with one).
6
+
7
+ - `unindexed_foreign_keys(tables)` -> STATIC audit of the schema (used by `snakeorm advise`).
8
+ - `index_hints_from_sql(sqls, tables)` -> pulls unindexed filter columns out of some SQL.
9
+ - `index_hints_from_records(rows, tables, min_ms=...)` -> for SLOW queries ONLY (used by the panel):
10
+ a fast query over an unindexed column does not deserve an index; a slow one does. Every suggestion
11
+ carries the WORST duration that triggered it, so they can be ordered and the advice justified.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+ from collections.abc import Iterable
18
+
19
+ from snakeorm.metadata import SnakeTableInfo
20
+ from snakeorm.metadata.relationship_kind import SnakeRelationshipKind
21
+
22
+ # Default threshold: below this a query is so fast that an index changes nothing.
23
+ DEFAULT_MIN_MS = 10.0
24
+
25
+ # The ORM's correlated subquery: `FROM "table" AS eN WHERE eN."col" = ...`.
26
+ _CORRELATED = re.compile(r'FROM "(\w+)" AS \w+\s+WHERE \w+\."(\w+)"')
27
+ # A plain WHERE over the FROM's table: `FROM "table" [AS x] WHERE ("col"` or `WHERE "col"`.
28
+ _SIMPLE_WHERE = re.compile(r'FROM "(\w+)"(?: AS \w+)? WHERE \(?"(\w+)"')
29
+
30
+
31
+ def _indexed_columns(table: SnakeTableInfo) -> set[str]:
32
+ """Columns of `table` that ALREADY have an index: PK, `unique=True` and declared indexes."""
33
+ names = {column.name for column in table.primary_key.columns}
34
+ names |= {column.name for column in table.columns if column.unique}
35
+ for index in table.indexes:
36
+ names.update(index.columns)
37
+ return names
38
+
39
+
40
+ def unindexed_foreign_keys(tables: Iterable[SnakeTableInfo]) -> list[tuple[str, str]]:
41
+ """`(table, column)` for every FK with NO index, deduplicated, in order of appearance."""
42
+ hints: list[tuple[str, str]] = []
43
+ seen: set[tuple[str, str]] = set()
44
+ for table in tables:
45
+ indexed = _indexed_columns(table)
46
+ for relationship in table.relationships:
47
+ if relationship.kind is not SnakeRelationshipKind.TO_ONE:
48
+ continue # only the side that OWNS the FK (to-one) has local columns
49
+ for local, _remote in relationship.foreign_key.pairs:
50
+ key = (table.name, local)
51
+ if local not in indexed and key not in seen:
52
+ seen.add(key)
53
+ hints.append(key)
54
+ return hints
55
+
56
+
57
+ def index_hints_from_sql(
58
+ sqls: Iterable[str], tables: Iterable[SnakeTableInfo]
59
+ ) -> list[tuple[str, str]]:
60
+ """`(table, column)` for columns FILTERED in `sqls` that are NOT indexed, deduplicated.
61
+
62
+ Parses the emitted SQL (correlated subqueries and plain WHEREs) and cross-checks the metadata.
63
+ Only suggests columns that EXIST in the table (which avoids false positives from an optimistic
64
+ parse).
65
+ """
66
+ by_name = {table.name: table for table in tables}
67
+ indexed = {name: _indexed_columns(table) for name, table in by_name.items()}
68
+ hints: list[tuple[str, str]] = []
69
+ seen: set[tuple[str, str]] = set()
70
+ for sql in sqls:
71
+ for pattern in (_CORRELATED, _SIMPLE_WHERE):
72
+ for table_name, column in pattern.findall(sql):
73
+ table = by_name.get(table_name)
74
+ key = (table_name, column)
75
+ if (
76
+ table is not None
77
+ and column not in indexed[table_name]
78
+ and key not in seen
79
+ and any(c.name == column for c in table.columns)
80
+ ):
81
+ seen.add(key)
82
+ hints.append(key)
83
+ return hints
84
+
85
+
86
+ def index_hints_from_records(
87
+ rows: Iterable[tuple[str, float]],
88
+ tables: Iterable[SnakeTableInfo],
89
+ *,
90
+ min_ms: float = DEFAULT_MIN_MS,
91
+ ) -> list[tuple[str, str, float]]:
92
+ """SMART suggestions `(table, column, worst_ms)`: from the SLOW queries only.
93
+
94
+ `rows` are `(sql, duration_ms)` pairs. Queries below `min_ms` are ignored (fast ones: an index
95
+ changes nothing, it would be noise). From the slow ones, the unindexed filter columns are pulled
96
+ out and the WORST duration per column is kept; the result comes ordered slowest first.
97
+ """
98
+ table_list = list(tables)
99
+ worst: dict[tuple[str, str], float] = {}
100
+ for sql, duration_ms in rows:
101
+ if duration_ms < min_ms:
102
+ continue
103
+ for hint in index_hints_from_sql([sql], table_list):
104
+ worst[hint] = max(worst.get(hint, 0.0), duration_ms)
105
+ return sorted(
106
+ ((table, column, ms) for (table, column), ms in worst.items()),
107
+ key=lambda hint: hint[2],
108
+ reverse=True,
109
+ )
@@ -0,0 +1,3 @@
1
+ """SnakeORM CLI: the `snakeorm` entry point (see [project.scripts]). Argparse only; it operates on the on-disk history through the loader and the runner."""
2
+
3
+ from snakeorm.cli.app import main as main