sqlsaber 0.17.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 sqlsaber might be problematic. Click here for more details.

sqlsaber/cli/commands.py CHANGED
@@ -7,24 +7,8 @@ from typing import Annotated
7
7
  import cyclopts
8
8
  from rich.console import Console
9
9
 
10
- from sqlsaber.agents import build_sqlsaber_agent
11
- from sqlsaber.cli.auth import create_auth_app
12
- from sqlsaber.cli.database import create_db_app
13
- from sqlsaber.cli.interactive import InteractiveSession
14
- from sqlsaber.cli.memory import create_memory_app
15
- from sqlsaber.cli.models import create_models_app
16
- from sqlsaber.cli.streaming import StreamingQueryHandler
17
- from sqlsaber.cli.threads import create_threads_app
10
+ # Lazy imports - only import what's needed for CLI parsing
18
11
  from sqlsaber.config.database import DatabaseConfigManager
19
- from sqlsaber.database.connection import (
20
- CSVConnection,
21
- DatabaseConnection,
22
- MySQLConnection,
23
- PostgreSQLConnection,
24
- SQLiteConnection,
25
- )
26
- from sqlsaber.database.resolver import DatabaseResolutionError, resolve_database
27
- from sqlsaber.threads import ThreadStorage
28
12
 
29
13
 
30
14
  class CLIError(Exception):
