python-corekit 0.1.1__py3-none-any.whl → 0.3.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.
Files changed (109) hide show
  1. corekit/api/__init__.py +18 -3
  2. corekit/api/application.py +275 -0
  3. corekit/api/lifespan.py +233 -0
  4. corekit/api/middleware.py +93 -0
  5. corekit/api/routers.py +109 -1
  6. corekit/concurrency/__init__.py +2 -2
  7. corekit/concurrency/decorators.py +32 -5
  8. corekit/concurrency/thread_local.py +2 -2
  9. corekit/concurrency/worker.py +74 -65
  10. corekit/config/loader.py +42 -5
  11. corekit/config/settings.py +11 -1
  12. corekit/connections/__init__.py +7 -1
  13. corekit/connections/connectable.py +45 -4
  14. corekit/connections/redis/connection.py +53 -10
  15. corekit/connections/sql/__init__.py +33 -4
  16. corekit/connections/sql/connection.py +56 -3
  17. corekit/connections/sql/fields/__init__.py +2 -2
  18. corekit/connections/sql/fields/jsonb.py +13 -6
  19. corekit/connections/sql/migration/__init__.py +9 -5
  20. corekit/connections/sql/migration/base.py +3 -3
  21. corekit/connections/sql/migration/operations.py +135 -44
  22. corekit/connections/sql/migration/registry.py +2 -2
  23. corekit/connections/sql/operations/__init__.py +24 -0
  24. corekit/connections/sql/operations/base.py +111 -0
  25. corekit/connections/sql/operations/statements.py +170 -0
  26. corekit/connections/sql/query.py +4 -62
  27. corekit/connections/sql/table.py +33 -29
  28. corekit/crypto/__init__.py +3 -1
  29. corekit/crypto/constants.py +2 -2
  30. corekit/crypto/hasher.py +9 -4
  31. corekit/data/__init__.py +8 -0
  32. corekit/data/dataset.py +8 -2
  33. corekit/data/expressions/__init__.py +10 -2
  34. corekit/data/expressions/comparison.py +142 -123
  35. corekit/data/expressions/expression.py +71 -98
  36. corekit/data/expressions/operator.py +39 -0
  37. corekit/data/expressions/target.py +21 -0
  38. corekit/data/record.py +147 -147
  39. corekit/data/stats.py +162 -157
  40. corekit/decorators/__init__.py +2 -2
  41. corekit/decorators/exception_handling.py +38 -9
  42. corekit/docker/watchdog.py +50 -31
  43. corekit/etl/__init__.py +2 -1
  44. corekit/etl/connection.py +46 -44
  45. corekit/etl/extract/extractor.py +6 -13
  46. corekit/etl/orchestrator.py +19 -2
  47. corekit/etl/schemas.py +2 -2
  48. corekit/etl/transform/transformer.py +4 -1
  49. corekit/events/publisher.py +1 -1
  50. corekit/events/reader.py +26 -21
  51. corekit/events/sse.py +4 -1
  52. corekit/events/websocket.py +27 -13
  53. corekit/exceptions/__init__.py +33 -0
  54. corekit/exceptions/base.py +139 -10
  55. corekit/exceptions/enum.py +17 -0
  56. corekit/exceptions/types.py +6 -6
  57. corekit/files/__init__.py +2 -4
  58. corekit/files/base.py +15 -2
  59. corekit/files/enum.py +0 -5
  60. corekit/files/json.py +16 -2
  61. corekit/http/__init__.py +51 -0
  62. corekit/http/api.py +24 -0
  63. corekit/http/client.py +100 -73
  64. corekit/http/exceptions.py +140 -0
  65. corekit/http/response.py +50 -1
  66. corekit/http/status.py +89 -0
  67. corekit/jobs/__init__.py +26 -0
  68. corekit/jobs/registry.py +87 -0
  69. corekit/jobs/runner.py +80 -0
  70. corekit/jobs/task.py +173 -0
  71. corekit/log_monitor/models.py +8 -2
  72. corekit/log_monitor/service.py +77 -38
  73. corekit/notifications/base.py +18 -10
  74. corekit/observability/__init__.py +12 -3
  75. corekit/observability/benchmarkable.py +23 -5
  76. corekit/observability/loggable.py +21 -0
  77. corekit/observability/request_context.py +188 -0
  78. corekit/observability/timing/timer.py +4 -2
  79. corekit/registry/__init__.py +12 -7
  80. corekit/registry/ordered.py +86 -0
  81. corekit/registry/registry.py +55 -14
  82. corekit/schemas/__init__.py +10 -0
  83. corekit/schemas/enum.py +70 -49
  84. corekit/schemas/models/arbitrary.py +11 -11
  85. corekit/schemas/pydantic/fields.py +35 -35
  86. corekit/schemas/types.py +45 -40
  87. corekit/serialization/__init__.py +24 -0
  88. corekit/serialization/pickle_file.py +61 -0
  89. corekit/serialization/serializable.py +22 -2
  90. corekit/serialization/serializer.py +10 -3
  91. corekit/utils/__init__.py +59 -5
  92. corekit/utils/coercion.py +118 -0
  93. corekit/utils/collections.py +124 -0
  94. corekit/utils/ids.py +61 -5
  95. corekit/utils/payload.py +112 -0
  96. corekit/utils/raise_exc.py +8 -8
  97. corekit/utils/text.py +56 -0
  98. corekit/utils/time.py +74 -21
  99. corekit/utils/validators.py +15 -15
  100. corekit/utils/void.py +8 -8
  101. {python_corekit-0.1.1.dist-info → python_corekit-0.3.0.dist-info}/METADATA +103 -97
  102. python_corekit-0.3.0.dist-info/RECORD +145 -0
  103. corekit/constants.py +0 -45
  104. corekit/exceptions/http/exceptions.py +0 -37
  105. corekit/files/pickle.py +0 -12
  106. python_corekit-0.1.1.dist-info/RECORD +0 -125
  107. {python_corekit-0.1.1.dist-info → python_corekit-0.3.0.dist-info}/WHEEL +0 -0
  108. {python_corekit-0.1.1.dist-info → python_corekit-0.3.0.dist-info}/licenses/LICENSE +0 -0
  109. {python_corekit-0.1.1.dist-info → python_corekit-0.3.0.dist-info}/top_level.txt +0 -0
