sqlseed 0.2.1__tar.gz → 0.2.3__tar.gz

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.
Files changed (75) hide show
  1. {sqlseed-0.2.1 → sqlseed-0.2.3}/.gitignore +4 -0
  2. {sqlseed-0.2.1 → sqlseed-0.2.3}/PKG-INFO +3 -3
  3. {sqlseed-0.2.1 → sqlseed-0.2.3}/README.md +2 -2
  4. {sqlseed-0.2.1 → sqlseed-0.2.3}/README.zh-CN.md +5 -5
  5. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/07-ai-plugin.ipynb +12 -19
  6. sqlseed-0.2.3/examples/quick_demo.py +148 -0
  7. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/cli/main.py +228 -43
  8. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/config/models.py +5 -1
  9. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/mapper.py +5 -2
  10. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/generators/stream.py +1 -2
  11. {sqlseed-0.2.1 → sqlseed-0.2.3}/CHANGELOG.md +0 -0
  12. {sqlseed-0.2.1 → sqlseed-0.2.3}/CHANGELOG.zh-CN.md +0 -0
  13. {sqlseed-0.2.1 → sqlseed-0.2.3}/LICENSE +0 -0
  14. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/build_demo_db.py +0 -0
  15. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/01-quickstart.ipynb +0 -0
  16. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/02-column-mapping.ipynb +0 -0
  17. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/03-generators.ipynb +0 -0
  18. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/04-database-advanced.ipynb +0 -0
  19. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/05-dag-and-constraints.ipynb +0 -0
  20. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/06-config-deep-dive.ipynb +0 -0
  21. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/08-mcp-server.ipynb +0 -0
  22. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/09-plugin-hooks.ipynb +0 -0
  23. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/10-cli-reference.ipynb +0 -0
  24. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/11-utilities.ipynb +0 -0
  25. {sqlseed-0.2.1 → sqlseed-0.2.3}/examples/notebooks/12-testing-patterns.ipynb +0 -0
  26. {sqlseed-0.2.1 → sqlseed-0.2.3}/pyproject.toml +0 -0
  27. {sqlseed-0.2.1 → sqlseed-0.2.3}/scripts/_create_demo_db.py +0 -0
  28. {sqlseed-0.2.1 → sqlseed-0.2.3}/scripts/quickstart.py +0 -0
  29. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/__init__.py +0 -0
  30. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/_utils/__init__.py +0 -0
  31. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/_utils/logger.py +0 -0
  32. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/_utils/metrics.py +0 -0
  33. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/_utils/paths.py +0 -0
  34. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/_utils/progress.py +0 -0
  35. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/_utils/schema_helpers.py +0 -0
  36. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/_utils/sql_safe.py +0 -0
  37. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/_version.py +0 -0
  38. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/cli/__init__.py +0 -0
  39. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/config/__init__.py +0 -0
  40. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/config/loader.py +0 -0
  41. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/config/snapshot.py +0 -0
  42. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/__init__.py +0 -0
  43. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/column_dag.py +0 -0
  44. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/constraints.py +0 -0
  45. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/enrichment.py +0 -0
  46. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/expression.py +0 -0
  47. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/orchestrator.py +0 -0
  48. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/plugin_mediator.py +0 -0
  49. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/relation.py +0 -0
  50. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/result.py +0 -0
  51. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/schema.py +0 -0
  52. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/transform.py +0 -0
  53. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/core/unique_adjuster.py +0 -0
  54. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/database/__init__.py +0 -0
  55. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/database/_base_adapter.py +0 -0
  56. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/database/_compat.py +2 -2
  57. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/database/_helpers.py +0 -0
  58. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/database/_protocol.py +0 -0
  59. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/database/optimizer.py +0 -0
  60. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/database/raw_sqlite_adapter.py +0 -0
  61. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/database/sqlite_utils_adapter.py +0 -0
  62. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/generators/__init__.py +0 -0
  63. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/generators/_dispatch.py +0 -0
  64. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/generators/_json_helpers.py +0 -0
  65. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/generators/_protocol.py +0 -0
  66. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/generators/_string_helpers.py +0 -0
  67. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/generators/base_provider.py +0 -0
  68. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/generators/faker_provider.py +0 -0
  69. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/generators/mimesis_provider.py +0 -0
  70. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/generators/registry.py +0 -0
  71. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/plugins/__init__.py +0 -0
  72. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/plugins/hookspecs.py +0 -0
  73. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/plugins/manager.py +0 -0
  74. {sqlseed-0.2.1 → sqlseed-0.2.3}/src/sqlseed/py.typed +0 -0
  75. {sqlseed-0.2.1 → sqlseed-0.2.3}/uv.lock +0 -0
@@ -33,6 +33,7 @@ ENV/
33
33
  .idea/
34
34
  .trae/
35
35
  .claude/
36
+ .gemini/
36
37
  .sonarlint/
37
38
  *.swp
38
39
  *.swo
@@ -62,6 +63,9 @@ examples/notebooks/batch_config.yaml
62
63
  *.nbconvert.ipynb
63
64
  .ipynb_checkpoints/
64
65
 
