python-corekit 0.1.0__py3-none-any.whl → 0.2.0__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.
- corekit/api/__init__.py +18 -3
- corekit/api/application.py +237 -0
- corekit/api/lifespan.py +210 -0
- corekit/api/middleware.py +93 -0
- corekit/api/routers.py +109 -1
- corekit/concurrency/worker.py +65 -65
- corekit/config/settings.py +3 -3
- corekit/connections/sql/__init__.py +31 -3
- corekit/connections/sql/connection.py +19 -0
- corekit/connections/sql/migration/__init__.py +5 -5
- corekit/connections/sql/migration/base.py +3 -3
- corekit/connections/sql/migration/operations.py +66 -42
- corekit/connections/sql/migration/registry.py +2 -2
- corekit/connections/sql/operations/__init__.py +24 -0
- corekit/connections/sql/operations/base.py +102 -0
- corekit/connections/sql/operations/statements.py +150 -0
- corekit/connections/sql/query.py +4 -62
- corekit/connections/sql/table.py +30 -4
- corekit/constants.py +45 -45
- corekit/crypto/constants.py +4 -4
- corekit/data/__init__.py +8 -0
- corekit/data/expressions/__init__.py +10 -2
- corekit/data/expressions/comparison.py +184 -104
- corekit/data/expressions/expression.py +103 -98
- corekit/data/expressions/operator.py +54 -0
- corekit/data/expressions/target.py +21 -0
- corekit/data/record.py +147 -147
- corekit/data/stats.py +159 -157
- corekit/decorators/__init__.py +2 -2
- corekit/decorators/exception_handling.py +2 -1
- corekit/etl/connection.py +44 -44
- corekit/events/websocket.py +3 -2
- corekit/exceptions/__init__.py +18 -0
- corekit/http/__init__.py +13 -0
- corekit/jobs/__init__.py +26 -0
- corekit/jobs/registry.py +87 -0
- corekit/jobs/runner.py +69 -0
- corekit/jobs/task.py +152 -0
- corekit/observability/__init__.py +5 -3
- corekit/observability/request_context.py +135 -0
- corekit/registry/__init__.py +11 -6
- corekit/registry/ordered.py +86 -0
- corekit/schemas/__init__.py +10 -0
- corekit/schemas/enum.py +49 -49
- corekit/schemas/models/arbitrary.py +11 -11
- corekit/schemas/pydantic/fields.py +35 -35
- corekit/schemas/types.py +40 -40
- corekit/serialization/__init__.py +22 -0
- corekit/serialization/serializer.py +1 -1
- corekit/utils/__init__.py +59 -5
- corekit/utils/coercion.py +118 -0
- corekit/utils/collections.py +115 -0
- corekit/utils/ids.py +61 -5
- corekit/utils/payload.py +100 -0
- corekit/utils/raise_exc.py +8 -8
- corekit/utils/text.py +56 -0
- corekit/utils/time.py +74 -21
- corekit/utils/validators.py +15 -15
- corekit/utils/void.py +8 -8
- {python_corekit-0.1.0.dist-info → python_corekit-0.2.0.dist-info}/METADATA +105 -100
- {python_corekit-0.1.0.dist-info → python_corekit-0.2.0.dist-info}/RECORD +64 -46
- {python_corekit-0.1.0.dist-info → python_corekit-0.2.0.dist-info}/WHEEL +0 -0
- {python_corekit-0.1.0.dist-info → python_corekit-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {python_corekit-0.1.0.dist-info → python_corekit-0.2.0.dist-info}/top_level.txt +0 -0
corekit/api/routers.py
CHANGED
|
@@ -12,18 +12,45 @@ the subscript syntax::
|
|
|
12
12
|
return await router.handler.list_users()
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
+
import inspect
|
|
15
16
|
from typing import Any, Generic, Type, TypeVar, cast
|
|
16
17
|
|
|
17
18
|
from fastapi import APIRouter, FastAPI
|
|
18
19
|
|
|
19
20
|
from corekit.api.handler import BaseHandler
|
|
20
21
|
from corekit.observability.benchmarkable import Benchmarkable
|
|
22
|
+
from corekit.registry import OrderedRegistry
|
|
21
23
|
|
|
22
|
-
__all__ = ["SimpleRouter", "SmartRouter"]
|
|
24
|
+
__all__ = ["CatchAllRouter", "SimpleRouter", "SmartRouter", "router_registry"]
|
|
23
25
|
|
|
24
26
|
H = TypeVar("H", bound=BaseHandler)
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
class _RouterRegistry(OrderedRegistry["SimpleRouter"]):
|
|
30
|
+
"""
|
|
31
|
+
Every ``SimpleRouter`` built in this process, in construction order.
|
|
32
|
+
|
|
33
|
+
``Application.discover_routers`` imports a package to populate this, then
|
|
34
|
+
mounts what is left unclaimed. A router that has been included into another
|
|
35
|
+
router claims itself, so only top-level routers are mounted.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def unclaimed(self) -> list["SimpleRouter"]:
|
|
40
|
+
"""
|
|
41
|
+
Top-level routers, with any catch-all last.
|
|
42
|
+
|
|
43
|
+
Sorting is stable, so everything else keeps construction order.
|
|
44
|
+
"""
|
|
45
|
+
return sorted(
|
|
46
|
+
self.where(lambda router: not router.claimed),
|
|
47
|
+
key=lambda router: isinstance(router, CatchAllRouter),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
router_registry = _RouterRegistry()
|
|
52
|
+
|
|
53
|
+
|
|
27
54
|
class SimpleRouter(APIRouter, Benchmarkable):
|
|
28
55
|
"""
|
|
29
56
|
An APIRouter that can log, time itself, and mount itself onto a parent.
|
|
@@ -39,13 +66,54 @@ class SimpleRouter(APIRouter, Benchmarkable):
|
|
|
39
66
|
APIRouter.__init__(self, **kwargs)
|
|
40
67
|
Benchmarkable.__init__(self)
|
|
41
68
|
|
|
69
|
+
# Discovery needs to see every router that was built, and to tell a
|
|
70
|
+
# top-level one from a sub-router. Both facts are recorded here rather
|
|
71
|
+
# than inferred later, when the import that created them is long done.
|
|
72
|
+
self._claimed = False
|
|
73
|
+
self._origin_module = self._caller_module()
|
|
74
|
+
router_registry.register(self)
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def claimed(self) -> bool:
|
|
78
|
+
"""Whether a parent router has taken ownership of this one."""
|
|
79
|
+
return self._claimed
|
|
80
|
+
|
|
81
|
+
def claim(self) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Mark this router as owned by a parent, so discovery skips it.
|
|
84
|
+
|
|
85
|
+
Called by ``include`` rather than by hand: a router that a parent has
|
|
86
|
+
already mounted must not also be mounted at the application root.
|
|
87
|
+
"""
|
|
88
|
+
self._claimed = True
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _caller_module() -> str:
|
|
92
|
+
"""
|
|
93
|
+
The module that constructed this router.
|
|
94
|
+
|
|
95
|
+
``type(self).__module__`` names where the class is defined, which is
|
|
96
|
+
always corekit; discovery logs need where the instance was built. Walk
|
|
97
|
+
out of corekit's own frames to find it.
|
|
98
|
+
"""
|
|
99
|
+
for frame in inspect.stack()[1:]:
|
|
100
|
+
module = frame.frame.f_globals.get("__name__", "")
|
|
101
|
+
if not module.startswith("corekit."):
|
|
102
|
+
return module
|
|
103
|
+
return "<unknown>"
|
|
104
|
+
|
|
42
105
|
def include(self, parent: APIRouter | FastAPI, *args: Any, **kwargs: Any) -> None:
|
|
43
106
|
"""
|
|
44
107
|
Add this router to a parent router or application.
|
|
45
108
|
|
|
46
109
|
The inverse of ``parent.include_router(self)``, so a module can hand
|
|
47
110
|
itself over: ``admin_router.include(app)``.
|
|
111
|
+
|
|
112
|
+
Including into another router claims this one, so ``discover_routers``
|
|
113
|
+
will not mount it a second time at the application root.
|
|
48
114
|
"""
|
|
115
|
+
if isinstance(parent, APIRouter):
|
|
116
|
+
self.claim()
|
|
49
117
|
parent.include_router(self, *args, **kwargs)
|
|
50
118
|
|
|
51
119
|
|
|
@@ -113,3 +181,43 @@ class SmartRouter(SimpleRouter, Generic[H]):
|
|
|
113
181
|
Replace the handler. Assigning a double is the supported way to test a router.
|
|
114
182
|
"""
|
|
115
183
|
self._handler = handler
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class CatchAllRouter(SmartRouter[H]):
|
|
187
|
+
"""
|
|
188
|
+
The router of last resort, mounted after every other route.
|
|
189
|
+
|
|
190
|
+
A single-page app needs one of these: a ``/{path:path}`` route that serves
|
|
191
|
+
``index.html`` so the client-side router can take over. Mounted in the wrong
|
|
192
|
+
order it shadows the entire API, because it matches everything.
|
|
193
|
+
|
|
194
|
+
Being a distinct type rather than an ordering flag means that position is a
|
|
195
|
+
property of what the router *is*. ``discover_routers`` always mounts it last,
|
|
196
|
+
and a second instance is refused -- two catch-alls means one of them silently
|
|
197
|
+
never matches, which is not a state worth allowing.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
_instance: "CatchAllRouter[Any] | None" = None
|
|
201
|
+
|
|
202
|
+
def __init__(self, handler: H | None = None, **kwargs: Any) -> None:
|
|
203
|
+
"""
|
|
204
|
+
Build the catch-all. Raises if one already exists in this process.
|
|
205
|
+
"""
|
|
206
|
+
existing = CatchAllRouter._instance
|
|
207
|
+
if existing is not None:
|
|
208
|
+
raise RuntimeError(
|
|
209
|
+
f"A CatchAllRouter already exists ({existing._origin_module}). "
|
|
210
|
+
f"Only one may exist: it matches every unmatched path, so a second "
|
|
211
|
+
f"could never receive a request. Delete one, or make it a SmartRouter "
|
|
212
|
+
f"with an explicit prefix."
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
super().__init__(handler=handler, **kwargs)
|
|
216
|
+
CatchAllRouter._instance = self
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
def reset(cls) -> None:
|
|
220
|
+
"""
|
|
221
|
+
Forget the registered instance. For tests that build more than one app.
|
|
222
|
+
"""
|
|
223
|
+
cls._instance = None
|
corekit/concurrency/worker.py
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import logging
|
|
3
|
-
import threading
|
|
4
|
-
import time
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class ThreadWorker(threading.Thread):
|
|
8
|
-
def __init__(
|
|
9
|
-
self,
|
|
10
|
-
can_start: threading.Event,
|
|
11
|
-
loop: asyncio.AbstractEventLoop | None = None,
|
|
12
|
-
timeout: int = 3,
|
|
13
|
-
delay: float = 1.0,
|
|
14
|
-
) -> None:
|
|
15
|
-
super().__init__(daemon=True)
|
|
16
|
-
self.loop = loop
|
|
17
|
-
self.timeout = timeout
|
|
18
|
-
self.delay = delay
|
|
19
|
-
self.running = True # Control flag for stopping the thread
|
|
20
|
-
self.can_start: threading.Event = can_start
|
|
21
|
-
self.logger = logging.getLogger(self.__class__.__name__)
|
|
22
|
-
self._logging_prefix = f"[{self.__class__.__name__}]"
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def logging_prefix(self) -> str:
|
|
26
|
-
return self._logging_prefix
|
|
27
|
-
|
|
28
|
-
def info(self, message: str) -> None:
|
|
29
|
-
self.logger.info(f"{self.logging_prefix}: {message}")
|
|
30
|
-
|
|
31
|
-
def warning(self, message: str) -> None:
|
|
32
|
-
self.logger.warning(f"{self.logging_prefix}: {message}")
|
|
33
|
-
|
|
34
|
-
def error(self, message: str) -> None:
|
|
35
|
-
self.logger.error(f"{self.logging_prefix}: {message}")
|
|
36
|
-
|
|
37
|
-
def set_loop(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
38
|
-
self.logger.info("Updating event loop")
|
|
39
|
-
self.loop = loop
|
|
40
|
-
|
|
41
|
-
def stop(self) -> None:
|
|
42
|
-
"""
|
|
43
|
-
Stops the worker gracefully
|
|
44
|
-
"""
|
|
45
|
-
self.running = False
|
|
46
|
-
self.info("Stopping ThreadWorker")
|
|
47
|
-
|
|
48
|
-
def run(self) -> None:
|
|
49
|
-
self.info("ThreadWorker started")
|
|
50
|
-
try:
|
|
51
|
-
self.info("ThreadWorker waiting until able to start")
|
|
52
|
-
self.can_start.wait()
|
|
53
|
-
|
|
54
|
-
self.info("can_start flag set. Starting ThreadWorker")
|
|
55
|
-
self.process()
|
|
56
|
-
|
|
57
|
-
except Exception as exc:
|
|
58
|
-
self.error(f"Error in ThreadWorker: {exc}")
|
|
59
|
-
time.sleep(self.delay)
|
|
60
|
-
|
|
61
|
-
finally:
|
|
62
|
-
self.info("ThreadWorker ending")
|
|
63
|
-
|
|
64
|
-
def process(self) -> None:
|
|
65
|
-
raise NotImplementedError()
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ThreadWorker(threading.Thread):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
can_start: threading.Event,
|
|
11
|
+
loop: asyncio.AbstractEventLoop | None = None,
|
|
12
|
+
timeout: int = 3,
|
|
13
|
+
delay: float = 1.0,
|
|
14
|
+
) -> None:
|
|
15
|
+
super().__init__(daemon=True)
|
|
16
|
+
self.loop = loop
|
|
17
|
+
self.timeout = timeout
|
|
18
|
+
self.delay = delay
|
|
19
|
+
self.running = True # Control flag for stopping the thread
|
|
20
|
+
self.can_start: threading.Event = can_start
|
|
21
|
+
self.logger = logging.getLogger(self.__class__.__name__)
|
|
22
|
+
self._logging_prefix = f"[{self.__class__.__name__}]"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def logging_prefix(self) -> str:
|
|
26
|
+
return self._logging_prefix
|
|
27
|
+
|
|
28
|
+
def info(self, message: str) -> None:
|
|
29
|
+
self.logger.info(f"{self.logging_prefix}: {message}")
|
|
30
|
+
|
|
31
|
+
def warning(self, message: str) -> None:
|
|
32
|
+
self.logger.warning(f"{self.logging_prefix}: {message}")
|
|
33
|
+
|
|
34
|
+
def error(self, message: str) -> None:
|
|
35
|
+
self.logger.error(f"{self.logging_prefix}: {message}")
|
|
36
|
+
|
|
37
|
+
def set_loop(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
38
|
+
self.logger.info("Updating event loop")
|
|
39
|
+
self.loop = loop
|
|
40
|
+
|
|
41
|
+
def stop(self) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Stops the worker gracefully
|
|
44
|
+
"""
|
|
45
|
+
self.running = False
|
|
46
|
+
self.info("Stopping ThreadWorker")
|
|
47
|
+
|
|
48
|
+
def run(self) -> None:
|
|
49
|
+
self.info("ThreadWorker started")
|
|
50
|
+
try:
|
|
51
|
+
self.info("ThreadWorker waiting until able to start")
|
|
52
|
+
self.can_start.wait()
|
|
53
|
+
|
|
54
|
+
self.info("can_start flag set. Starting ThreadWorker")
|
|
55
|
+
self.process()
|
|
56
|
+
|
|
57
|
+
except Exception as exc:
|
|
58
|
+
self.error(f"Error in ThreadWorker: {exc}")
|
|
59
|
+
time.sleep(self.delay)
|
|
60
|
+
|
|
61
|
+
finally:
|
|
62
|
+
self.info("ThreadWorker ending")
|
|
63
|
+
|
|
64
|
+
def process(self) -> None:
|
|
65
|
+
raise NotImplementedError()
|
corekit/config/settings.py
CHANGED
|
@@ -89,9 +89,9 @@ class CryptoSettings(BaseModel):
|
|
|
89
89
|
"""
|
|
90
90
|
Hashing configuration.
|
|
91
91
|
|
|
92
|
-
``salt`` has no default on purpose
|
|
93
|
-
|
|
94
|
-
one is supplied.
|
|
92
|
+
``salt`` has no default on purpose. A salt shipped with a library is shared
|
|
93
|
+
by every install, which defeats the point of salting, so hashing raises
|
|
94
|
+
until one is supplied.
|
|
95
95
|
"""
|
|
96
96
|
|
|
97
97
|
salt: str | None = None
|
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
"""
|
|
2
|
-
SQL building blocks: a Connectable session,
|
|
2
|
+
SQL building blocks: a Connectable session, statement primitives and a base table.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from corekit.connections.sql.connection import PaginatedResult, SQLConnection
|
|
6
6
|
from corekit.connections.sql.fields import JSONBField, PydanticJSON
|
|
7
|
+
from corekit.connections.sql.operations import (
|
|
8
|
+
Conditional,
|
|
9
|
+
DdlOperation,
|
|
10
|
+
Delete,
|
|
11
|
+
DmlOperation,
|
|
12
|
+
DqlOperation,
|
|
13
|
+
Insert,
|
|
14
|
+
Operation,
|
|
15
|
+
Select,
|
|
16
|
+
Update,
|
|
17
|
+
)
|
|
7
18
|
from corekit.connections.sql.query import Query
|
|
8
|
-
from corekit.connections.sql.table import NamedTable
|
|
19
|
+
from corekit.connections.sql.table import NamedTable, TableExportItem
|
|
9
20
|
|
|
10
|
-
__all__ = [
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Conditional",
|
|
23
|
+
"DdlOperation",
|
|
24
|
+
"Delete",
|
|
25
|
+
"DmlOperation",
|
|
26
|
+
"DqlOperation",
|
|
27
|
+
"Insert",
|
|
28
|
+
"JSONBField",
|
|
29
|
+
"NamedTable",
|
|
30
|
+
"Operation",
|
|
31
|
+
"PaginatedResult",
|
|
32
|
+
"PydanticJSON",
|
|
33
|
+
"Query",
|
|
34
|
+
"Select",
|
|
35
|
+
"SQLConnection",
|
|
36
|
+
"TableExportItem",
|
|
37
|
+
"Update",
|
|
38
|
+
]
|
|
@@ -24,6 +24,7 @@ from sqlmodel import Session, SQLModel, create_engine, select
|
|
|
24
24
|
|
|
25
25
|
from corekit.config import get_settings
|
|
26
26
|
from corekit.connections import Connectable
|
|
27
|
+
from corekit.connections.sql.operations import DmlOperation, DqlOperation, Operation
|
|
27
28
|
from corekit.connections.sql.query import Query
|
|
28
29
|
from corekit.connections.sql.table import NamedTable
|
|
29
30
|
|
|
@@ -320,6 +321,24 @@ class SQLConnection(Connectable):
|
|
|
320
321
|
"""
|
|
321
322
|
return list(self._exec(query.build()))
|
|
322
323
|
|
|
324
|
+
def execute(self, operation: Operation) -> Any:
|
|
325
|
+
"""
|
|
326
|
+
Run an operation, committing if it writes.
|
|
327
|
+
|
|
328
|
+
A read returns its rows; a write returns the result, whose
|
|
329
|
+
``rowcount`` reports how many rows it touched.
|
|
330
|
+
"""
|
|
331
|
+
result = self._exec(operation.build())
|
|
332
|
+
if isinstance(operation, DmlOperation):
|
|
333
|
+
self.session.commit()
|
|
334
|
+
return result
|
|
335
|
+
|
|
336
|
+
def fetch(self, operation: DqlOperation) -> list[NamedTable]:
|
|
337
|
+
"""
|
|
338
|
+
Every row an operation selects.
|
|
339
|
+
"""
|
|
340
|
+
return list(self.execute(operation))
|
|
341
|
+
|
|
323
342
|
def fetch_one_by_query(self, query: Query) -> NamedTable | None:
|
|
324
343
|
"""
|
|
325
344
|
The first row matching a Query.
|
|
@@ -8,7 +8,7 @@ Define a migration by subclassing ``Migration`` and listing its operations::
|
|
|
8
8
|
name = "user_is_active"
|
|
9
9
|
description = "Adds the is_active flag to users."
|
|
10
10
|
|
|
11
|
-
def operations(self) -> list[
|
|
11
|
+
def operations(self) -> list[MigrationOperation]:
|
|
12
12
|
return [AddColumn(table="user", column="is_active", dtype="BOOLEAN", default="true")]
|
|
13
13
|
|
|
14
14
|
Collect them from a package and run them::
|
|
@@ -25,12 +25,12 @@ from corekit.connections.sql.migration.operations import (
|
|
|
25
25
|
AddColumn,
|
|
26
26
|
AlterColumnType,
|
|
27
27
|
CreateIndex,
|
|
28
|
-
|
|
28
|
+
DataMigrationOperation,
|
|
29
29
|
DropColumn,
|
|
30
30
|
DropColumnDefault,
|
|
31
31
|
DropIndex,
|
|
32
32
|
DropTable,
|
|
33
|
-
|
|
33
|
+
MigrationOperation,
|
|
34
34
|
RenameColumn,
|
|
35
35
|
RenameTable,
|
|
36
36
|
SetColumnDefault,
|
|
@@ -42,7 +42,7 @@ __all__ = [
|
|
|
42
42
|
"AddColumn",
|
|
43
43
|
"AlterColumnType",
|
|
44
44
|
"CreateIndex",
|
|
45
|
-
"
|
|
45
|
+
"DataMigrationOperation",
|
|
46
46
|
"DropColumn",
|
|
47
47
|
"DropColumnDefault",
|
|
48
48
|
"DropIndex",
|
|
@@ -50,7 +50,7 @@ __all__ = [
|
|
|
50
50
|
"Migration",
|
|
51
51
|
"MigrationChecksumError",
|
|
52
52
|
"MigrationRegistry",
|
|
53
|
-
"
|
|
53
|
+
"MigrationOperation",
|
|
54
54
|
"RenameColumn",
|
|
55
55
|
"RenameTable",
|
|
56
56
|
"SchemaMigration",
|
|
@@ -5,7 +5,7 @@ A Migration is defined by:
|
|
|
5
5
|
- version: unique positive integer, determines execution order
|
|
6
6
|
- name: short human-readable label (used in logs and the DB table)
|
|
7
7
|
- description: longer explanation of what and why
|
|
8
|
-
- operations: ordered list of
|
|
8
|
+
- operations: ordered list of MigrationOperation instances to execute
|
|
9
9
|
|
|
10
10
|
The checksum is computed at class definition time from the canonical
|
|
11
11
|
representations of all operations. It is stored in the schema_migration
|
|
@@ -17,7 +17,7 @@ and will cause a hard crash.
|
|
|
17
17
|
import hashlib
|
|
18
18
|
from abc import ABC, abstractmethod
|
|
19
19
|
|
|
20
|
-
from corekit.connections.sql.migration.operations import
|
|
20
|
+
from corekit.connections.sql.migration.operations import MigrationOperation
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class Migration(ABC):
|
|
@@ -26,7 +26,7 @@ class Migration(ABC):
|
|
|
26
26
|
description: str
|
|
27
27
|
|
|
28
28
|
@abstractmethod
|
|
29
|
-
def operations(self) -> list[
|
|
29
|
+
def operations(self) -> list[MigrationOperation]:
|
|
30
30
|
raise NotImplementedError
|
|
31
31
|
|
|
32
32
|
@property
|