@@ -23,6 +23,7 @@ from redis import asyncio as aioredis
23
23
 
24
24
  from corekit.config import get_settings
25
25
  from corekit.connections import Connectable
26
+ from corekit.exceptions import InternalCoreException, Retryability
26
27
 
27
28
  __all__ = ["RedisConnection", "RedisNotConnectedError"]
28
29
 
@@ -31,11 +32,14 @@ logger = logging.getLogger(__name__)
31
32
  DEFAULT_URL = "redis://localhost:6379/0"
32
33
 
33
34
 
34
- class RedisNotConnectedError(RuntimeError):
35
+ class RedisNotConnectedError(InternalCoreException):
35
36
  """
36
37
  Raised when a client is used before its connection is opened.
37
38
  """
38
39
 
40
+ def __init__(self, message: str, *, error: str | None = None) -> None:
41
+ super().__init__(message, retryable=Retryability.NON_RETRYABLE, error=error)
42
+
39
43
 
40
44
  def _encode(value: Any) -> str:
41
45
  """
@@ -65,14 +69,15 @@ class RedisConnection(Connectable):
65
69
  """
66
70
  A Connectable wrapper over a Redis client.
67
71
 
68
- The sync client is shared per URL for the life of the process, because a
72
+ The sync client is shared per URL for the life of its holders, because a
69
73
  Redis client is a connection pool and building one per caller defeats it.
70
- ``_disconnect`` is therefore a no-op: one caller finishing must not close a
71
- pool others are still using. Async clients are per-instance, since they are
72
- bound to a running event loop.
74
+ Each ``connect`` takes a hold; ``disconnect`` releases it and closes the
75
+ pool only when no holder remains. Async clients are per-instance, since they
76
+ are bound to a running event loop.
73
77
  """
74
78
 
75
79
  _shared_clients: dict[str, Any] = {}
80
+ _shared_refs: dict[str, int] = {}
76
81
 
77
82
  def __init__(self, url: str | None = None, safe: bool = False, **kwargs: Any) -> None:
78
83
  """