66
+ # AI-generated config outputs
67
+ *_config.yaml
68
+
65
69
  # OS
66
70
  .DS_Store
67
71
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlseed
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Declarative SQLite test data generation toolkit
5
5
  Project-URL: Homepage, https://github.com/sunbos/sqlseed
6
6
  Project-URL: Documentation, https://github.com/sunbos/sqlseed#readme
@@ -726,7 +726,7 @@ sqlseed ai-suggest app.db --table projects --output projects.yaml
726
726
  sqlseed ai-suggest app.db --table projects --output projects.yaml --verify
727
727
 
728
728
  # Specify model (defaults to Gemma 4 26B via Google AI Studio)
729
- sqlseed ai-suggest app.db --table projects --output projects.yaml --model gemma-4-26b-it
729
+ sqlseed ai-suggest app.db --table projects --output projects.yaml --model gemma-4-26b-a4b-it
730
730
 
731
731
  # Use local LM Studio / Ollama
732
732
  sqlseed ai-suggest app.db --table projects --output projects.yaml --backend lm_studio --model google/gemma-4-e4b
@@ -1079,7 +1079,7 @@ Tests cover all core modules, with path structure mirroring `src/`: `test_core/`
1079
1079
  | `sqlseed[faker]` | + faker>=30.0 | Faker data engine |
1080
1080
  | `sqlseed[mimesis]` | + mimesis>=18.0 | Mimesis data engine (recommended) |
1081
1081
  | `sqlseed[docs]` | + mkdocs-material, mkdocstrings | Documentation build |
1082
- | `sqlseed-ai` | sqlseed, **openai>=1.0**, **google-generativeai>=0.8** | AI plugin (Gemma 4 Native Function Calling), auto-registered via entry-point |
1082
+ | `sqlseed-ai` | sqlseed, **openai>=1.0** | AI plugin (Gemma 4 Native Function Calling), auto-registered via entry-point |
1083
1083
  | `mcp-server-sqlseed` | sqlseed, **mcp>=1.0** | MCP server, standalone CLI tool |
1084
1084
  | `mcp-server-sqlseed[ai]` | + sqlseed-ai | MCP server with AI support |
1085
1085
 
@@ -666,7 +666,7 @@ sqlseed ai-suggest app.db --table projects --output projects.yaml
666
666
  sqlseed ai-suggest app.db --table projects --output projects.yaml --verify
667
667
 
668
668
  # Specify model (defaults to Gemma 4 26B via Google AI Studio)
669
- sqlseed ai-suggest app.db --table projects --output projects.yaml --model gemma-4-26b-it
669
+ sqlseed ai-suggest app.db --table projects --output projects.yaml --model gemma-4-26b-a4b-it
670
670
 
671
671
  # Use local LM Studio / Ollama
672
672
  sqlseed ai-suggest app.db --table projects --output projects.yaml --backend lm_studio --model google/gemma-4-e4b
@@ -1019,7 +1019,7 @@ Tests cover all core modules, with path structure mirroring `src/`: `test_core/`
1019
1019
  | `sqlseed[faker]` | + faker>=30.0 | Faker data engine |
1020
1020
  | `sqlseed[mimesis]` | + mimesis>=18.0 | Mimesis data engine (recommended) |
1021
1021
  | `sqlseed[docs]` | + mkdocs-material, mkdocstrings | Documentation build |
1022
- | `sqlseed-ai` | sqlseed, **openai>=1.0**, **google-generativeai>=0.8** | AI plugin (Gemma 4 Native Function Calling), auto-registered via entry-point |
1022
+ | `sqlseed-ai` | sqlseed, **openai>=1.0** | AI plugin (Gemma 4 Native Function Calling), auto-registered via entry-point |
1023
1023
  | `mcp-server-sqlseed` | sqlseed, **mcp>=1.0** | MCP server, standalone CLI tool |
1024
1024
  | `mcp-server-sqlseed[ai]` | + sqlseed-ai | MCP server with AI support |
1025
1025
 
@@ -562,10 +562,10 @@ sqlseed ai-suggest app.db --table projects --output projects.yaml
562
562
  sqlseed ai-suggest app.db --table projects --output projects.yaml --verify
563
563
 
564
564
  # 指定模型(支持多后端:Google AI Studio、LM Studio、Ollama、OpenAI-compatible)
565
- sqlseed ai-suggest app.db --table projects -o projects.yaml --model gemma-4-26b-it --backend google_ai_studio
565
+ sqlseed ai-suggest app.db --table projects -o projects.yaml --model gemma-4-26b-a4b-it --backend google_ai_studio
566
566
  sqlseed ai-suggest app.db --table projects -o projects.yaml --model gemma-4-31b-it --backend google_ai_studio
567
567
  sqlseed ai-suggest app.db --table projects -o projects.yaml --model google/gemma-4-e4b --backend lm_studio