@@ -108,6 +92,21 @@ def query(
108
92
  """
109
93
 
110
94
  async def run_session():
95
+ # Import heavy dependencies only when actually running a query
96
+ # This is only done to speed up startup time
97
+ from sqlsaber.agents import build_sqlsaber_agent
98
+ from sqlsaber.cli.interactive import InteractiveSession
99
+ from sqlsaber.cli.streaming import StreamingQueryHandler
100
+ from sqlsaber.database.connection import (
101
+ CSVConnection,
102
+ DatabaseConnection,
103
+ MySQLConnection,
104
+ PostgreSQLConnection,
105
+ SQLiteConnection,
106
+ )
107
+ from sqlsaber.database.resolver import DatabaseResolutionError, resolve_database
108
+ from sqlsaber.threads import ThreadStorage
109
+
111
110
  # Check if query_text is None and stdin has data
112
111
  actual_query = query_text
113
112
  if query_text is None and not sys.stdin.isatty():
@@ -196,25 +195,45 @@ def query(
196
195
  sys.exit(e.exit_code)
197
196
 
198
197
 
199
- # Add authentication management commands
200
- auth_app = create_auth_app()
201
- app.command(auth_app, name="auth")
198
+ # Use lazy imports for fast CLI startup time
199
+ @app.command(name="auth")
200
+ def auth(*args, **kwargs):
201
+ """Manage authentication configuration."""
202
+ from sqlsaber.cli.auth import create_auth_app
203
+
204
+ return create_auth_app()(*args, **kwargs)
205
+
206
+
207
+ @app.command(name="db")
208
+ def db(*args, **kwargs):
209
+ """Manage database connections."""
210
+ from sqlsaber.cli.database import create_db_app
211
+
212
+ return create_db_app()(*args, **kwargs)
213
+
214
+
215
+ @app.command(name="memory")
216
+ def memory(*args, **kwargs):
217
+ """Manage database-specific memories."""
218
+ from sqlsaber.cli.memory import create_memory_app
219
+
220
+ return create_memory_app()(*args, **kwargs)
221
+
222
+
223
+ @app.command(name="models")
224
+ def models(*args, **kwargs):
225
+ """Select and manage models."""
226
+ from sqlsaber.cli.models import create_models_app
202
227
 
203
- # Add database management commands after main callback is defined
204
- db_app = create_db_app()
205
- app.command(db_app, name="db")
228
+ return create_models_app()(*args, **kwargs)
206
229
 
207
- # Add memory management commands
208
- memory_app = create_memory_app()
209
- app.command(memory_app, name="memory")
210
230
 
211
- # Add model management commands
212
- models_app = create_models_app()
213
- app.command(models_app, name="models")
231
+ @app.command(name="threads")
232
+ def threads(*args, **kwargs):
233
+ """Manage SQLsaber threads."""
234
+ from sqlsaber.cli.threads import create_threads_app
214
235
 
215
- # Add threads management commands
216
- threads_app = create_threads_app()
217
- app.command(threads_app, name="threads")
236
+ return create_threads_app()(*args, **kwargs)
218
237
 
219
238
 
220
239
  def main():
@@ -1,8 +1,12 @@
1
1
  """Interactive mode handling for the CLI."""
2
2
 
3
3
  import asyncio
4
+ from pathlib import Path
4
5
 
5
- import questionary
6
+ import platformdirs
7
+ from prompt_toolkit import PromptSession
8
+ from prompt_toolkit.history import FileHistory
9
+ from prompt_toolkit.styles import Style
6
10
  from pydantic_ai import Agent
7
11
  from rich.console import Console
8
12
  from rich.panel import Panel
@@ -24,6 +28,23 @@ from sqlsaber.database.schema import SchemaManager
24
28
  from sqlsaber.threads import ThreadStorage
25
29
 
26
30
 
31
+ def bottom_toolbar():
32
+ return [
33
+ (
34
+ "class:bottom-toolbar",
35
+ " Use 'Esc-Enter' or 'Meta-Enter' to submit.",
36
+ )
37
+ ]
38
+
39
+
40
+ style = Style.from_dict(
41
+ {
42
+ "frame.border": "#ebbcba",
43
+ "bottom-toolbar": "#ebbcba bg:#21202e",
44
+ }
45
+ )
46
+
47
+
27
48
  class InteractiveSession:
28
49
  """Manages interactive CLI sessions."""
29
50
 
@@ -82,7 +103,7 @@ class InteractiveSession:
82
103
  self.console.print(
83
104
  "\n",
84
105
  "[dim] > Use '/clear' to reset conversation",
85
- "[dim] > Use '/exit' or '/quit' to leave[/dim]",
106
+ "[dim] > Use 'Ctrl+D', '/exit' or '/quit' to leave[/dim]",
86
107
  "[dim] > Use 'Ctrl+C' to interrupt and return to prompt\n\n",
87
108
  "[dim] > Start message with '#' to add something to agent's memory for this database",
88
109
  "[dim] > Type '@' to get table name completions",
@@ -137,7 +158,10 @@ class InteractiveSession:
137
158
  # Create the query task
138
159
  query_task = asyncio.create_task(
139
160
  self.streaming_handler.execute_streaming_query(
140
- user_query, self.agent, self.cancellation_token, self.message_history
161
+ user_query,
162
+ self.agent,
163
+ self.cancellation_token,
164
+ self.message_history,
141
165
  )
142
166
  )
143
167
  self.current_task = query_task
@@ -183,17 +207,26 @@ class InteractiveSession:
183
207
  # Initialize table cache
184
208
  await self._update_table_cache()
185
209
 
210
+ session = PromptSession(
211
+ history=FileHistory(
212
+ Path(platformdirs.user_config_dir("sqlsaber")) / "history"
213
+ )
214
+ )
215
+
186
216
  while True:
187
217
  try:
188
- user_query = await questionary.text(
189
- ">",
190
- qmark="",
218
+ # with patch_stdout():
219
+ user_query = await session.prompt_async(
220
+ "",
191
221
  multiline=True,
192
- instruction="",
222
+ mouse_support=True,
193
223
  completer=CompositeCompleter(
194
224
  SlashCommandCompleter(), self.table_completer
195
225
  ),
196
- ).ask_async()
226
+ show_frame=True,
227
+ bottom_toolbar=bottom_toolbar,
228
+ style=style,
229
+ )
197
230
 
198
231
  if not user_query:
199
232
  continue
@@ -276,7 +309,10 @@ class InteractiveSession:
276
309
  self.console.print("\n[yellow]Query interrupted[/yellow]")
277
310
  else:
278
311
  self.console.print(
279
- "\n[yellow]Use '/exit' or '/quit' to leave.[/yellow]"
312
+ "\n[yellow]Press Ctrl+D to exit. Or use '/exit' or '/quit' slash command.[/yellow]"
280
313
  )
314
+ except EOFError:
315
+ # Exit when Ctrl+D is pressed
316
+ break
281
317
  except Exception as e:
282
318
  self.console.print(f"[bold red]Error:[/bold red] {str(e)}")
@@ -9,7 +9,6 @@ from urllib.parse import parse_qs, urlparse
9
9
  import aiomysql
10
10
  import aiosqlite
11
11
  import asyncpg
12
- import pandas as pd
13
12
 
14
13
 
15
14
  class BaseDatabaseConnection(ABC):
@@ -324,6 +323,10 @@ class CSVConnection(BaseDatabaseConnection):
324
323
  return
325
324
 
326
325
  try:
326
+ # Import pandas only when needed for CSV operations
327
+ # This improves CLI load times
328
+ import pandas as pd
329
+
327
330
  # Read CSV file using pandas
328
331
  df = pd.read_csv(
329
332
  self.csv_path,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlsaber
3
- Version: 0.17.0
3
+ Version: 0.19.0
4
4
  Summary: SQLsaber - Open-source agentic SQL assistant
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -13,6 +13,7 @@ Requires-Dist: httpx>=0.28.1
13
13
  Requires-Dist: keyring>=25.6.0
14
14
  Requires-Dist: pandas>=2.0.0
15
15
  Requires-Dist: platformdirs>=4.0.0
16
+ Requires-Dist: prompt-toolkit>3.0.51
16
17
  Requires-Dist: pydantic-ai
17
18
  Requires-Dist: questionary>=2.1.0
18
19
  Requires-Dist: rich>=13.7.0
@@ -6,11 +6,11 @@ sqlsaber/agents/mcp.py,sha256=GcJTx7YDYH6aaxIADEIxSgcWAdWakUx395JIzVnf17U,768
6
6
  sqlsaber/agents/pydantic_ai_agent.py,sha256=dGdsgyxCZvfK-v-MH8KimKOr-xb2aSfSWY8CMcOUCT8,6795
7
7
  sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
8
8
  sqlsaber/cli/auth.py,sha256=jTsRgbmlGPlASSuIKmdjjwfqtKvjfKd_cTYxX0-QqaQ,7400
9
- sqlsaber/cli/commands.py,sha256=ffEJq8WOfX7YBJzn5UCgeXBpU5lXnVBUMkbJEQ0R9WY,8169
9
+ sqlsaber/cli/commands.py,sha256=CmCqDC6KiE8JD6Vkpsry4lBQInCiS8TBeKKx3gdxZcM,8689
10
10
  sqlsaber/cli/completers.py,sha256=HsUPjaZweLSeYCWkAcgMl8FylQ1xjWBWYTEL_9F6xfU,6430
11
11
  sqlsaber/cli/database.py,sha256=atwg3l8acQ3YTDuhq7vNrBN6tpOv0syz6V62KTF-Bh8,12910
12
12
  sqlsaber/cli/display.py,sha256=wa7BjTBwXwqLT145Q1AEL0C28pQJTrvDN10mnFMjqsg,8554
13
- sqlsaber/cli/interactive.py,sha256=QqBjSsjtb6XoBVRyGS520cQrm7DvWC-obQ_EflcygbU,12051
13
+ sqlsaber/cli/interactive.py,sha256=uoJdLWPoaDBkfdFN-59u6zduU8XGY98L16zpNsGu7nE,12964
14
14
  sqlsaber/cli/memory.py,sha256=OufHFJFwV0_GGn7LvKRTJikkWhV1IwNIUDOxFPHXOaQ,7794
15
15
  sqlsaber/cli/models.py,sha256=ZewtwGQwhd9b-yxBAPKePolvI1qQG-EkmeWAGMqtWNQ,8986
16
16
  sqlsaber/cli/streaming.py,sha256=WNqBYYbWtL5CNQkRg5YWhYpWKI8qz7JmqneB2DXTOHY,5259
@@ -24,7 +24,7 @@ sqlsaber/config/oauth_tokens.py,sha256=C9z35hyx-PvSAYdC1LNf3rg9_wsEIY56hkEczelba
24
24
  sqlsaber/config/providers.py,sha256=JFjeJv1K5Q93zWSlWq3hAvgch1TlgoF0qFa0KJROkKY,2957
25
25
  sqlsaber/config/settings.py,sha256=vgb_RXaM-7DgbxYDmWNw1cSyMqwys4j3qNCvM4bljwI,5586
26
26
  sqlsaber/database/__init__.py,sha256=a_gtKRJnZVO8-fEZI7g3Z8YnGa6Nio-5Y50PgVp07ss,176
27
- sqlsaber/database/connection.py,sha256=sJtIIe0GVbo-1Py9-j66UxJoY1aKL9gqk68jkDL-Kvk,15123
27
+ sqlsaber/database/connection.py,sha256=kwx18bnwr4kyTUfQT0OW-DXzJUNWIQJP54spJBqU_48,15243
28
28
  sqlsaber/database/resolver.py,sha256=RPXF5EoKzvQDDLmPGNHYd2uG_oNICH8qvUjBp6iXmNY,3348
29
29
  sqlsaber/database/schema.py,sha256=r12qoN3tdtAXdO22EKlauAe7QwOm8lL2vTMM59XEMMY,26594
30
30
  sqlsaber/mcp/__init__.py,sha256=COdWq7wauPBp5Ew8tfZItFzbcLDSEkHBJSMhxzy8C9c,112
@@ -40,8 +40,8 @@ sqlsaber/tools/enums.py,sha256=CH32mL-0k9ZA18911xLpNtsgpV6tB85TktMj6uqGz54,411
40
40
  sqlsaber/tools/instructions.py,sha256=X-x8maVkkyi16b6Tl0hcAFgjiYceZaSwyWTfmrvx8U8,9024
41
41
  sqlsaber/tools/registry.py,sha256=HWOQMsNIdL4XZS6TeNUyrL-5KoSDH6PHsWd3X66o-18,3211
42
42
  sqlsaber/tools/sql_tools.py,sha256=hM6tKqW5MDhFUt6MesoqhTUqIpq_5baIIDoN1MjDCXY,9647
43
- sqlsaber-0.17.0.dist-info/METADATA,sha256=epsxWGdmHWrFxLbbxWmOVsGqFMYmH1xUNAIGyGxpl_w,6141
44
- sqlsaber-0.17.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
- sqlsaber-0.17.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
46
- sqlsaber-0.17.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
47
- sqlsaber-0.17.0.dist-info/RECORD,,
43
+ sqlsaber-0.19.0.dist-info/METADATA,sha256=oH4qQnKuu5n7ucEEtSccDyvI1obYPPeZV3xNAn-8Mzc,6178
44
+ sqlsaber-0.19.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ sqlsaber-0.19.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
46
+ sqlsaber-0.19.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
47
+ sqlsaber-0.19.0.dist-info/RECORD,,