@@ -83,6 +88,7 @@ class RedisConnection(Connectable):
83
88
  self._url = url or get_settings().redis.url or DEFAULT_URL
84
89
  self._safe = safe
85
90
  self._async_client: Any = None
91
+ self._holds_shared = False
86
92
 
87
93
  @property
88
94
  def url(self) -> str:
@@ -93,7 +99,7 @@ class RedisConnection(Connectable):
93
99
 
94
100
  @property
95
101
  def is_connected(self) -> bool:
96
- return RedisConnection._shared_clients.get(self._url) is not None
102
+ return self._holds_shared
97
103
 
98
104
  @property
99
105
  def is_async_connected(self) -> bool:
@@ -102,8 +108,10 @@ class RedisConnection(Connectable):
102
108
  @property
103
109
  def client(self) -> Any:
104
110
  """
105
- The sync client, raising if the connection was never opened.
111
+ The sync client, raising if this instance has not connected.
106
112
  """
113
+ if not self._holds_shared:
114
+ raise RedisNotConnectedError("RedisConnection is not connected. Call connect() or use a with block.")
107
115
  existing = RedisConnection._shared_clients.get(self._url)
108
116
  if existing is None:
109
117
  raise RedisNotConnectedError("RedisConnection is not connected. Call connect() or use a with block.")
@@ -135,11 +143,29 @@ class RedisConnection(Connectable):
135
143
  if RedisConnection._shared_clients.get(self._url) is None:
136
144
  self.info(f"Creating shared Redis client for {self._url}")
137
145
  RedisConnection._shared_clients[self._url] = self._build(is_async=False)
146
+ RedisConnection._shared_refs[self._url] = 0
147
+ if not self._holds_shared:
148
+ RedisConnection._shared_refs[self._url] = RedisConnection._shared_refs.get(self._url, 0) + 1
149
+ self._holds_shared = True
138
150
 
139
151
  def _disconnect(self) -> None:
140
152
  """
141
- Deliberately a no-op: the sync client is a process-wide pool.
153
+ Release this instance's hold on the shared pool; close it when last.
142
154
  """
155
+ if not self._holds_shared:
156
+ return
157
+ self._holds_shared = False
158
+ refs = RedisConnection._shared_refs.get(self._url, 1) - 1
159
+ if refs <= 0:
160
+ client = RedisConnection._shared_clients.pop(self._url, None)
161
+ RedisConnection._shared_refs.pop(self._url, None)
162
+ if client is not None:
163
+ try:
164
+ client.close()
165
+ except Exception as exc:
166
+ self.debug(f"Error closing Redis client for {self._url}: {exc}")
167
+ else:
168
+ RedisConnection._shared_refs[self._url] = refs
143
169
 
144
170
  async def _async_connect(self) -> None:
145
171
  self._async_client = self._build(is_async=True)
@@ -166,6 +192,7 @@ class RedisConnection(Connectable):
166
192
  except Exception as exc:
167
193
  logger.debug("Error closing Redis client for %s: %s", url, exc)
168
194
  del cls._shared_clients[url]
195
+ cls._shared_refs.clear()
169
196
 
170
197
  # ------------------------------------------------------------------
171
198
  # Sync API
@@ -175,9 +202,11 @@ class RedisConnection(Connectable):
175
202
  return _decode(self.client.get(key))
176
203
 
177
204
  def set(self, key: str, value: Any, **kwargs: Any) -> None:
205
+ self.require_writable()
178
206
  self.client.set(key, _encode(value), **kwargs)
179
207
 
180
208
  def delete(self, *keys: str) -> None:
209
+ self.require_writable()
181
210
  self.client.delete(*keys)
182
211
 
183
212
  def ping(self) -> bool:
@@ -187,15 +216,18 @@ class RedisConnection(Connectable):
187
216
  return bool(self.client.exists(key))
188
217
 
189
218
  def expire(self, key: str, seconds: int) -> None:
219
+ self.require_writable()
190
220
  self.client.expire(key, seconds)
191
221
 
192
222
  def ttl(self, key: str) -> int:
193
223
  return self.client.ttl(key)
194
224
 
195
225
  def incr(self, key: str, amount: int = 1) -> int:
226
+ self.require_writable()
196
227
  return self.client.incr(key, amount)
197
228
 
198
229
  def decr(self, key: str, amount: int = 1) -> int:
230
+ self.require_writable()
199
231
  return self.client.decr(key, amount)
200
232
 
201
233
  def keys(self, pattern: str = "*") -> list[Any]:
@@ -205,13 +237,19 @@ class RedisConnection(Connectable):
205
237
  return _decode(self.client.hget(key, field))
206
238
 
207
239
  def hset(self, key: str, field: str, value: Any) -> None:
240
+ self.require_writable()
208
241
  self.client.hset(key, field, _encode(value))
209
242
 
210
243
  def hdel(self, key: str, *fields: str) -> None:
244
+ self.require_writable()
211
245
  self.client.hdel(key, *fields)
212
246
 
213
247
  def publish(self, channel: str, message: Any) -> int:
214
- return self.client.publish(channel, _encode(message))
248
+ self.require_writable()
249
+ # Pre-serialized pub/sub payloads (JSON strings, bytes) pass through;
250
+ # structured values are encoded the same way as cache writes.
251
+ payload = message if isinstance(message, (str, bytes, bytearray)) else _encode(message)
252
+ return self.client.publish(channel, payload)
215
253
 
216
254
  def pubsub(self) -> Any:
217
255
  return self.client.pubsub()
@@ -224,16 +262,21 @@ class RedisConnection(Connectable):
224
262
  return _decode(await self.async_client.get(key))
225
263
 
226
264
  async def aset(self, key: str, value: Any, **kwargs: Any) -> None:
265
+ self.require_writable()
227
266
  await self.async_client.set(key, _encode(value), **kwargs)
228
267
 
229
268
  async def asetex(self, key: str, seconds: int, value: Any) -> None:
269
+ self.require_writable()
230
270
  await self.async_client.setex(key, seconds, _encode(value))
231
271
 
232
272
  async def adelete(self, *keys: str) -> None:
273
+ self.require_writable()
233
274
  await self.async_client.delete(*keys)
234
275
 
235
276
  async def akeys(self, pattern: str = "*") -> list[Any]:
236
277
  return await self.async_client.keys(pattern)
237
278
 
238
279
  async def apublish(self, channel: str, message: Any) -> int:
239
- return await self.async_client.publish(channel, _encode(message))
280
+ self.require_writable()
281
+ payload = message if isinstance(message, (str, bytes, bytearray)) else _encode(message)
282
+ return await self.async_client.publish(channel, payload)
@@ -1,10 +1,39 @@
1
1
  """
2
- SQL building blocks: a Connectable session, a query builder and a base table.
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
- from corekit.connections.sql.fields import JSONBField, PydanticJSON
6
+ from corekit.connections.sql.fields import JSONBField, JsonModelField, 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__ = ["JSONBField", "NamedTable", "PaginatedResult", "PydanticJSON", "Query", "SQLConnection"]
21
+ __all__ = [
22
+ "Conditional",
23
+ "DdlOperation",
24
+ "Delete",
25
+ "DmlOperation",
26
+ "DqlOperation",
27
+ "Insert",
28
+ "JsonModelField",
29
+ "JSONBField",
30
+ "NamedTable",
31
+ "Operation",
32
+ "PaginatedResult",
33
+ "PydanticJSON",
34
+ "Query",
35
+ "Select",
36
+ "SQLConnection",
37
+ "TableExportItem",
38
+ "Update",
39
+ ]
@@ -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 DqlOperation, Operation, Select
27
28
  from corekit.connections.sql.query import Query
28
29
  from corekit.connections.sql.table import NamedTable
29
30
 
@@ -203,12 +204,14 @@ class SQLConnection(Connectable):
203
204
  """
204
205
  Create any tables present in SQLModel metadata but missing from the database.
205
206
  """
207
+ self.require_writable()
206
208
  SQLModel.metadata.create_all(_get_engine(self._connection_url))
207
209
 
208
210
  def exec_ddl(self, sql: str) -> None:
209
211
  """
210
212
  Run a raw DDL statement. Intended for migrations, not application code.
211
213
  """
214
+ self.require_writable()
212
215
  self._start_session()
213
216
  self.session.exec(text(sql))
214
217
  self.session.commit()
@@ -221,6 +224,7 @@ class SQLConnection(Connectable):
221
224
  """
