sqlsaber 0.4.0__tar.gz → 0.5.0__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.

Potentially problematic release.


This version of sqlsaber might be problematic. Click here for more details.

Files changed (50) hide show
  1. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/CHANGELOG.md +13 -0
  2. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/PKG-INFO +37 -6
  3. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/README.md +35 -5
  4. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/pyproject.toml +2 -1
  5. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/agents/anthropic.py +50 -1
  6. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/agents/base.py +92 -0
  7. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/cli/commands.py +3 -0
  8. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/cli/display.py +30 -0
  9. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/cli/interactive.py +9 -6
  10. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/cli/streaming.py +5 -0
  11. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/models/events.py +1 -1
  12. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/uv.lock +25 -1
  13. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/.github/workflows/publish.yml +0 -0
  14. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/.gitignore +0 -0
  15. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/.python-version +0 -0
  16. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/CLAUDE.md +0 -0
  17. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/LICENSE +0 -0
  18. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/pytest.ini +0 -0
  19. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/__init__.py +0 -0
  20. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/__main__.py +0 -0
  21. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/agents/__init__.py +0 -0
  22. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/agents/mcp.py +0 -0
  23. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/agents/streaming.py +0 -0
  24. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/cli/__init__.py +0 -0
  25. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/cli/database.py +0 -0
  26. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/cli/memory.py +0 -0
  27. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/cli/models.py +0 -0
  28. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/config/__init__.py +0 -0
  29. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/config/api_keys.py +0 -0
  30. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/config/database.py +0 -0
  31. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/config/settings.py +0 -0
  32. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/database/__init__.py +0 -0
  33. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/database/connection.py +0 -0
  34. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/database/schema.py +0 -0
  35. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/mcp/__init__.py +0 -0
  36. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/mcp/mcp.py +0 -0
  37. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/memory/__init__.py +0 -0
  38. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/memory/manager.py +0 -0
  39. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/memory/storage.py +0 -0
  40. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/models/__init__.py +0 -0
  41. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/src/sqlsaber/models/types.py +0 -0
  42. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/tests/__init__.py +0 -0
  43. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/tests/conftest.py +0 -0
  44. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/tests/test_cli/__init__.py +0 -0
  45. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/tests/test_cli/test_commands.py +0 -0
  46. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/tests/test_config/__init__.py +0 -0
  47. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/tests/test_config/test_database.py +0 -0
  48. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/tests/test_config/test_settings.py +0 -0
  49. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/tests/test_database/__init__.py +0 -0
  50. {sqlsaber-0.4.0 → sqlsaber-0.5.0}/tests/test_database/test_connection.py +0 -0
@@ -4,6 +4,19 @@ All notable changes to SQLSaber will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### Added
8
+
9
+ - Added support for plotting data from query results.
10
+ - The agent can decide if plotting will useful and create a plot with query results.
11
+ - Small updates to system prompt
12
+
13
+ ## [0.4.1] - 2025-06-26
14
+
15
+ ### Added
16
+
17
+ - Show connected database information at the start of a session
18
+ - Update welcome message for clarity
19
+
7
20
  ## [0.4.0] - 2025-06-25
8
21
 
9
22
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlsaber
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: SQLSaber - Agentic SQL assistant like Claude Code
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -16,6 +16,7 @@ Requires-Dist: platformdirs>=4.0.0
16
16
  Requires-Dist: questionary>=2.1.0
17
17
  Requires-Dist: rich>=13.7.0
18
18
  Requires-Dist: typer>=0.16.0
19
+ Requires-Dist: uniplot>=0.21.2
19
20
  Description-Content-Type: text/markdown
20
21
 
21
22
  # SQLSaber
@@ -24,7 +25,28 @@ Description-Content-Type: text/markdown
24
25
 
25
26
  SQLSaber is an agentic SQL assistant. Think Claude Code but for SQL.
26
27
 
