sqlspec 0.16.1__cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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.

Potentially problematic release.


This version of sqlspec might be problematic. Click here for more details.

Files changed (148) hide show
  1. 51ff5a9eadfdefd49f98__mypyc.cpython-311-aarch64-linux-gnu.so +0 -0
  2. sqlspec/__init__.py +92 -0
  3. sqlspec/__main__.py +12 -0
  4. sqlspec/__metadata__.py +14 -0
  5. sqlspec/_serialization.py +77 -0
  6. sqlspec/_sql.py +1780 -0
  7. sqlspec/_typing.py +680 -0
  8. sqlspec/adapters/__init__.py +0 -0
  9. sqlspec/adapters/adbc/__init__.py +5 -0
  10. sqlspec/adapters/adbc/_types.py +12 -0
  11. sqlspec/adapters/adbc/config.py +361 -0
  12. sqlspec/adapters/adbc/driver.py +512 -0
  13. sqlspec/adapters/aiosqlite/__init__.py +19 -0
  14. sqlspec/adapters/aiosqlite/_types.py +13 -0
  15. sqlspec/adapters/aiosqlite/config.py +253 -0
  16. sqlspec/adapters/aiosqlite/driver.py +248 -0
  17. sqlspec/adapters/asyncmy/__init__.py +19 -0
  18. sqlspec/adapters/asyncmy/_types.py +12 -0
  19. sqlspec/adapters/asyncmy/config.py +180 -0
  20. sqlspec/adapters/asyncmy/driver.py +274 -0
  21. sqlspec/adapters/asyncpg/__init__.py +21 -0
  22. sqlspec/adapters/asyncpg/_types.py +17 -0
  23. sqlspec/adapters/asyncpg/config.py +229 -0
  24. sqlspec/adapters/asyncpg/driver.py +344 -0
  25. sqlspec/adapters/bigquery/__init__.py +18 -0
  26. sqlspec/adapters/bigquery/_types.py +12 -0
  27. sqlspec/adapters/bigquery/config.py +298 -0
  28. sqlspec/adapters/bigquery/driver.py +558 -0
  29. sqlspec/adapters/duckdb/__init__.py +22 -0
  30. sqlspec/adapters/duckdb/_types.py +12 -0
  31. sqlspec/adapters/duckdb/config.py +504 -0
  32. sqlspec/adapters/duckdb/driver.py +368 -0
  33. sqlspec/adapters/oracledb/__init__.py +32 -0
  34. sqlspec/adapters/oracledb/_types.py +14 -0
  35. sqlspec/adapters/oracledb/config.py +317 -0
  36. sqlspec/adapters/oracledb/driver.py +538 -0
  37. sqlspec/adapters/psqlpy/__init__.py +16 -0
  38. sqlspec/adapters/psqlpy/_types.py +11 -0
  39. sqlspec/adapters/psqlpy/config.py +214 -0
  40. sqlspec/adapters/psqlpy/driver.py +530 -0
  41. sqlspec/adapters/psycopg/__init__.py +32 -0
  42. sqlspec/adapters/psycopg/_types.py +17 -0
  43. sqlspec/adapters/psycopg/config.py +426 -0
  44. sqlspec/adapters/psycopg/driver.py +796 -0
  45. sqlspec/adapters/sqlite/__init__.py +15 -0
  46. sqlspec/adapters/sqlite/_types.py +11 -0
  47. sqlspec/adapters/sqlite/config.py +240 -0
  48. sqlspec/adapters/sqlite/driver.py +294 -0
  49. sqlspec/base.py +571 -0
  50. sqlspec/builder/__init__.py +62 -0
  51. sqlspec/builder/_base.py +473 -0
  52. sqlspec/builder/_column.py +320 -0
  53. sqlspec/builder/_ddl.py +1346 -0
  54. sqlspec/builder/_ddl_utils.py +103 -0
  55. sqlspec/builder/_delete.py +76 -0
  56. sqlspec/builder/_insert.py +256 -0
  57. sqlspec/builder/_merge.py +71 -0
  58. sqlspec/builder/_parsing_utils.py +140 -0
  59. sqlspec/builder/_select.py +170 -0
  60. sqlspec/builder/_update.py +188 -0
  61. sqlspec/builder/mixins/__init__.py +55 -0
  62. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  63. sqlspec/builder/mixins/_delete_operations.py +41 -0
  64. sqlspec/builder/mixins/_insert_operations.py +244 -0
  65. sqlspec/builder/mixins/_join_operations.py +122 -0
  66. sqlspec/builder/mixins/_merge_operations.py +476 -0
  67. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  68. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  69. sqlspec/builder/mixins/_select_operations.py +603 -0
  70. sqlspec/builder/mixins/_update_operations.py +187 -0
  71. sqlspec/builder/mixins/_where_clause.py +621 -0
  72. sqlspec/cli.py +247 -0
  73. sqlspec/config.py +395 -0
  74. sqlspec/core/__init__.py +63 -0
  75. sqlspec/core/cache.cpython-311-aarch64-linux-gnu.so +0 -0
  76. sqlspec/core/cache.py +871 -0
  77. sqlspec/core/compiler.cpython-311-aarch64-linux-gnu.so +0 -0
  78. sqlspec/core/compiler.py +417 -0
  79. sqlspec/core/filters.cpython-311-aarch64-linux-gnu.so +0 -0
  80. sqlspec/core/filters.py +830 -0
  81. sqlspec/core/hashing.cpython-311-aarch64-linux-gnu.so +0 -0
  82. sqlspec/core/hashing.py +310 -0
  83. sqlspec/core/parameters.cpython-311-aarch64-linux-gnu.so +0 -0
  84. sqlspec/core/parameters.py +1237 -0
  85. sqlspec/core/result.cpython-311-aarch64-linux-gnu.so +0 -0
  86. sqlspec/core/result.py +677 -0
  87. sqlspec/core/splitter.cpython-311-aarch64-linux-gnu.so +0 -0
  88. sqlspec/core/splitter.py +819 -0
  89. sqlspec/core/statement.cpython-311-aarch64-linux-gnu.so +0 -0
  90. sqlspec/core/statement.py +676 -0
  91. sqlspec/driver/__init__.py +19 -0
  92. sqlspec/driver/_async.py +502 -0
  93. sqlspec/driver/_common.py +631 -0
  94. sqlspec/driver/_sync.py +503 -0
  95. sqlspec/driver/mixins/__init__.py +6 -0
  96. sqlspec/driver/mixins/_result_tools.py +193 -0
  97. sqlspec/driver/mixins/_sql_translator.py +86 -0
  98. sqlspec/exceptions.py +193 -0
  99. sqlspec/extensions/__init__.py +0 -0
  100. sqlspec/extensions/aiosql/__init__.py +10 -0
  101. sqlspec/extensions/aiosql/adapter.py +461 -0
  102. sqlspec/extensions/litestar/__init__.py +6 -0
  103. sqlspec/extensions/litestar/_utils.py +52 -0
  104. sqlspec/extensions/litestar/cli.py +48 -0
  105. sqlspec/extensions/litestar/config.py +92 -0
  106. sqlspec/extensions/litestar/handlers.py +260 -0
  107. sqlspec/extensions/litestar/plugin.py +145 -0
  108. sqlspec/extensions/litestar/providers.py +454 -0
  109. sqlspec/loader.cpython-311-aarch64-linux-gnu.so +0 -0
  110. sqlspec/loader.py +760 -0
  111. sqlspec/migrations/__init__.py +35 -0
  112. sqlspec/migrations/base.py +414 -0
  113. sqlspec/migrations/commands.py +443 -0
  114. sqlspec/migrations/loaders.py +402 -0
  115. sqlspec/migrations/runner.py +213 -0
  116. sqlspec/migrations/tracker.py +140 -0
  117. sqlspec/migrations/utils.py +129 -0
  118. sqlspec/protocols.py +407 -0
  119. sqlspec/py.typed +0 -0
  120. sqlspec/storage/__init__.py +23 -0
  121. sqlspec/storage/backends/__init__.py +0 -0
  122. sqlspec/storage/backends/base.py +163 -0
  123. sqlspec/storage/backends/fsspec.py +386 -0
  124. sqlspec/storage/backends/obstore.py +459 -0
  125. sqlspec/storage/capabilities.py +102 -0
  126. sqlspec/storage/registry.py +239 -0
  127. sqlspec/typing.py +299 -0
  128. sqlspec/utils/__init__.py +3 -0
  129. sqlspec/utils/correlation.py +150 -0
  130. sqlspec/utils/deprecation.py +106 -0
  131. sqlspec/utils/fixtures.cpython-311-aarch64-linux-gnu.so +0 -0
  132. sqlspec/utils/fixtures.py +58 -0
  133. sqlspec/utils/logging.py +127 -0
  134. sqlspec/utils/module_loader.py +89 -0
  135. sqlspec/utils/serializers.py +4 -0
  136. sqlspec/utils/singleton.py +32 -0
  137. sqlspec/utils/sync_tools.cpython-311-aarch64-linux-gnu.so +0 -0
  138. sqlspec/utils/sync_tools.py +237 -0
  139. sqlspec/utils/text.cpython-311-aarch64-linux-gnu.so +0 -0
  140. sqlspec/utils/text.py +96 -0
  141. sqlspec/utils/type_guards.cpython-311-aarch64-linux-gnu.so +0 -0
  142. sqlspec/utils/type_guards.py +1139 -0
  143. sqlspec-0.16.1.dist-info/METADATA +365 -0
  144. sqlspec-0.16.1.dist-info/RECORD +148 -0
  145. sqlspec-0.16.1.dist-info/WHEEL +7 -0
  146. sqlspec-0.16.1.dist-info/entry_points.txt +2 -0
  147. sqlspec-0.16.1.dist-info/licenses/LICENSE +21 -0
  148. sqlspec-0.16.1.dist-info/licenses/NOTICE +29 -0
