sqlspec 0.13.1__py3-none-any.whl → 0.14.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.

Potentially problematic release.


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

Files changed (110) hide show
  1. sqlspec/__init__.py +39 -1
  2. sqlspec/adapters/adbc/config.py +4 -40
  3. sqlspec/adapters/adbc/driver.py +29 -16
  4. sqlspec/adapters/aiosqlite/config.py +2 -20
  5. sqlspec/adapters/aiosqlite/driver.py +36 -18
  6. sqlspec/adapters/asyncmy/config.py +2 -33
  7. sqlspec/adapters/asyncmy/driver.py +23 -16
  8. sqlspec/adapters/asyncpg/config.py +5 -39
  9. sqlspec/adapters/asyncpg/driver.py +41 -18
  10. sqlspec/adapters/bigquery/config.py +2 -43
  11. sqlspec/adapters/bigquery/driver.py +26 -14
  12. sqlspec/adapters/duckdb/config.py +2 -49
  13. sqlspec/adapters/duckdb/driver.py +35 -16
  14. sqlspec/adapters/oracledb/config.py +4 -83
  15. sqlspec/adapters/oracledb/driver.py +54 -27
  16. sqlspec/adapters/psqlpy/config.py +2 -55
  17. sqlspec/adapters/psqlpy/driver.py +28 -8
  18. sqlspec/adapters/psycopg/config.py +4 -73
  19. sqlspec/adapters/psycopg/driver.py +69 -24
  20. sqlspec/adapters/sqlite/config.py +3 -21
  21. sqlspec/adapters/sqlite/driver.py +50 -26
  22. sqlspec/cli.py +248 -0
  23. sqlspec/config.py +18 -20
  24. sqlspec/driver/_async.py +28 -10
  25. sqlspec/driver/_common.py +5 -4
  26. sqlspec/driver/_sync.py +28 -10
  27. sqlspec/driver/mixins/__init__.py +6 -0
  28. sqlspec/driver/mixins/_cache.py +114 -0
  29. sqlspec/driver/mixins/_pipeline.py +0 -4
  30. sqlspec/{service/base.py → driver/mixins/_query_tools.py} +86 -421
  31. sqlspec/driver/mixins/_result_utils.py +0 -2
  32. sqlspec/driver/mixins/_sql_translator.py +0 -2
  33. sqlspec/driver/mixins/_storage.py +4 -18
  34. sqlspec/driver/mixins/_type_coercion.py +0 -2
  35. sqlspec/driver/parameters.py +4 -4
  36. sqlspec/extensions/aiosql/adapter.py +4 -4
  37. sqlspec/extensions/litestar/__init__.py +2 -1
  38. sqlspec/extensions/litestar/cli.py +48 -0
  39. sqlspec/extensions/litestar/plugin.py +3 -0
  40. sqlspec/loader.py +1 -1
  41. sqlspec/migrations/__init__.py +23 -0
  42. sqlspec/migrations/base.py +390 -0
  43. sqlspec/migrations/commands.py +525 -0
  44. sqlspec/migrations/runner.py +215 -0
  45. sqlspec/migrations/tracker.py +153 -0
  46. sqlspec/migrations/utils.py +89 -0
  47. sqlspec/protocols.py +37 -3
  48. sqlspec/statement/builder/__init__.py +8 -8
  49. sqlspec/statement/builder/{column.py → _column.py} +82 -52
  50. sqlspec/statement/builder/{ddl.py → _ddl.py} +5 -5
  51. sqlspec/statement/builder/_ddl_utils.py +1 -1
  52. sqlspec/statement/builder/{delete.py → _delete.py} +1 -1
  53. sqlspec/statement/builder/{insert.py → _insert.py} +1 -1
  54. sqlspec/statement/builder/{merge.py → _merge.py} +1 -1
  55. sqlspec/statement/builder/_parsing_utils.py +5 -3
  56. sqlspec/statement/builder/{select.py → _select.py} +59 -61
  57. sqlspec/statement/builder/{update.py → _update.py} +2 -2
  58. sqlspec/statement/builder/mixins/__init__.py +24 -30
  59. sqlspec/statement/builder/mixins/{_set_ops.py → _cte_and_set_ops.py} +86 -2
  60. sqlspec/statement/builder/mixins/{_delete_from.py → _delete_operations.py} +2 -0
  61. sqlspec/statement/builder/mixins/{_insert_values.py → _insert_operations.py} +70 -1
  62. sqlspec/statement/builder/mixins/{_merge_clauses.py → _merge_operations.py} +2 -0
  63. sqlspec/statement/builder/mixins/_order_limit_operations.py +123 -0
  64. sqlspec/statement/builder/mixins/{_pivot.py → _pivot_operations.py} +71 -2
  65. sqlspec/statement/builder/mixins/_select_operations.py +612 -0
  66. sqlspec/statement/builder/mixins/{_update_set.py → _update_operations.py} +73 -2
  67. sqlspec/statement/builder/mixins/_where_clause.py +536 -0
  68. sqlspec/statement/cache.py +50 -0
  69. sqlspec/statement/filters.py +37 -8
  70. sqlspec/statement/parameters.py +154 -25
  71. sqlspec/statement/pipelines/__init__.py +1 -1
  72. sqlspec/statement/pipelines/context.py +4 -4
  73. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +3 -3
  74. sqlspec/statement/pipelines/validators/_parameter_style.py +22 -22
  75. sqlspec/statement/pipelines/validators/_performance.py +1 -5
  76. sqlspec/statement/sql.py +246 -176
  77. sqlspec/utils/__init__.py +2 -1
  78. sqlspec/utils/statement_hashing.py +203 -0
  79. sqlspec/utils/type_guards.py +32 -0
  80. {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/METADATA +1 -1
  81. sqlspec-0.14.0.dist-info/RECORD +143 -0
  82. sqlspec-0.14.0.dist-info/entry_points.txt +2 -0
  83. sqlspec/service/__init__.py +0 -4
  84. sqlspec/service/_util.py +0 -147
  85. sqlspec/service/pagination.py +0 -26
  86. sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
  87. sqlspec/statement/builder/mixins/_case_builder.py +0 -91
  88. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
  89. sqlspec/statement/builder/mixins/_from.py +0 -63
  90. sqlspec/statement/builder/mixins/_group_by.py +0 -118
  91. sqlspec/statement/builder/mixins/_having.py +0 -35
  92. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
  93. sqlspec/statement/builder/mixins/_insert_into.py +0 -36
  94. sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
  95. sqlspec/statement/builder/mixins/_order_by.py +0 -46
  96. sqlspec/statement/builder/mixins/_returning.py +0 -37
  97. sqlspec/statement/builder/mixins/_select_columns.py +0 -61
  98. sqlspec/statement/builder/mixins/_unpivot.py +0 -77
  99. sqlspec/statement/builder/mixins/_update_from.py +0 -55
  100. sqlspec/statement/builder/mixins/_update_table.py +0 -29
  101. sqlspec/statement/builder/mixins/_where.py +0 -401
  102. sqlspec/statement/builder/mixins/_window_functions.py +0 -86
  103. sqlspec/statement/parameter_manager.py +0 -220
  104. sqlspec/statement/sql_compiler.py +0 -140
  105. sqlspec-0.13.1.dist-info/RECORD +0 -150
  106. /sqlspec/statement/builder/{base.py → _base.py} +0 -0
  107. /sqlspec/statement/builder/mixins/{_join.py → _join_operations.py} +0 -0
  108. {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/WHEEL +0 -0
  109. {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/licenses/LICENSE +0 -0
  110. {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,525 @@
1
+ """Migration command implementations for SQLSpec.
2
+
3
+ This module provides the main command interface for database migrations.
4
+ """
5
+
6
+ from typing import TYPE_CHECKING, Any, Union, cast
7
+
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from sqlspec.migrations.base import BaseMigrationCommands
12
+ from sqlspec.migrations.runner import AsyncMigrationRunner, SyncMigrationRunner
13
+ from sqlspec.migrations.tracker import AsyncMigrationTracker, SyncMigrationTracker
14
+ from sqlspec.migrations.utils import create_migration_file
15
+ from sqlspec.statement.sql import SQL
16
+ from sqlspec.utils.logging import get_logger
17
+ from sqlspec.utils.sync_tools import await_
18
+
19
+ if TYPE_CHECKING:
20
+ from sqlspec.config import AsyncConfigT, SyncConfigT
21
+
22
+ __all__ = ("AsyncMigrationCommands", "MigrationCommands", "SyncMigrationCommands")
23
+
24
+ logger = get_logger("migrations.commands")
25
+ console = Console()
26
+
27
+
28
+ class SyncMigrationCommands(BaseMigrationCommands["SyncConfigT", Any]):
29
+ """SQLSpec native migration commands (sync version)."""
30
+
31
+ def __init__(self, config: "SyncConfigT") -> None:
32
+ """Initialize migration commands.
33
+
34
+ Args:
35
+ config: The SQLSpec configuration.
36
+ """
37
+ super().__init__(config)
38
+ self.tracker = SyncMigrationTracker(self.version_table)
39
+ self.runner = SyncMigrationRunner(self.migrations_path)
40
+
41
+ def init(self, directory: str, package: bool = True) -> None:
42
+ """Initialize migration directory structure.
43
+
44
+ Args:
45
+ directory: Directory to initialize migrations in.
46
+ package: Whether to create __init__.py file.
47
+ """
48
+ self.init_directory(directory, package)
49
+
50
+ def current(self, verbose: bool = False) -> None:
51
+ """Show current migration version.
52
+
53
+ Args:
54
+ verbose: Whether to show detailed migration history.
55
+ """
56
+ with self.config.provide_session() as driver:
57
+ self.tracker.ensure_tracking_table(driver)
58
+
59
+ current = self.tracker.get_current_version(driver)
60
+ if not current:
61
+ console.print("[yellow]No migrations applied yet[/]")
62
+ return
63
+
64
+ console.print(f"[green]Current version:[/] {current}")
65
+
66
+ if verbose:
67
+ applied = self.tracker.get_applied_migrations(driver)
68
+
69
+ table = Table(title="Applied Migrations")
70
+ table.add_column("Version", style="cyan")
71
+ table.add_column("Description")
72
+ table.add_column("Applied At")
73
+ table.add_column("Time (ms)", justify="right")
74
+ table.add_column("Applied By")
75
+
76
+ for migration in applied:
77
+ table.add_row(
78
+ migration["version_num"],
79
+ migration.get("description", ""),
80
+ str(migration.get("applied_at", "")),
81
+ str(migration.get("execution_time_ms", "")),
82
+ migration.get("applied_by", ""),
83
+ )
84
+
85
+ console.print(table)
86
+
87
+ def upgrade(self, revision: str = "head") -> None:
88
+ """Upgrade to a target revision.
89
+
90
+ Args:
91
+ revision: Target revision or "head" for latest.
92
+ """
93
+ with self.config.provide_session() as driver:
94
+ self.tracker.ensure_tracking_table(driver)
95
+
96
+ current = self.tracker.get_current_version(driver)
97
+ all_migrations = self.runner.get_migration_files()
98
+
99
+ # Determine pending migrations
100
+ pending = []
101
+ for version, file_path in all_migrations:
102
+ if (current is None or version > current) and (revision == "head" or version <= revision):
103
+ pending.append((version, file_path))
104
+
105
+ if not pending:
106
+ console.print("[green]Already at latest version[/]")
107
+ return
108
+
109
+ console.print(f"[yellow]Found {len(pending)} pending migrations[/]")
110
+
111
+ # Execute migrations
112
+ for version, file_path in pending:
113
+ migration = self.runner.load_migration(file_path)
114
+
115
+ console.print(f"\n[cyan]Applying {version}:[/] {migration['description']}")
116
+
117
+ try:
118
+ # Execute migration
119
+ _, execution_time = self.runner.execute_upgrade(driver, migration)
120
+
121
+ # Record in tracking table
122
+ self.tracker.record_migration(
123
+ driver, migration["version"], migration["description"], execution_time, migration["checksum"]
124
+ )
125
+
126
+ console.print(f"[green]✓ Applied in {execution_time}ms[/]")
127
+
128
+ except Exception as e:
129
+ console.print(f"[red]✗ Failed: {e}[/]")
130
+ raise
131
+
132
+ def downgrade(self, revision: str = "-1") -> None:
133
+ """Downgrade to a target revision.
134
+
135
+ Args:
136
+ revision: Target revision or "-1" for one step back.
137
+ """
138
+ with self.config.provide_session() as driver:
139
+ self.tracker.ensure_tracking_table(driver)
140
+
141
+ applied = self.tracker.get_applied_migrations(driver)
142
+ if not applied:
143
+ console.print("[yellow]No migrations to downgrade[/]")
144
+ return
145
+
146
+ # Determine migrations to revert
147
+ to_revert = []
148
+ if revision == "-1":
149
+ # Downgrade one step
150
+ to_revert = [applied[-1]]
151
+ else:
152
+ # Downgrade to specific version
153
+ for migration in reversed(applied):
154
+ if migration["version_num"] > revision:
155
+ to_revert.append(migration)
156
+
157
+ if not to_revert:
158
+ console.print("[yellow]Nothing to downgrade[/]")
159
+ return
160
+
161
+ console.print(f"[yellow]Reverting {len(to_revert)} migrations[/]")
162
+
163
+ # Load migration files
164
+ all_files = dict(self.runner.get_migration_files())
165
+
166
+ for migration_record in to_revert:
167
+ version = migration_record["version_num"]
168
+
169
+ if version not in all_files:
170
+ console.print(f"[red]Migration file not found for {version}[/]")
171
+ continue
172
+
173
+ migration = self.runner.load_migration(all_files[version])
174
+ console.print(f"\n[cyan]Reverting {version}:[/] {migration['description']}")
175
+
176
+ try:
177
+ # Execute downgrade
178
+ _, execution_time = self.runner.execute_downgrade(driver, migration)
179
+
180
+ # Remove from tracking table
181
+ self.tracker.remove_migration(driver, version)
182
+
183
+ console.print(f"[green]✓ Reverted in {execution_time}ms[/]")
184
+
185
+ except Exception as e:
186
+ console.print(f"[red]✗ Failed: {e}[/]")
187
+ raise
188
+
189
+ def stamp(self, revision: str) -> None:
190
+ """Mark database as being at a specific revision without running migrations.
191
+
192
+ Args:
193
+ revision: The revision to stamp.
194
+ """
195
+ with self.config.provide_session() as driver:
196
+ self.tracker.ensure_tracking_table(driver)
197
+
198
+ # Validate revision exists
199
+ all_migrations = dict(self.runner.get_migration_files())
200
+ if revision not in all_migrations:
201
+ console.print(f"[red]Unknown revision: {revision}[/]")
202
+ return
203
+
204
+ # Clear existing records and stamp
205
+ clear_sql = SQL(f"DELETE FROM {self.tracker.version_table}")
206
+ driver.execute(clear_sql)
207
+
208
+ self.tracker.record_migration(driver, revision, f"Stamped to {revision}", 0, "manual-stamp")
209
+
210
+ console.print(f"[green]Database stamped at revision {revision}[/]")
211
+
212
+ def revision(self, message: str) -> None:
213
+ """Create a new migration file.
214
+
215
+ Args:
216
+ message: Description for the migration.
217
+ """
218
+ # Determine next version number
219
+ existing = self.runner.get_migration_files()
220
+ if existing:
221
+ last_version = existing[-1][0]
222
+ next_num = int(last_version) + 1
223
+ else:
224
+ next_num = 1
225
+
226
+ next_version = str(next_num).zfill(4)
227
+
228
+ # Create migration file
229
+ file_path = create_migration_file(self.migrations_path, next_version, message)
230
+
231
+ console.print(f"[green]Created migration:[/] {file_path}")
232
+
233
+
234
+ class AsyncMigrationCommands(BaseMigrationCommands["AsyncConfigT", Any]):
235
+ """SQLSpec native async migration commands."""
236
+
237
+ def __init__(self, sqlspec_config: "AsyncConfigT") -> None:
238
+ """Initialize async migration commands.
239
+
240
+ Args:
241
+ sqlspec_config: The async SQLSpec configuration.
242
+ """
243
+ super().__init__(sqlspec_config)
244
+ self.tracker = AsyncMigrationTracker(self.version_table)
245
+ self.runner = AsyncMigrationRunner(self.migrations_path)
246
+
247
+ async def init(self, directory: str, package: bool = True) -> None:
248
+ """Initialize migration directory structure.
249
+
250
+ Args:
251
+ directory: Directory path for migrations.
252
+ package: Whether to create __init__.py in the directory.
253
+ """
254
+ # For async, we still use sync directory operations
255
+ self.init_directory(directory, package)
256
+
257
+ async def current(self, verbose: bool = False) -> None:
258
+ """Show current migration version.
259
+
260
+ Args:
261
+ verbose: Whether to show detailed migration history.
262
+ """
263
+ async with self.config.provide_session() as driver:
264
+ await self.tracker.ensure_tracking_table(driver)
265
+
266
+ current = await self.tracker.get_current_version(driver)
267
+ if not current:
268
+ console.print("[yellow]No migrations applied yet[/]")
269
+ return
270
+
271
+ console.print(f"[green]Current version:[/] {current}")
272
+
273
+ if verbose:
274
+ applied = await self.tracker.get_applied_migrations(driver)
275
+
276
+ table = Table(title="Applied Migrations")
277
+ table.add_column("Version", style="cyan")
278
+ table.add_column("Description")
279
+ table.add_column("Applied At")
280
+ table.add_column("Time (ms)", justify="right")
281
+ table.add_column("Applied By")
282
+
283
+ for migration in applied:
284
+ table.add_row(
285
+ migration["version_num"],
286
+ migration.get("description", ""),
287
+ str(migration.get("applied_at", "")),
288
+ str(migration.get("execution_time_ms", "")),
289
+ migration.get("applied_by", ""),
290
+ )
291
+
292
+ console.print(table)
293
+
294
+ async def upgrade(self, revision: str = "head") -> None:
295
+ """Upgrade to a target revision.
296
+
297
+ Args:
298
+ revision: Target revision (default: "head" for latest).
299
+ """
300
+ async with self.config.provide_session() as driver:
301
+ await self.tracker.ensure_tracking_table(driver)
302
+
303
+ current = await self.tracker.get_current_version(driver)
304
+ all_migrations = await self.runner.get_migration_files()
305
+
306
+ # Determine pending migrations
307
+ pending = []
308
+ for version, file_path in all_migrations:
309
+ if (current is None or version > current) and (revision == "head" or version <= revision):
310
+ pending.append((version, file_path))
311
+
312
+ if not pending:
313
+ console.print("[green]Already at latest version[/]")
314
+ return
315
+
316
+ console.print(f"[yellow]Found {len(pending)} pending migrations[/]")
317
+
318
+ # Execute migrations
319
+ for version, file_path in pending:
320
+ migration = await self.runner.load_migration(file_path)
321
+
322
+ console.print(f"\n[cyan]Applying {version}:[/] {migration['description']}")
323
+
324
+ try:
325
+ # Execute migration
326
+ _, execution_time = await self.runner.execute_upgrade(driver, migration)
327
+
328
+ # Record in tracking table
329
+ await self.tracker.record_migration(
330
+ driver, migration["version"], migration["description"], execution_time, migration["checksum"]
331
+ )
332
+
333
+ console.print(f"[green]✓ Applied in {execution_time}ms[/]")
334
+
335
+ except Exception as e:
336
+ console.print(f"[red]✗ Failed: {e}[/]")
337
+ raise
338
+
339
+ async def downgrade(self, revision: str = "-1") -> None:
340
+ """Downgrade to a target revision.
341
+
342
+ Args:
343
+ revision: Target revision (default: "-1" for one step back).
344
+ """
345
+ async with self.config.provide_session() as driver:
346
+ await self.tracker.ensure_tracking_table(driver)
347
+
348
+ applied = await self.tracker.get_applied_migrations(driver)
349
+ if not applied:
350
+ console.print("[yellow]No migrations to downgrade[/]")
351
+ return
352
+
353
+ # Determine migrations to revert
354
+ to_revert = []
355
+ if revision == "-1":
356
+ # Downgrade one step
357
+ to_revert = [applied[-1]]
358
+ else:
359
+ # Downgrade to specific version
360
+ for migration in reversed(applied):
361
+ if migration["version_num"] > revision:
362
+ to_revert.append(migration)
363
+
364
+ if not to_revert:
365
+ console.print("[yellow]Nothing to downgrade[/]")
366
+ return
367
+
368
+ console.print(f"[yellow]Reverting {len(to_revert)} migrations[/]")
369
+
370
+ # Load migration files
371
+ all_files = dict(await self.runner.get_migration_files())
372
+
373
+ for migration_record in to_revert:
374
+ version = migration_record["version_num"]
375
+
376
+ if version not in all_files:
377
+ console.print(f"[red]Migration file not found for {version}[/]")
378
+ continue
379
+
380
+ migration = await self.runner.load_migration(all_files[version])
381
+ console.print(f"\n[cyan]Reverting {version}:[/] {migration['description']}")
382
+
383
+ try:
384
+ # Execute downgrade
385
+ _, execution_time = await self.runner.execute_downgrade(driver, migration)
386
+
387
+ # Remove from tracking table
388
+ await self.tracker.remove_migration(driver, version)
389
+
390
+ console.print(f"[green]✓ Reverted in {execution_time}ms[/]")
391
+
392
+ except Exception as e:
393
+ console.print(f"[red]✗ Failed: {e}[/]")
394
+ raise
395
+
396
+ async def stamp(self, revision: str) -> None:
397
+ """Mark database as being at a specific revision without running migrations.
398
+
399
+ Args:
400
+ revision: The revision to stamp.
401
+ """
402
+ async with self.config.provide_session() as driver:
403
+ await self.tracker.ensure_tracking_table(driver)
404
+
405
+ # Validate revision exists
406
+ all_migrations = dict(await self.runner.get_migration_files())
407
+ if revision not in all_migrations:
408
+ console.print(f"[red]Unknown revision: {revision}[/]")
409
+ return
410
+
411
+ # Clear existing records and stamp
412
+ clear_sql = SQL(f"DELETE FROM {self.tracker.version_table}")
413
+ await driver.execute(clear_sql)
414
+
415
+ await self.tracker.record_migration(driver, revision, f"Stamped to {revision}", 0, "manual-stamp")
416
+
417
+ console.print(f"[green]Database stamped at revision {revision}[/]")
418
+
419
+ async def revision(self, message: str) -> None:
420
+ """Create a new migration file.
421
+
422
+ Args:
423
+ message: Description of the migration.
424
+ """
425
+ # Determine next version number
426
+ existing = await self.runner.get_migration_files()
427
+ if existing:
428
+ last_version = existing[-1][0]
429
+ next_num = int(last_version) + 1
430
+ else:
431
+ next_num = 1
432
+
433
+ next_version = str(next_num).zfill(4)
434
+
435
+ # Create migration file
436
+ file_path = create_migration_file(self.migrations_path, next_version, message)
437
+
438
+ console.print(f"[green]Created migration:[/] {file_path}")
439
+
440
+
441
+ class MigrationCommands:
442
+ """Unified migration commands that adapt to sync/async configs."""
443
+
444
+ def __init__(self, config: "Union[SyncConfigT, AsyncConfigT]") -> None:
445
+ """Initialize migration commands with appropriate sync/async implementation.
446
+
447
+ Args:
448
+ config: The SQLSpec configuration (sync or async).
449
+ """
450
+
451
+ if config.is_async:
452
+ self._impl: Union[AsyncMigrationCommands[Any], SyncMigrationCommands[Any]] = AsyncMigrationCommands(
453
+ cast("AsyncConfigT", config)
454
+ )
455
+ else:
456
+ self._impl = SyncMigrationCommands(cast("SyncConfigT", config))
457
+
458
+ self._is_async = config.is_async
459
+
460
+ def init(self, directory: str, package: bool = True) -> None:
461
+ """Initialize migration directory structure.
462
+
463
+ Args:
464
+ directory: Directory to initialize migrations in.
465
+ package: Whether to create __init__.py file.
466
+ """
467
+ if self._is_async:
468
+ await_(cast("AsyncMigrationCommands[Any]", self._impl).init)(directory, package=package)
469
+ else:
470
+ cast("SyncMigrationCommands[Any]", self._impl).init(directory, package=package)
471
+
472
+ def current(self, verbose: bool = False) -> None:
473
+ """Show current migration version.
474
+
475
+ Args:
476
+ verbose: Whether to show detailed migration history.
477
+ """
478
+ if self._is_async:
479
+ await_(cast("AsyncMigrationCommands[Any]", self._impl).current, raise_sync_error=False)(verbose=verbose)
480
+ else:
481
+ cast("SyncMigrationCommands[Any]", self._impl).current(verbose=verbose)
482
+
483
+ def upgrade(self, revision: str = "head") -> None:
484
+ """Upgrade to a target revision.
485
+
486
+ Args:
487
+ revision: Target revision or "head" for latest.
488
+ """
489
+ if self._is_async:
490
+ await_(cast("AsyncMigrationCommands[Any]", self._impl).upgrade, raise_sync_error=False)(revision=revision)
491
+ else:
492
+ cast("SyncMigrationCommands[Any]", self._impl).upgrade(revision=revision)
493
+
494
+ def downgrade(self, revision: str = "-1") -> None:
495
+ """Downgrade to a target revision.
496
+
497
+ Args:
498
+ revision: Target revision or "-1" for one step back.
499
+ """
500
+ if self._is_async:
501
+ await_(cast("AsyncMigrationCommands[Any]", self._impl).downgrade, raise_sync_error=False)(revision=revision)
502
+ else:
503
+ cast("SyncMigrationCommands[Any]", self._impl).downgrade(revision=revision)
504
+
505
+ def stamp(self, revision: str) -> None:
506
+ """Mark database as being at a specific revision without running migrations.
507
+
508
+ Args:
509
+ revision: The revision to stamp.
510
+ """
511
+ if self._is_async:
512
+ await_(cast("AsyncMigrationCommands[Any]", self._impl).stamp, raise_sync_error=False)(revision)
513
+ else:
514
+ cast("SyncMigrationCommands[Any]", self._impl).stamp(revision)
515
+
516
+ def revision(self, message: str) -> None:
517
+ """Create a new migration file.
518
+
519
+ Args:
520
+ message: Description for the migration.
521
+ """
522
+ if self._is_async:
523
+ await_(cast("AsyncMigrationCommands[Any]", self._impl).revision, raise_sync_error=False)(message)
524
+ else:
525
+ cast("SyncMigrationCommands[Any]", self._impl).revision(message)