27
- Ask your questions in natural language and it will gather the right context and answer your query by writing SQL and analyzing the results.
28
+ Ask your questions in natural language and it will gather the right context automatically and answer your query by writing SQL and analyzing the results.
29
+
30
+ ## Table of Contents
31
+
32
+ - [Features](#features)
33
+ - [Installation](#installation)
34
+ - [Configuration](#configuration)
35
+ - [Database Connection](#database-connection)
36
+ - [AI Model Configuration](#ai-model-configuration)
37
+ - [Memory Management](#memory-management)
38
+ - [Usage](#usage)
39
+ - [Interactive Mode](#interactive-mode)
40
+ - [Single Query](#single-query)
41
+ - [Database Selection](#database-selection)
42
+ - [Examples](#examples)
43
+ - [MCP Server Integration](#mcp-server-integration)
44
+ - [Starting the MCP Server](#starting-the-mcp-server)
45
+ - [Configuring MCP Clients](#configuring-mcp-clients)
46
+ - [Available MCP Tools](#available-mcp-tools)
47
+ - [How It Works](#how-it-works)
48
+ - [Contributing](#contributing)
49
+ - [License](#license)
28
50
 
29
51
  ## Features
30
52
 
@@ -39,16 +61,25 @@ Ask your questions in natural language and it will gather the right context and
39
61
 
40
62
  ## Installation
41
63
 
64
+ ### `uv`
65
+
42
66
  ```bash
43
67
  uv tool install sqlsaber
44
68
  ```
45
69
 
46
- or
70
+ ### `pipx`
47
71
 
48
72
  ```bash
49
73
  pipx install sqlsaber
50
74
  ```
51
75
 
76
+ ### `brew`
77
+
78
+ ```bash
79
+ brew install uv
80
+ uv tool install sqlsaber
81
+ ```
82
+
52
83
  ## Configuration
53
84
 
54
85
  ### Database Connection
@@ -151,7 +182,7 @@ SQLSaber includes an MCP (Model Context Protocol) server that allows AI agents l
151
182
  Run the MCP server using uvx:
152
183
 
153
184
  ```bash
154
- uvx saber-mcp
185
+ uvx --from sqlsaber saber-mcp
155
186
  ```
156
187
 
157
188
  ### Configuring MCP Clients
@@ -161,12 +192,12 @@ uvx saber-mcp
161
192
  Add SQLSaber as an MCP server in Claude Code:
162
193
 
163
194
  ```bash
164
- claude mcp add -- uvx saber-mcp
195
+ claude mcp add -- uvx --from sqlsaber saber-mcp
165
196
  ```
166
197
 
167
198
  #### Other MCP Clients
168
199
 
169
- For other MCP clients, configure them to run the command: `uvx saber-mcp`
200
+ For other MCP clients, configure them to run the command: `uvx --from sqlsaber saber-mcp`
170
201
 
171
202
  ### Available MCP Tools
172
203
 
@@ -4,7 +4,28 @@
4
4
 
5
5
  SQLSaber is an agentic SQL assistant. Think Claude Code but for SQL.
6
6
 
7
- Ask your questions in natural language and it will gather the right context and answer your query by writing SQL and analyzing the results.
7
+ Ask your questions in natural language and it will gather the right context automatically and answer your query by writing SQL and analyzing the results.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Features](#features)
12
+ - [Installation](#installation)
13
+ - [Configuration](#configuration)
14
+ - [Database Connection](#database-connection)
15
+ - [AI Model Configuration](#ai-model-configuration)
16
+ - [Memory Management](#memory-management)
17
+ - [Usage](#usage)
18
+ - [Interactive Mode](#interactive-mode)
19
+ - [Single Query](#single-query)
20
+ - [Database Selection](#database-selection)
21
+ - [Examples](#examples)
22
+ - [MCP Server Integration](#mcp-server-integration)
23
+ - [Starting the MCP Server](#starting-the-mcp-server)
24
+ - [Configuring MCP Clients](#configuring-mcp-clients)
25
+ - [Available MCP Tools](#available-mcp-tools)
26
+ - [How It Works](#how-it-works)
27
+ - [Contributing](#contributing)
28
+ - [License](#license)
8
29
 
9
30
  ## Features
10
31
 
@@ -19,16 +40,25 @@ Ask your questions in natural language and it will gather the right context and
19
40
 
20
41
  ## Installation
21
42
 
43
+ ### `uv`
44
+
22
45
  ```bash
23
46
  uv tool install sqlsaber
24
47
  ```
25
48
 
26
- or
49
+ ### `pipx`
27
50
 
28
51
  ```bash
29
52
  pipx install sqlsaber
30
53
  ```
31
54
 
55
+ ### `brew`
56
+
57
+ ```bash
58
+ brew install uv
59
+ uv tool install sqlsaber
60
+ ```
61
+
32
62
  ## Configuration
33
63
 
34
64
  ### Database Connection
@@ -131,7 +161,7 @@ SQLSaber includes an MCP (Model Context Protocol) server that allows AI agents l
131
161
  Run the MCP server using uvx:
132
162
 
133
163
  ```bash
134
- uvx saber-mcp
164
+ uvx --from sqlsaber saber-mcp
135
165
  ```
136
166
 
137
167
  ### Configuring MCP Clients
@@ -141,12 +171,12 @@ uvx saber-mcp
141
171
  Add SQLSaber as an MCP server in Claude Code:
142
172
 
143
173
  ```bash
144
- claude mcp add -- uvx saber-mcp
174
+ claude mcp add -- uvx --from sqlsaber saber-mcp
145
175
  ```
146
176
 
147
177
  #### Other MCP Clients
148
178
 
149
- For other MCP clients, configure them to run the command: `uvx saber-mcp`
179
+ For other MCP clients, configure them to run the command: `uvx --from sqlsaber saber-mcp`
150
180
 
151
181
  ### Available MCP Tools
152
182
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sqlsaber"
3
- version = "0.4.0"
3
+ version = "0.5.0"
4
4
  description = "SQLSaber - Agentic SQL assistant like Claude Code"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -17,6 +17,7 @@ dependencies = [
17
17
  "aiosqlite>=0.21.0",
18
18
  "pandas>=2.0.0",
19
19
  "fastmcp>=2.9.0",
20
+ "uniplot>=0.21.2",
20
21
  ]
21
22
 
22
23
  [tool.uv]
@@ -82,6 +82,44 @@ class AnthropicSQLAgent(BaseSQLAgent):
82
82
  "required": ["query"],
83
83
  },
84
84
  },
85
+ {
86
+ "name": "plot_data",
87
+ "description": "Create a plot of query results.",
88
+ "input_schema": {
89
+ "type": "object",
90
+ "properties": {
91
+ "y_values": {
92
+ "type": "array",
93
+ "items": {"type": ["number", "null"]},
94
+ "description": "Y-axis data points (required)",
95
+ },
96
+ "x_values": {
97
+ "type": "array",
98
+ "items": {"type": ["number", "null"]},
99
+ "description": "X-axis data points (optional, will use indices if not provided)",
100
+ },
101
+ "plot_type": {
102
+ "type": "string",
103
+ "enum": ["line", "scatter", "histogram"],
104
+ "description": "Type of plot to create (default: line)",
105
+ "default": "line",
106
+ },
107
+ "title": {
108
+ "type": "string",
109
+ "description": "Title for the plot",
110
+ },
111
+ "x_label": {
112
+ "type": "string",
113
+ "description": "Label for X-axis",
114
+ },
115
+ "y_label": {
116
+ "type": "string",
117
+ "description": "Label for Y-axis",
118
+ },
119
+ },
120
+ "required": ["y_values"],
121
+ },
122
+ },
85
123
  ]
86
124
 
87
125
  # Build system prompt with memories if available
@@ -96,13 +134,15 @@ Your responsibilities:
96
134
  1. Understand user's natural language requests, think and convert them to SQL
97
135
  2. Use the provided tools efficiently to explore database schema
98
136
  3. Generate appropriate SQL queries
99
- 4. Execute queries safely (only SELECT queries unless explicitly allowed)
137
+ 4. Execute queries safely - queries that modify the database are not allowed
100
138
  5. Format and explain results clearly
139
+ 6. Create visualizations when requested or when they would be helpful
101
140
 
102
141
  IMPORTANT - Schema Discovery Strategy:
103
142
  1. ALWAYS start with 'list_tables' to see available tables and row counts
104
143
  2. Based on the user's query, identify which specific tables are relevant
105
144
  3. Use 'introspect_schema' with a table_pattern to get details ONLY for relevant tables
145
+ 4. Timestamp columns must be converted to text when you write queries
106
146
 
107
147
  Guidelines:
108
148
  - Use list_tables first, then introspect_schema for specific tables only
@@ -249,6 +289,15 @@ Guidelines:
249
289
  "result": tool_result,
250
290
  },
251
291
  )
292
+ elif block["name"] == "plot_data":
293
+ yield StreamEvent(
294
+ "plot_result",
295
+ {
296
+ "tool_name": block["name"],
297
+ "input": block["input"],
298
+ "result": tool_result,
299
+ },
300
+ )
252
301
 
253
302
  tool_results.append(build_tool_result_block(block["id"], tool_result))
254
303
 
@@ -4,6 +4,8 @@ import json
4
4
  from abc import ABC, abstractmethod
5
5
  from typing import Any, AsyncIterator, Dict, List, Optional
6
6
 
7
+ from uniplot import histogram, plot
8
+
7
9
  from sqlsaber.database.connection import (
8
10
  BaseDatabaseConnection,
9
11
  CSVConnection,
@@ -146,6 +148,15 @@ class BaseSQLAgent(ABC):
146
148
  return await self.execute_sql(
147
149
  tool_input["query"], tool_input.get("limit", 100)
148
150
  )
151
+ elif tool_name == "plot_data":
152
+ return await self.plot_data(
153
+ y_values=tool_input["y_values"],
154
+ x_values=tool_input.get("x_values"),
155
+ plot_type=tool_input.get("plot_type", "line"),
156
+ title=tool_input.get("title"),
157
+ x_label=tool_input.get("x_label"),
158
+ y_label=tool_input.get("y_label"),
159
+ )
149
160
  else:
150
161
  return json.dumps({"error": f"Unknown tool: {tool_name}"})
151
162
 
@@ -182,3 +193,84 @@ class BaseSQLAgent(ABC):
182
193
  if query_upper.startswith("SELECT") and "LIMIT" not in query_upper:
183
194
  return f"{query.rstrip(';')} LIMIT {limit};"
184
195
  return query
196
+
197
+ async def plot_data(
198
+ self,
199
+ y_values: List[float],
200
+ x_values: Optional[List[float]] = None,
201
+ plot_type: str = "line",
202
+ title: Optional[str] = None,
203
+ x_label: Optional[str] = None,
204
+ y_label: Optional[str] = None,
205
+ ) -> str:
206
+ """Create a terminal plot using uniplot.
207
+
208
+ Args:
209
+ y_values: Y-axis data points
210
+ x_values: X-axis data points (optional)
211
+ plot_type: Type of plot - "line", "scatter", or "histogram"
212
+ title: Plot title
213
+ x_label: X-axis label
214
+ y_label: Y-axis label
215
+
216
+ Returns:
217
+ JSON string with success status and plot details
218
+ """
219
+ try:
220
+ # Validate inputs
221
+ if not y_values:
222
+ return json.dumps({"error": "No data provided for plotting"})
223
+
224
+ # Convert to floats if needed
225
+ try:
226
+ y_values = [float(v) if v is not None else None for v in y_values]
227
+ if x_values:
228
+ x_values = [float(v) if v is not None else None for v in x_values]
229
+ except (ValueError, TypeError) as e:
230
+ return json.dumps({"error": f"Invalid data format: {str(e)}"})
231
+
232
+ # Create the plot
233
+ if plot_type == "histogram":
234
+ # For histogram, we only need y_values
235
+ histogram(
236
+ y_values,
237
+ title=title,
238
+ bins=min(20, len(set(y_values))), # Adaptive bin count
239
+ )
240
+ plot_info = {
241
+ "type": "histogram",
242
+ "data_points": len(y_values),
243
+ "title": title or "Histogram",
244
+ }
245
+ elif plot_type in ["line", "scatter"]:
246
+ # For line/scatter plots
247
+ plot_kwargs = {
248
+ "ys": y_values,
249
+ "title": title,
250
+ "lines": plot_type == "line",
251
+ }
252
+
253
+ if x_values:
254
+ plot_kwargs["xs"] = x_values
255
+ if x_label:
256
+ plot_kwargs["x_unit"] = x_label
257
+ if y_label:
258
+ plot_kwargs["y_unit"] = y_label
259
+
260
+ plot(**plot_kwargs)
261
+
262
+ plot_info = {
263
+ "type": plot_type,
264
+ "data_points": len(y_values),
265
+ "title": title or f"{plot_type.capitalize()} Plot",
266
+ "has_x_values": x_values is not None,
267
+ }
268
+ else:
269
+ return json.dumps({"error": f"Unsupported plot type: {plot_type}"})
270
+
271
+ return json.dumps(
272
+ {"success": True, "plot_rendered": True, "plot_info": plot_info}
273
+ )
274
+
275
+ except Exception as e:
276
+ return json.dumps({"error": f"Error creating plot: {str(e)}"})
@@ -117,6 +117,9 @@ def query(
117
117
  if query_text:
118
118
  # Single query mode with streaming
119
119
  streaming_handler = StreamingQueryHandler(console)
120
+ console.print(
121
+ f"[bold blue]Connected to:[/bold blue] {db_name} {agent._get_database_type_name()}\n"
122
+ )
120
123
  await streaming_handler.execute_streaming_query(query_text, agent)
121
124
  else:
122
125
  # Interactive mode
@@ -205,3 +205,33 @@ class DisplayManager:
205
205
  self.show_error("Failed to parse schema data")
206
206
  except Exception as e:
207
207
  self.show_error(f"Error displaying schema information: {str(e)}")
208
+
209
+ def show_plot(self, plot_data: dict):
210
+ """Display plot information and status."""
211
+ try:
212
+ # Parse the result if it's a string
213
+ if isinstance(plot_data.get("result"), str):
214
+ result = json.loads(plot_data["result"])
215
+ else:
216
+ result = plot_data.get("result", {})
217
+
218
+ # Check if there was an error
219
+ if "error" in result:
220
+ self.show_error(f"Plot error: {result['error']}")
221
+ return
222
+
223
+ # If plot was successful, show plot info
224
+ if result.get("success") and result.get("plot_rendered"):
225
+ plot_info = result.get("plot_info", {})
226
+ self.console.print(
227
+ f"\n[bold green]✓ Plot rendered:[/bold green] {plot_info.get('title', 'Plot')}"
228
+ )
229
+ self.console.print(
230
+ f"[dim] Type: {plot_info.get('type', 'unknown')}, "
231
+ f"Data points: {plot_info.get('data_points', 0)}[/dim]"
232
+ )
233
+
234
+ except json.JSONDecodeError:
235
+ self.show_error("Failed to parse plot result")
236
+ except Exception as e:
237
+ self.show_error(f"Error displaying plot: {str(e)}")
@@ -20,21 +20,24 @@ class InteractiveSession:
20
20
 
21
21
  def show_welcome_message(self):
22
22
  """Display welcome message for interactive mode."""
23
+ # Show database information
24
+ db_name = getattr(self.agent, "database_name", None) or "Unknown"
25
+ db_type = self.agent._get_database_type_name()
26
+
23
27
  self.console.print(
24
28
  Panel.fit(
25
29
  "[bold green]SQLSaber - Use the agent Luke![/bold green]\n\n"
26
- "Type your queries in natural language.\n\n"
27
- "Press Esc-Enter or Meta-Enter to submit your query.\n\n"
28
- "Type 'exit' or 'quit' to leave.",
30
+ "[bold]Your agentic SQL assistant.[/bold]\n\n\n"
31
+ "[dim]Use 'clear' to reset conversation, 'exit' or 'quit' to leave.[/dim]\n\n"
32
+ "[dim]Start a message with '#' to add something to agent's memory for this database.[/dim]",
29
33
  border_style="green",
30
34
  )
31
35
  )
32
-
33
36
  self.console.print(
34
- "[dim]Commands: 'clear' to reset conversation, 'exit' or 'quit' to leave[/dim]"
37
+ f"[bold blue]Connected to:[/bold blue] {db_name} ({db_type})\n"
35
38
  )
36
39
  self.console.print(
37
- "[dim]Memory: Start a message with '#' to add it as a memory for this database[/dim]\n"
40
+ "[dim]Press Esc-Enter or Meta-Enter to submit your query.[/dim]\n"
38
41
  )
39
42
 
40
43
  async def run(self):
@@ -63,6 +63,11 @@ class StreamingQueryHandler:
63
63
  self.display.show_schema_info(event.data["result"])
64
64
  has_content = True
65
65
 
66
+ elif event.type == "plot_result":
67
+ # Handle plot results
68
+ self.display.show_plot(event.data)
69
+ has_content = True
70
+
66
71
  elif event.type == "processing":
67
72
  # Show status when processing tool results
68
73
  if explanation_started:
@@ -7,7 +7,7 @@ class StreamEvent:
7
7
  """Event emitted during streaming processing."""
8
8
 
9
9
  def __init__(self, event_type: str, data: Any = None):
10
- # 'tool_use', 'text', 'query_result', 'error', 'processing'
10
+ # 'tool_use', 'text', 'query_result', 'plot_result', 'error', 'processing'
11
11
  self.type = event_type
12
12
  self.data = data
13
13
 
@@ -774,6 +774,15 @@ wheels = [
774
774
  { url = "https://files.pythonhosted.org/packages/ad/3f/11dd4cd4f39e05128bfd20138faea57bec56f9ffba6185d276e3107ba5b2/questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec", size = 36747 },
775
775
  ]
776
776
 
777
+ [[package]]
778
+ name = "readchar"
779
+ version = "4.2.1"
780
+ source = { registry = "https://pypi.org/simple" }
781
+ sdist = { url = "https://files.pythonhosted.org/packages/dd/f8/8657b8cbb4ebeabfbdf991ac40eca8a1d1bd012011bd44ad1ed10f5cb494/readchar-4.2.1.tar.gz", hash = "sha256:91ce3faf07688de14d800592951e5575e9c7a3213738ed01d394dcc949b79adb", size = 9685 }
782
+ wheels = [
783
+ { url = "https://files.pythonhosted.org/packages/a9/10/e4b1e0e5b6b6745c8098c275b69bc9d73e9542d5c7da4f137542b499ed44/readchar-4.2.1-py3-none-any.whl", hash = "sha256:a769305cd3994bb5fa2764aa4073452dc105a4ec39068ffe6efd3c20c60acc77", size = 9350 },
784
+ ]
785
+
777
786
  [[package]]
778
787
  name = "rich"
779
788
  version = "14.0.0"
@@ -854,7 +863,7 @@ wheels = [
854
863
 
855
864
  [[package]]
856
865
  name = "sqlsaber"
857
- version = "0.4.0"
866
+ version = "0.5.0"
858
867
  source = { editable = "." }
859
868
  dependencies = [
860
869
  { name = "aiomysql" },
@@ -869,6 +878,7 @@ dependencies = [
869
878
  { name = "questionary" },
870
879
  { name = "rich" },
871
880
  { name = "typer" },
881
+ { name = "uniplot" },
872
882
  ]
873
883
 
874
884
  [package.dev-dependencies]
@@ -892,6 +902,7 @@ requires-dist = [
892
902
  { name = "questionary", specifier = ">=2.1.0" },
893
903
  { name = "rich", specifier = ">=13.7.0" },
894
904
  { name = "typer", specifier = ">=0.16.0" },
905
+ { name = "uniplot", specifier = ">=0.21.2" },
895
906
  ]
896
907
 
897
908
  [package.metadata.requires-dev]
@@ -971,6 +982,19 @@ wheels = [
971
982
  { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 },
972
983
  ]
973
984
 
985
+ [[package]]
986
+ name = "uniplot"
987
+ version = "0.21.2"
988
+ source = { registry = "https://pypi.org/simple" }
989
+ dependencies = [
990
+ { name = "numpy" },
991
+ { name = "readchar" },
992
+ ]
993
+ sdist = { url = "https://files.pythonhosted.org/packages/87/65/b9db385152a5283c88f955710123c6539a7c79436d2de377b3449995b041/uniplot-0.21.2.tar.gz", hash = "sha256:fc350d6e0f2352822747a3426fef7f521d1b3973585ad2e2967c702dfc6e8440", size = 33412 }
994
+ wheels = [
995
+ { url = "https://files.pythonhosted.org/packages/3a/0e/0b2e41841eb18017e7e125bc8294180d2597a4ca049641068f55355bcc69/uniplot-0.21.2-py3-none-any.whl", hash = "sha256:cae5875eac0d06fd75cbb7076ea3fa49565ef1d71140f0b9a39f7be96085536b", size = 36419 },
996
+ ]
997
+
974
998
  [[package]]
975
999
  name = "uvicorn"
976
1000
  version = "0.34.3"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes