sqlspec 0.18.0__py3-none-any.whl → 0.20.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 (64) hide show
  1. sqlspec/adapters/adbc/driver.py +192 -28
  2. sqlspec/adapters/asyncmy/driver.py +72 -15
  3. sqlspec/adapters/asyncpg/config.py +23 -3
  4. sqlspec/adapters/asyncpg/driver.py +30 -14
  5. sqlspec/adapters/bigquery/driver.py +79 -9
  6. sqlspec/adapters/duckdb/driver.py +39 -56
  7. sqlspec/adapters/oracledb/driver.py +99 -52
  8. sqlspec/adapters/psqlpy/driver.py +89 -31
  9. sqlspec/adapters/psycopg/driver.py +11 -23
  10. sqlspec/adapters/sqlite/driver.py +77 -8
  11. sqlspec/base.py +29 -25
  12. sqlspec/builder/__init__.py +1 -1
  13. sqlspec/builder/_base.py +4 -5
  14. sqlspec/builder/_column.py +3 -3
  15. sqlspec/builder/_ddl.py +5 -1
  16. sqlspec/builder/_delete.py +5 -6
  17. sqlspec/builder/_insert.py +6 -7
  18. sqlspec/builder/_merge.py +5 -5
  19. sqlspec/builder/_parsing_utils.py +3 -3
  20. sqlspec/builder/_select.py +6 -5
  21. sqlspec/builder/_update.py +4 -5
  22. sqlspec/builder/mixins/_cte_and_set_ops.py +5 -1
  23. sqlspec/builder/mixins/_delete_operations.py +5 -1
  24. sqlspec/builder/mixins/_insert_operations.py +5 -1
  25. sqlspec/builder/mixins/_join_operations.py +5 -0
  26. sqlspec/builder/mixins/_merge_operations.py +5 -1
  27. sqlspec/builder/mixins/_order_limit_operations.py +5 -1
  28. sqlspec/builder/mixins/_pivot_operations.py +4 -1
  29. sqlspec/builder/mixins/_select_operations.py +5 -1
  30. sqlspec/builder/mixins/_update_operations.py +5 -1
  31. sqlspec/builder/mixins/_where_clause.py +5 -1
  32. sqlspec/cli.py +281 -33
  33. sqlspec/config.py +160 -10
  34. sqlspec/core/compiler.py +11 -3
  35. sqlspec/core/filters.py +30 -9
  36. sqlspec/core/parameters.py +67 -67
  37. sqlspec/core/result.py +62 -31
  38. sqlspec/core/splitter.py +160 -34
  39. sqlspec/core/statement.py +95 -14
  40. sqlspec/driver/_common.py +12 -3
  41. sqlspec/driver/mixins/_result_tools.py +21 -4
  42. sqlspec/driver/mixins/_sql_translator.py +45 -7
  43. sqlspec/extensions/aiosql/adapter.py +1 -1
  44. sqlspec/extensions/litestar/_utils.py +1 -1
  45. sqlspec/extensions/litestar/handlers.py +21 -0
  46. sqlspec/extensions/litestar/plugin.py +15 -8
  47. sqlspec/loader.py +12 -12
  48. sqlspec/migrations/loaders.py +5 -2
  49. sqlspec/migrations/utils.py +2 -2
  50. sqlspec/storage/backends/obstore.py +1 -3
  51. sqlspec/storage/registry.py +1 -1
  52. sqlspec/utils/__init__.py +7 -0
  53. sqlspec/utils/deprecation.py +6 -0
  54. sqlspec/utils/fixtures.py +239 -30
  55. sqlspec/utils/module_loader.py +5 -1
  56. sqlspec/utils/serializers.py +6 -0
  57. sqlspec/utils/singleton.py +6 -0
  58. sqlspec/utils/sync_tools.py +10 -1
  59. {sqlspec-0.18.0.dist-info → sqlspec-0.20.0.dist-info}/METADATA +1 -1
  60. {sqlspec-0.18.0.dist-info → sqlspec-0.20.0.dist-info}/RECORD +64 -64
  61. {sqlspec-0.18.0.dist-info → sqlspec-0.20.0.dist-info}/WHEEL +0 -0
  62. {sqlspec-0.18.0.dist-info → sqlspec-0.20.0.dist-info}/entry_points.txt +0 -0
  63. {sqlspec-0.18.0.dist-info → sqlspec-0.20.0.dist-info}/licenses/LICENSE +0 -0
  64. {sqlspec-0.18.0.dist-info → sqlspec-0.20.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,7 +1,7 @@
1
- """Safe SQL query builder with validation and parameter binding.
1
+ """INSERT statement builder.
2
2
 
3
- This module provides a fluent interface for building SQL queries safely,
4
- with automatic parameter binding and validation.
3
+ Provides a fluent interface for building SQL INSERT queries with
4
+ parameter binding and validation.
5
5
  """
6
6
 
7
7
  from typing import TYPE_CHECKING, Any, Final, Optional
@@ -31,8 +31,7 @@ ERR_MSG_EXPRESSION_NOT_INITIALIZED: Final[str] = "Internal error: base expressio
31
31
  class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSelectMixin, InsertIntoClauseMixin):
32
32
  """Builder for INSERT statements.
33
33
 
34
- This builder facilitates the construction of SQL INSERT queries
35
- in a safe and dialect-agnostic manner with automatic parameter binding.
34
+ Constructs SQL INSERT queries with parameter binding and validation.
36
35
  """
37
36
 
38
37
  __slots__ = ("_columns", "_table", "_values_added_count")
@@ -316,8 +315,8 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
316
315
  class ConflictBuilder:
317
316
  """Builder for ON CONFLICT clauses in INSERT statements.
318
317
 
319
- This builder provides a fluent interface for constructing conflict resolution
320
- clauses using PostgreSQL-style syntax, which SQLGlot can transpile to other dialects.
318
+ Constructs conflict resolution clauses using PostgreSQL-style syntax,
319
+ which SQLGlot can transpile to other dialects.
321
320
  """
322
321
 
323
322
  __slots__ = ("_columns", "_insert_builder")
sqlspec/builder/_merge.py CHANGED
@@ -1,7 +1,7 @@
1
- """Safe SQL query builder with validation and parameter binding.
1
+ """MERGE statement builder.
2
2
 
3
- This module provides a fluent interface for building SQL queries safely,
4
- with automatic parameter binding and validation.
3
+ Provides a fluent interface for building SQL MERGE queries with
4
+ parameter binding and validation.
5
5
  """
6
6
 
7
7
  from typing import Any, Optional
@@ -33,8 +33,8 @@ class Merge(
33
33
  ):
34
34
  """Builder for MERGE statements.
35
35
 
36
- This builder provides a fluent interface for constructing SQL MERGE statements
37
- (also known as UPSERT in some databases) with automatic parameter binding and validation.
36
+ Constructs SQL MERGE statements (also known as UPSERT in some databases)
37
+ with parameter binding and validation.
38
38
  """
39
39
 
40
40
  __slots__ = ()
@@ -1,7 +1,7 @@
1
- """Centralized parsing utilities for SQLSpec builders.
1
+ """Parsing utilities for SQL builders.
2
2
 
3
- This module provides common parsing functions to handle complex SQL expressions
4
- that users might pass as strings to various builder methods.
3
+ Provides common parsing functions to handle SQL expressions
4
+ passed as strings to builder methods.
5
5
  """
6
6
 
7
7
  import contextlib
@@ -1,7 +1,7 @@
1
- """Safe SQL query builder with validation and parameter binding.
1
+ """SELECT statement builder.
2
2
 
3
- This module provides a fluent interface for building SQL queries safely,
4
- with automatic parameter binding and validation.
3
+ Provides a fluent interface for building SQL SELECT queries with
4
+ parameter binding and validation.
5
5
  """
6
6
 
7
7
  import re
@@ -44,9 +44,10 @@ class Select(
44
44
  PivotClauseMixin,
45
45
  UnpivotClauseMixin,
46
46
  ):
47
- """Type-safe builder for SELECT queries with schema/model integration.
47
+ """Builder for SELECT queries.
48
48
 
49
- This builder provides a fluent, safe interface for constructing SQL SELECT statements.
49
+ Provides a fluent interface for constructing SQL SELECT statements
50
+ with parameter binding and validation.
50
51
 
51
52
  Example:
52
53
  >>> class User(BaseModel):
@@ -1,7 +1,7 @@
1
- """Safe SQL query builder with validation and parameter binding.
1
+ """UPDATE statement builder.
2
2
 
3
- This module provides a fluent interface for building SQL queries safely,
4
- with automatic parameter binding and validation.
3
+ Provides a fluent interface for building SQL UPDATE queries with
4
+ parameter binding and validation.
5
5
  """
6
6
 
7
7
  from typing import TYPE_CHECKING, Any, Optional, Union
@@ -36,8 +36,7 @@ class Update(
36
36
  ):
37
37
  """Builder for UPDATE statements.
38
38
 
39
- This builder provides a fluent interface for constructing SQL UPDATE statements
40
- with automatic parameter binding and validation.
39
+ Constructs SQL UPDATE statements with parameter binding and validation.
41
40
 
42
41
  Example:
43
42
  ```python
@@ -1,4 +1,8 @@
1
- """CTE (Common Table Expression) and Set Operations mixins for SQL builders."""
1
+ """CTE and set operation mixins.
2
+
3
+ Provides mixins for Common Table Expressions (WITH clause) and
4
+ set operations (UNION, INTERSECT, EXCEPT).
5
+ """
2
6
 
3
7
  from typing import Any, Optional, Union
4
8
 
@@ -1,4 +1,8 @@
1
- """Delete operation mixins for SQL builders."""
1
+ """DELETE operation mixins.
2
+
3
+ Provides mixins for DELETE statement functionality including
4
+ FROM clause specification.
5
+ """
2
6
 
3
7
  from typing import Optional
4
8
 
@@ -1,4 +1,8 @@
1
- """Insert operation mixins for SQL builders."""
1
+ """INSERT operation mixins.
2
+
3
+ Provides mixins for INSERT statement functionality including
4
+ INTO clauses, VALUES clauses, and INSERT FROM SELECT operations.
5
+ """
2
6
 
3
7
  from collections.abc import Sequence
4
8
  from typing import Any, Optional, TypeVar, Union
@@ -1,3 +1,8 @@
1
+ """JOIN operation mixins.
2
+
3
+ Provides mixins for JOIN operations in SELECT statements.
4
+ """
5
+
1
6
  from typing import TYPE_CHECKING, Any, Optional, Union, cast
2
7
 
3
8
  from mypy_extensions import trait
@@ -1,4 +1,8 @@
1
- """Merge operation mixins for SQL builders."""
1
+ """MERGE operation mixins.
2
+
3
+ Provides mixins for MERGE statement functionality including INTO,
4
+ USING, ON, WHEN MATCHED, and WHEN NOT MATCHED clauses.
5
+ """
2
6
 
3
7
  from typing import Any, Optional, Union
4
8
 
@@ -1,4 +1,8 @@
1
- """Order, Limit, Offset and Returning operations mixins for SQL builders."""
1
+ """ORDER BY, LIMIT, OFFSET, and RETURNING clause mixins.
2
+
3
+ Provides mixins for query result ordering, limiting, and result
4
+ returning functionality.
5
+ """
2
6
 
3
7
  from typing import TYPE_CHECKING, Optional, Union, cast
4
8
 
@@ -1,4 +1,7 @@
1
- """Pivot and Unpivot operations mixins for SQL builders."""
1
+ """PIVOT and UNPIVOT operation mixins.
2
+
3
+ Provides mixins for PIVOT and UNPIVOT operations in SELECT statements.
4
+ """
2
5
 
3
6
  from typing import TYPE_CHECKING, Optional, Union, cast
4
7
 
@@ -1,4 +1,8 @@
1
- """SELECT clause mixins consolidated into a single module."""
1
+ """SELECT clause mixins.
2
+
3
+ Provides mixins for SELECT statement functionality including column selection,
4
+ CASE expressions, subqueries, and window functions.
5
+ """
2
6
 
3
7
  from typing import TYPE_CHECKING, Any, Optional, Union, cast
4
8
 
@@ -1,4 +1,8 @@
1
- """Update operation mixins for SQL builders."""
1
+ """UPDATE operation mixins.
2
+
3
+ Provides mixins for UPDATE statement functionality including
4
+ table specification, SET clauses, and FROM clauses.
5
+ """
2
6
 
3
7
  from collections.abc import Mapping
4
8
  from typing import Any, Optional, Union, cast
@@ -1,5 +1,9 @@
1
1
  # ruff: noqa: PLR2004
2
- """Consolidated WHERE and HAVING clause mixins."""
2
+ """WHERE and HAVING clause mixins.
3
+
4
+ Provides mixins for WHERE and HAVING clause functionality with
5
+ parameter binding and various condition operators.
6
+ """
3
7
 
4
8
  from typing import TYPE_CHECKING, Any, Optional, Union, cast
5
9
 
sqlspec/cli.py CHANGED
@@ -1,3 +1,4 @@
1
+ # ruff: noqa: C901
1
2
  import sys
2
3
  from collections.abc import Sequence
3
4
  from typing import TYPE_CHECKING, Any, Optional, Union, cast
@@ -99,6 +100,15 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
99
100
  show_default=True,
100
101
  is_flag=True,
101
102
  )
103
+ include_option = click.option(
104
+ "--include", multiple=True, help="Include only specific configurations (can be used multiple times)"
105
+ )
106
+ exclude_option = click.option(
107
+ "--exclude", multiple=True, help="Exclude specific configurations (can be used multiple times)"
108
+ )
109
+ dry_run_option = click.option(
110
+ "--dry-run", is_flag=True, default=False, help="Show what would be executed without making changes"
111
+ )
102
112
 
103
113
  def get_config_by_bind_key(
104
114
  ctx: "click.Context", bind_key: Optional[str]
@@ -114,35 +124,180 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
114
124
  """
115
125
  configs = ctx.obj["configs"]
116
126
  if bind_key is None:
117
- return cast("Union[AsyncDatabaseConfig[Any, Any, Any], SyncDatabaseConfig[Any, Any, Any]]", configs[0])
127
+ config = configs[0]
128
+ else:
129
+ config = None
130
+ for cfg in configs:
131
+ config_name = getattr(cfg, "name", None) or getattr(cfg, "bind_key", None)
132
+ if config_name == bind_key:
133
+ config = cfg
134
+ break
135
+
136
+ if config is None:
137
+ console.print(f"[red]No config found for bind key: {bind_key}[/]")
138
+ sys.exit(1)
139
+
140
+ # Extract the actual config from DatabaseConfig wrapper if needed
141
+ from sqlspec.extensions.litestar.config import DatabaseConfig
142
+
143
+ if isinstance(config, DatabaseConfig):
144
+ config = config.config
145
+
146
+ return cast("Union[AsyncDatabaseConfig[Any, Any, Any], SyncDatabaseConfig[Any, Any, Any]]", config)
147
+
148
+ def get_configs_with_migrations(ctx: "click.Context", enabled_only: bool = False) -> "list[tuple[str, Any]]":
149
+ """Get all configurations that have migrations enabled.
150
+
151
+ Args:
152
+ ctx: The click context.
153
+ enabled_only: If True, only return configs with enabled=True.
154
+
155
+ Returns:
156
+ List of tuples (config_name, config) for configs with migrations enabled.
157
+ """
158
+ configs = ctx.obj["configs"]
159
+ migration_configs = []
160
+
161
+ from sqlspec.extensions.litestar.config import DatabaseConfig
118
162
 
119
163
  for config in configs:
120
- config_name = getattr(config, "name", None) or getattr(config, "bind_key", None)
121
- if config_name == bind_key:
122
- return cast("Union[AsyncDatabaseConfig[Any, Any, Any], SyncDatabaseConfig[Any, Any, Any]]", config)
164
+ # Extract the actual config from DatabaseConfig wrapper if needed
165
+ actual_config = config.config if isinstance(config, DatabaseConfig) else config
166
+
167
+ migration_config = getattr(actual_config, "migration_config", None)
168
+ if migration_config:
169
+ enabled = migration_config.get("enabled", True)
170
+ 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
+ )
176
+ migration_configs.append((config_name, actual_config))
177
+
178
+ return migration_configs
179
+
180
+ def filter_configs(
181
+ configs: "list[tuple[str, Any]]", include: "tuple[str, ...]", exclude: "tuple[str, ...]"
182
+ ) -> "list[tuple[str, Any]]":
183
+ """Filter configuration list based on include/exclude criteria.
184
+
185
+ Args:
186
+ configs: List of (config_name, config) tuples.
187
+ include: Config names to include (empty means include all).
188
+ exclude: Config names to exclude.
189
+
190
+ Returns:
191
+ Filtered list of configurations.
192
+ """
193
+ filtered = configs
194
+ if include:
195
+ filtered = [(name, config) for name, config in filtered if name in include]
196
+ if exclude:
197
+ filtered = [(name, config) for name, config in filtered if name not in exclude]
198
+ return filtered
199
+
200
+ def process_multiple_configs(
201
+ ctx: "click.Context",
202
+ bind_key: Optional[str],
203
+ include: "tuple[str, ...]",
204
+ exclude: "tuple[str, ...]",
205
+ dry_run: bool,
206
+ operation_name: str,
207
+ ) -> "Optional[list[tuple[str, Any]]]":
208
+ """Process configuration selection for multi-config operations.
209
+
210
+ Args:
211
+ ctx: Click context.
212
+ bind_key: Specific bind key to target.
213
+ include: Config names to include.
214
+ exclude: Config names to exclude.
215
+ dry_run: Whether this is a dry run.
216
+ operation_name: Name of the operation for display.
217
+
218
+ Returns:
219
+ List of (config_name, config) tuples to process, or None for single config mode.
220
+ """
221
+ # If specific bind_key requested, use single config mode
222
+ if bind_key and not include and not exclude:
223
+ return None
224
+
225
+ # Get enabled configs by default, all configs if include/exclude specified
226
+ enabled_only = not include and not exclude
227
+ migration_configs = get_configs_with_migrations(ctx, enabled_only=enabled_only)
228
+
229
+ # If only one config and no filtering, use single config mode
230
+ if len(migration_configs) <= 1 and not include and not exclude:
231
+ return None
232
+
233
+ # Apply filtering
234
+ configs_to_process = filter_configs(migration_configs, include, exclude)
235
+
236
+ if not configs_to_process:
237
+ console.print("[yellow]No configurations match the specified criteria.[/]")
238
+ return []
123
239
 
124
- console.print(f"[red]No config found for bind key: {bind_key}[/]")
125
- sys.exit(1)
240
+ # Show what will be processed
241
+ if dry_run:
242
+ console.print(f"[blue]Dry run: Would {operation_name} {len(configs_to_process)} configuration(s)[/]")
243
+ for config_name, _ in configs_to_process:
244
+ console.print(f" • {config_name}")
245
+ return []
246
+
247
+ return configs_to_process
126
248
 
127
249
  @database_group.command(name="show-current-revision", help="Shows the current revision for the database.")
128
250
  @bind_key_option
129
251
  @verbose_option
130
- def show_database_revision(bind_key: Optional[str], verbose: bool) -> None: # pyright: ignore[reportUnusedFunction]
252
+ @include_option
253
+ @exclude_option
254
+ def show_database_revision( # pyright: ignore[reportUnusedFunction]
255
+ bind_key: Optional[str], verbose: bool, include: "tuple[str, ...]", exclude: "tuple[str, ...]"
256
+ ) -> None:
131
257
  """Show current database revision."""
132
258
  from sqlspec.migrations.commands import MigrationCommands
133
259
 
134
260
  ctx = click.get_current_context()
135
- console.rule("[yellow]Listing current revision[/]", align="left")
136
- sqlspec_config = get_config_by_bind_key(ctx, bind_key)
137
- migration_commands = MigrationCommands(config=sqlspec_config)
138
- migration_commands.current(verbose=verbose)
261
+
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
+ )
266
+
267
+ if configs_to_process is not None:
268
+ if not configs_to_process:
269
+ return
270
+
271
+ console.rule("[yellow]Listing current revisions for all configurations[/]", align="left")
272
+
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)
139
286
 
140
287
  @database_group.command(name="downgrade", help="Downgrade database to a specific revision.")
141
288
  @bind_key_option
142
289
  @no_prompt_option
290
+ @include_option
291
+ @exclude_option
292
+ @dry_run_option
143
293
  @click.argument("revision", type=str, default="-1")
144
294
  def downgrade_database( # pyright: ignore[reportUnusedFunction]
145
- bind_key: Optional[str], revision: str, no_prompt: bool
295
+ bind_key: Optional[str],
296
+ revision: str,
297
+ no_prompt: bool,
298
+ include: "tuple[str, ...]",
299
+ exclude: "tuple[str, ...]",
300
+ dry_run: bool,
146
301
  ) -> None:
147
302
  """Downgrade the database to the latest revision."""
148
303
  from rich.prompt import Confirm
@@ -150,23 +305,59 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
150
305
  from sqlspec.migrations.commands import MigrationCommands
151
306
 
152
307
  ctx = click.get_current_context()
153
- console.rule("[yellow]Starting database downgrade process[/]", align="left")
154
- input_confirmed = (
155
- True
156
- if no_prompt
157
- else Confirm.ask(f"Are you sure you want to downgrade the database to the `{revision}` revision?")
308
+
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}"
158
312
  )
159
- if input_confirmed:
160
- sqlspec_config = get_config_by_bind_key(ctx, bind_key)
161
- migration_commands = MigrationCommands(config=sqlspec_config)
162
- migration_commands.downgrade(revision=revision)
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?")
341
+ )
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)
163
346
 
164
347
  @database_group.command(name="upgrade", help="Upgrade database to a specific revision.")
165
348
  @bind_key_option
166
349
  @no_prompt_option
350
+ @include_option
351
+ @exclude_option
352
+ @dry_run_option
167
353
  @click.argument("revision", type=str, default="head")
168
354
  def upgrade_database( # pyright: ignore[reportUnusedFunction]
169
- bind_key: Optional[str], revision: str, no_prompt: bool
355
+ bind_key: Optional[str],
356
+ revision: str,
357
+ no_prompt: bool,
358
+ include: "tuple[str, ...]",
359
+ exclude: "tuple[str, ...]",
360
+ dry_run: bool,
170
361
  ) -> None:
171
362
  """Upgrade the database to the latest revision."""
172
363
  from rich.prompt import Confirm
@@ -174,16 +365,44 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
174
365
  from sqlspec.migrations.commands import MigrationCommands
175
366
 
176
367
  ctx = click.get_current_context()
177
- console.rule("[yellow]Starting database upgrade process[/]", align="left")
178
- input_confirmed = (
179
- True
180
- if no_prompt
181
- else Confirm.ask(f"[bold]Are you sure you want migrate the database to the `{revision}` revision?[/]")
368
+
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}"
182
372
  )
183
- if input_confirmed:
184
- sqlspec_config = get_config_by_bind_key(ctx, bind_key)
185
- migration_commands = MigrationCommands(config=sqlspec_config)
186
- migration_commands.upgrade(revision=revision)
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?[/]")
401
+ )
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)
187
406
 
188
407
  @database_group.command(help="Stamp the revision table with the given revision")
189
408
  @click.argument("revision", type=str)
@@ -217,10 +436,14 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
217
436
  )
218
437
  if input_confirmed:
219
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
+
220
441
  for config in configs:
221
- migration_config = getattr(config, "migration_config", {})
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", {})
222
445
  directory = migration_config.get("script_location", "migrations") if directory is None else directory
223
- migration_commands = MigrationCommands(config=config)
446
+ migration_commands = MigrationCommands(config=actual_config)
224
447
  migration_commands.init(directory=cast("str", directory), package=package)
225
448
 
226
449
  @database_group.command(name="make-migrations", help="Create a new migration revision.")
@@ -244,4 +467,29 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
244
467
  migration_commands = MigrationCommands(config=sqlspec_config)
245
468
  migration_commands.revision(message=message)
246
469
 
470
+ @database_group.command(name="show-config", help="Show all configurations with migrations enabled.")
471
+ def show_config() -> None: # pyright: ignore[reportUnusedFunction]
472
+ """Show and display all configurations with migrations enabled."""
473
+ from rich.table import Table
474
+
475
+ ctx = click.get_current_context()
476
+ migration_configs = get_configs_with_migrations(ctx)
477
+
478
+ if not migration_configs:
479
+ console.print("[yellow]No configurations with migrations detected.[/]")
480
+ return
481
+
482
+ table = Table(title="Migration Configurations")
483
+ table.add_column("Configuration Name", style="cyan")
484
+ table.add_column("Migration Path", style="blue")
485
+ table.add_column("Status", style="green")
486
+
487
+ for config_name, config in migration_configs:
488
+ migration_config = getattr(config, "migration_config", {})
489
+ script_location = migration_config.get("script_location", "migrations")
490
+ table.add_row(config_name, script_location, "Migration Enabled")
491
+
492
+ console.print(table)
493
+ console.print(f"[blue]Found {len(migration_configs)} configuration(s) with migrations enabled.[/]")
494
+
247
495
  return database_group