222
225
  Insert new rows.
223
226
  """
227
+ self.require_writable()
224
228
  self._start_session()
225
229
  self.session.add_all(items)
226
230
  self.session.commit()
@@ -229,6 +233,7 @@ class SQLConnection(Connectable):
229
233
  """
230
234
  Insert or update rows. Slower than insert, but tolerates existing ids.
231
235
  """
236
+ self.require_writable()
232
237
  self._start_session()
233
238
  for item in items:
234
239
  self.session.merge(item)
@@ -238,6 +243,7 @@ class SQLConnection(Connectable):
238
243
  """
239
244
  Delete a row by id.
240
245
  """
246
+ self.require_writable()
241
247
  item = self.fetch_one_by_id(table, identifier)
242
248
  if item is None:
243
249
  return
@@ -295,8 +301,12 @@ class SQLConnection(Connectable):
295
301
  def fetch_one_by_condition(self, table: type[NamedTable], key: Any, value: Any) -> NamedTable | None:
296
302
  """
297
303
  The first row where a column equals a value.
304
+
305
+ ``key`` may be a column attribute (``Person.city``) or a column name
306
+ string resolved against ``table``.
298
307
  """
299
- return self.first(select(table).where(key == value))
308
+ column = getattr(table, key) if isinstance(key, str) else key
309
+ return self.first(select(table).where(column == value))
300
310
 
301
311
  def exists(self, table: type[NamedTable], identifier: str) -> bool:
302
312
  """
@@ -320,6 +330,36 @@ class SQLConnection(Connectable):
320
330
  """
321
331
  return list(self._exec(query.build()))
322
332
 
333
+ def delete_many_by_query(self, query: Query) -> None:
334
+ """
335
+ Delete every row matching a Query.
336
+ """
337
+ self.require_writable()
338
+ self._start_session()
339
+ for item in self.fetch_many_by_query(query):
340
+ self.session.delete(item)
341
+ self.session.commit()
342
+
343
+ def execute(self, operation: Operation) -> Any:
344
+ """
345
+ Run an operation, committing if it writes.
346
+
347
+ A read returns its rows; a write returns the result, whose
348
+ ``rowcount`` reports how many rows it touched.
349
+ """
350
+ if operation.writes:
351
+ self.require_writable()
352
+ result = self._exec(operation.build())
353
+ if operation.writes:
354
+ self.session.commit()
355
+ return result
356
+
357
+ def fetch(self, operation: DqlOperation) -> list[NamedTable]:
358
+ """
359
+ Every row an operation selects.
360
+ """
361
+ return list(self.execute(operation))
362
+
323
363
  def fetch_one_by_query(self, query: Query) -> NamedTable | None:
324
364
  """
325
365
  The first row matching a Query.
@@ -333,10 +373,23 @@ class SQLConnection(Connectable):
333
373
  self._start_session()
334
374
  return self.session.exec(select(func.count()).select_from(table)).one()
335
375
 
336
- def paginate(self, table: type[NamedTable], offset: int = 0, limit: int = 50) -> PaginatedResult:
376
+ def paginate(
377
+ self,
378
+ table: type[NamedTable],
379
+ offset: int = 0,
380
+ limit: int = 50,
381
+ *,
382
+ order_by: Any = None,
383
+ desc: bool = False,
384
+ ) -> PaginatedResult:
337
385
  """
338
386
  A page of rows, together with the total row count.
387
+
388
+ Pages are ordered: pass ``order_by`` for a column, or omit it to order
389
+ by ``table.id`` so offsets are stable.
339
390
  """
340
391
  total = self.count(table)
341
- rows = list(self._exec(select(table).offset(offset).limit(limit)))
392
+ query = Select(table=table).offset(offset).limit(limit)
393
+ query.order_by(order_by if order_by is not None else table.id, desc=desc)
394
+ rows = self.fetch(query)
342
395
  return PaginatedResult(rows=rows, total=total)
@@ -2,6 +2,6 @@
2
2
  Custom SQL column types.
3
3
  """
4
4
 
5
- from corekit.connections.sql.fields.jsonb import JSONBField, PydanticJSON
5
+ from corekit.connections.sql.fields.jsonb import JSONBField, JsonModelField, PydanticJSON
6
6
 
7
- __all__ = ["JSONBField", "PydanticJSON"]
7
+ __all__ = ["JsonModelField", "JSONBField", "PydanticJSON"]
@@ -1,18 +1,21 @@
1
1
  """