@@ -0,0 +1,402 @@
1
+ """Migration loader abstractions for SQLSpec."""
2
+
3
+ import abc
4
+ import inspect
5
+ import sys
6
+ import types
7
+ from collections.abc import Iterator
8
+ from contextlib import contextmanager
9
+ from pathlib import Path
10
+ from typing import Any, Final, Optional
11
+
12
+ from sqlspec.loader import SQLFileLoader as CoreSQLFileLoader
13
+
14
+ __all__ = ("BaseMigrationLoader", "MigrationLoadError", "PythonFileLoader", "SQLFileLoader", "get_migration_loader")
15
+
16
+ PROJECT_ROOT_MARKERS: Final[list[str]] = ["pyproject.toml", ".git", "setup.cfg", "setup.py"]
17
+
18
+
19
+ class MigrationLoadError(Exception):
20
+ """Exception raised when migration loading fails."""
21
+
22
+
23
+ class BaseMigrationLoader(abc.ABC):
24
+ """Abstract base class for migration loaders."""
25
+
26
+ __slots__ = ()
27
+
28
+ @abc.abstractmethod
29
+ async def get_up_sql(self, path: Path) -> list[str]:
30
+ """Load and return the 'up' SQL statements from a migration file.
31
+
32
+ Args:
33
+ path: Path to the migration file.
34
+
35
+ Returns:
36
+ List of SQL statements to execute for upgrade.
37
+
38
+ Raises:
39
+ MigrationLoadError: If loading fails.
40
+ """
41
+ ...
42
+
43
+ @abc.abstractmethod
44
+ async def get_down_sql(self, path: Path) -> list[str]:
45
+ """Load and return the 'down' SQL statements from a migration file.
46
+
47
+ Args:
48
+ path: Path to the migration file.
49
+
50
+ Returns:
51
+ List of SQL statements to execute for downgrade.
52
+ Empty list if no downgrade is available.
53
+
54
+ Raises:
55
+ MigrationLoadError: If loading fails.
56
+ """
57
+ ...
58
+
59
+ @abc.abstractmethod
60
+ def validate_migration_file(self, path: Path) -> None:
61
+ """Validate that the migration file has required components.
62
+
63
+ Args:
64
+ path: Path to the migration file.
65
+
66
+ Raises:
67
+ MigrationLoadError: If validation fails.
68
+ """
69
+ ...
70
+
71
+
72
+ class SQLFileLoader(BaseMigrationLoader):
73
+ """Loader for SQL migration files using SQLFileLoader."""
74
+
75
+ __slots__ = ("sql_loader",)
76
+
77
+ def __init__(self) -> None:
78
+ """Initialize SQL file loader."""
79
+ self.sql_loader: CoreSQLFileLoader = CoreSQLFileLoader()
80
+
81
+ async def get_up_sql(self, path: Path) -> list[str]:
82
+ """Extract the 'up' SQL from a SQL migration file.
83
+
84
+ Args:
85
+ path: Path to SQL migration file.
86
+
87
+ Returns:
88
+ List containing single SQL statement for upgrade.
89
+
90
+ Raises:
91
+ MigrationLoadError: If migration file is invalid or missing up query.
92
+ """
93
+ self.sql_loader.clear_cache()
94
+ self.sql_loader.load_sql(path)
95
+
96
+ version = self._extract_version(path.name)
97
+ up_query = f"migrate-{version}-up"
98
+
99
+ if not self.sql_loader.has_query(up_query):
100
+ msg = f"Migration {path} missing 'up' query: {up_query}"
101
+ raise MigrationLoadError(msg)
102
+
103
+ sql_obj = self.sql_loader.get_sql(up_query)
104
+ return [sql_obj.sql]
105
+
106
+ async def get_down_sql(self, path: Path) -> list[str]:
107
+ """Extract the 'down' SQL from a SQL migration file.
108
+
109
+ Args:
110
+ path: Path to SQL migration file.
111
+
112
+ Returns:
113
+ List containing single SQL statement for downgrade, or empty list.
114
+ """
115
+ self.sql_loader.clear_cache()
116
+ self.sql_loader.load_sql(path)
117
+
118
+ version = self._extract_version(path.name)
119
+ down_query = f"migrate-{version}-down"
120
+
121
+ if not self.sql_loader.has_query(down_query):
122
+ return []
123
+
124
+ sql_obj = self.sql_loader.get_sql(down_query)
125
+ return [sql_obj.sql]
126
+
127
+ def validate_migration_file(self, path: Path) -> None:
128
+ """Validate SQL migration file has required up query.
129
+
130
+ Args:
131
+ path: Path to SQL migration file.
132
+
133
+ Raises:
134
+ MigrationLoadError: If file is invalid or missing required query.
135
+ """
136
+ version = self._extract_version(path.name)
137
+ if not version:
138
+ msg = f"Invalid migration filename: {path.name}"
139
+ raise MigrationLoadError(msg)
140
+
141
+ self.sql_loader.clear_cache()
142
+ self.sql_loader.load_sql(path)
143
+ up_query = f"migrate-{version}-up"
144
+ if not self.sql_loader.has_query(up_query):
145
+ msg = f"Migration {path} missing required 'up' query: {up_query}"
146
+ raise MigrationLoadError(msg)
147
+
148
+ def _extract_version(self, filename: str) -> str:
149
+ """Extract version from filename.
150
+
151
+ Args:
152
+ filename: Migration filename to parse.
153
+
154
+ Returns:
155
+ Zero-padded version string or empty string if invalid.
156
+ """
157
+ parts = filename.split("_", 1)
158
+ return parts[0].zfill(4) if parts and parts[0].isdigit() else ""
159
+
160
+
161
+ class PythonFileLoader(BaseMigrationLoader):
162
+ """Loader for Python migration files."""
163
+
164
+ __slots__ = ("migrations_dir", "project_root")
165
+
166
+ def __init__(self, migrations_dir: Path, project_root: "Optional[Path]" = None) -> None:
167
+ """Initialize Python file loader.
168
+
169
+ Args:
170
+ migrations_dir: Directory containing migration files.
171
+ project_root: Optional project root directory for imports.
172
+ """
173
+ self.migrations_dir = migrations_dir
174
+ self.project_root = project_root if project_root is not None else self._find_project_root(migrations_dir)
175
+
176
+ async def get_up_sql(self, path: Path) -> list[str]:
177
+ """Load Python migration and execute upgrade function.
178
+
179
+ Args:
180
+ path: Path to Python migration file.
181
+
182
+ Returns:
183
+ List of SQL statements for upgrade.
184
+
185
+ Raises:
186
+ MigrationLoadError: If function is missing or execution fails.
187
+ """
188
+ with self._temporary_project_path():
189
+ module = self._load_module_from_path(path)
190
+
191
+ upgrade_func = None
192
+ func_name = None
193
+
194
+ if hasattr(module, "up") and callable(module.up):
195
+ upgrade_func = module.up
196
+ func_name = "up"
197
+ elif hasattr(module, "migrate_up") and callable(module.migrate_up):
198
+ upgrade_func = module.migrate_up
199
+ func_name = "migrate_up"
200
+ else:
201
+ msg = f"No upgrade function found in {path}. Expected 'up()' or 'migrate_up()'"
202
+ raise MigrationLoadError(msg)
203
+
204
+ if not callable(upgrade_func):
205
+ msg = f"'{func_name}' is not callable in {path}"
206
+ raise MigrationLoadError(msg)
207
+
208
+ if inspect.iscoroutinefunction(upgrade_func):
209
+ sql_result = await upgrade_func()
210
+ else:
211
+ sql_result = upgrade_func()
212
+
213
+ return self._normalize_and_validate_sql(sql_result, path)
214
+
215
+ async def get_down_sql(self, path: Path) -> list[str]:
216
+ """Load Python migration and execute downgrade function.
217
+
218
+ Args:
219
+ path: Path to Python migration file.
220
+
221
+ Returns:
222
+ List of SQL statements for downgrade, or empty list if not available.
223
+ """
224
+ with self._temporary_project_path():
225
+ module = self._load_module_from_path(path)
226
+
227
+ downgrade_func = None
228
+
229
+ if hasattr(module, "down") and callable(module.down):
230
+ downgrade_func = module.down
231
+ elif hasattr(module, "migrate_down") and callable(module.migrate_down):
232
+ downgrade_func = module.migrate_down
233
+ else:
234
+ return []
235
+
236
+ if not callable(downgrade_func):
237
+ return []
238
+
239
+ if inspect.iscoroutinefunction(downgrade_func):
240
+ sql_result = await downgrade_func()
241
+ else:
242
+ sql_result = downgrade_func()
243
+
244
+ return self._normalize_and_validate_sql(sql_result, path)
245
+
246
+ def validate_migration_file(self, path: Path) -> None:
247
+ """Validate Python migration file has required upgrade function.
248
+
249
+ Args:
250
+ path: Path to Python migration file.
251
+
252
+ Raises:
253
+ MigrationLoadError: If validation fails.
254
+ """
255
+ with self._temporary_project_path():
256
+ module = self._load_module_from_path(path)
257
+
258
+ upgrade_func = None
259
+ func_name = None
260
+
261
+ if hasattr(module, "up") and callable(module.up):
262
+ upgrade_func = module.up
263
+ func_name = "up"
264
+ elif hasattr(module, "migrate_up") and callable(module.migrate_up):
265
+ upgrade_func = module.migrate_up
266
+ func_name = "migrate_up"
267
+ else:
268
+ msg = f"Migration {path} missing required upgrade function. Expected 'up()' or 'migrate_up()'"
269
+ raise MigrationLoadError(msg)
270
+
271
+ if not callable(upgrade_func):
272
+ msg = f"Migration {path} '{func_name}' is not callable"
273
+ raise MigrationLoadError(msg)
274
+
275
+ def _find_project_root(self, start_path: Path) -> Path:
276
+ """Find project root by searching upwards for marker files.
277
+
278
+ Args:
279
+ start_path: Directory to start searching from.
280
+
281
+ Returns:
282
+ Path to project root or parent directory.
283
+ """
284
+ current_path = start_path.resolve()
285
+
286
+ while current_path != current_path.parent:
287
+ for marker in PROJECT_ROOT_MARKERS:
288
+ if (current_path / marker).exists():
289
+ return current_path
290
+ current_path = current_path.parent
291
+
292
+ return start_path.resolve().parent
293
+
294
+ @contextmanager
295
+ def _temporary_project_path(self) -> Iterator[None]:
296
+ """Temporarily add project root to sys.path for imports."""
297
+ path_to_add = str(self.project_root)
298
+ if path_to_add in sys.path:
299
+ yield
300
+ return
301
+
302
+ sys.path.insert(0, path_to_add)
303
+ try:
304
+ yield
305
+ finally:
306
+ sys.path.remove(path_to_add)
307
+
308
+ def _load_module_from_path(self, path: Path) -> Any:
309
+ """Load a Python module from file path.
310
+
311
+ Args:
312
+ path: Path to Python migration file.
313
+
314
+ Returns:
315
+ Loaded module object.
316
+
317
+ Raises:
318
+ MigrationLoadError: If module loading fails.
319
+ """
320
+ module_name = f"sqlspec_migration_{path.stem}"
321
+
322
+ if module_name in sys.modules:
323
+ sys.modules.pop(module_name, None)
324
+
325
+ try:
326
+ source_code = path.read_text(encoding="utf-8")
327
+ compiled_code = compile(source_code, str(path), "exec")
328
+
329
+ module = types.ModuleType(module_name)
330
+ module.__file__ = str(path)
331
+
332
+ sys.modules[module_name] = module
333
+
334
+ exec(compiled_code, module.__dict__) # noqa: S102
335
+
336
+ except Exception as e:
337
+ sys.modules.pop(module_name, None)
338
+ msg = f"Failed to execute migration module {path}: {e}"
339
+ raise MigrationLoadError(msg) from e
340
+
341
+ return module
342
+
343
+ def _normalize_and_validate_sql(self, sql: Any, migration_path: Path) -> list[str]:
344
+ """Validate return type and normalize to list of strings.
345
+
346
+ Args:
347
+ sql: Return value from migration function.
348
+ migration_path: Path to migration file for error messages.
349
+
350
+ Returns:
351
+ List of SQL statements.
352
+
353
+ Raises:
354
+ MigrationLoadError: If return type is invalid.
355
+ """
356
+ if isinstance(sql, str):
357
+ stripped = sql.strip()
358
+ return [stripped] if stripped else []
359
+ if isinstance(sql, list):
360
+ result = []
361
+ for i, item in enumerate(sql):
362
+ if not isinstance(item, str):
363
+ msg = (
364
+ f"Migration {migration_path} returned a list containing a non-string "
365
+ f"element at index {i} (type: {type(item).__name__})."
366
+ )
367
+ raise MigrationLoadError(msg)
368
+ stripped_item = item.strip()
369
+ if stripped_item:
370
+ result.append(stripped_item)
371
+ return result
372
+
373
+ msg = (
374
+ f"Migration {migration_path} must return a 'str' or 'List[str]', but returned type '{type(sql).__name__}'."
375
+ )
376
+ raise MigrationLoadError(msg)
377
+
378
+
379
+ def get_migration_loader(
380
+ file_path: Path, migrations_dir: Path, project_root: "Optional[Path]" = None
381
+ ) -> BaseMigrationLoader:
382
+ """Factory function to get appropriate loader for migration file.
383
+
384
+ Args:
385
+ file_path: Path to the migration file.
386
+ migrations_dir: Directory containing migration files.
387
+ project_root: Optional project root directory for Python imports.
388
+
389
+ Returns:
390
+ Appropriate loader instance for the file type.
391
+
392
+ Raises:
393
+ MigrationLoadError: If file type is not supported.
394
+ """
395
+ suffix = file_path.suffix
396
+
397
+ if suffix == ".py":
398
+ return PythonFileLoader(migrations_dir, project_root)
399
+ if suffix == ".sql":
400
+ return SQLFileLoader()
401
+ msg = f"Unsupported migration file type: {suffix}"
402
+ raise MigrationLoadError(msg)
@@ -0,0 +1,213 @@
1
+ """Migration execution engine for SQLSpec.
2
+
3
+ This module handles migration file loading and execution using SQLFileLoader.
4
+ """
5
+
6
+ import time
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any, Optional
9
+
10
+ from sqlspec.core.statement import SQL
11
+ from sqlspec.migrations.base import BaseMigrationRunner
12
+ from sqlspec.migrations.loaders import get_migration_loader
13
+ from sqlspec.utils.logging import get_logger
14
+ from sqlspec.utils.sync_tools import run_
15
+
16
+ if TYPE_CHECKING:
17
+ from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
18
+
19
+ __all__ = ("AsyncMigrationRunner", "SyncMigrationRunner")
20
+
21
+ logger = get_logger("migrations.runner")
22
+
23
+
24
+ class SyncMigrationRunner(BaseMigrationRunner["SyncDriverAdapterBase"]):
25
+ """Executes migrations using SQLFileLoader."""
26
+
27
+ def get_migration_files(self) -> "list[tuple[str, Path]]":
28
+ """Get all migration files sorted by version.
29
+
30
+ Returns:
31
+ List of (version, path) tuples sorted by version.
32
+ """
33
+ return self._get_migration_files_sync()
34
+
35
+ def load_migration(self, file_path: Path) -> "dict[str, Any]":
36
+ """Load a migration file and extract its components.
37
+
38
+ Args:
39
+ file_path: Path to the migration file.
40
+
41
+ Returns:
42
+ Dictionary containing migration metadata and queries.
43
+ """
44
+ return self._load_migration_metadata(file_path)
45
+
46
+ def execute_upgrade(
47
+ self, driver: "SyncDriverAdapterBase", migration: "dict[str, Any]"
48
+ ) -> "tuple[Optional[str], int]":
49
+ """Execute an upgrade migration.
50
+
51
+ Args:
52
+ driver: The database driver to use.
53
+ migration: Migration metadata dictionary.
54
+
55
+ Returns:
56
+ Tuple of (sql_content, execution_time_ms).
57
+ """
58
+ upgrade_sql = self._get_migration_sql(migration, "up")
59
+ if upgrade_sql is None:
60
+ return None, 0
61
+
62
+ start_time = time.time()
63
+ driver.execute(upgrade_sql)
64
+ execution_time = int((time.time() - start_time) * 1000)
65
+ return None, execution_time
66
+
67
+ def execute_downgrade(
68
+ self, driver: "SyncDriverAdapterBase", migration: "dict[str, Any]"
69
+ ) -> "tuple[Optional[str], int]":
70
+ """Execute a downgrade migration.
71
+
72
+ Args:
73
+ driver: The database driver to use.
74
+ migration: Migration metadata dictionary.
75
+
76
+ Returns:
77
+ Tuple of (sql_content, execution_time_ms).
78
+ """
79
+ downgrade_sql = self._get_migration_sql(migration, "down")
80
+ if downgrade_sql is None:
81
+ return None, 0
82
+
83
+ start_time = time.time()
84
+ driver.execute(downgrade_sql)
85
+ execution_time = int((time.time() - start_time) * 1000)
86
+ return None, execution_time
87
+
88
+ def load_all_migrations(self) -> "dict[str, SQL]":
89
+ """Load all migrations into a single namespace for bulk operations.
90
+
91
+ Returns:
92
+ Dictionary mapping query names to SQL objects.
93
+ """
94
+ all_queries = {}
95
+ migrations = self.get_migration_files()
96
+
97
+ for version, file_path in migrations:
98
+ if file_path.suffix == ".sql":
99
+ self.loader.load_sql(file_path)
100
+ for query_name in self.loader.list_queries():
101
+ all_queries[query_name] = self.loader.get_sql(query_name)
102
+ else:
103
+ loader = get_migration_loader(file_path, self.migrations_path, self.project_root)
104
+
105
+ try:
106
+ up_sql = run_(loader.get_up_sql)(file_path)
107
+ down_sql = run_(loader.get_down_sql)(file_path)
108
+
109
+ if up_sql:
110
+ all_queries[f"migrate-{version}-up"] = SQL(up_sql[0])
111
+ if down_sql:
112
+ all_queries[f"migrate-{version}-down"] = SQL(down_sql[0])
113
+
114
+ except Exception as e:
115
+ logger.debug("Failed to load Python migration %s: %s", file_path, e)
116
+
117
+ return all_queries
118
+
119
+
120
+ class AsyncMigrationRunner(BaseMigrationRunner["AsyncDriverAdapterBase"]):
121
+ """Executes migrations using SQLFileLoader."""
122
+
123
+ async def get_migration_files(self) -> "list[tuple[str, Path]]":
124
+ """Get all migration files sorted by version.
125
+
126
+ Returns:
127
+ List of tuples containing (version, file_path).
128
+ """
129
+ return self._get_migration_files_sync()
130
+
131
+ async def load_migration(self, file_path: Path) -> "dict[str, Any]":
132
+ """Load a migration file and extract its components.
133
+
134
+ Args:
135
+ file_path: Path to the migration file.
136
+
137
+ Returns:
138
+ Dictionary containing migration metadata.
139
+ """
140
+ return self._load_migration_metadata(file_path)
141
+
142
+ async def execute_upgrade(
143
+ self, driver: "AsyncDriverAdapterBase", migration: "dict[str, Any]"
144
+ ) -> "tuple[Optional[str], int]":
145
+ """Execute an upgrade migration.
146
+
147
+ Args:
148
+ driver: The async database driver to use.
149
+ migration: Migration metadata dictionary.
150
+
151
+ Returns:
152
+ Tuple of (sql_content, execution_time_ms).
153
+ """
154
+ upgrade_sql = self._get_migration_sql(migration, "up")
155
+ if upgrade_sql is None:
156
+ return None, 0
157
+
158
+ start_time = time.time()
159
+ await driver.execute(upgrade_sql)
160
+ execution_time = int((time.time() - start_time) * 1000)
161
+ return None, execution_time
162
+
163
+ async def execute_downgrade(
164
+ self, driver: "AsyncDriverAdapterBase", migration: "dict[str, Any]"
165
+ ) -> "tuple[Optional[str], int]":
166
+ """Execute a downgrade migration.
167
+
168
+ Args:
169
+ driver: The async database driver to use.
170
+ migration: Migration metadata dictionary.
171
+
172
+ Returns:
173
+ Tuple of (sql_content, execution_time_ms).
174
+ """
175
+ downgrade_sql = self._get_migration_sql(migration, "down")
176
+ if downgrade_sql is None:
177
+ return None, 0
178
+
179
+ start_time = time.time()
180
+ await driver.execute(downgrade_sql)
181
+ execution_time = int((time.time() - start_time) * 1000)
182
+ return None, execution_time
183
+
184
+ async def load_all_migrations(self) -> "dict[str, SQL]":
185
+ """Load all migrations into a single namespace for bulk operations.
186
+
187
+ Returns:
188
+ Dictionary mapping query names to SQL objects.
189
+ """
190
+ all_queries = {}
191
+ migrations = await self.get_migration_files()
192
+
193
+ for version, file_path in migrations:
194
+ if file_path.suffix == ".sql":
195
+ self.loader.load_sql(file_path)
196
+ for query_name in self.loader.list_queries():
197
+ all_queries[query_name] = self.loader.get_sql(query_name)
198
+ else:
199
+ loader = get_migration_loader(file_path, self.migrations_path, self.project_root)
200
+
201
+ try:
202
+ up_sql = await loader.get_up_sql(file_path)
203
+ down_sql = await loader.get_down_sql(file_path)
204
+
205
+ if up_sql:
206
+ all_queries[f"migrate-{version}-up"] = SQL(up_sql[0])
207
+ if down_sql:
208
+ all_queries[f"migrate-{version}-down"] = SQL(down_sql[0])
209
+
210
+ except Exception as e:
211
+ logger.debug("Failed to load Python migration %s: %s", file_path, e)
212
+
213
+ return all_queries