sqlspec 0.25.0__py3-none-any.whl → 0.26.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 (84) hide show
  1. sqlspec/_serialization.py +223 -21
  2. sqlspec/_sql.py +12 -50
  3. sqlspec/_typing.py +9 -0
  4. sqlspec/adapters/adbc/config.py +8 -1
  5. sqlspec/adapters/adbc/data_dictionary.py +290 -0
  6. sqlspec/adapters/adbc/driver.py +127 -18
  7. sqlspec/adapters/adbc/type_converter.py +159 -0
  8. sqlspec/adapters/aiosqlite/config.py +3 -0
  9. sqlspec/adapters/aiosqlite/data_dictionary.py +117 -0
  10. sqlspec/adapters/aiosqlite/driver.py +17 -3
  11. sqlspec/adapters/asyncmy/_types.py +1 -1
  12. sqlspec/adapters/asyncmy/config.py +11 -8
  13. sqlspec/adapters/asyncmy/data_dictionary.py +122 -0
  14. sqlspec/adapters/asyncmy/driver.py +31 -7
  15. sqlspec/adapters/asyncpg/config.py +3 -0
  16. sqlspec/adapters/asyncpg/data_dictionary.py +134 -0
  17. sqlspec/adapters/asyncpg/driver.py +19 -4
  18. sqlspec/adapters/bigquery/config.py +3 -0
  19. sqlspec/adapters/bigquery/data_dictionary.py +109 -0
  20. sqlspec/adapters/bigquery/driver.py +21 -3
  21. sqlspec/adapters/bigquery/type_converter.py +93 -0
  22. sqlspec/adapters/duckdb/_types.py +1 -1
  23. sqlspec/adapters/duckdb/config.py +2 -0
  24. sqlspec/adapters/duckdb/data_dictionary.py +124 -0
  25. sqlspec/adapters/duckdb/driver.py +32 -5
  26. sqlspec/adapters/duckdb/pool.py +1 -1
  27. sqlspec/adapters/duckdb/type_converter.py +103 -0
  28. sqlspec/adapters/oracledb/config.py +6 -0
  29. sqlspec/adapters/oracledb/data_dictionary.py +442 -0
  30. sqlspec/adapters/oracledb/driver.py +63 -9
  31. sqlspec/adapters/oracledb/migrations.py +51 -67
  32. sqlspec/adapters/oracledb/type_converter.py +132 -0
  33. sqlspec/adapters/psqlpy/config.py +3 -0
  34. sqlspec/adapters/psqlpy/data_dictionary.py +133 -0
  35. sqlspec/adapters/psqlpy/driver.py +23 -179
  36. sqlspec/adapters/psqlpy/type_converter.py +73 -0
  37. sqlspec/adapters/psycopg/config.py +6 -0
  38. sqlspec/adapters/psycopg/data_dictionary.py +257 -0
  39. sqlspec/adapters/psycopg/driver.py +40 -5
  40. sqlspec/adapters/sqlite/config.py +3 -0
  41. sqlspec/adapters/sqlite/data_dictionary.py +117 -0
  42. sqlspec/adapters/sqlite/driver.py +18 -3
  43. sqlspec/adapters/sqlite/pool.py +13 -4
  44. sqlspec/builder/_base.py +82 -42
  45. sqlspec/builder/_column.py +57 -24
  46. sqlspec/builder/_ddl.py +84 -34
  47. sqlspec/builder/_insert.py +30 -52
  48. sqlspec/builder/_parsing_utils.py +104 -8
  49. sqlspec/builder/_select.py +147 -2
  50. sqlspec/builder/mixins/_cte_and_set_ops.py +1 -2
  51. sqlspec/builder/mixins/_join_operations.py +14 -30
  52. sqlspec/builder/mixins/_merge_operations.py +167 -61
  53. sqlspec/builder/mixins/_order_limit_operations.py +3 -10
  54. sqlspec/builder/mixins/_select_operations.py +3 -9
  55. sqlspec/builder/mixins/_update_operations.py +3 -22
  56. sqlspec/builder/mixins/_where_clause.py +4 -10
  57. sqlspec/cli.py +246 -140
  58. sqlspec/config.py +33 -19
  59. sqlspec/core/cache.py +2 -2
  60. sqlspec/core/compiler.py +56 -1
  61. sqlspec/core/parameters.py +7 -3
  62. sqlspec/core/statement.py +5 -0
  63. sqlspec/core/type_conversion.py +234 -0
  64. sqlspec/driver/__init__.py +6 -3
  65. sqlspec/driver/_async.py +106 -3
  66. sqlspec/driver/_common.py +156 -4
  67. sqlspec/driver/_sync.py +106 -3
  68. sqlspec/exceptions.py +5 -0
  69. sqlspec/migrations/__init__.py +4 -3
  70. sqlspec/migrations/base.py +153 -14
  71. sqlspec/migrations/commands.py +34 -96
  72. sqlspec/migrations/context.py +145 -0
  73. sqlspec/migrations/loaders.py +25 -8
  74. sqlspec/migrations/runner.py +352 -82
  75. sqlspec/typing.py +2 -0
  76. sqlspec/utils/config_resolver.py +153 -0
  77. sqlspec/utils/serializers.py +50 -2
  78. {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/METADATA +1 -1
  79. sqlspec-0.26.0.dist-info/RECORD +157 -0
  80. sqlspec-0.25.0.dist-info/RECORD +0 -139
  81. {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/WHEEL +0 -0
  82. {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/entry_points.txt +0 -0
  83. {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/licenses/LICENSE +0 -0
  84. {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/cli.py CHANGED
@@ -1,6 +1,8 @@
1
1
  # ruff: noqa: C901
2
+ import inspect
2
3
  import sys
3
4
  from collections.abc import Sequence
5
+ from pathlib import Path
4
6
  from typing import TYPE_CHECKING, Any, Optional, Union, cast
5
7
 
6
8
  if TYPE_CHECKING:
@@ -33,28 +35,56 @@ def get_sqlspec_group() -> "Group":
33
35
  @click.group(name="sqlspec")
34
36
  @click.option(
35
37
  "--config",
36
- help="Dotted path to SQLSpec config(s) (e.g. 'myapp.config.sqlspec_configs')",
38
+ help="Dotted path to SQLSpec config(s) or callable function (e.g. 'myapp.config.get_configs')",
37
39
  required=True,
38
40
  type=str,
39
41
  )
42
+ @click.option(
43
+ "--validate-config", is_flag=True, default=False, help="Validate configuration before executing migrations"
44
+ )
40
45
  @click.pass_context
41
- def sqlspec_group(ctx: "click.Context", config: str) -> None:
46
+ def sqlspec_group(ctx: "click.Context", config: str, validate_config: bool) -> None:
42
47
  """SQLSpec CLI commands."""
43
48
  from rich import get_console
44
49
 
45
- from sqlspec.utils import module_loader
50
+ from sqlspec.exceptions import ConfigResolverError
51
+ from sqlspec.utils.config_resolver import resolve_config_sync
46
52
 
47
53
  console = get_console()
48
54
  ctx.ensure_object(dict)
55
+
56
+ # Add current working directory to sys.path to allow loading local config modules
57
+ cwd = str(Path.cwd())
58
+ cwd_added = False
59
+ if cwd not in sys.path:
60
+ sys.path.insert(0, cwd)
61
+ cwd_added = True
62
+
49
63
  try:
50
- config_instance = module_loader.import_string(config)
51
- if isinstance(config_instance, Sequence):
52
- ctx.obj["configs"] = config_instance
64
+ config_result = resolve_config_sync(config)
65
+ if isinstance(config_result, Sequence) and not isinstance(config_result, str):
66
+ ctx.obj["configs"] = list(config_result)
53
67
  else:
54
- ctx.obj["configs"] = [config_instance]
55
- except ImportError as e:
68
+ ctx.obj["configs"] = [config_result]
69
+
70
+ ctx.obj["validate_config"] = validate_config
71
+
72
+ if validate_config:
73
+ console.print(f"[green]✓[/] Successfully loaded {len(ctx.obj['configs'])} config(s)")
74
+ for i, cfg in enumerate(ctx.obj["configs"]):
75
+ config_name = cfg.bind_key or f"config-{i}"
76
+ config_type = type(cfg).__name__
77
+ is_async = cfg.is_async
78
+ execution_hint = "[dim cyan](async-capable)[/]" if is_async else "[dim](sync)[/]"
79
+ console.print(f" [dim]•[/] {config_name}: {config_type} {execution_hint}")
80
+
81
+ except (ImportError, ConfigResolverError) as e:
56
82
  console.print(f"[red]Error loading config: {e}[/]")
57
83
  ctx.exit(1)
84
+ finally:
85
+ # Clean up: remove the cwd from sys.path if we added it
86
+ if cwd_added and cwd in sys.path and sys.path[0] == cwd:
87
+ sys.path.remove(cwd)
58
88
 
59
89
  return sqlspec_group
60
90
 
@@ -109,6 +139,12 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
109
139
  dry_run_option = click.option(
110
140
  "--dry-run", is_flag=True, default=False, help="Show what would be executed without making changes"
111
141
  )
142
+ execution_mode_option = click.option(
143
+ "--execution-mode",
144
+ type=click.Choice(["auto", "sync", "async"]),
145
+ default="auto",
146
+ help="Force execution mode (auto-detects by default)",
147
+ )
112
148
 
113
149
  def get_config_by_bind_key(
114
150
  ctx: "click.Context", bind_key: Optional[str]
@@ -128,7 +164,7 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
128
164
  else:
129
165
  config = None
130
166
  for cfg in configs:
131
- config_name = getattr(cfg, "name", None) or getattr(cfg, "bind_key", None)
167
+ config_name = cfg.bind_key
132
168
  if config_name == bind_key:
133
169
  config = cfg
134
170
  break
@@ -164,15 +200,11 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
164
200
  # Extract the actual config from DatabaseConfig wrapper if needed
165
201
  actual_config = config.config if isinstance(config, DatabaseConfig) else config
166
202
 
167
- migration_config = getattr(actual_config, "migration_config", None)
203
+ migration_config = actual_config.migration_config
168
204
  if migration_config:
169
205
  enabled = migration_config.get("enabled", True)
170
206
  if not enabled_only or enabled:
171
- config_name = (
172
- getattr(actual_config, "name", None)
173
- or getattr(actual_config, "bind_key", None)
174
- or str(type(actual_config).__name__)
175
- )
207
+ config_name = actual_config.bind_key or str(type(actual_config).__name__)
176
208
  migration_configs.append((config_name, actual_config))
177
209
 
178
210
  return migration_configs
@@ -197,6 +229,12 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
197
229
  filtered = [(name, config) for name, config in filtered if name not in exclude]
198
230
  return filtered
199
231
 
232
+ async def maybe_await(result: Any) -> Any:
233
+ """Await result if it's a coroutine, otherwise return it directly."""
234
+ if inspect.iscoroutine(result):
235
+ return await result
236
+ return result
237
+
200
238
  def process_multiple_configs(
201
239
  ctx: "click.Context",
202
240
  bind_key: Optional[str],
@@ -255,34 +293,42 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
255
293
  bind_key: Optional[str], verbose: bool, include: "tuple[str, ...]", exclude: "tuple[str, ...]"
256
294
  ) -> None:
257
295
  """Show current database revision."""
258
- from sqlspec.migrations.commands import MigrationCommands
296
+ from sqlspec.migrations.commands import create_migration_commands
297
+ from sqlspec.utils.sync_tools import run_
259
298
 
260
299
  ctx = click.get_current_context()
261
300
 
262
- # Check if this is a multi-config operation
263
- configs_to_process = process_multiple_configs(
264
- ctx, bind_key, include, exclude, dry_run=False, operation_name="show current revision"
265
- )
301
+ async def _show_current_revision() -> None:
302
+ # Check if this is a multi-config operation
303
+ configs_to_process = process_multiple_configs(
304
+ cast("click.Context", ctx),
305
+ bind_key,
306
+ include,
307
+ exclude,
308
+ dry_run=False,
309
+ operation_name="show current revision",
310
+ )
311
+
312
+ if configs_to_process is not None:
313
+ if not configs_to_process:
314
+ return
266
315
 
267
- if configs_to_process is not None:
268
- if not configs_to_process:
269
- return
316
+ console.rule("[yellow]Listing current revisions for all configurations[/]", align="left")
270
317
 
271
- console.rule("[yellow]Listing current revisions for all configurations[/]", align="left")
318
+ for config_name, config in configs_to_process:
319
+ console.print(f"\n[blue]Configuration: {config_name}[/]")
320
+ try:
321
+ migration_commands = create_migration_commands(config=config)
322
+ await maybe_await(migration_commands.current(verbose=verbose))
323
+ except Exception as e:
324
+ console.print(f"[red]✗ Failed to get current revision for {config_name}: {e}[/]")
325
+ else:
326
+ console.rule("[yellow]Listing current revision[/]", align="left")
327
+ sqlspec_config = get_config_by_bind_key(cast("click.Context", ctx), bind_key)
328
+ migration_commands = create_migration_commands(config=sqlspec_config)
329
+ await maybe_await(migration_commands.current(verbose=verbose))
272
330
 
273
- for config_name, config in configs_to_process:
274
- console.print(f"\n[blue]Configuration: {config_name}[/]")
275
- try:
276
- migration_commands = MigrationCommands(config=config)
277
- migration_commands.current(verbose=verbose)
278
- except Exception as e:
279
- console.print(f"[red]✗ Failed to get current revision for {config_name}: {e}[/]")
280
- else:
281
- # Single config operation
282
- console.rule("[yellow]Listing current revision[/]", align="left")
283
- sqlspec_config = get_config_by_bind_key(ctx, bind_key)
284
- migration_commands = MigrationCommands(config=sqlspec_config)
285
- migration_commands.current(verbose=verbose)
331
+ run_(_show_current_revision)()
286
332
 
287
333
  @database_group.command(name="downgrade", help="Downgrade database to a specific revision.")
288
334
  @bind_key_option
@@ -302,47 +348,56 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
302
348
  """Downgrade the database to the latest revision."""
303
349
  from rich.prompt import Confirm
304
350
 
305
- from sqlspec.migrations.commands import MigrationCommands
351
+ from sqlspec.migrations.commands import create_migration_commands
352
+ from sqlspec.utils.sync_tools import run_
306
353
 
307
354
  ctx = click.get_current_context()
308
355
 
309
- # Check if this is a multi-config operation
310
- configs_to_process = process_multiple_configs(
311
- ctx, bind_key, include, exclude, dry_run=dry_run, operation_name=f"downgrade to {revision}"
312
- )
313
-
314
- if configs_to_process is not None:
315
- if not configs_to_process:
316
- return
317
-
318
- if not no_prompt and not Confirm.ask(
319
- f"[bold]Are you sure you want to downgrade {len(configs_to_process)} configuration(s) to revision {revision}?[/]"
320
- ):
321
- console.print("[yellow]Operation cancelled.[/]")
322
- return
323
-
324
- console.rule("[yellow]Starting multi-configuration downgrade process[/]", align="left")
325
-
326
- for config_name, config in configs_to_process:
327
- console.print(f"[blue]Downgrading configuration: {config_name}[/]")
328
- try:
329
- migration_commands = MigrationCommands(config=config)
330
- migration_commands.downgrade(revision=revision)
331
- console.print(f"[green]✓ Successfully downgraded: {config_name}[/]")
332
- except Exception as e:
333
- console.print(f"[red]✗ Failed to downgrade {config_name}: {e}[/]")
334
- else:
335
- # Single config operation
336
- console.rule("[yellow]Starting database downgrade process[/]", align="left")
337
- input_confirmed = (
338
- True
339
- if no_prompt
340
- else Confirm.ask(f"Are you sure you want to downgrade the database to the `{revision}` revision?")
356
+ async def _downgrade_database() -> None:
357
+ # Check if this is a multi-config operation
358
+ configs_to_process = process_multiple_configs(
359
+ cast("click.Context", ctx),
360
+ bind_key,
361
+ include,
362
+ exclude,
363
+ dry_run=dry_run,
364
+ operation_name=f"downgrade to {revision}",
341
365
  )
342
- if input_confirmed:
343
- sqlspec_config = get_config_by_bind_key(ctx, bind_key)
344
- migration_commands = MigrationCommands(config=sqlspec_config)
345
- migration_commands.downgrade(revision=revision)
366
+
367
+ if configs_to_process is not None:
368
+ if not configs_to_process:
369
+ return
370
+
371
+ if not no_prompt and not Confirm.ask(
372
+ f"[bold]Are you sure you want to downgrade {len(configs_to_process)} configuration(s) to revision {revision}?[/]"
373
+ ):
374
+ console.print("[yellow]Operation cancelled.[/]")
375
+ return
376
+
377
+ console.rule("[yellow]Starting multi-configuration downgrade process[/]", align="left")
378
+
379
+ for config_name, config in configs_to_process:
380
+ console.print(f"[blue]Downgrading configuration: {config_name}[/]")
381
+ try:
382
+ migration_commands = create_migration_commands(config=config)
383
+ await maybe_await(migration_commands.downgrade(revision=revision))
384
+ console.print(f"[green]✓ Successfully downgraded: {config_name}[/]")
385
+ except Exception as e:
386
+ console.print(f"[red]✗ Failed to downgrade {config_name}: {e}[/]")
387
+ else:
388
+ # Single config operation
389
+ console.rule("[yellow]Starting database downgrade process[/]", align="left")
390
+ input_confirmed = (
391
+ True
392
+ if no_prompt
393
+ else Confirm.ask(f"Are you sure you want to downgrade the database to the `{revision}` revision?")
394
+ )
395
+ if input_confirmed:
396
+ sqlspec_config = get_config_by_bind_key(cast("click.Context", ctx), bind_key)
397
+ migration_commands = create_migration_commands(config=sqlspec_config)
398
+ await maybe_await(migration_commands.downgrade(revision=revision))
399
+
400
+ run_(_downgrade_database)()
346
401
 
347
402
  @database_group.command(name="upgrade", help="Upgrade database to a specific revision.")
348
403
  @bind_key_option
@@ -350,6 +405,7 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
350
405
  @include_option
351
406
  @exclude_option
352
407
  @dry_run_option
408
+ @execution_mode_option
353
409
  @click.argument("revision", type=str, default="head")
354
410
  def upgrade_database( # pyright: ignore[reportUnusedFunction]
355
411
  bind_key: Optional[str],
@@ -358,63 +414,79 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
358
414
  include: "tuple[str, ...]",
359
415
  exclude: "tuple[str, ...]",
360
416
  dry_run: bool,
417
+ execution_mode: str,
361
418
  ) -> None:
362
419
  """Upgrade the database to the latest revision."""
363
420
  from rich.prompt import Confirm
364
421
 
365
- from sqlspec.migrations.commands import MigrationCommands
422
+ from sqlspec.migrations.commands import create_migration_commands
423
+ from sqlspec.utils.sync_tools import run_
366
424
 
367
425
  ctx = click.get_current_context()
368
426
 
369
- # Check if this is a multi-config operation
370
- configs_to_process = process_multiple_configs(
371
- ctx, bind_key, include, exclude, dry_run, operation_name=f"upgrade to {revision}"
372
- )
373
-
374
- if configs_to_process is not None:
375
- if not configs_to_process:
376
- return
377
-
378
- if not no_prompt and not Confirm.ask(
379
- f"[bold]Are you sure you want to upgrade {len(configs_to_process)} configuration(s) to revision {revision}?[/]"
380
- ):
381
- console.print("[yellow]Operation cancelled.[/]")
382
- return
383
-
384
- console.rule("[yellow]Starting multi-configuration upgrade process[/]", align="left")
385
-
386
- for config_name, config in configs_to_process:
387
- console.print(f"[blue]Upgrading configuration: {config_name}[/]")
388
- try:
389
- migration_commands = MigrationCommands(config=config)
390
- migration_commands.upgrade(revision=revision)
391
- console.print(f"[green]✓ Successfully upgraded: {config_name}[/]")
392
- except Exception as e:
393
- console.print(f"[red]✗ Failed to upgrade {config_name}: {e}[/]")
394
- else:
395
- # Single config operation
396
- console.rule("[yellow]Starting database upgrade process[/]", align="left")
397
- input_confirmed = (
398
- True
399
- if no_prompt
400
- else Confirm.ask(f"[bold]Are you sure you want migrate the database to the `{revision}` revision?[/]")
427
+ async def _upgrade_database() -> None:
428
+ # Report execution mode when specified
429
+ if execution_mode != "auto":
430
+ console.print(f"[dim]Execution mode: {execution_mode}[/]")
431
+
432
+ # Check if this is a multi-config operation
433
+ configs_to_process = process_multiple_configs(
434
+ cast("click.Context", ctx), bind_key, include, exclude, dry_run, operation_name=f"upgrade to {revision}"
401
435
  )
402
- if input_confirmed:
403
- sqlspec_config = get_config_by_bind_key(ctx, bind_key)
404
- migration_commands = MigrationCommands(config=sqlspec_config)
405
- migration_commands.upgrade(revision=revision)
436
+
437
+ if configs_to_process is not None:
438
+ if not configs_to_process:
439
+ return
440
+
441
+ if not no_prompt and not Confirm.ask(
442
+ f"[bold]Are you sure you want to upgrade {len(configs_to_process)} configuration(s) to revision {revision}?[/]"
443
+ ):
444
+ console.print("[yellow]Operation cancelled.[/]")
445
+ return
446
+
447
+ console.rule("[yellow]Starting multi-configuration upgrade process[/]", align="left")
448
+
449
+ for config_name, config in configs_to_process:
450
+ console.print(f"[blue]Upgrading configuration: {config_name}[/]")
451
+ try:
452
+ migration_commands = create_migration_commands(config=config)
453
+ await maybe_await(migration_commands.upgrade(revision=revision))
454
+ console.print(f"[green]✓ Successfully upgraded: {config_name}[/]")
455
+ except Exception as e:
456
+ console.print(f"[red]✗ Failed to upgrade {config_name}: {e}[/]")
457
+ else:
458
+ # Single config operation
459
+ console.rule("[yellow]Starting database upgrade process[/]", align="left")
460
+ input_confirmed = (
461
+ True
462
+ if no_prompt
463
+ else Confirm.ask(
464
+ f"[bold]Are you sure you want migrate the database to the `{revision}` revision?[/]"
465
+ )
466
+ )
467
+ if input_confirmed:
468
+ sqlspec_config = get_config_by_bind_key(cast("click.Context", ctx), bind_key)
469
+ migration_commands = create_migration_commands(config=sqlspec_config)
470
+ await maybe_await(migration_commands.upgrade(revision=revision))
471
+
472
+ run_(_upgrade_database)()
406
473
 
407
474
  @database_group.command(help="Stamp the revision table with the given revision")
408
475
  @click.argument("revision", type=str)
409
476
  @bind_key_option
410
477
  def stamp(bind_key: Optional[str], revision: str) -> None: # pyright: ignore[reportUnusedFunction]
411
478
  """Stamp the revision table with the given revision."""
412
- from sqlspec.migrations.commands import MigrationCommands
479
+ from sqlspec.migrations.commands import create_migration_commands
480
+ from sqlspec.utils.sync_tools import run_
413
481
 
414
482
  ctx = click.get_current_context()
415
- sqlspec_config = get_config_by_bind_key(ctx, bind_key)
416
- migration_commands = MigrationCommands(config=sqlspec_config)
417
- migration_commands.stamp(revision=revision)
483
+
484
+ async def _stamp() -> None:
485
+ sqlspec_config = get_config_by_bind_key(cast("click.Context", ctx), bind_key)
486
+ migration_commands = create_migration_commands(config=sqlspec_config)
487
+ await maybe_await(migration_commands.stamp(revision=revision))
488
+
489
+ run_(_stamp)()
418
490
 
419
491
  @database_group.command(name="init", help="Initialize migrations for the project.")
420
492
  @bind_key_option
@@ -427,24 +499,37 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
427
499
  """Initialize the database migrations."""
428
500
  from rich.prompt import Confirm
429
501
 
430
- from sqlspec.migrations.commands import MigrationCommands
502
+ from sqlspec.extensions.litestar.config import DatabaseConfig
503
+ from sqlspec.migrations.commands import create_migration_commands
504
+ from sqlspec.utils.sync_tools import run_
431
505
 
432
506
  ctx = click.get_current_context()
433
- console.rule("[yellow]Initializing database migrations.", align="left")
434
- input_confirmed = (
435
- True if no_prompt else Confirm.ask("[bold]Are you sure you want initialize migrations for the project?[/]")
436
- )
437
- if input_confirmed:
438
- configs = [get_config_by_bind_key(ctx, bind_key)] if bind_key is not None else ctx.obj["configs"]
439
- from sqlspec.extensions.litestar.config import DatabaseConfig
440
-
441
- for config in configs:
442
- # Extract the actual config from DatabaseConfig wrapper if needed
443
- actual_config = config.config if isinstance(config, DatabaseConfig) else config
444
- migration_config = getattr(actual_config, "migration_config", {})
445
- directory = migration_config.get("script_location", "migrations") if directory is None else directory
446
- migration_commands = MigrationCommands(config=actual_config)
447
- migration_commands.init(directory=cast("str", directory), package=package)
507
+
508
+ async def _init_sqlspec() -> None:
509
+ console.rule("[yellow]Initializing database migrations.", align="left")
510
+ input_confirmed = (
511
+ True
512
+ if no_prompt
513
+ else Confirm.ask("[bold]Are you sure you want initialize migrations for the project?[/]")
514
+ )
515
+ if input_confirmed:
516
+ configs = (
517
+ [get_config_by_bind_key(cast("click.Context", ctx), bind_key)]
518
+ if bind_key is not None
519
+ else cast("click.Context", ctx).obj["configs"]
520
+ )
521
+
522
+ for config in configs:
523
+ # Extract the actual config from DatabaseConfig wrapper if needed
524
+ actual_config = config.config if isinstance(config, DatabaseConfig) else config
525
+ migration_config = getattr(actual_config, "migration_config", {})
526
+ target_directory = (
527
+ migration_config.get("script_location", "migrations") if directory is None else directory
528
+ )
529
+ migration_commands = create_migration_commands(config=actual_config)
530
+ await maybe_await(migration_commands.init(directory=cast("str", target_directory), package=package))
531
+
532
+ run_(_init_sqlspec)()
448
533
 
449
534
  @database_group.command(name="make-migrations", help="Create a new migration revision.")
450
535
  @bind_key_option
@@ -456,24 +541,45 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
456
541
  """Create a new database revision."""
457
542
  from rich.prompt import Prompt
458
543
 
459
- from sqlspec.migrations.commands import MigrationCommands
544
+ from sqlspec.migrations.commands import create_migration_commands
545
+ from sqlspec.utils.sync_tools import run_
460
546
 
461
547
  ctx = click.get_current_context()
462
- console.rule("[yellow]Creating new migration revision[/]", align="left")
463
- if message is None:
464
- message = "new migration" if no_prompt else Prompt.ask("Please enter a message describing this revision")
465
548
 
466
- sqlspec_config = get_config_by_bind_key(ctx, bind_key)
467
- migration_commands = MigrationCommands(config=sqlspec_config)
468
- migration_commands.revision(message=message)
549
+ async def _create_revision() -> None:
550
+ console.rule("[yellow]Creating new migration revision[/]", align="left")
551
+ message_text = message
552
+ if message_text is None:
553
+ message_text = (
554
+ "new migration" if no_prompt else Prompt.ask("Please enter a message describing this revision")
555
+ )
556
+
557
+ sqlspec_config = get_config_by_bind_key(cast("click.Context", ctx), bind_key)
558
+ migration_commands = create_migration_commands(config=sqlspec_config)
559
+ await maybe_await(migration_commands.revision(message=message_text))
560
+
561
+ run_(_create_revision)()
469
562
 
470
563
  @database_group.command(name="show-config", help="Show all configurations with migrations enabled.")
471
- def show_config() -> None: # pyright: ignore[reportUnusedFunction]
564
+ @bind_key_option
565
+ def show_config(bind_key: Optional[str] = None) -> None: # pyright: ignore[reportUnusedFunction]
472
566
  """Show and display all configurations with migrations enabled."""
473
567
  from rich.table import Table
474
568
 
475
569
  ctx = click.get_current_context()
476
- migration_configs = get_configs_with_migrations(ctx)
570
+
571
+ # If bind_key is provided, filter to only that config
572
+ if bind_key is not None:
573
+ get_config_by_bind_key(cast("click.Context", ctx), bind_key)
574
+ # Convert single config to list format for compatibility
575
+ all_configs = cast("click.Context", ctx).obj["configs"]
576
+ migration_configs = []
577
+ for cfg in all_configs:
578
+ config_name = cfg.bind_key
579
+ if config_name == bind_key and hasattr(cfg, "migration_config") and cfg.migration_config:
580
+ migration_configs.append((config_name, cfg))
581
+ else:
582
+ migration_configs = get_configs_with_migrations(cast("click.Context", ctx))
477
583
 
478
584
  if not migration_configs:
479
585
  console.print("[yellow]No configurations with migrations detected.[/]")