2
2
  Columns that store a Pydantic model as JSON.
3
3
 
4
- ``JSONBField`` round-trips a model through a JSON column, so a table can hold
5
- structured data without a separate table and without hand-written encoding at
6
- every call site::
4
+ ``JsonModelField`` round-trips a model through a JSON column, so a table can
5
+ hold structured data without a separate table and without hand-written encoding
6
+ at every call site::
7
7
 
8
8
  class Profile(BaseModel):
9
9
  theme: str = "dark"
10
10
 
11
11
  class User(NamedTable, table=True):
12
12
  id: str = Field(primary_key=True)
13
- profile: Profile = JSONBField(Profile)
13
+ profile: Profile = JsonModelField(Profile)
14
14
 
15
15
  Reading ``user.profile`` gives a ``Profile``, not a dict.
16
+
17
+ The SQLAlchemy ``impl`` is ``JSON``, not PostgreSQL ``JSONB``. The historical
18
+ ``JSONBField`` name is kept as an alias.
16
19
  """
17
20
 
18
21
  from typing import Any
@@ -22,7 +25,7 @@ from sqlalchemy import Column
22
25
  from sqlalchemy.types import JSON, TypeDecorator
23
26
  from sqlmodel import Field
24
27
 
25
- __all__ = ["JSONBField", "PydanticJSON"]
28
+ __all__ = ["JsonModelField", "JSONBField", "PydanticJSON"]
26
29
 
27
30
 
28
31
  class PydanticJSON(TypeDecorator):
@@ -60,8 +63,12 @@ class PydanticJSON(TypeDecorator):
60
63
  return self.pydantic_model(**value)
61
64
 
62
65
 
63
- def JSONBField(pydantic_model: type[BaseModel], **kwargs: Any) -> Any: # noqa: N802 - reads as a field constructor
66
+ def JsonModelField(pydantic_model: type[BaseModel], **kwargs: Any) -> Any: # noqa: N802 - reads as a field constructor
64
67
  """
65
68
  Declare a column holding ``pydantic_model`` as JSON.
66
69
  """
67
70
  return Field(sa_column=Column(PydanticJSON(pydantic_model)), **kwargs)
71
+
72
+
73
+ #: Historical name. The column type is SQLAlchemy ``JSON``, not PostgreSQL JSONB.
74
+ JSONBField = JsonModelField
@@ -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[Operation]:
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::
@@ -24,13 +24,15 @@ from corekit.connections.sql.migration.base import Migration
24
24
  from corekit.connections.sql.migration.operations import (
25
25
  AddColumn,
26
26
  AlterColumnType,
27
+ ColumnSpec,
27
28
  CreateIndex,
28
- DataOperation,
29
+ CreateTable,
30
+ DataMigrationOperation,
29
31
  DropColumn,
30
32
  DropColumnDefault,
31
33
  DropIndex,
32
34
  DropTable,
33
- Operation,
35
+ MigrationOperation,
34
36
  RenameColumn,
35
37
  RenameTable,
36
38
  SetColumnDefault,
@@ -41,8 +43,10 @@ from corekit.connections.sql.migration.table import SchemaMigration
41
43
  __all__ = [
42
44
  "AddColumn",
43
45
  "AlterColumnType",
46
+ "ColumnSpec",
44
47
  "CreateIndex",
45
- "DataOperation",
48
+ "CreateTable",
49
+ "DataMigrationOperation",
46
50
  "DropColumn",
47
51
  "DropColumnDefault",
48
52
  "DropIndex",
@@ -50,7 +54,7 @@ __all__ = [
50
54
  "Migration",
51
55
  "MigrationChecksumError",
52
56
  "MigrationRegistry",
53
- "Operation",
57
+ "MigrationOperation",
54
58
  "RenameColumn",
55
59
  "RenameTable",
56
60
  "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 Operation instances to execute
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 Operation
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[Operation]:
29
+ def operations(self) -> list[MigrationOperation]:
30
30
  raise NotImplementedError
31
31
 
32
32
  @property