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
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Immutable metadata of a compiled model (the graph's root node)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from snakeorm.core.placement import DEFAULT_DATABASE, DEFAULT_SCHEMA
|
|
8
|
+
from snakeorm.metadata.check import SnakeCheckInfo
|
|
9
|
+
from snakeorm.metadata.column import SnakeColumnInfo
|
|
10
|
+
from snakeorm.metadata.index import SnakeIndexInfo
|
|
11
|
+
from snakeorm.metadata.polymorphic import SnakePolymorphicInfo
|
|
12
|
+
from snakeorm.metadata.primary_key import SnakePrimaryKeyInfo
|
|
13
|
+
from snakeorm.metadata.relationship import SnakeRelationshipInfo
|
|
14
|
+
from snakeorm.metadata.table_kind import SnakeTableKind
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class SnakeTableInfo:
|
|
19
|
+
"""Compiled model: name, columns, primary key, relations and indexes.
|
|
20
|
+
|
|
21
|
+
This is the graph's root node. The whole ORM (SQL, migrations, query) reads from here;
|
|
22
|
+
the runtime never inspects the class again.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
name: str
|
|
26
|
+
columns: tuple[SnakeColumnInfo, ...]
|
|
27
|
+
primary_key: SnakePrimaryKeyInfo
|
|
28
|
+
relationships: tuple[SnakeRelationshipInfo, ...] = ()
|
|
29
|
+
schema: str = DEFAULT_SCHEMA
|
|
30
|
+
# Connection the node lives on. An axis INDEPENDENT of `kind` (what it is vs where it lives).
|
|
31
|
+
database: str = DEFAULT_DATABASE
|
|
32
|
+
db_comment: str | None = None # COMMENT ON TABLE
|
|
33
|
+
indexes: tuple[SnakeIndexInfo, ...] = ()
|
|
34
|
+
# Domain rules the DB enforces (`CHECK (...)`). Condition as an AST, not emitted SQL.
|
|
35
|
+
checks: tuple[SnakeCheckInfo, ...] = ()
|
|
36
|
+
# What the node IS and who governs it, one single axis instead of booleans: see `SnakeTableKind`.
|
|
37
|
+
kind: SnakeTableKind = SnakeTableKind.TABLE
|
|
38
|
+
view_definition: str | None = None # the view's RAW SELECT (only with `sql=`)
|
|
39
|
+
view_query: object = None
|
|
40
|
+
"""The `SnakeQuery` that defines the view, uncompiled (only with `query=`).
|
|
41
|
+
|
|
42
|
+
It is stored UNCOMPILED on purpose: a view's body is written differently on every engine
|
|
43
|
+
—quoting, schema qualification, literals, LIMIT/OFFSET— and compiling it at model declaration
|
|
44
|
+
time froze it into one engine's. The emitter is the one that compiles it, with the target
|
|
45
|
+
dialect.
|
|
46
|
+
|
|
47
|
+
Typed `object` and not `SnakeQuery` because `metadata/` sits BELOW `query/` in the layer graph:
|
|
48
|
+
annotating it with its real type would invert the dependency. Whoever uses it narrows it.
|
|
49
|
+
"""
|
|
50
|
+
# EXPLICIT dependencies of this view on OTHER views (resolved names). Forces the migration's
|
|
51
|
+
# topological order (create A before B). Only between views; empty on a normal table.
|
|
52
|
+
depends_on: tuple[str, ...] = ()
|
|
53
|
+
# Participation in a POLYMORPHIC hierarchy. `None` on a normal table. Another axis independent
|
|
54
|
+
# of `kind`/`database`: see `SnakePolymorphicInfo`.
|
|
55
|
+
polymorphic: SnakePolymorphicInfo | None = None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def is_polymorphic_child(self) -> bool:
|
|
59
|
+
"""Whether it is a CHILD of a hierarchy: shares a table with the base, filtered by its value.
|
|
60
|
+
|
|
61
|
+
Since the physical table is a single one: autogen does NOT emit its `CREATE TABLE` (the base
|
|
62
|
+
does) and queries DO filter it by its discriminator.
|
|
63
|
+
"""
|
|
64
|
+
return self.polymorphic is not None and not self.polymorphic.is_base
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def is_view(self) -> bool:
|
|
68
|
+
"""Whether the node is a VIEW (read only, no constraints). Derived from `kind`."""
|
|
69
|
+
return self.kind is SnakeTableKind.VIEW
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def is_managed(self) -> bool:
|
|
73
|
+
"""Whether MIGRATIONS govern this node.
|
|
74
|
+
|
|
75
|
+
An `EXTERNAL` mirror (`@snake_db_first`) is queried and written like any other model, but
|
|
76
|
+
autogen ignores it: its schema is governed by the DB, not by us.
|
|
77
|
+
"""
|
|
78
|
+
return self.kind is not SnakeTableKind.EXTERNAL
|
|
79
|
+
|
|
80
|
+
def get_column(self, name: str) -> SnakeColumnInfo | None:
|
|
81
|
+
"""Returns the column with that SQL name, or None if it does not exist."""
|
|
82
|
+
for column in self.columns:
|
|
83
|
+
if column.name == name:
|
|
84
|
+
return column
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def get_column_by_attr(self, attr: str) -> SnakeColumnInfo | None:
|
|
88
|
+
"""Returns the column whose Python ATTRIBUTE is `attr`, or None if it does not exist.
|
|
89
|
+
|
|
90
|
+
With `snake_column(name=...)` the SQL name and the attribute's differ, and whoever walks the
|
|
91
|
+
graph uses the attribute. It tries `attr_name` and falls back to the SQL name (hand-written
|
|
92
|
+
metadata may not carry it).
|
|
93
|
+
"""
|
|
94
|
+
for column in self.columns:
|
|
95
|
+
if column.attr_name == attr:
|
|
96
|
+
return column
|
|
97
|
+
return self.get_column(attr)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""What a graph node IS and WHO governs it."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SnakeTableKind(Enum):
|
|
9
|
+
"""Nature of a graph node: one single axis, not a bag of booleans.
|
|
10
|
+
|
|
11
|
+
Replaces `is_view`/`managed`: N booleans give you illegal states (a managed external view?);
|
|
12
|
+
the enum makes them unwritable. `database` does NOT belong here (an axis independent of what
|
|
13
|
+
the node is).
|
|
14
|
+
|
|
15
|
+
- `TABLE`: a table we manage (create/alter/drop).
|
|
16
|
+
- `VIEW`: a view we manage (`@snake_view`). READ ONLY, no constraints.
|
|
17
|
+
- `EXTERNAL`: a mirror of something existing that we do NOT govern (`@snake_db_first`);
|
|
18
|
+
migrations IGNORE it.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
TABLE = "table"
|
|
22
|
+
VIEW = "view"
|
|
23
|
+
EXTERNAL = "external"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Metadata of a TRIGGER: the signal that lives in the database.
|
|
2
|
+
|
|
3
|
+
Against a code signal (`snakeorm/signals.py`), a trigger holds even when the row is written by
|
|
4
|
+
another process or by a `psql`, because the rule lives in the schema. That is why the `body` is SQL
|
|
5
|
+
and only SQL: no callables, this is the border between the two mechanisms.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
from snakeorm.core.placement import DEFAULT_SCHEMA
|
|
14
|
+
from snakeorm.core.exceptions import SnakeModelDefinitionError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SnakeTriggerTiming(Enum):
|
|
18
|
+
"""When it fires relative to the operation. Engine-agnostic: all three are standard SQL."""
|
|
19
|
+
|
|
20
|
+
BEFORE = "BEFORE"
|
|
21
|
+
AFTER = "AFTER"
|
|
22
|
+
INSTEAD_OF = "INSTEAD OF"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SnakeTriggerEvent(Enum):
|
|
26
|
+
"""Which operation fires it."""
|
|
27
|
+
|
|
28
|
+
INSERT = "INSERT"
|
|
29
|
+
UPDATE = "UPDATE"
|
|
30
|
+
DELETE = "DELETE"
|
|
31
|
+
TRUNCATE = "TRUNCATE"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, slots=True)
|
|
35
|
+
class SnakeTriggerInfo:
|
|
36
|
+
"""A trigger on a table: when, on what, and what it runs.
|
|
37
|
+
|
|
38
|
+
`body` is raw and opaque (`EXECUTE FUNCTION audit()`), as in `SnakeRoutineInfo`: the diff only
|
|
39
|
+
compares the string (typing PL/pgSQL would mean putting a whole language inside the ORM).
|
|
40
|
+
`for_each_row` tells a per-ROW trigger apart from a per-STATEMENT one; per row by default
|
|
41
|
+
(required for `NEW`/`OLD`).
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
name: str
|
|
45
|
+
table: str
|
|
46
|
+
timing: SnakeTriggerTiming
|
|
47
|
+
events: tuple[SnakeTriggerEvent, ...]
|
|
48
|
+
body: str
|
|
49
|
+
schema: str = DEFAULT_SCHEMA
|
|
50
|
+
for_each_row: bool = True
|
|
51
|
+
|
|
52
|
+
def __post_init__(self) -> None:
|
|
53
|
+
"""A trigger with no events fires on nothing: a dead object.
|
|
54
|
+
|
|
55
|
+
It is rejected at DECLARATION time (not at emission), while the user still remembers what
|
|
56
|
+
they meant to write.
|
|
57
|
+
"""
|
|
58
|
+
if not self.events:
|
|
59
|
+
raise SnakeModelDefinitionError(
|
|
60
|
+
f"Trigger '{self.name}' does not declare any event, so it would never fire. "
|
|
61
|
+
f"Declare at least one event (INSERT, UPDATE, DELETE or TRUNCATE)."
|
|
62
|
+
)
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""SQL parameters of a column, grouped BY TYPE FAMILY.
|
|
2
|
+
|
|
3
|
+
Every Python type family has its own parameters: an `int` has width, a `str` has length, a
|
|
4
|
+
`Decimal` has precision and scale, a `dict` has backing. Loose and flat inside `SnakeColumnInfo`,
|
|
5
|
+
nothing stopped you from writing a column with `max_length` AND `precision` at once — a state no
|
|
6
|
+
engine can represent, living in the graph that is supposed to BE the truth.
|
|
7
|
+
|
|
8
|
+
Grouped, that combination cannot even be built. And the dialect now receives ONE parameter instead
|
|
9
|
+
of five: that is what finally lets `precision` enter `map_type` (it used to be concatenated onto
|
|
10
|
+
the type FROM OUTSIDE, with an f-string, which is why it was the only unvalidated one).
|
|
11
|
+
|
|
12
|
+
Adding a new family = a dataclass here and a branch in each dialect. Neither the compiler nor
|
|
13
|
+
`SnakeColumnInfo` gets touched: the guard is structural and reads `python_type`.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import get_origin
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from datetime import datetime, time
|
|
22
|
+
from decimal import Decimal
|
|
23
|
+
|
|
24
|
+
from snakeorm.core.exceptions import SnakeModelDefinitionError
|
|
25
|
+
from snakeorm.metadata.int_size import SnakeIntSize
|
|
26
|
+
from snakeorm.metadata.json_storage import SnakeJsonStorage
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def base_type(python_type: object) -> object:
|
|
30
|
+
"""A parameterised generic reduced to its ORIGIN; anything else unchanged.
|
|
31
|
+
|
|
32
|
+
`dict[str, object]` answers `dict`, `list[int]` answers `list`, and `bool` answers `bool` —
|
|
33
|
+
`get_origin` of a plain class is `None`, which is what keeps the identity comparison below
|
|
34
|
+
strict where it has to be.
|
|
35
|
+
|
|
36
|
+
It exists because the guard compared `python_type is self.python_type`, so a user who
|
|
37
|
+
parameterised their column —the only way to write a JSON column WITHOUT dragging in `Any`—
|
|
38
|
+
got a refusal at import, with a message that asked them to change `dict` into `dict`. The
|
|
39
|
+
project's rule number one is zero `Any`, and the ORM was forcing it.
|
|
40
|
+
"""
|
|
41
|
+
return get_origin(python_type) or python_type
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class SnakeIntParams:
|
|
46
|
+
"""Parameters of an `int` column: how much room it takes in the database."""
|
|
47
|
+
|
|
48
|
+
size: SnakeIntSize = SnakeIntSize.BIGINT
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def python_type(self) -> type:
|
|
52
|
+
"""The Python type this family belongs to. The compiler's guard uses it."""
|
|
53
|
+
return int
|
|
54
|
+
|
|
55
|
+
def accepts(self, python_type: object) -> bool:
|
|
56
|
+
"""The family covers EXACTLY its type. By identity, not by inheritance: `bool` is a
|
|
57
|
+
subclass of `int` and accepting it would size a boolean column as an integer.
|
|
58
|
+
|
|
59
|
+
The ORIGIN is what gets compared, so `dict[str, object]` is a `dict` — which is the only
|
|
60
|
+
way to declare a JSON column without a bare `dict` and the `Any` it drags in. It does not
|
|
61
|
+
loosen the identity: `get_origin(bool)` is `None`, so `bool` still compares as itself."""
|
|
62
|
+
return base_type(python_type) is self.python_type
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def declarator(self) -> str:
|
|
66
|
+
"""The name of the field specifier that declares them. It goes into the error messages, so
|
|
67
|
+
the warning says WHAT to write and not just what is wrong."""
|
|
68
|
+
return "snake_int"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True, slots=True)
|
|
72
|
+
class SnakeStrParams:
|
|
73
|
+
"""Parameters of a `str` column: its maximum length, if it declares one.
|
|
74
|
+
|
|
75
|
+
The CEILING is set by each engine (Postgres and MySQL do not agree, and MySQL's even depends on
|
|
76
|
+
the collation); here we only head off what means nothing on any of them.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
max_length: int | None = None
|
|
80
|
+
fixed: bool = False
|
|
81
|
+
"""FIXED length (`CHAR(n)`) instead of variable (`VARCHAR(n)`).
|
|
82
|
+
|
|
83
|
+
It is not a stricter VARCHAR: `CHAR` pads with spaces up to `n` and compares ignoring that
|
|
84
|
+
padding. Whoever stores country codes, ISINs or a hash of known length wants it for exactly that
|
|
85
|
+
reason, and everybody else should not pay for it — which is why the default is still VARCHAR.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __post_init__(self) -> None:
|
|
89
|
+
"""A VARCHAR has to fit at least one character, and a CHAR has to say how many.
|
|
90
|
+
|
|
91
|
+
`VARCHAR(0)` is LEGAL on both engines and useless: it only accepts the empty string. That
|
|
92
|
+
the engine swallows it does not make it a reasonable declaration — nobody wants a column
|
|
93
|
+
like that — and this ORM shouts instead of letting through what is almost certainly a slip.
|
|
94
|
+
"""
|
|
95
|
+
if self.max_length is not None and self.max_length < 1:
|
|
96
|
+
raise SnakeModelDefinitionError(
|
|
97
|
+
f"A VARCHAR has to fit at least one character, and max_length={self.max_length} "
|
|
98
|
+
f"was asked for. For unbounded text, do not declare max_length at all: "
|
|
99
|
+
f"snake_str() already emits TEXT."
|
|
100
|
+
)
|
|
101
|
+
if self.fixed and self.max_length is None:
|
|
102
|
+
# A bare `CHAR` is `CHAR(1)` in SQL. Guessing it would mean taking a decision the user
|
|
103
|
+
# never took on their behalf, and a one-character column where text was expected does
|
|
104
|
+
# not show up until something gets truncated.
|
|
105
|
+
raise SnakeModelDefinitionError(
|
|
106
|
+
"A fixed-length column has to say HOW MANY characters: declare "
|
|
107
|
+
"snake_str(max_length=n, fixed=True). A CHAR without a length is CHAR(1) in SQL, "
|
|
108
|
+
"and that is almost never what anyone wants."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def python_type(self) -> type:
|
|
113
|
+
"""The Python type this family belongs to. The compiler's guard uses it."""
|
|
114
|
+
return str
|
|
115
|
+
|
|
116
|
+
def accepts(self, python_type: object) -> bool:
|
|
117
|
+
"""The family covers EXACTLY its type. By identity, not by inheritance: `bool` is a
|
|
118
|
+
subclass of `int` and accepting it would size a boolean column as an integer.
|
|
119
|
+
|
|
120
|
+
The ORIGIN is what gets compared, so `dict[str, object]` is a `dict` — which is the only
|
|
121
|
+
way to declare a JSON column without a bare `dict` and the `Any` it drags in. It does not
|
|
122
|
+
loosen the identity: `get_origin(bool)` is `None`, so `bool` still compares as itself."""
|
|
123
|
+
return base_type(python_type) is self.python_type
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def declarator(self) -> str:
|
|
127
|
+
"""The name of the field specifier that declares them. It goes into the error messages, so
|
|
128
|
+
the warning says WHAT to write and not just what is wrong."""
|
|
129
|
+
return "snake_str"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass(frozen=True, slots=True)
|
|
133
|
+
class SnakeDecimalParams:
|
|
134
|
+
"""Parameters of a `Decimal` column: the NUMERIC's digits.
|
|
135
|
+
|
|
136
|
+
`precision` is mandatory: a NUMERIC without it accepts any number of digits, so rounding stops
|
|
137
|
+
being declared and starts depending on whatever comes in.
|
|
138
|
+
|
|
139
|
+
Only what is absurd on ANY engine is rejected here. The ceiling —1000 digits in Postgres, 65 in
|
|
140
|
+
MySQL— is engine knowledge and lives in its dialect, like `max_bind_params`.
|
|
141
|
+
|
|
142
|
+
On scale: Postgres 15 accepts negative scales and scales greater than the precision, as its own
|
|
143
|
+
extension. It is not exposed, on purpose. The standard demands `0 <= scale <= precision`, MySQL
|
|
144
|
+
demands it, and Postgres demanded it up to 15; opening it would make a model stop being portable
|
|
145
|
+
depending on the VERSION of the server behind it, which is exactly what the Dialect/Driver axis
|
|
146
|
+
avoids.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
precision: int
|
|
150
|
+
scale: int | None = None
|
|
151
|
+
|
|
152
|
+
def __post_init__(self) -> None:
|
|
153
|
+
"""Heads off the three impossible numbers at construction, the earliest point there is."""
|
|
154
|
+
if self.precision < 1:
|
|
155
|
+
raise SnakeModelDefinitionError(
|
|
156
|
+
f"A NUMERIC has to have at least one digit, and "
|
|
157
|
+
f"precision={self.precision} was asked for. Precision is the TOTAL number of "
|
|
158
|
+
f"digits; the decimal places go in scale."
|
|
159
|
+
)
|
|
160
|
+
if self.scale is None:
|
|
161
|
+
return
|
|
162
|
+
if self.scale < 0:
|
|
163
|
+
raise SnakeModelDefinitionError(
|
|
164
|
+
f"The scale of a NUMERIC cannot be negative (scale={self.scale} was asked for). "
|
|
165
|
+
f"It is the decimal places: 2 for money, 0 for exact integers."
|
|
166
|
+
)
|
|
167
|
+
if self.scale > self.precision:
|
|
168
|
+
raise SnakeModelDefinitionError(
|
|
169
|
+
f"The scale cannot exceed the precision: {self.scale} decimal places were asked "
|
|
170
|
+
f"for inside {self.precision} total digits, leaving nothing for the integer "
|
|
171
|
+
f"part. Raise precision or lower scale."
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def python_type(self) -> type:
|
|
176
|
+
"""The Python type this family belongs to. The compiler's guard uses it."""
|
|
177
|
+
return Decimal
|
|
178
|
+
|
|
179
|
+
def accepts(self, python_type: object) -> bool:
|
|
180
|
+
"""The family covers EXACTLY its type. By identity, not by inheritance: `bool` is a
|
|
181
|
+
subclass of `int` and accepting it would size a boolean column as an integer.
|
|
182
|
+
|
|
183
|
+
The ORIGIN is what gets compared, so `dict[str, object]` is a `dict` — which is the only
|
|
184
|
+
way to declare a JSON column without a bare `dict` and the `Any` it drags in. It does not
|
|
185
|
+
loosen the identity: `get_origin(bool)` is `None`, so `bool` still compares as itself."""
|
|
186
|
+
return base_type(python_type) is self.python_type
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def declarator(self) -> str:
|
|
190
|
+
"""The name of the field specifier that declares them. It goes into the error messages, so
|
|
191
|
+
the warning says WHAT to write and not just what is wrong."""
|
|
192
|
+
return "snake_decimal"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass(frozen=True, slots=True)
|
|
196
|
+
class SnakeJsonParams:
|
|
197
|
+
"""Parameters of a `dict` column: which DB object backs it."""
|
|
198
|
+
|
|
199
|
+
storage: SnakeJsonStorage = SnakeJsonStorage.JSONB
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def python_type(self) -> type:
|
|
203
|
+
"""The Python type this family belongs to. The compiler's guard uses it."""
|
|
204
|
+
return dict
|
|
205
|
+
|
|
206
|
+
def accepts(self, python_type: object) -> bool:
|
|
207
|
+
"""The family covers EXACTLY its type. By identity, not by inheritance: `bool` is a
|
|
208
|
+
subclass of `int` and accepting it would size a boolean column as an integer.
|
|
209
|
+
|
|
210
|
+
The ORIGIN is what gets compared, so `dict[str, object]` is a `dict` — which is the only
|
|
211
|
+
way to declare a JSON column without a bare `dict` and the `Any` it drags in. It does not
|
|
212
|
+
loosen the identity: `get_origin(bool)` is `None`, so `bool` still compares as itself."""
|
|
213
|
+
return base_type(python_type) is self.python_type
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def declarator(self) -> str:
|
|
217
|
+
"""The name of the field specifier that declares them. It goes into the error messages, so
|
|
218
|
+
the warning says WHAT to write and not just what is wrong."""
|
|
219
|
+
return "snake_json"
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass(frozen=True, slots=True)
|
|
223
|
+
class SnakeDateTimeParams:
|
|
224
|
+
"""Parameters of a date column: whether the column carries a zone, and its resolution.
|
|
225
|
+
|
|
226
|
+
`tz` is NOT a knob you pick by hand: the declarator sets it (`snake_datetimetz()` sets it to
|
|
227
|
+
`True`, `snake_datetime()` to `False`) and a compiler guard demands it match the annotation.
|
|
228
|
+
That way the MODEL says which column gets created —just as `snake_int(size=SMALLINT)` says
|
|
229
|
+
SMALLINT— without the redundancy being able to lie. Same treatment as `snake_enum(Status)` over
|
|
230
|
+
a `SnakeColumn[Status]`.
|
|
231
|
+
|
|
232
|
+
`precision` is the fractional-second digits: `0` whole seconds, `3` milliseconds, `6` the
|
|
233
|
+
Postgres default (exactly the resolution of Python's `datetime`).
|
|
234
|
+
|
|
235
|
+
Only what means nothing on ANY engine is rejected here —a negative digit count—. The CEILING is
|
|
236
|
+
set by each dialect: Postgres and MySQL stop at 6, SQL Server reaches 7 and Oracle 9, so pinning
|
|
237
|
+
it here would put a specific engine inside the model, and the project's golden rule is that the
|
|
238
|
+
metadata graph stays agnostic.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
tz: bool = False
|
|
242
|
+
precision: int | None = None
|
|
243
|
+
|
|
244
|
+
def __post_init__(self) -> None:
|
|
245
|
+
"""Heads off a negative precision at construction, the earliest point there is."""
|
|
246
|
+
if self.precision is not None and self.precision < 0:
|
|
247
|
+
raise SnakeModelDefinitionError(
|
|
248
|
+
f"The precision of a date cannot be negative ({self.precision} was asked for). "
|
|
249
|
+
f"It is the fractional-second digits: 0 is whole seconds, 3 milliseconds and "
|
|
250
|
+
f"6 microseconds, which is the resolution of Python's datetime."
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def python_type(self) -> type:
|
|
255
|
+
"""The Python type this family belongs to. The compiler's guard uses it."""
|
|
256
|
+
return datetime
|
|
257
|
+
|
|
258
|
+
def accepts(self, python_type: object) -> bool:
|
|
259
|
+
"""The family covers `datetime` AND `SnakeUtc`, which is a subclass of it.
|
|
260
|
+
|
|
261
|
+
The other families compare by identity; this one cannot, because `SnakeUtc` is the same data
|
|
262
|
+
type with a guarantee on top.
|
|
263
|
+
"""
|
|
264
|
+
return isinstance(python_type, type) and issubclass(python_type, datetime)
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def declarator(self) -> str:
|
|
268
|
+
"""The name of the field specifier that declares them. It goes into the error messages, so
|
|
269
|
+
the warning says WHAT to write and not just what is wrong."""
|
|
270
|
+
return "snake_datetime"
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@dataclass(frozen=True, slots=True)
|
|
274
|
+
class SnakeFloatParams:
|
|
275
|
+
"""Parameters of a `float` column: its WIDTH in bytes.
|
|
276
|
+
|
|
277
|
+
A Python `float` is double precision, so 8 is the default and it does not change: changing it
|
|
278
|
+
would make an already-written model lose precision silently on upgrade. Declaring 4 is a storage
|
|
279
|
+
decision —half the bytes per row— taken knowing what you lose.
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
size: int = 8
|
|
283
|
+
|
|
284
|
+
def __post_init__(self) -> None:
|
|
285
|
+
"""Only 4 and 8: there is no engine with any other floating-point width.
|
|
286
|
+
|
|
287
|
+
Like every other structural guard, what means nothing on ANY engine dies at declaration
|
|
288
|
+
time. Per-engine ceilings are another matter and live in the dialect.
|
|
289
|
+
"""
|
|
290
|
+
if self.size not in (4, 8):
|
|
291
|
+
raise SnakeModelDefinitionError(
|
|
292
|
+
f"A float is either 4 or 8 bytes, and size={self.size} was asked for. 4 is single "
|
|
293
|
+
f"precision (REAL/FLOAT) and 8 is double (DOUBLE PRECISION/DOUBLE), which is what a "
|
|
294
|
+
f"Python float is and the default of this ORM."
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def python_type(self) -> type:
|
|
299
|
+
"""The Python type this family belongs to. The compiler's guard uses it."""
|
|
300
|
+
return float
|
|
301
|
+
|
|
302
|
+
def accepts(self, python_type: object) -> bool:
|
|
303
|
+
"""The family covers EXACTLY its type, by identity and not by inheritance."""
|
|
304
|
+
return python_type is self.python_type
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def declarator(self) -> str:
|
|
308
|
+
"""The name of the field specifier that declares them, so the error says WHAT to write."""
|
|
309
|
+
return "snake_float"
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@dataclass(frozen=True, slots=True)
|
|
313
|
+
class SnakeTimeParams:
|
|
314
|
+
"""Parameters of a `time` column: whether it carries a ZONE.
|
|
315
|
+
|
|
316
|
+
Two declarators, as with dates (`snake_datetime` / `snake_datetimetz`), and for the same reason:
|
|
317
|
+
the column SAYS which type it creates instead of it depending on whether the first value that
|
|
318
|
+
arrived carried an offset. A bare `TIME` throws the zone away, and an opening time stops meaning
|
|
319
|
+
the same thing when seen from somewhere else.
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
with_timezone: bool = False
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def python_type(self) -> type:
|
|
326
|
+
"""The Python type this family belongs to. The compiler's guard uses it."""
|
|
327
|
+
return time
|
|
328
|
+
|
|
329
|
+
def accepts(self, python_type: object) -> bool:
|
|
330
|
+
"""The family covers EXACTLY its type, by identity and not by inheritance."""
|
|
331
|
+
return python_type is self.python_type
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def declarator(self) -> str:
|
|
335
|
+
"""The name of the field specifier that declares them, so the error says WHAT to write."""
|
|
336
|
+
return "snake_timetz" if self.with_timezone else "snake_time"
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
SnakeTypeParams = (
|
|
340
|
+
SnakeIntParams
|
|
341
|
+
| SnakeStrParams
|
|
342
|
+
| SnakeDecimalParams
|
|
343
|
+
| SnakeJsonParams
|
|
344
|
+
| SnakeDateTimeParams
|
|
345
|
+
| SnakeFloatParams
|
|
346
|
+
| SnakeTimeParams
|
|
347
|
+
)
|
|
348
|
+
"""The parameters of ONE family. `None` on a column that has none (`bool`, `date`...)."""
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""SnakeORM migrations: DDL generation from the compiled metadata."""
|
|
2
|
+
|
|
3
|
+
from snakeorm.migration.asyncrunner import AsyncMigrationRunner as AsyncMigrationRunner
|
|
4
|
+
from snakeorm.migration.autodetect import autodetect as autodetect
|
|
5
|
+
from snakeorm.migration.autodetect import current_routines as current_routines
|
|
6
|
+
from snakeorm.migration.autodetect import current_schema as current_schema
|
|
7
|
+
from snakeorm.migration.autodetect import replay as replay
|
|
8
|
+
from snakeorm.migration.ddl import emit_add_check as emit_add_check
|
|
9
|
+
from snakeorm.migration.ddl import emit_add_column as emit_add_column
|
|
10
|
+
from snakeorm.migration.ddl import emit_add_foreign_key as emit_add_foreign_key
|
|
11
|
+
from snakeorm.migration.ddl import emit_alter_column as emit_alter_column
|
|
12
|
+
from snakeorm.migration.ddl import emit_column_comment as emit_column_comment
|
|
13
|
+
from snakeorm.migration.ddl import emit_comments as emit_comments
|
|
14
|
+
from snakeorm.migration.ddl import emit_create_function as emit_create_function
|
|
15
|
+
from snakeorm.migration.ddl import emit_create_index as emit_create_index
|
|
16
|
+
from snakeorm.migration.ddl import emit_create_schema as emit_create_schema
|
|
17
|
+
from snakeorm.migration.ddl import emit_create_table as emit_create_table
|
|
18
|
+
from snakeorm.migration.ddl import emit_create_trigger as emit_create_trigger
|
|
19
|
+
from snakeorm.migration.ddl import emit_create_view as emit_create_view
|
|
20
|
+
from snakeorm.migration.ddl import emit_drop_check as emit_drop_check
|
|
21
|
+
from snakeorm.migration.ddl import emit_drop_column as emit_drop_column
|
|
22
|
+
from snakeorm.migration.ddl import emit_drop_foreign_key as emit_drop_foreign_key
|
|
23
|
+
from snakeorm.migration.ddl import foreign_key_name as foreign_key_name
|
|
24
|
+
from snakeorm.migration.ddl import emit_drop_function as emit_drop_function
|
|
25
|
+
from snakeorm.migration.ddl import emit_drop_index as emit_drop_index
|
|
26
|
+
from snakeorm.migration.ddl import emit_drop_schema as emit_drop_schema
|
|
27
|
+
from snakeorm.migration.ddl import emit_drop_table as emit_drop_table
|
|
28
|
+
from snakeorm.migration.ddl import emit_drop_trigger as emit_drop_trigger
|
|
29
|
+
from snakeorm.migration.ddl import emit_drop_view as emit_drop_view
|
|
30
|
+
from snakeorm.migration.ddl import emit_rebuild_table as emit_rebuild_table
|
|
31
|
+
from snakeorm.migration.ddl import emit_rename_column as emit_rename_column
|
|
32
|
+
from snakeorm.migration.ddl import emit_rename_table as emit_rename_table
|
|
33
|
+
from snakeorm.migration.ddl import emit_replace_view as emit_replace_view
|
|
34
|
+
from snakeorm.migration.ddl import emit_table_comment as emit_table_comment
|
|
35
|
+
from snakeorm.migration.ddl import inlined_foreign_keys as inlined_foreign_keys
|
|
36
|
+
from snakeorm.migration.ddl import sql_type_of as sql_type_of
|
|
37
|
+
from snakeorm.migration.diff import diff_routines as diff_routines
|
|
38
|
+
from snakeorm.migration.diff import diff_schema as diff_schema
|
|
39
|
+
from snakeorm.migration.diff import drop_order as drop_order
|
|
40
|
+
from snakeorm.migration.diff import diff_triggers as diff_triggers
|
|
41
|
+
from snakeorm.migration.loader import load as load
|
|
42
|
+
from snakeorm.migration.operations import AddCheck as AddCheck
|
|
43
|
+
from snakeorm.migration.operations import AddColumn as AddColumn
|
|
44
|
+
from snakeorm.migration.operations import AddForeignKey as AddForeignKey
|
|
45
|
+
from snakeorm.migration.operations import AlterColumn as AlterColumn
|
|
46
|
+
from snakeorm.migration.operations import AlterTableComment as AlterTableComment
|
|
47
|
+
from snakeorm.migration.operations import AlterFunction as AlterFunction
|
|
48
|
+
from snakeorm.migration.operations import AlterTrigger as AlterTrigger
|
|
49
|
+
from snakeorm.migration.operations import AlterView as AlterView
|
|
50
|
+
from snakeorm.migration.operations import CreateFunction as CreateFunction
|
|
51
|
+
from snakeorm.migration.operations import CreateIndex as CreateIndex
|
|
52
|
+
from snakeorm.migration.operations import CreateSchema as CreateSchema
|
|
53
|
+
from snakeorm.migration.operations import CreateTable as CreateTable
|
|
54
|
+
from snakeorm.migration.operations import CreateTrigger as CreateTrigger
|
|
55
|
+
from snakeorm.migration.operations import CreateView as CreateView
|
|
56
|
+
from snakeorm.migration.operations import DropCheck as DropCheck
|
|
57
|
+
from snakeorm.migration.operations import DropColumn as DropColumn
|
|
58
|
+
from snakeorm.migration.operations import DropForeignKey as DropForeignKey
|
|
59
|
+
from snakeorm.migration.operations import DropFunction as DropFunction
|
|
60
|
+
from snakeorm.migration.operations import DropIndex as DropIndex
|
|
61
|
+
from snakeorm.migration.operations import DropSchema as DropSchema
|
|
62
|
+
from snakeorm.migration.operations import DropTable as DropTable
|
|
63
|
+
from snakeorm.migration.operations import DropTrigger as DropTrigger
|
|
64
|
+
from snakeorm.migration.operations import DropView as DropView
|
|
65
|
+
from snakeorm.migration.operations import RebuildTable as RebuildTable
|
|
66
|
+
from snakeorm.migration.operations import RenameColumn as RenameColumn
|
|
67
|
+
from snakeorm.migration.operations import RenameTable as RenameTable
|
|
68
|
+
from snakeorm.migration.operations import RunPython as RunPython
|
|
69
|
+
from snakeorm.migration.operations import RunSQL as RunSQL
|
|
70
|
+
from snakeorm.migration.operations import SnakeDataOperation as SnakeDataOperation
|
|
71
|
+
from snakeorm.migration.operations import (
|
|
72
|
+
SnakeMigrationOperation as SnakeMigrationOperation,
|
|
73
|
+
)
|
|
74
|
+
from snakeorm.migration.operations import SnakeOperation as SnakeOperation
|
|
75
|
+
from snakeorm.migration.realize import realize as realize
|
|
76
|
+
from snakeorm.migration.renames import RenameSuggestion as RenameSuggestion
|
|
77
|
+
from snakeorm.migration.renames import format_narrowing_hint as format_narrowing_hint
|
|
78
|
+
from snakeorm.migration.renames import format_rename_hint as format_rename_hint
|
|
79
|
+
from snakeorm.migration.renames import narrowing_warnings as narrowing_warnings
|
|
80
|
+
from snakeorm.migration.renames import rename_suggestions as rename_suggestions
|
|
81
|
+
from snakeorm.migration.renames import standing_view_warning as standing_view_warning
|
|
82
|
+
from snakeorm.migration.render import render_migration as render_migration
|
|
83
|
+
from snakeorm.migration.runner import Migration as Migration
|
|
84
|
+
from snakeorm.migration.planner import dependency_order as dependency_order
|
|
85
|
+
from snakeorm.migration.runner import MigrationRunner as MigrationRunner
|
|
86
|
+
from snakeorm.migration.runner import squash_already_done as squash_already_done
|
|
87
|
+
from snakeorm.migration.squash import squash as squash
|
|
88
|
+
from snakeorm.migration.state import SchemaState as SchemaState
|