568
- sqlseed ai-suggest app.db --table projects -o projects.yaml --model gemma-4-4b-it --backend ollama
568
+ sqlseed ai-suggest app.db --table projects -o projects.yaml --model gemma-4-e4b-it --backend ollama
569
569
  ```
570
570
 
571
571
  **Gemma 4 原生函数调用(GEMMA_TOOLS)**:
@@ -697,8 +697,8 @@ sqlseed ai-suggest app.db -t users -o users.yaml --max-retries 0
697
697
  sqlseed ai-suggest app.db -t users -o users.yaml --no-cache
698
698
 
699
699
  # ═══ AI 后端选择 ═══
700
- sqlseed ai-suggest app.db -t users -o users.yaml --backend google_ai_studio --model gemma-4-26b-it
701
- sqlseed ai-suggest app.db -t users -o users.yaml --backend ollama --model gemma-4-4b-it
700
+ sqlseed ai-suggest app.db -t users -o users.yaml --backend google_ai_studio --model gemma-4-26b-a4b-it
701
+ sqlseed ai-suggest app.db -t users -o users.yaml --backend ollama --model gemma-4-e4b-it
702
702
  sqlseed ai-suggest app.db -t users -o users.yaml --backend lm_studio --model google/gemma-4-e4b
703
703
  sqlseed ai-suggest app.db -t users -o users.yaml --backend openai_compat --model your-model --base-url https://your-api-endpoint
704
704
  ```
@@ -827,7 +827,7 @@ mypy src/sqlseed/ # 类型检查
827
827
  | `sqlseed[faker]` | + faker>=30.0 | Faker 数据引擎 |
828
828
  | `sqlseed[mimesis]` | + mimesis>=18.0 | Mimesis 数据引擎(推荐) |
829
829
  | `sqlseed[docs]` | + mkdocs-material, mkdocstrings | 文档构建 |
830
- | `sqlseed-ai` | sqlseed, **openai>=1.0**, **google-generativeai>=0.8** | AI 插件,通过 entry-point 自动注册,支持 Gemma 4 GEMMA_TOOLS |
830
+ | `sqlseed-ai` | sqlseed, **openai>=1.0** | AI 插件,通过 entry-point 自动注册,支持 Gemma 4 GEMMA_TOOLS |
831
831
  | `mcp-server-sqlseed` | sqlseed, **mcp>=1.0** | MCP 服务器,独立 CLI 工具 |
832
832
  | `mcp-server-sqlseed[ai]` | + sqlseed-ai | MCP 服务器含 AI 支持 |
833
833
 
@@ -367,32 +367,25 @@
367
367
  "name": "stdout",
368
368
  "output_type": "stream",
369
369
  "text": [
370
- "偏好免费模型 (12 个):\n",
371
- " 1. nvidia/nemotron-3-super-120b-a12b:free\n",
372
- " 2. tencent/hy3-preview:free\n",
373
- " 3. inclusionai/ling-2.6-1t:free\n",
374
- " 4. inclusionai/ling-2.6-flash:free\n",
375
- " 5. z-ai/glm-4.5-air:free\n",
376
- " 6. minimax/minimax-m2.5:free\n",
377
- " 7. openai/gpt-oss-120b:free\n",
378
- " 8. nvidia/nemotron-3-nano-30b-a3b:free\n",
379
- " 9. google/gemma-4-31b-it:free\n",
380
- " 10. nvidia/nemotron-nano-9b-v2:free\n",
381
- " 11. openai/gpt-oss-20b:free\n",
382
- " 12. google/gemma-4-26b-a4b-it:free\n",
370
+ "Gemma 4 模型优先级 (5 个):\n",
371
+ " 1. gemma-4-31b-it (Gemma 4 31B Dense)\n",
372
+ " 2. gemma-4-26b-a4b-it (Gemma 4 26B A4B MoE (Recommended))\n",
373
+ " 3. gemma-4-12b-it (Gemma 4 12B Unified (Laptop))\n",
374
+ " 4. gemma-4-e4b-it (Gemma 4 E4B (4B Effective, Edge))\n",
375
+ " 5. gemma-4-e2b-it (Gemma 4 E2B (2B Effective, Edge))\n",
383
376
  "\n",
384
- "select_best_free_model() 按优先级尝试, 自动回退\n"
377
+ "select_gemma_model() 按优先级尝试, 自动回退\n"
385
378
  ]
386
379
  }
387
380
  ],
388
381
  "source": [
389
- "from sqlseed_ai._model_selector import PREFERRED_FREE_MODELS\n",
382
+ "from sqlseed_ai._model_selector import _GEMMA_MODEL_PRIORITY\n",
390
383
  "\n",
391
- "print(f'偏好免费模型 ({len(PREFERRED_FREE_MODELS)} 个):')\n",
392
- "for i, model in enumerate(PREFERRED_FREE_MODELS, 1):\n",
393
- " print(f' {i}. {model}')\n",
384
+ "print(f'Gemma 4 模型优先级 ({len(_GEMMA_MODEL_PRIORITY)} 个):')\n",
385
+ "for i, model in enumerate(_GEMMA_MODEL_PRIORITY, 1):\n",
386
+ " print(f' {i}. {model.value} ({model.display_name})')\n",
394
387
  "\n",
395
- "print('\\nselect_best_free_model() 按优先级尝试, 自动回退')\n",
388
+ "print('\\nselect_gemma_model() 按优先级尝试, 自动回退')\n",
396
389
  "\n"
397
390
  ]
398
391
  },
@@ -0,0 +1,148 @@
1
+ """
2
+ QuickStart Demo Script with Real-time Progress Display
3
+
4
+ Features:
5
+ - Creates demo database with 8 tables
6
+ - Fills data with real-time progress bars
7
+ - Shows completion stats and sample data
8
+ - Fast execution (~10 seconds total)
9
+
10
+ Usage:
11
+ python quick_demo.py
12
+
13
+ Perfect for live demonstrations!
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import sqlite3
19
+ from pathlib import Path
20
+
21
+ from build_demo_db import build as build_demo
22
+ from rich.console import Console
23
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn, TimeRemainingColumn
24
+ from rich.table import Table
25
+
26
+ from sqlseed import fill
27
+
28
+
29
+ def main():
30
+ console = Console()
31
+
32
+ # Step 1: Create database
33
+ console.print("\n[bold blue]🚀 QuickStart Demo: sqlseed SQLite Test Data Generator[/bold blue]")
34
+ console.print("=" * 60)
35
+
36
+ with Progress(
37
+ SpinnerColumn(),
38
+ TextColumn("[progress.description]{task.description}"),
39
+ console=console,
40
+ transient=False,
41
+ ) as progress:
42
+ task = progress.add_task("Creating demo database...", total=None)
43
+ db_path = str(build_demo(Path(__file__).parent / "_demo.db"))
44
+ progress.update(task, completed=1)
45
+
46
+ console.print(f"✅ Database created: [green]{db_path}[/green]")
47
+
48
+ # Step 2: Fill data with progress
49
+ console.print("\n[bold blue]📊 Filling test data with real-time progress:[/bold blue]")
50
+
51
+ fill_stats = []
52
+
53
+ with Progress(
54
+ SpinnerColumn(),
55
+ TextColumn("[progress.description]{task.description}"),
56
+ BarColumn(),
57
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
58
+ TextColumn("{task.completed}/{task.total}"),
59
+ TimeRemainingColumn(),
60
+ TimeElapsedColumn(),
61
+ console=console,
62
+ transient=False,
63
+ refresh_per_second=2,
64
+ ) as progress:
65
+ # organizations
66
+ task = progress.add_task("organizations", total=10)
67
+ result = fill(db_path, table="organizations", count=10)
68
+ fill_stats.append(("organizations", result.count, result.elapsed))
69
+ progress.update(task, completed=10)
70
+
71
+ # members
72
+ task = progress.add_task("members", total=100)
73
+ result = fill(db_path, table="members", count=100)
74
+ fill_stats.append(("members", result.count, result.elapsed))
75
+ progress.update(task, completed=100)
76
+
77
+ # projects
78
+ task = progress.add_task("projects", total=30)
79
+ result = fill(db_path, table="projects", count=30)
80
+ fill_stats.append(("projects", result.count, result.elapsed))
81
+ progress.update(task, completed=30)
82
+
83
+ # tasks
84
+ task = progress.add_task("tasks", total=200)
85
+ result = fill(db_path, table="tasks", count=200)
86
+ fill_stats.append(("tasks", result.count, result.elapsed))
87
+ progress.update(task, completed=200)
88
+
89
+ # Step 3: Show summary
90
+ console.print("\n[bold blue]📈 Fill Summary:[/bold blue]")
91
+ table = Table(show_header=True, header_style="bold green")
92
+ table.add_column("Table")
93
+ table.add_column("Rows")
94
+ table.add_column("Time (s)", justify="right")
95
+
96
+ total_rows = 0
97
+ total_time = 0.0
98
+ for table_name, count, elapsed in fill_stats:
99
+ table.add_row(table_name, str(count), f"{elapsed:.2f}")
100
+ total_rows += count
101
+ total_time += elapsed
102
+
103
+ table.add_row("[bold]Total[/bold]", str(total_rows), f"[bold]{total_time:.2f}[/bold]")
104
+ console.print(table)
105
+
106
+ # Step 4: Show sample data
107
+ console.print("\n[bold blue]🔍 Sample Data Preview:[/bold blue]")
108
+
109
+ conn = sqlite3.connect(db_path)
110
+ conn.row_factory = sqlite3.Row
111
+
112
+ # Preview members
113
+ console.print("\n[bold green]members[/bold green] (3 rows):")
114
+ cursor = conn.execute("SELECT member_id, name, email, org_code FROM members LIMIT 3")
115
+ rows = cursor.fetchall()
116
+ member_table = Table()
117
+ for col in ("member_id", "name", "email", "org_code"):
118
+ member_table.add_column(col, style="cyan")
119
+ for row in rows:
120
+ member_table.add_row(*[str(row[c]) for c in ("member_id", "name", "email", "org_code")])
121
+ console.print(member_table)
122
+
123
+ # Preview tasks
124
+ console.print("\n[bold green]tasks[/bold green] (3 rows):")
125
+ cursor = conn.execute("SELECT task_id, project_id, title, priority, status FROM tasks LIMIT 3")
126
+ rows = cursor.fetchall()
127
+ task_table = Table()
128
+ for col in ("task_id", "project_id", "title", "priority", "status"):
129
+ task_table.add_column(col, style="cyan")
130
+ for row in rows:
131
+ task_table.add_row(*[str(row[c]) for c in ("task_id", "project_id", "title", "priority", "status")])
132
+ console.print(task_table)
133
+
134
+ conn.close()
135
+
136
+ # Step 5: Final message
137
+ console.print("\n" + "=" * 60)
138
+ console.print("[bold green]🎉 Demo Complete! [/bold green]")
139
+ console.print("\nKey Features Demonstrated:")
140
+ console.print(" • Auto-schema detection")
141
+ console.print(" • Smart column type matching")
142
+ console.print(" • Foreign key integrity")
143
+ console.print(" • Real-time progress tracking")
144
+ console.print(f"\n[italic]Total time:[/italic] [bold]{total_time:.2f} seconds[/bold]")
145
+
146
+
147
+ if __name__ == "__main__":
148
+ main()
@@ -8,8 +8,9 @@ from typing import Any
8
8
  import click
9
9
  import yaml
10
10
  from rich.console import Console
11
- from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
11
+ from rich.live import Live
12
12
  from rich.table import Table as RichTable
13
+ from rich.text import Text
13
14
 
14
15
  from sqlseed import fill as api_fill
15
16
  from sqlseed import fill_from_config
@@ -21,17 +22,17 @@ from sqlseed.config.models import GeneratorConfig, ProviderType, TableConfig
21
22
  from sqlseed.config.snapshot import SnapshotManager
22
23
  from sqlseed.core.orchestrator import DataOrchestrator
23
24
 
24
- logger = get_logger(__name__)
25
-
26
25
  try:
27
26
  from sqlseed_ai.analyzer import SchemaAnalyzer
28
- from sqlseed_ai.config import AIConfig
29
- from sqlseed_ai.refiner import AiConfigRefiner, AISuggestionFailedError
27
+ from sqlseed_ai.config import AIBackend, AIConfig
28
+ from sqlseed_ai.refiner import AiConfigRefiner
30
29
 
31
30
  HAS_AI_PLUGIN = True
32
31
  except ImportError:
33
32
  HAS_AI_PLUGIN = False
34
33
 
34
+ logger = get_logger(__name__)
35
+
35
36
 
36
37
  @click.group()
37
38
  @click.version_option(version=__version__, prog_name="sqlseed")
@@ -137,9 +138,6 @@ def fill(**kwargs: Any) -> None:
137
138
  "--count is required when not using --config. Use -n <number> to specify the number of rows to generate."
138
139
  )
139
140
 
140
- if not config_path and count is None:
141
- count = _FILL_DEFAULT_COUNT
142
-
143
141
  kwargs["count"] = count
144
142
  _execute_fill(kwargs)
145
143
 
@@ -355,52 +353,241 @@ def _sanitize_table_config(config_dict: dict[str, Any]) -> None:
355
353
  col["name"] = re.sub(r"^[:.]+", "", col_name)
356
354
 
357
355
 
358
- def _handle_ai_verification(analyzer: Any, db_path: str, table: str, max_retries: int, no_cache: bool) -> Any:
356
+ class _StreamingProgressDisplay:
357
+ """Rich Live display for streaming LLM output with phase indicators."""
358
+
359
+ def __init__(self) -> None:
360
+ self._phase = "connecting"
361
+ self._model = ""
362
+ self._token_count = 0
363
+ self._preview = ""
364
+ self._attempt = 0
365
+ self._max_retries = 0
366
+ self._live: Live | None = None
367
+
368
+ def _render(self) -> Text:
369
+ parts: list[tuple[str, str]] = []
370
+
371
+ # Phase icon and description
372
+ if self._phase == "connecting":
373
+ parts.append(("⏳", "bold yellow"))
374
+ parts.append((f" Connecting to {self._model}...", "yellow"))
375
+ elif self._phase == "streaming":
376
+ parts.append(("⚡", "bold cyan"))
377
+ parts.append((f" Generating ({self._token_count} tokens)", "cyan"))
378
+ if self._preview:
379
+ parts.append((f" {self._preview[-60:]}", "dim"))
380
+ elif self._phase == "parsing":
381
+ parts.append(("📋", "bold blue"))
382
+ parts.append((f" Parsing response ({self._token_count} tokens)...", "blue"))
383
+ elif self._phase == "validating":
384
+ parts.append(("✅", "bold green"))
385
+ parts.append((" Validating configuration...", "green"))
386
+ elif self._phase == "refining":
387
+ parts.append(("🔄", "bold yellow"))
388
+ retry_info = f" (attempt {self._attempt + 1}/{self._max_retries})" if self._max_retries > 0 else ""
389
+ parts.append((f" Self-correction{retry_info}...", "yellow"))
390
+ elif self._phase == "done":
391
+ parts.append(("✅", "bold green"))
392
+ parts.append((f" Done ({self._token_count} tokens)", "green"))
393
+
394
+ text = Text("")
395
+ for content, style in parts:
396
+ text.append(content, style=style)
397
+ return text
398
+
399
+ def start(self) -> None:
400
+ self._live = Live(self._render(), console=Console(), transient=False, refresh_per_second=8)
401
+ self._live.start()
402
+
403
+ def stop(self) -> None:
404
+ if self._live:
405
+ self._live.stop()
406
+ self._live = None
407
+
408
+ def update(self, phase: str, info: dict[str, Any]) -> None:
409
+ self._phase = phase
410
+ if "model" in info:
411
+ self._model = info["model"]
412
+ if "count" in info:
413
+ self._token_count = info["count"]
414
+ if "token" in info:
415
+ # Show a rolling preview of the last few tokens
416
+ self._preview += info["token"]
417
+ if len(self._preview) > 80:
418
+ self._preview = self._preview[-80:]
419
+ if "tokens" in info:
420
+ self._token_count = info["tokens"]
421
+ if "attempt" in info:
422
+ self._attempt = info["attempt"]
423
+ if "max_retries" in info:
424
+ self._max_retries = info["max_retries"]
425
+ if self._live:
426
+ self._live.update(self._render())
427
+
428
+
429
+ def _run_ai_analysis(
430
+ analyzer: Any,
431
+ db_path: str,
432
+ table: str,
433
+ verify: bool,
434
+ max_retries: int,
435
+ no_cache: bool,
436
+ ) -> Any:
437
+ config = analyzer._config
438
+ use_streaming = config.should_use_streaming() if config else True
439
+ use_compact = config.should_use_ultra_compact() if config else False
440
+
441
+ # Probe inference speed for local backends and show estimated time
442
+ if config and config.backend in (AIBackend.LM_STUDIO, AIBackend.OLLAMA):
443
+ speed_info = config.probe_inference_speed()
444
+ if speed_info and speed_info.get("is_slow"):
445
+ tps = speed_info["tokens_per_second"]
446
+ est_seconds = int(512 / tps) if tps > 0 else 120
447
+ timeout_display = int(config.resolve_timeout())
448
+ click.echo(
449
+ f"Local inference speed: ~{tps} tok/s. "
450
+ f"Estimated wait: ~{est_seconds}s (timeout: {timeout_display}s). "
451
+ f"Using optimized settings for {config.model}.",
452
+ err=True,
453
+ )
454
+ elif speed_info is None:
455
+ # Probe failed — LM Studio may not be running
456
+ click.echo(
457
+ "Warning: Could not probe local inference speed. "
458
+ "Ensure LM Studio/Ollama is running and a model is loaded.",
459
+ err=True,
460
+ )
461
+
462
+ if use_streaming:
463
+ display = _StreamingProgressDisplay()
464
+ if verify and max_retries > 0:
465
+ return _handle_ai_verification_streaming(
466
+ analyzer,
467
+ db_path,
468
+ table,
469
+ max_retries,
470
+ no_cache,
471
+ display,
472
+ use_compact=use_compact,
473
+ )
474
+ return _handle_ai_direct(analyzer, db_path, table, use_compact=use_compact, display=display)
475
+
476
+ # Small local models: non-streaming is 5-7x faster (14s vs 75-100s)
477
+ # because streaming TTFT is extremely high on limited GPU hardware.
478
+ if verify and max_retries > 0:
479
+ return _handle_ai_verification_non_streaming(
480
+ analyzer, db_path, table, max_retries, no_cache, use_compact=use_compact
481
+ )
482
+ return _handle_ai_direct(analyzer, db_path, table, use_compact=use_compact)
483
+
484
+
485
+ def _handle_ai_direct(
486
+ analyzer: Any,
487
+ db_path: str,
488
+ table: str,
489
+ *,
490
+ use_compact: bool = False,
491
+ display: _StreamingProgressDisplay | None = None,
492
+ ) -> Any:
493
+ """Handle AI direct suggestion with prompt-level fallback.
494
+
495
+ Args:
496
+ analyzer: SchemaAnalyzer instance.
497
+ db_path: Path to the SQLite database.
498
+ table: Table name to analyze.
499
+ use_compact: Whether to force ultra-compact mode.
500
+ display: If provided, use streaming with this progress display.
501
+ """
502
+ with DataOrchestrator(db_path) as orch:
503
+ schema_ctx = orch.get_schema_context(table)
504
+
505
+ prompt_levels = [(True, True)] if use_compact else [(False, False), (True, False), (True, True)]
506
+
507
+ for compact, ultra in prompt_levels:
508
+ messages = analyzer.build_initial_messages(schema_ctx, compact=compact, ultra_compact=ultra)
509
+ try:
510
+ if display:
511
+ display.start()
512
+ result = analyzer.call_llm_streaming(messages, on_progress=display.update)
513
+ display.stop()
514
+ else:
515
+ mode = "ultra-compact" if ultra else ("compact" if compact else "standard")
516
+ timeout_s = int(analyzer._config.resolve_timeout()) if analyzer._config else 300
517
+ click.echo(f"Analyzing schema & generating AI suggestions ({mode} mode, timeout: {timeout_s}s)...")
518
+ result = analyzer.call_llm(messages)
519
+ if result:
520
+ return result
521
+ click.echo("AI returned empty result, retrying with shorter prompt...", err=True)
522
+ continue
523
+ except (ValueError, RuntimeError, OSError) as e:
524
+ if display:
525
+ display.stop()
526
+ err_msg = str(e).lower()
527
+ if "context" in err_msg and "exceed" in err_msg and not ultra:
528
+ click.echo("Context size exceeded, retrying with shorter prompt...", err=True)
529
+ continue
530
+ click.echo(f"AI suggestion failed: {e}", err=True)
531
+ return None
532
+ return None
533
+
534
+
535
+ def _handle_ai_verification_non_streaming(
536
+ analyzer: Any,
537
+ db_path: str,
538
+ table: str,
539
+ max_retries: int,
540
+ no_cache: bool,
541
+ *,
542
+ use_compact: bool = False,
543
+ ) -> Any:
544
+ if AiConfigRefiner is None:
545
+ raise ImportError("sqlseed-ai plugin not installed. Install with: pip install sqlseed-ai")
546
+
359
547
  refiner = AiConfigRefiner(analyzer, db_path)
360
548
  try:
549
+ timeout_s = int(analyzer._config.resolve_timeout()) if analyzer._config else 300
550
+ click.echo(f"Analyzing schema & generating AI suggestions with self-correction (timeout: {timeout_s}s)...")
361
551
  return refiner.generate_and_refine(
362
552
  table_name=table,
363
553
  max_retries=max_retries,
364
554
  no_cache=no_cache,
555
+ use_compact=use_compact,
365
556
  )
366
- except AISuggestionFailedError as e:
557
+ except (ValueError, RuntimeError, OSError) as e:
367
558
  click.echo(f"AI suggestion failed: {e}", err=True)
368
559
  return None
369
560
 
370
561
 
371
- def _handle_ai_direct(analyzer: Any, db_path: str, table: str) -> Any:
372
- with DataOrchestrator(db_path) as orch:
373
- schema_ctx = orch.get_schema_context(table)
374
- messages = analyzer.build_initial_messages(schema_ctx)
375
- try:
376
- return analyzer.call_llm(messages)
377
- except (ValueError, RuntimeError) as e:
378
- click.echo(f"AI suggestion failed: {e}", err=True)
379
- return None
380
-
381
-
382
- def _run_ai_analysis(
562
+ def _handle_ai_verification_streaming(
383
563
  analyzer: Any,
384
564
  db_path: str,
385
565
  table: str,
386
- verify: bool,
387
566
  max_retries: int,
388
567
  no_cache: bool,
568
+ display: _StreamingProgressDisplay,
569
+ *,
570
+ use_compact: bool = False,
389
571
  ) -> Any:
390
- with Progress(
391
- SpinnerColumn(),
392
- TextColumn("[progress.description]{task.description}"),
393
- TimeElapsedColumn(),
394
- console=Console(),
395
- transient=False,
396
- ) as progress:
397
- task = progress.add_task("Analyzing schema & generating AI suggestions...", total=None)
398
- if verify and max_retries > 0:
399
- result = _handle_ai_verification(analyzer, db_path, table, max_retries, no_cache)
400
- else:
401
- result = _handle_ai_direct(analyzer, db_path, table)
402
- progress.update(task, completed=1, total=1)
403
- return result
572
+ if AiConfigRefiner is None:
573
+ raise ImportError("sqlseed-ai plugin not installed. Install with: pip install sqlseed-ai")
574
+
575
+ refiner = AiConfigRefiner(analyzer, db_path)
576
+ try:
577
+ display.start()
578
+ result = refiner.generate_and_refine_streaming(
579
+ table_name=table,
580
+ max_retries=max_retries,
581
+ no_cache=no_cache,
582
+ on_progress=display.update,
583
+ use_compact=use_compact,
584
+ )
585
+ display.stop()
586
+ return result
587
+ except (ValueError, RuntimeError, OSError) as e:
588
+ display.stop()
589
+ click.echo(f"AI suggestion failed: {e}", err=True)
590
+ return None
404
591
 
405
592
 
406
593
  def _write_ai_output(output: str, db_path: str, result: Any) -> None:
@@ -444,7 +631,7 @@ def _report_ai_failure() -> None:
444
631
  @click.argument("db_path")
445
632
  @click.option("--table", "-t", required=True, help="Target table name")
446
633
  @click.option("--output", "-o", required=True, help="Output YAML file path")
447
- @click.option("--model", "-m", default=None, help="AI model name (default: auto-select best free model via OpenRouter)")
634
+ @click.option("--model", "-m", default=None, help="AI model name (default: auto-select based on backend)")
448
635
  @click.option("--api-key", envvar="SQLSEED_AI_API_KEY", default=None, help="AI API key (env: SQLSEED_AI_API_KEY)")
449
636
  @click.option(
450
637
  "--base-url",
@@ -455,7 +642,7 @@ def _report_ai_failure() -> None:
455
642
  @click.option("--max-retries", default=3, type=int, help="Max refinement retries, 0=disable (default: 3)")
456
643
  @click.option("--verify/--no-verify", default=True, help="Enable AI config self-correction (default: verify)")
457
644
  @click.option("--no-cache", is_flag=True, help="Skip cached AI configs")
458
- @click.option("--timeout", default=120, type=float, help="API call timeout in seconds (default: 120)")
645
+ @click.option("--timeout", default=0, type=float, help="API call timeout in seconds (0=auto, default: auto)")
459
646
  def ai_suggest(
460
647
  db_path: str,
461
648
  table: str,
@@ -490,7 +677,8 @@ def ai_suggest(
490
677
  click.echo(f"Using AI model: {resolved_model} (via {backend_name})")
491
678
 
492
679
  analyzer = SchemaAnalyzer(config=ai_config)
493
- total_timeout = timeout * 2
680
+ resolved_timeout = ai_config.resolve_timeout()
681
+ total_timeout = resolved_timeout * 2
494
682
 
495
683
  old_handler: Any = None
496
684
  if hasattr(signal, "SIGALRM"):
@@ -507,9 +695,6 @@ def ai_suggest(
507
695
  if old_handler is not None:
508
696
  signal.signal(signal.SIGALRM, old_handler)
509
697
 
510
- if ai_config.model != resolved_model:
511
- click.echo(f"Model fallback: {resolved_model} → {ai_config.model}")
512
-
513
698
  if result:
514
699
  _write_ai_output(output, db_path, result)
515
700
  else:
@@ -12,7 +12,6 @@ class ProviderType(str, Enum):
12
12
  FAKER = "faker"
13
13
  MIMESIS = "mimesis"
14
14
  CUSTOM = "custom"
15
- AI = "ai"
16
15
 
17
16
 
18
17
  class ColumnConstraintsConfig(BaseModel):
@@ -54,6 +53,11 @@ class ColumnConfig(BaseModel):
54
53
  # === 约束 ===
55
54
  constraints: ColumnConstraintsConfig | None = None
56
55
 
56
+ # === Native method overrides (from AI suggestions) ===
57
+ faker_method: str | None = None
58
+ mimesis_method: str | None = None
59
+ native_params: dict[str, Any] = Field(default_factory=dict)
60
+
57
61
  model_config = {"extra": "ignore"}
58
62
 
59
63
  @model_validator(mode="before")
@@ -127,7 +127,7 @@ class ColumnMapper:
127
127
  "comment": {"min_length": 10, "max_length": 200},
128
128
  }
129
129
 
130
- PATTERN_MATCH_RULES: ClassVar[list[tuple[str, str, dict[str, Any]]]] = [
130
+ PATTERN_MATCH_RULES: ClassVar[tuple[tuple[str, str, dict[str, Any]], ...]] = (
131
131
  (r"^id$", "autoincrement", {}),
132
132
  (r".*_id$", "foreign_key_or_integer", {}),
133
133
  (
@@ -159,7 +159,7 @@ class ColumnMapper:
159
159
  (r".*_address$", "address", {}),
160
160
  (r".*_description$|.*_desc$|.*_text$|.*_content$|.*_body$", "text", {"min_length": 50, "max_length": 300}),
161
161
  (r".*_title$|.*_subject$|.*_headline$", "sentence", {}),
162
- ]
162
+ )
163
163
 
164
164
  TYPE_FALLBACK_RULES: ClassVar[dict[str, tuple[str, dict[str, Any]]]] = {
165
165
  "INTEGER": ("integer", {"min_value": 0, "max_value": 999999}),
@@ -229,6 +229,9 @@ class ColumnMapper:
229
229
  params=user_config.params if hasattr(user_config, "params") else {},
230
230
  null_ratio=user_config.null_ratio if hasattr(user_config, "null_ratio") else 0.0,
231
231
  provider=provider_val,
232
+ native_faker_method=getattr(user_config, "faker_method", None),
233
+ native_mimesis_method=getattr(user_config, "mimesis_method", None),
234
+ native_params=getattr(user_config, "native_params", None) or None,
232
235
  )
233
236
  return None
234
237
 
@@ -6,8 +6,6 @@ from typing import TYPE_CHECKING, Any
6
6
  from sqlseed._utils.logger import get_logger
7
7
  from sqlseed.generators._protocol import ConfigurationError, GenerationError, UnknownGeneratorError
8
8
 
9
- _NATIVE_MISS = object()
10
-
11
9
  if TYPE_CHECKING:
12
10
  from collections.abc import Iterator
13
11
 
@@ -17,6 +15,7 @@ if TYPE_CHECKING:
17
15
  from sqlseed.core.mapper import GeneratorSpec
18
16
  from sqlseed.core.transform import RowTransformFn
19
17
 
18
+ _NATIVE_MISS = object()
20
19
  logger = get_logger(__name__)
21
20
 
22
21
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -5,8 +5,6 @@ from typing import Any
5
5
 
6
6
  from sqlseed._utils.logger import get_logger
7
7
 
8
- logger = get_logger(__name__)
9
-
10
8
  try:
11
9
  import sqlite_utils as _sqlite_utils
12
10
 
@@ -16,6 +14,8 @@ except ImportError:
16
14
  HAS_SQLITE_UTILS = False
17
15
  sqlite_utils = None
18
16
 
17
+ logger = get_logger(__name__)
18
+
19
19
  __all__ = ["HAS_SQLITE_UTILS", "sqlite_utils"]
20
20
 
21
21
 
File without changes
File without changes