sqlspec 0.18.0__py3-none-any.whl → 0.19.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/base.py +18 -14
- sqlspec/cli.py +281 -33
- sqlspec/config.py +151 -1
- sqlspec/extensions/litestar/plugin.py +15 -8
- {sqlspec-0.18.0.dist-info → sqlspec-0.19.0.dist-info}/METADATA +1 -1
- {sqlspec-0.18.0.dist-info → sqlspec-0.19.0.dist-info}/RECORD +10 -10
- {sqlspec-0.18.0.dist-info → sqlspec-0.19.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.18.0.dist-info → sqlspec-0.19.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.18.0.dist-info → sqlspec-0.19.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.18.0.dist-info → sqlspec-0.19.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/base.py
CHANGED
|
@@ -593,22 +593,18 @@ class SQLSpec:
|
|
|
593
593
|
)
|
|
594
594
|
)
|
|
595
595
|
|
|
596
|
-
def _ensure_sql_loader(self) -> "SQLFileLoader":
|
|
597
|
-
"""Ensure SQL loader is initialized lazily."""
|
|
598
|
-
if self._sql_loader is None:
|
|
599
|
-
from sqlspec.loader import SQLFileLoader
|
|
600
|
-
|
|
601
|
-
self._sql_loader = SQLFileLoader()
|
|
602
|
-
return self._sql_loader
|
|
603
|
-
|
|
604
596
|
def load_sql_files(self, *paths: "Union[str, Path]") -> None:
|
|
605
597
|
"""Load SQL files from paths or directories.
|
|
606
598
|
|
|
607
599
|
Args:
|
|
608
600
|
*paths: One or more file paths or directory paths to load.
|
|
609
601
|
"""
|
|
610
|
-
|
|
611
|
-
|
|
602
|
+
if self._sql_loader is None:
|
|
603
|
+
from sqlspec.loader import SQLFileLoader
|
|
604
|
+
|
|
605
|
+
self._sql_loader = SQLFileLoader()
|
|
606
|
+
|
|
607
|
+
self._sql_loader.load_sql(*paths)
|
|
612
608
|
logger.debug("Loaded SQL files: %s", paths)
|
|
613
609
|
|
|
614
610
|
def add_named_sql(self, name: str, sql: str, dialect: "Optional[str]" = None) -> None:
|
|
@@ -619,8 +615,12 @@ class SQLSpec:
|
|
|
619
615
|
sql: Raw SQL content.
|
|
620
616
|
dialect: Optional dialect for the SQL statement.
|
|
621
617
|
"""
|
|
622
|
-
|
|
623
|
-
|
|
618
|
+
if self._sql_loader is None:
|
|
619
|
+
from sqlspec.loader import SQLFileLoader
|
|
620
|
+
|
|
621
|
+
self._sql_loader = SQLFileLoader()
|
|
622
|
+
|
|
623
|
+
self._sql_loader.add_named_sql(name, sql, dialect)
|
|
624
624
|
logger.debug("Added named SQL: %s", name)
|
|
625
625
|
|
|
626
626
|
def get_sql(self, name: str) -> "SQL":
|
|
@@ -633,8 +633,12 @@ class SQLSpec:
|
|
|
633
633
|
Returns:
|
|
634
634
|
SQL object ready for execution.
|
|
635
635
|
"""
|
|
636
|
-
|
|
637
|
-
|
|
636
|
+
if self._sql_loader is None:
|
|
637
|
+
from sqlspec.loader import SQLFileLoader
|
|
638
|
+
|
|
639
|
+
self._sql_loader = SQLFileLoader()
|
|
640
|
+
|
|
641
|
+
return self._sql_loader.get_sql(name)
|
|
638
642
|
|
|
639
643
|
def list_sql_queries(self) -> "list[str]":
|
|
640
644
|
"""List all available query names.
|
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
|
-
|
|
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
|
-
|
|
121
|
-
if
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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],
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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],
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
sqlspec/config.py
CHANGED
|
@@ -11,8 +11,11 @@ from sqlspec.utils.logging import get_logger
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from collections.abc import Awaitable
|
|
13
13
|
from contextlib import AbstractAsyncContextManager, AbstractContextManager
|
|
14
|
+
from pathlib import Path
|
|
14
15
|
|
|
15
16
|
from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
|
|
17
|
+
from sqlspec.loader import SQLFileLoader
|
|
18
|
+
from sqlspec.migrations.commands import MigrationCommands
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
__all__ = (
|
|
@@ -76,11 +79,24 @@ class MigrationConfig(TypedDict, total=False):
|
|
|
76
79
|
project_root: NotRequired[str]
|
|
77
80
|
"""Path to the project root directory. Used for relative path resolution."""
|
|
78
81
|
|
|
82
|
+
enabled: NotRequired[bool]
|
|
83
|
+
"""Whether this configuration should be included in CLI operations. Defaults to True."""
|
|
84
|
+
|
|
79
85
|
|
|
80
86
|
class DatabaseConfigProtocol(ABC, Generic[ConnectionT, PoolT, DriverT]):
|
|
81
87
|
"""Protocol defining the interface for database configurations."""
|
|
82
88
|
|
|
83
|
-
__slots__ = (
|
|
89
|
+
__slots__ = (
|
|
90
|
+
"_migration_commands",
|
|
91
|
+
"_migration_loader",
|
|
92
|
+
"driver_features",
|
|
93
|
+
"migration_config",
|
|
94
|
+
"pool_instance",
|
|
95
|
+
"statement_config",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
_migration_loader: "SQLFileLoader"
|
|
99
|
+
_migration_commands: "MigrationCommands"
|
|
84
100
|
driver_type: "ClassVar[type[Any]]"
|
|
85
101
|
connection_type: "ClassVar[type[Any]]"
|
|
86
102
|
is_async: "ClassVar[bool]" = False
|
|
@@ -153,6 +169,136 @@ class DatabaseConfigProtocol(ABC, Generic[ConnectionT, PoolT, DriverT]):
|
|
|
153
169
|
"""
|
|
154
170
|
return {}
|
|
155
171
|
|
|
172
|
+
def _initialize_migration_components(self) -> None:
|
|
173
|
+
"""Initialize migration loader and commands with necessary imports.
|
|
174
|
+
|
|
175
|
+
This method handles the circular import between config and commands
|
|
176
|
+
by importing at runtime when needed.
|
|
177
|
+
"""
|
|
178
|
+
from sqlspec.loader import SQLFileLoader
|
|
179
|
+
from sqlspec.migrations.commands import MigrationCommands
|
|
180
|
+
|
|
181
|
+
self._migration_loader = SQLFileLoader()
|
|
182
|
+
self._migration_commands = MigrationCommands(self) # type: ignore[arg-type]
|
|
183
|
+
|
|
184
|
+
def _ensure_migration_loader(self) -> "SQLFileLoader":
|
|
185
|
+
"""Get the migration SQL loader and auto-load files if needed.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
The SQLFileLoader instance for migration files.
|
|
189
|
+
"""
|
|
190
|
+
# Auto-load migration files from configured migration path if it exists
|
|
191
|
+
migration_config = self.migration_config or {}
|
|
192
|
+
script_location = migration_config.get("script_location", "migrations")
|
|
193
|
+
|
|
194
|
+
from pathlib import Path
|
|
195
|
+
|
|
196
|
+
migration_path = Path(script_location)
|
|
197
|
+
if migration_path.exists() and not self._migration_loader.list_files():
|
|
198
|
+
self._migration_loader.load_sql(migration_path)
|
|
199
|
+
logger.debug("Auto-loaded migration SQL files from %s", migration_path)
|
|
200
|
+
|
|
201
|
+
return self._migration_loader
|
|
202
|
+
|
|
203
|
+
def _ensure_migration_commands(self) -> "MigrationCommands":
|
|
204
|
+
"""Get the migration commands instance.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
The MigrationCommands instance for this config.
|
|
208
|
+
"""
|
|
209
|
+
return self._migration_commands
|
|
210
|
+
|
|
211
|
+
def get_migration_loader(self) -> "SQLFileLoader":
|
|
212
|
+
"""Get the SQL loader for migration files.
|
|
213
|
+
|
|
214
|
+
This provides access to migration SQL files loaded from the configured
|
|
215
|
+
script_location directory. Files are loaded lazily on first access.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
SQLFileLoader instance with migration files loaded.
|
|
219
|
+
"""
|
|
220
|
+
return self._ensure_migration_loader()
|
|
221
|
+
|
|
222
|
+
def load_migration_sql_files(self, *paths: "Union[str, Path]") -> None:
|
|
223
|
+
"""Load additional migration SQL files from specified paths.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
*paths: One or more file paths or directory paths to load migration SQL files from.
|
|
227
|
+
"""
|
|
228
|
+
from pathlib import Path
|
|
229
|
+
|
|
230
|
+
loader = self._ensure_migration_loader()
|
|
231
|
+
for path in paths:
|
|
232
|
+
path_obj = Path(path)
|
|
233
|
+
if path_obj.exists():
|
|
234
|
+
loader.load_sql(path_obj)
|
|
235
|
+
logger.debug("Loaded migration SQL files from %s", path_obj)
|
|
236
|
+
else:
|
|
237
|
+
logger.warning("Migration path does not exist: %s", path_obj)
|
|
238
|
+
|
|
239
|
+
def get_migration_commands(self) -> "MigrationCommands":
|
|
240
|
+
"""Get migration commands for this configuration.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
MigrationCommands instance configured for this database.
|
|
244
|
+
"""
|
|
245
|
+
return self._ensure_migration_commands()
|
|
246
|
+
|
|
247
|
+
def migrate_up(self, revision: str = "head") -> None:
|
|
248
|
+
"""Apply migrations up to the specified revision.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
revision: Target revision or "head" for latest. Defaults to "head".
|
|
252
|
+
"""
|
|
253
|
+
commands = self._ensure_migration_commands()
|
|
254
|
+
commands.upgrade(revision)
|
|
255
|
+
|
|
256
|
+
def migrate_down(self, revision: str = "-1") -> None:
|
|
257
|
+
"""Apply migrations down to the specified revision.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
revision: Target revision, "-1" for one step back, or "base" for all migrations. Defaults to "-1".
|
|
261
|
+
"""
|
|
262
|
+
commands = self._ensure_migration_commands()
|
|
263
|
+
commands.downgrade(revision)
|
|
264
|
+
|
|
265
|
+
def get_current_migration(self, verbose: bool = False) -> "Optional[str]":
|
|
266
|
+
"""Get the current migration version.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
verbose: Whether to show detailed migration history.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
The current migration version or None if no migrations applied.
|
|
273
|
+
"""
|
|
274
|
+
commands = self._ensure_migration_commands()
|
|
275
|
+
return commands.current(verbose=verbose)
|
|
276
|
+
|
|
277
|
+
def create_migration(self, message: str, file_type: str = "sql") -> None:
|
|
278
|
+
"""Create a new migration file.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
message: Description for the migration.
|
|
282
|
+
file_type: Type of migration file to create ('sql' or 'py'). Defaults to 'sql'.
|
|
283
|
+
"""
|
|
284
|
+
commands = self._ensure_migration_commands()
|
|
285
|
+
commands.revision(message, file_type)
|
|
286
|
+
|
|
287
|
+
def init_migrations(self, directory: "Optional[str]" = None, package: bool = True) -> None:
|
|
288
|
+
"""Initialize migration directory structure.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
directory: Directory to initialize migrations in. Uses script_location from migration_config if not provided.
|
|
292
|
+
package: Whether to create __init__.py file. Defaults to True.
|
|
293
|
+
"""
|
|
294
|
+
if directory is None:
|
|
295
|
+
migration_config = self.migration_config or {}
|
|
296
|
+
directory = migration_config.get("script_location") or "migrations"
|
|
297
|
+
|
|
298
|
+
commands = self._ensure_migration_commands()
|
|
299
|
+
assert directory is not None
|
|
300
|
+
commands.init(directory, package)
|
|
301
|
+
|
|
156
302
|
|
|
157
303
|
class NoPoolSyncConfig(DatabaseConfigProtocol[ConnectionT, None, DriverT]):
|
|
158
304
|
"""Base class for a sync database configurations that do not implement a pool."""
|
|
@@ -173,6 +319,7 @@ class NoPoolSyncConfig(DatabaseConfigProtocol[ConnectionT, None, DriverT]):
|
|
|
173
319
|
self.pool_instance = None
|
|
174
320
|
self.connection_config = connection_config or {}
|
|
175
321
|
self.migration_config: Union[dict[str, Any], MigrationConfig] = migration_config or {}
|
|
322
|
+
self._initialize_migration_components()
|
|
176
323
|
|
|
177
324
|
if statement_config is None:
|
|
178
325
|
default_parameter_config = ParameterStyleConfig(
|
|
@@ -226,6 +373,7 @@ class NoPoolAsyncConfig(DatabaseConfigProtocol[ConnectionT, None, DriverT]):
|
|
|
226
373
|
self.pool_instance = None
|
|
227
374
|
self.connection_config = connection_config or {}
|
|
228
375
|
self.migration_config: Union[dict[str, Any], MigrationConfig] = migration_config or {}
|
|
376
|
+
self._initialize_migration_components()
|
|
229
377
|
|
|
230
378
|
if statement_config is None:
|
|
231
379
|
default_parameter_config = ParameterStyleConfig(
|
|
@@ -280,6 +428,7 @@ class SyncDatabaseConfig(DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]):
|
|
|
280
428
|
self.pool_instance = pool_instance
|
|
281
429
|
self.pool_config = pool_config or {}
|
|
282
430
|
self.migration_config: Union[dict[str, Any], MigrationConfig] = migration_config or {}
|
|
431
|
+
self._initialize_migration_components()
|
|
283
432
|
|
|
284
433
|
if statement_config is None:
|
|
285
434
|
default_parameter_config = ParameterStyleConfig(
|
|
@@ -356,6 +505,7 @@ class AsyncDatabaseConfig(DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]):
|
|
|
356
505
|
self.pool_instance = pool_instance
|
|
357
506
|
self.pool_config = pool_config or {}
|
|
358
507
|
self.migration_config: Union[dict[str, Any], MigrationConfig] = migration_config or {}
|
|
508
|
+
self._initialize_migration_components()
|
|
359
509
|
|
|
360
510
|
if statement_config is None:
|
|
361
511
|
self.statement_config = StatementConfig(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING,
|
|
1
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
2
2
|
|
|
3
3
|
from litestar.di import Provide
|
|
4
4
|
from litestar.plugins import CLIPlugin, InitPluginProtocol
|
|
@@ -14,23 +14,31 @@ if TYPE_CHECKING:
|
|
|
14
14
|
from click import Group
|
|
15
15
|
from litestar.config.app import AppConfig
|
|
16
16
|
|
|
17
|
+
from sqlspec.loader import SQLFileLoader
|
|
18
|
+
|
|
17
19
|
logger = get_logger("extensions.litestar")
|
|
18
20
|
|
|
19
21
|
|
|
20
|
-
class SQLSpec(InitPluginProtocol, CLIPlugin
|
|
22
|
+
class SQLSpec(SQLSpecBase, InitPluginProtocol, CLIPlugin):
|
|
21
23
|
"""Litestar plugin for SQLSpec database integration."""
|
|
22
24
|
|
|
23
|
-
__slots__ = ("
|
|
25
|
+
__slots__ = ("_plugin_configs",)
|
|
24
26
|
|
|
25
|
-
def __init__(
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
config: Union["SyncConfigT", "AsyncConfigT", "DatabaseConfig", list["DatabaseConfig"]],
|
|
30
|
+
*,
|
|
31
|
+
loader: "Optional[SQLFileLoader]" = None,
|
|
32
|
+
) -> None:
|
|
26
33
|
"""Initialize SQLSpec plugin.
|
|
27
34
|
|
|
28
35
|
Args:
|
|
29
36
|
config: Database configuration for SQLSpec plugin.
|
|
37
|
+
loader: Optional SQL file loader instance.
|
|
30
38
|
"""
|
|
31
|
-
|
|
39
|
+
super().__init__(loader=loader)
|
|
32
40
|
if isinstance(config, DatabaseConfigProtocol):
|
|
33
|
-
self._plugin_configs: list[DatabaseConfig] = [DatabaseConfig(config=config)]
|
|
41
|
+
self._plugin_configs: list[DatabaseConfig] = [DatabaseConfig(config=config)] # pyright: ignore
|
|
34
42
|
elif isinstance(config, DatabaseConfig):
|
|
35
43
|
self._plugin_configs = [config]
|
|
36
44
|
else:
|
|
@@ -83,8 +91,7 @@ class SQLSpec(InitPluginProtocol, CLIPlugin, SQLSpecBase):
|
|
|
83
91
|
app_config.signature_types.append(c.config.connection_type) # type: ignore[union-attr]
|
|
84
92
|
app_config.signature_types.append(c.config.driver_type) # type: ignore[union-attr]
|
|
85
93
|
|
|
86
|
-
|
|
87
|
-
signature_namespace.update(c.config.get_signature_namespace()) # type: ignore[attr-defined]
|
|
94
|
+
signature_namespace.update(c.config.get_signature_namespace()) # type: ignore[union-attr]
|
|
88
95
|
|
|
89
96
|
app_config.before_send.append(c.before_send_handler)
|
|
90
97
|
app_config.lifespan.append(c.lifespan_handler) # pyright: ignore[reportUnknownMemberType]
|
|
@@ -4,9 +4,9 @@ sqlspec/__metadata__.py,sha256=IUw6MCTy1oeUJ1jAVYbuJLkOWbiAWorZ5W-E-SAD9N4,395
|
|
|
4
4
|
sqlspec/_serialization.py,sha256=6U5-smk2h2yl0i6am2prtOLJTdu4NJQdcLlSfSUMaUQ,2590
|
|
5
5
|
sqlspec/_sql.py,sha256=j9WljOgCme4jTfL6NegEWOhK-Rr3JEmhtbneh8ZN1bQ,45221
|
|
6
6
|
sqlspec/_typing.py,sha256=jv-7QHGLrJLfnP86bR-Xcmj3PDoddNZEKDz_vYRBiAU,22684
|
|
7
|
-
sqlspec/base.py,sha256
|
|
8
|
-
sqlspec/cli.py,sha256=
|
|
9
|
-
sqlspec/config.py,sha256=
|
|
7
|
+
sqlspec/base.py,sha256=ajQUBYz2NhDFZ8cMJ2nSNuNWT73mfRcaId08n74YR88,25468
|
|
8
|
+
sqlspec/cli.py,sha256=Fe5Wbnrb_fkE9qm4gbBEXx3d0Q7VR-S-1t76ouAx2mg,20120
|
|
9
|
+
sqlspec/config.py,sha256=cQGBxaUACaiIhhZ9GOQ6DvNMsrVowR212_13gHdLMvc,21620
|
|
10
10
|
sqlspec/exceptions.py,sha256=zBnzQOfYAgqX04GoaC9Io6ardzinldkEuZ3YtR5vr9U,6071
|
|
11
11
|
sqlspec/loader.py,sha256=HDfMZDj7l9aLAxeZR2Rv-HC-dWah4CPkwZ6HKQ7yP-Y,23398
|
|
12
12
|
sqlspec/protocols.py,sha256=Of6uJyxvawExCEyR3u7jbxOckUcwG0HHOEXmfHyev40,13106
|
|
@@ -103,7 +103,7 @@ sqlspec/extensions/litestar/_utils.py,sha256=o-FuUj1_WkDrLxQxiP6hXDak66XfyRP3QLy
|
|
|
103
103
|
sqlspec/extensions/litestar/cli.py,sha256=X4DlAx3Ry-ccOjAQSxe8SMtyJKCFJVLTbENPU_efKuU,1356
|
|
104
104
|
sqlspec/extensions/litestar/config.py,sha256=3UI_vhtbupCLsf1nhUgUpRlCoUS5c0GsAjWvegT0c3c,4462
|
|
105
105
|
sqlspec/extensions/litestar/handlers.py,sha256=3LreU8rZvuHaJnKlN09ttu4wSorWJedsuKgeLT-cOEc,9993
|
|
106
|
-
sqlspec/extensions/litestar/plugin.py,sha256=
|
|
106
|
+
sqlspec/extensions/litestar/plugin.py,sha256=u4Mjq16EYb4oxzbONCLdQY2P40Po-e8wQXnVnJKXGCA,5732
|
|
107
107
|
sqlspec/extensions/litestar/providers.py,sha256=5LRb5JvRV_XZdNOKkdaIy3j5x-dFCcAi1ea1pgwuapI,18882
|
|
108
108
|
sqlspec/migrations/__init__.py,sha256=RiDi_HkUIgXtu_33QnRdvYNqcCn-euHUiWwTiPr5IGc,1055
|
|
109
109
|
sqlspec/migrations/base.py,sha256=vIzQzUtQrNKDec6XUeRHcCBuWU1KNtRCFpOvVxsp3sQ,13093
|
|
@@ -130,9 +130,9 @@ sqlspec/utils/singleton.py,sha256=SKnszJi1NPeERgX7IjVIGYAYx4XqR1E_rph3bU6olAU,10
|
|
|
130
130
|
sqlspec/utils/sync_tools.py,sha256=f_bjHTXUaDcsNQY63-L7j5xn2MHfzXro-ShzJM-WLUI,8356
|
|
131
131
|
sqlspec/utils/text.py,sha256=W97aX77A3NzG795AHjhdX6zqOBDmvLaXLCno2JIugCo,3081
|
|
132
132
|
sqlspec/utils/type_guards.py,sha256=9C4SRebO4JiQrMzcJZFUA0KjSU48G26RmX6lbijyjBg,30476
|
|
133
|
-
sqlspec-0.
|
|
134
|
-
sqlspec-0.
|
|
135
|
-
sqlspec-0.
|
|
136
|
-
sqlspec-0.
|
|
137
|
-
sqlspec-0.
|
|
138
|
-
sqlspec-0.
|
|
133
|
+
sqlspec-0.19.0.dist-info/METADATA,sha256=ax4hmcjtDgGFAQWuE3lw25tqBX-EkByjo0t3H-xnkrg,16822
|
|
134
|
+
sqlspec-0.19.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
135
|
+
sqlspec-0.19.0.dist-info/entry_points.txt,sha256=G-ZqY1Nuuw3Iys7nXw23f6ILenk_Lt47VdK2mhJCWHg,53
|
|
136
|
+
sqlspec-0.19.0.dist-info/licenses/LICENSE,sha256=MdujfZ6l5HuLz4mElxlu049itenOR3gnhN1_Nd3nVcM,1078
|
|
137
|
+
sqlspec-0.19.0.dist-info/licenses/NOTICE,sha256=Lyir8ozXWov7CyYS4huVaOCNrtgL17P-bNV-5daLntQ,1634
|
|
138
|
+
sqlspec-0.19.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|