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.
- snake_orm-0.1.0b1.dist-info/METADATA +366 -0
- snake_orm-0.1.0b1.dist-info/RECORD +198 -0
- snake_orm-0.1.0b1.dist-info/WHEEL +4 -0
- snake_orm-0.1.0b1.dist-info/entry_points.txt +2 -0
- snake_orm-0.1.0b1.dist-info/licenses/LICENSE +21 -0
- snakeorm/__init__.py +214 -0
- snakeorm/advisor.py +109 -0
- snakeorm/cli/__init__.py +3 -0
- snakeorm/cli/app.py +992 -0
- snakeorm/cli/discovery.py +179 -0
- snakeorm/cli/hooks.py +86 -0
- snakeorm/compiler/__init__.py +3 -0
- snakeorm/compiler/compiler.py +330 -0
- snakeorm/connection.py +228 -0
- snakeorm/contrib/__init__.py +17 -0
- snakeorm/contrib/asgi.py +192 -0
- snakeorm/contrib/config.py +264 -0
- snakeorm/contrib/deliver.py +256 -0
- snakeorm/contrib/django.py +245 -0
- snakeorm/contrib/sidecar.py +44 -0
- snakeorm/contrib/wsgi.py +176 -0
- snakeorm/core/__init__.py +1 -0
- snakeorm/core/config.py +173 -0
- snakeorm/core/converters.py +172 -0
- snakeorm/core/exceptions.py +175 -0
- snakeorm/core/placement.py +15 -0
- snakeorm/core/sentinels.py +37 -0
- snakeorm/core/signals.py +114 -0
- snakeorm/debug/__init__.py +42 -0
- snakeorm/debug/assets/css/panel.css +616 -0
- snakeorm/debug/assets/css/snake_orm_app.css +293 -0
- snakeorm/debug/assets/img/icon.svg +5 -0
- snakeorm/debug/assets/js/history.js +425 -0
- snakeorm/debug/assets/js/language.js +283 -0
- snakeorm/debug/assets/js/panel.js +241 -0
- snakeorm/debug/assets/js/snake_orm_app.js +82 -0
- snakeorm/debug/assets/pages/cli.html +28 -0
- snakeorm/debug/assets/pages/config.html +34 -0
- snakeorm/debug/assets/pages/database_first.html +29 -0
- snakeorm/debug/assets/pages/help.html +21 -0
- snakeorm/debug/assets/pages/history.html +27 -0
- snakeorm/debug/capture.py +234 -0
- snakeorm/debug/channel.py +131 -0
- snakeorm/debug/collector.py +144 -0
- snakeorm/debug/config.py +149 -0
- snakeorm/debug/html.py +578 -0
- snakeorm/debug/matrix.py +60 -0
- snakeorm/debug/origin.py +42 -0
- snakeorm/debug/otel/__init__.py +68 -0
- snakeorm/debug/otel/context.py +43 -0
- snakeorm/debug/otel/exporter.py +277 -0
- snakeorm/debug/otel/payload.py +97 -0
- snakeorm/debug/otel/report.py +29 -0
- snakeorm/debug/otel/spans.py +393 -0
- snakeorm/debug/otel/summary.py +59 -0
- snakeorm/debug/record.py +54 -0
- snakeorm/debug/report.py +311 -0
- snakeorm/debug/testing.py +23 -0
- snakeorm/decorators/__init__.py +13 -0
- snakeorm/decorators/abstract.py +26 -0
- snakeorm/decorators/db_first.py +104 -0
- snakeorm/decorators/function.py +28 -0
- snakeorm/decorators/model.py +268 -0
- snakeorm/decorators/polymorphic.py +140 -0
- snakeorm/decorators/result.py +167 -0
- snakeorm/decorators/row.py +90 -0
- snakeorm/decorators/trigger.py +47 -0
- snakeorm/decorators/view.py +149 -0
- snakeorm/dialects/__init__.py +6 -0
- snakeorm/dialects/base.py +373 -0
- snakeorm/dialects/capabilities.py +571 -0
- snakeorm/dialects/literals.py +46 -0
- snakeorm/dialects/matrix.py +384 -0
- snakeorm/dialects/mysql.py +519 -0
- snakeorm/dialects/postgres.py +504 -0
- snakeorm/dialects/sqlite.py +417 -0
- snakeorm/drivers/__init__.py +18 -0
- snakeorm/drivers/asyncbase.py +73 -0
- snakeorm/drivers/asyncdecorators.py +244 -0
- snakeorm/drivers/asyncpool.py +228 -0
- snakeorm/drivers/asyncpsycopg.py +124 -0
- snakeorm/drivers/asyncpymysql.py +50 -0
- snakeorm/drivers/asyncsqlite.py +30 -0
- snakeorm/drivers/base.py +76 -0
- snakeorm/drivers/failures.py +181 -0
- snakeorm/drivers/logging.py +167 -0
- snakeorm/drivers/pool.py +352 -0
- snakeorm/drivers/psycopg.py +188 -0
- snakeorm/drivers/pymysql.py +176 -0
- snakeorm/drivers/savepoints.py +46 -0
- snakeorm/drivers/sqlite.py +170 -0
- snakeorm/drivers/threaded.py +143 -0
- snakeorm/drivers/timeout.py +87 -0
- snakeorm/dto/__init__.py +55 -0
- snakeorm/dto/read.py +231 -0
- snakeorm/dto/region.py +326 -0
- snakeorm/dto/resolve.py +354 -0
- snakeorm/dto/spec.py +201 -0
- snakeorm/expressions/__init__.py +88 -0
- snakeorm/expressions/conditional.py +178 -0
- snakeorm/expressions/expression.py +810 -0
- snakeorm/expressions/functions.py +71 -0
- snakeorm/expressions/keys.py +299 -0
- snakeorm/expressions/paths.py +43 -0
- snakeorm/expressions/scalar.py +293 -0
- snakeorm/expressions/window.py +248 -0
- snakeorm/fields/__init__.py +68 -0
- snakeorm/fields/check.py +79 -0
- snakeorm/fields/column.py +316 -0
- snakeorm/fields/enum.py +64 -0
- snakeorm/fields/index.py +46 -0
- snakeorm/fields/relationship.py +738 -0
- snakeorm/fields/typed.py +736 -0
- snakeorm/helpers/__init__.py +1 -0
- snakeorm/helpers/annotations.py +21 -0
- snakeorm/helpers/inheritance.py +41 -0
- snakeorm/helpers/pyliteral.py +52 -0
- snakeorm/helpers/pytype.py +50 -0
- snakeorm/introspection/__init__.py +13 -0
- snakeorm/introspection/base.py +30 -0
- snakeorm/introspection/drift.py +78 -0
- snakeorm/introspection/models.py +633 -0
- snakeorm/introspection/mysql.py +367 -0
- snakeorm/introspection/postgres.py +397 -0
- snakeorm/introspection/sqlite.py +217 -0
- snakeorm/introspection/unsupported.py +94 -0
- snakeorm/linker/__init__.py +3 -0
- snakeorm/linker/linker.py +521 -0
- snakeorm/metadata/__init__.py +41 -0
- snakeorm/metadata/check.py +37 -0
- snakeorm/metadata/column.py +201 -0
- snakeorm/metadata/enum_storage.py +33 -0
- snakeorm/metadata/fk_action.py +19 -0
- snakeorm/metadata/foreign_key.py +26 -0
- snakeorm/metadata/index.py +43 -0
- snakeorm/metadata/index_method.py +19 -0
- snakeorm/metadata/int_size.py +20 -0
- snakeorm/metadata/json_storage.py +21 -0
- snakeorm/metadata/polymorphic.py +30 -0
- snakeorm/metadata/primary_key.py +23 -0
- snakeorm/metadata/relationship.py +60 -0
- snakeorm/metadata/relationship_kind.py +43 -0
- snakeorm/metadata/routine.py +26 -0
- snakeorm/metadata/server_default.py +20 -0
- snakeorm/metadata/table.py +97 -0
- snakeorm/metadata/table_kind.py +23 -0
- snakeorm/metadata/trigger.py +62 -0
- snakeorm/metadata/type_params.py +348 -0
- snakeorm/migration/__init__.py +88 -0
- snakeorm/migration/asyncrunner.py +168 -0
- snakeorm/migration/autodetect.py +96 -0
- snakeorm/migration/ddl.py +1172 -0
- snakeorm/migration/diff.py +701 -0
- snakeorm/migration/loader.py +77 -0
- snakeorm/migration/operations.py +952 -0
- snakeorm/migration/planner.py +78 -0
- snakeorm/migration/realize.py +420 -0
- snakeorm/migration/renames.py +153 -0
- snakeorm/migration/render.py +1095 -0
- snakeorm/migration/runner.py +314 -0
- snakeorm/migration/squash.py +60 -0
- snakeorm/migration/state.py +107 -0
- snakeorm/model.py +162 -0
- snakeorm/py.typed +0 -0
- snakeorm/query/__init__.py +9 -0
- snakeorm/query/compound.py +288 -0
- snakeorm/query/join_kind.py +19 -0
- snakeorm/query/joined.py +143 -0
- snakeorm/query/query.py +1001 -0
- snakeorm/query/recursive.py +244 -0
- snakeorm/registry/__init__.py +5 -0
- snakeorm/registry/by_module.py +163 -0
- snakeorm/registry/registry.py +166 -0
- snakeorm/session/__init__.py +8 -0
- snakeorm/session/asyncsession.py +638 -0
- snakeorm/session/coercion.py +306 -0
- snakeorm/session/factory.py +29 -0
- snakeorm/session/guards.py +269 -0
- snakeorm/session/isolation.py +23 -0
- snakeorm/session/mapper.py +213 -0
- snakeorm/session/planning.py +815 -0
- snakeorm/session/retry.py +96 -0
- snakeorm/session/session.py +871 -0
- snakeorm/session/shared.py +355 -0
- snakeorm/sql/__init__.py +18 -0
- snakeorm/sql/adapt.py +104 -0
- snakeorm/sql/aggregate.py +232 -0
- snakeorm/sql/condition.py +359 -0
- snakeorm/sql/delete.py +46 -0
- snakeorm/sql/insert.py +149 -0
- snakeorm/sql/joins.py +158 -0
- snakeorm/sql/pk_subquery.py +54 -0
- snakeorm/sql/refs.py +19 -0
- snakeorm/sql/resolver.py +28 -0
- snakeorm/sql/select.py +175 -0
- snakeorm/sql/update.py +82 -0
- snakeorm/sql/value.py +552 -0
- 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
|
+
)
|
snakeorm/cli/__init__.py
ADDED