sqlspec 0.24.1__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.
- sqlspec/_serialization.py +223 -21
- sqlspec/_sql.py +20 -62
- sqlspec/_typing.py +11 -0
- sqlspec/adapters/adbc/config.py +8 -1
- sqlspec/adapters/adbc/data_dictionary.py +290 -0
- sqlspec/adapters/adbc/driver.py +129 -20
- sqlspec/adapters/adbc/type_converter.py +159 -0
- sqlspec/adapters/aiosqlite/config.py +3 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +117 -0
- sqlspec/adapters/aiosqlite/driver.py +17 -3
- sqlspec/adapters/asyncmy/_types.py +1 -1
- sqlspec/adapters/asyncmy/config.py +11 -8
- sqlspec/adapters/asyncmy/data_dictionary.py +122 -0
- sqlspec/adapters/asyncmy/driver.py +31 -7
- sqlspec/adapters/asyncpg/config.py +3 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +134 -0
- sqlspec/adapters/asyncpg/driver.py +19 -4
- sqlspec/adapters/bigquery/config.py +3 -0
- sqlspec/adapters/bigquery/data_dictionary.py +109 -0
- sqlspec/adapters/bigquery/driver.py +21 -3
- sqlspec/adapters/bigquery/type_converter.py +93 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/config.py +2 -0
- sqlspec/adapters/duckdb/data_dictionary.py +124 -0
- sqlspec/adapters/duckdb/driver.py +32 -5
- sqlspec/adapters/duckdb/pool.py +1 -1
- sqlspec/adapters/duckdb/type_converter.py +103 -0
- sqlspec/adapters/oracledb/config.py +6 -0
- sqlspec/adapters/oracledb/data_dictionary.py +442 -0
- sqlspec/adapters/oracledb/driver.py +68 -9
- sqlspec/adapters/oracledb/migrations.py +51 -67
- sqlspec/adapters/oracledb/type_converter.py +132 -0
- sqlspec/adapters/psqlpy/config.py +3 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +133 -0
- sqlspec/adapters/psqlpy/driver.py +23 -179
- sqlspec/adapters/psqlpy/type_converter.py +73 -0
- sqlspec/adapters/psycopg/config.py +8 -4
- sqlspec/adapters/psycopg/data_dictionary.py +257 -0
- sqlspec/adapters/psycopg/driver.py +40 -5
- sqlspec/adapters/sqlite/config.py +3 -0
- sqlspec/adapters/sqlite/data_dictionary.py +117 -0
- sqlspec/adapters/sqlite/driver.py +18 -3
- sqlspec/adapters/sqlite/pool.py +13 -4
- sqlspec/base.py +3 -4
- sqlspec/builder/_base.py +130 -48
- sqlspec/builder/_column.py +66 -24
- sqlspec/builder/_ddl.py +91 -41
- sqlspec/builder/_insert.py +40 -58
- sqlspec/builder/_parsing_utils.py +127 -12
- sqlspec/builder/_select.py +147 -2
- sqlspec/builder/_update.py +1 -1
- sqlspec/builder/mixins/_cte_and_set_ops.py +31 -23
- sqlspec/builder/mixins/_delete_operations.py +12 -7
- sqlspec/builder/mixins/_insert_operations.py +50 -36
- sqlspec/builder/mixins/_join_operations.py +15 -30
- sqlspec/builder/mixins/_merge_operations.py +210 -78
- sqlspec/builder/mixins/_order_limit_operations.py +4 -10
- sqlspec/builder/mixins/_pivot_operations.py +1 -0
- sqlspec/builder/mixins/_select_operations.py +44 -22
- sqlspec/builder/mixins/_update_operations.py +30 -37
- sqlspec/builder/mixins/_where_clause.py +52 -70
- sqlspec/cli.py +246 -140
- sqlspec/config.py +33 -19
- sqlspec/core/__init__.py +3 -2
- sqlspec/core/cache.py +298 -352
- sqlspec/core/compiler.py +61 -4
- sqlspec/core/filters.py +246 -213
- sqlspec/core/hashing.py +9 -11
- sqlspec/core/parameters.py +27 -10
- sqlspec/core/statement.py +72 -12
- sqlspec/core/type_conversion.py +234 -0
- sqlspec/driver/__init__.py +6 -3
- sqlspec/driver/_async.py +108 -5
- sqlspec/driver/_common.py +186 -17
- sqlspec/driver/_sync.py +108 -5
- sqlspec/driver/mixins/_result_tools.py +60 -7
- sqlspec/exceptions.py +5 -0
- sqlspec/loader.py +8 -9
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +153 -14
- sqlspec/migrations/commands.py +34 -96
- sqlspec/migrations/context.py +145 -0
- sqlspec/migrations/loaders.py +25 -8
- sqlspec/migrations/runner.py +352 -82
- sqlspec/storage/backends/fsspec.py +1 -0
- sqlspec/typing.py +4 -0
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/serializers.py +50 -2
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/METADATA +1 -1
- sqlspec-0.26.0.dist-info/RECORD +157 -0
- sqlspec-0.24.1.dist-info/RECORD +0 -139
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.24.1.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.
|
|
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.
|
|
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
|
-
|
|
51
|
-
if isinstance(
|
|
52
|
-
ctx.obj["configs"] =
|
|
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"] = [
|
|
55
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
268
|
-
if not configs_to_process:
|
|
269
|
-
return
|
|
316
|
+
console.rule("[yellow]Listing current revisions for all configurations[/]", align="left")
|
|
270
317
|
|
|
271
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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.
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.[/]")
|