sqlsaber 0.6.0__py3-none-any.whl → 0.7.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/completers.py +172 -0
- sqlsaber/cli/display.py +22 -0
- sqlsaber/cli/interactive.py +47 -31
- sqlsaber/cli/streaming.py +8 -0
- sqlsaber/database/schema.py +17 -0
- {sqlsaber-0.6.0.dist-info → sqlsaber-0.7.0.dist-info}/METADATA +9 -8
- {sqlsaber-0.6.0.dist-info → sqlsaber-0.7.0.dist-info}/RECORD +10 -9
- {sqlsaber-0.6.0.dist-info → sqlsaber-0.7.0.dist-info}/WHEEL +0 -0
- {sqlsaber-0.6.0.dist-info → sqlsaber-0.7.0.dist-info}/entry_points.txt +0 -0
- {sqlsaber-0.6.0.dist-info → sqlsaber-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Command line completers for the CLI interface."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SlashCommandCompleter(Completer):
|
|
9
|
+
"""Custom completer for slash commands."""
|
|
10
|
+
|
|
11
|
+
def get_completions(self, document, complete_event):
|
|
12
|
+
"""Get completions for slash commands."""
|
|
13
|
+
# Only provide completions if the line starts with "/"
|
|
14
|
+
text = document.text
|
|
15
|
+
if text.startswith("/"):
|
|
16
|
+
# Get the partial command after the slash
|
|
17
|
+
partial_cmd = text[1:]
|
|
18
|
+
|
|
19
|
+
# Define available commands with descriptions
|
|
20
|
+
commands = [
|
|
21
|
+
("clear", "Clear conversation history"),
|
|
22
|
+
("exit", "Exit the interactive session"),
|
|
23
|
+
("quit", "Exit the interactive session"),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
# Yield completions that match the partial command
|
|
27
|
+
for cmd, description in commands:
|
|
28
|
+
if cmd.startswith(partial_cmd):
|
|
29
|
+
yield Completion(
|
|
30
|
+
cmd,
|
|
31
|
+
start_position=-len(partial_cmd),
|
|
32
|
+
display_meta=description,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TableNameCompleter(Completer):
|
|
37
|
+
"""Custom completer for table names."""
|
|
38
|
+
|
|
39
|
+
def __init__(self):
|
|
40
|
+
self._table_cache: List[Tuple[str, str]] = []
|
|
41
|
+
|
|
42
|
+
def update_cache(self, tables_data: List[Tuple[str, str]]):
|
|
43
|
+
"""Update the cache with fresh table data."""
|
|
44
|
+
self._table_cache = tables_data
|
|
45
|
+
|
|
46
|
+
def _get_table_names(self) -> List[Tuple[str, str]]:
|
|
47
|
+
"""Get table names from cache."""
|
|
48
|
+
return self._table_cache
|
|
49
|
+
|
|
50
|
+
def get_completions(self, document, complete_event):
|
|
51
|
+
"""Get completions for table names with fuzzy matching."""
|
|
52
|
+
text = document.text
|
|
53
|
+
cursor_position = document.cursor_position
|
|
54
|
+
|
|
55
|
+
# Find the last "@" before the cursor position
|
|
56
|
+
at_pos = text.rfind("@", 0, cursor_position)
|
|
57
|
+
|
|
58
|
+
if at_pos >= 0:
|
|
59
|
+
# Extract text after the "@" up to the cursor
|
|
60
|
+
partial_table = text[at_pos + 1 : cursor_position].lower()
|
|
61
|
+
|
|
62
|
+
# Check if this looks like a valid table reference context
|
|
63
|
+
# (not inside quotes, and followed by word characters or end of input)
|
|
64
|
+
if self._is_valid_table_context(text, at_pos, cursor_position):
|
|
65
|
+
# Get table names
|
|
66
|
+
tables = self._get_table_names()
|
|
67
|
+
|
|
68
|
+
# Collect matches with scores for ranking
|
|
69
|
+
matches = []
|
|
70
|
+
|
|
71
|
+
for table_name, description in tables:
|
|
72
|
+
table_lower = table_name.lower()
|
|
73
|
+
score = self._calculate_match_score(
|
|
74
|
+
partial_table, table_name, table_lower
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if score > 0:
|
|
78
|
+
matches.append((score, table_name, description))
|
|
79
|
+
|
|
80
|
+
# Sort by score (higher is better) and yield completions
|
|
81
|
+
matches.sort(key=lambda x: x[0], reverse=True)
|
|
82
|
+
|
|
83
|
+
for score, table_name, description in matches:
|
|
84
|
+
yield Completion(
|
|
85
|
+
table_name,
|
|
86
|
+
start_position=at_pos
|
|
87
|
+
+ 1
|
|
88
|
+
- cursor_position, # Start from after the @
|
|
89
|
+
display_meta=description if description else None,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def _is_valid_table_context(self, text: str, at_pos: int, cursor_pos: int) -> bool:
|
|
93
|
+
"""Check if the @ is in a valid context for table completion."""
|
|
94
|
+
# Simple heuristic: avoid completion inside quoted strings
|
|
95
|
+
|
|
96
|
+
# Count quotes before the @ position
|
|
97
|
+
single_quotes = text[:at_pos].count("'") - text[:at_pos].count("\\'")
|
|
98
|
+
double_quotes = text[:at_pos].count('"') - text[:at_pos].count('\\"')
|
|
99
|
+
|
|
100
|
+
# If we're inside quotes, don't complete
|
|
101
|
+
if single_quotes % 2 == 1 or double_quotes % 2 == 1:
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
# Check if the character after the cursor (if any) is part of a word
|
|
105
|
+
# This helps avoid breaking existing words
|
|
106
|
+
if cursor_pos < len(text):
|
|
107
|
+
next_char = text[cursor_pos]
|
|
108
|
+
if next_char.isalnum() or next_char == "_":
|
|
109
|
+
# We're in the middle of a word, check if it looks like a table name
|
|
110
|
+
partial = (
|
|
111
|
+
text[at_pos + 1 :].split()[0] if text[at_pos + 1 :].split() else ""
|
|
112
|
+
)
|
|
113
|
+
if not any(c in partial for c in [".", "_"]):
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
def _calculate_match_score(
|
|
119
|
+
self, partial: str, table_name: str, table_lower: str
|
|
120
|
+
) -> int:
|
|
121
|
+
"""Calculate match score for fuzzy matching (higher is better)."""
|
|
122
|
+
if not partial:
|
|
123
|
+
return 1 # Empty search matches everything with low score
|
|
124
|
+
|
|
125
|
+
# Score 100: Exact full name prefix match
|
|
126
|
+
if table_lower.startswith(partial):
|
|
127
|
+
return 100
|
|
128
|
+
|
|
129
|
+
# Score 90: Table name (after schema) prefix match
|
|
130
|
+
if "." in table_name:
|
|
131
|
+
table_part = table_name.split(".")[-1].lower()
|
|
132
|
+
if table_part.startswith(partial):
|
|
133
|
+
return 90
|
|
134
|
+
|
|
135
|
+
# Score 80: Exact table name match (for short names)
|
|
136
|
+
if "." in table_name:
|
|
137
|
+
table_part = table_name.split(".")[-1].lower()
|
|
138
|
+
if table_part == partial:
|
|
139
|
+
return 80
|
|
140
|
+
|
|
141
|
+
# Score 70: Word boundary matches (e.g., "user" matches "user_accounts")
|
|
142
|
+
if "." in table_name:
|
|
143
|
+
table_part = table_name.split(".")[-1].lower()
|
|
144
|
+
if table_part.startswith(partial + "_") or table_part.startswith(
|
|
145
|
+
partial + "-"
|
|
146
|
+
):
|
|
147
|
+
return 70
|
|
148
|
+
|
|
149
|
+
# Score 50: Substring match in table name part
|
|
150
|
+
if "." in table_name:
|
|
151
|
+
table_part = table_name.split(".")[-1].lower()
|
|
152
|
+
if partial in table_part:
|
|
153
|
+
return 50
|
|
154
|
+
|
|
155
|
+
# Score 30: Substring match in full name
|
|
156
|
+
if partial in table_lower:
|
|
157
|
+
return 30
|
|
158
|
+
|
|
159
|
+
# Score 0: No match
|
|
160
|
+
return 0
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class CompositeCompleter(Completer):
|
|
164
|
+
"""Combines multiple completers."""
|
|
165
|
+
|
|
166
|
+
def __init__(self, *completers: Completer):
|
|
167
|
+
self.completers = completers
|
|
168
|
+
|
|
169
|
+
def get_completions(self, document, complete_event):
|
|
170
|
+
"""Get completions from all registered completers."""
|
|
171
|
+
for completer in self.completers:
|
|
172
|
+
yield from completer.get_completions(document, complete_event)
|
sqlsaber/cli/display.py
CHANGED
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
6
|
from rich.console import Console
|
|
7
|
+
from rich.markdown import Markdown
|
|
7
8
|
from rich.syntax import Syntax
|
|
8
9
|
from rich.table import Table
|
|
9
10
|
|
|
@@ -243,3 +244,24 @@ class DisplayManager:
|
|
|
243
244
|
self.show_error("Failed to parse plot result")
|
|
244
245
|
except Exception as e:
|
|
245
246
|
self.show_error(f"Error displaying plot: {str(e)}")
|
|
247
|
+
|
|
248
|
+
def show_markdown_response(self, content: list):
|
|
249
|
+
"""Display the assistant's response as rich markdown."""
|
|
250
|
+
if not content:
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
# Extract text from content blocks
|
|
254
|
+
text_parts = []
|
|
255
|
+
for block in content:
|
|
256
|
+
if isinstance(block, dict) and block.get("type") == "text":
|
|
257
|
+
text = block.get("text", "")
|
|
258
|
+
if text:
|
|
259
|
+
text_parts.append(text)
|
|
260
|
+
|
|
261
|
+
# Join all text parts and display as markdown
|
|
262
|
+
full_text = "".join(text_parts).strip()
|
|
263
|
+
if full_text:
|
|
264
|
+
self.console.print() # Add spacing before markdown
|
|
265
|
+
markdown = Markdown(full_text)
|
|
266
|
+
self.console.print(markdown)
|
|
267
|
+
self.console.print() # Add spacing after markdown
|
sqlsaber/cli/interactive.py
CHANGED
|
@@ -4,43 +4,19 @@ import asyncio
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
6
|
import questionary
|
|
7
|
-
from prompt_toolkit.completion import Completer, Completion
|
|
8
7
|
from rich.console import Console
|
|
9
8
|
from rich.panel import Panel
|
|
10
9
|
|
|
11
10
|
from sqlsaber.agents.base import BaseSQLAgent
|
|
11
|
+
from sqlsaber.cli.completers import (
|
|
12
|
+
CompositeCompleter,
|
|
13
|
+
SlashCommandCompleter,
|
|
14
|
+
TableNameCompleter,
|
|
15
|
+
)
|
|
12
16
|
from sqlsaber.cli.display import DisplayManager
|
|
13
17
|
from sqlsaber.cli.streaming import StreamingQueryHandler
|
|
14
18
|
|
|
15
19
|
|
|
16
|
-
class SlashCommandCompleter(Completer):
|
|
17
|
-
"""Custom completer for slash commands."""
|
|
18
|
-
|
|
19
|
-
def get_completions(self, document, complete_event):
|
|
20
|
-
"""Get completions for slash commands."""
|
|
21
|
-
# Only provide completions if the line starts with "/"
|
|
22
|
-
text = document.text
|
|
23
|
-
if text.startswith("/"):
|
|
24
|
-
# Get the partial command after the slash
|
|
25
|
-
partial_cmd = text[1:]
|
|
26
|
-
|
|
27
|
-
# Define available commands with descriptions
|
|
28
|
-
commands = [
|
|
29
|
-
("clear", "Clear conversation history"),
|
|
30
|
-
("exit", "Exit the interactive session"),
|
|
31
|
-
("quit", "Exit the interactive session"),
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
# Yield completions that match the partial command
|
|
35
|
-
for cmd, description in commands:
|
|
36
|
-
if cmd.startswith(partial_cmd):
|
|
37
|
-
yield Completion(
|
|
38
|
-
cmd,
|
|
39
|
-
start_position=-len(partial_cmd),
|
|
40
|
-
display_meta=description,
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
|
|
44
20
|
class InteractiveSession:
|
|
45
21
|
"""Manages interactive CLI sessions."""
|
|
46
22
|
|
|
@@ -51,6 +27,7 @@ class InteractiveSession:
|
|
|
51
27
|
self.streaming_handler = StreamingQueryHandler(console)
|
|
52
28
|
self.current_task: Optional[asyncio.Task] = None
|
|
53
29
|
self.cancellation_token: Optional[asyncio.Event] = None
|
|
30
|
+
self.table_completer = TableNameCompleter()
|
|
54
31
|
|
|
55
32
|
def show_welcome_message(self):
|
|
56
33
|
"""Display welcome message for interactive mode."""
|
|
@@ -63,7 +40,8 @@ class InteractiveSession:
|
|
|
63
40
|
"[bold green]SQLSaber - Use the agent Luke![/bold green]\n\n"
|
|
64
41
|
"[bold]Your agentic SQL assistant.[/bold]\n\n\n"
|
|
65
42
|
"[dim]Use '/clear' to reset conversation, '/exit' or '/quit' to leave.[/dim]\n\n"
|
|
66
|
-
"[dim]Start a message with '#' to add something to agent's memory for this database.[/dim]"
|
|
43
|
+
"[dim]Start a message with '#' to add something to agent's memory for this database.[/dim]\n\n"
|
|
44
|
+
"[dim]Type '@' to get table name completions.[/dim]",
|
|
67
45
|
border_style="green",
|
|
68
46
|
)
|
|
69
47
|
)
|
|
@@ -75,6 +53,39 @@ class InteractiveSession:
|
|
|
75
53
|
"[dim]Press Ctrl+C during query execution to interrupt and return to prompt.[/dim]\n"
|
|
76
54
|
)
|
|
77
55
|
|
|
56
|
+
async def _update_table_cache(self):
|
|
57
|
+
"""Update the table completer cache with fresh data."""
|
|
58
|
+
try:
|
|
59
|
+
# Use the schema manager directly which has built-in caching
|
|
60
|
+
tables_data = await self.agent.schema_manager.list_tables()
|
|
61
|
+
|
|
62
|
+
# Parse the table information
|
|
63
|
+
table_list = []
|
|
64
|
+
if isinstance(tables_data, dict) and "tables" in tables_data:
|
|
65
|
+
for table in tables_data["tables"]:
|
|
66
|
+
if isinstance(table, dict):
|
|
67
|
+
name = table.get("name", "")
|
|
68
|
+
schema = table.get("schema", "")
|
|
69
|
+
full_name = table.get("full_name", "")
|
|
70
|
+
|
|
71
|
+
# Use full_name if available, otherwise construct it
|
|
72
|
+
if full_name:
|
|
73
|
+
table_name = full_name
|
|
74
|
+
elif schema and schema != "main":
|
|
75
|
+
table_name = f"{schema}.{name}"
|
|
76
|
+
else:
|
|
77
|
+
table_name = name
|
|
78
|
+
|
|
79
|
+
# No description needed - cleaner completions
|
|
80
|
+
table_list.append((table_name, ""))
|
|
81
|
+
|
|
82
|
+
# Update the completer cache
|
|
83
|
+
self.table_completer.update_cache(table_list)
|
|
84
|
+
|
|
85
|
+
except Exception:
|
|
86
|
+
# If there's an error, just use empty cache
|
|
87
|
+
self.table_completer.update_cache([])
|
|
88
|
+
|
|
78
89
|
async def _execute_query_with_cancellation(self, user_query: str):
|
|
79
90
|
"""Execute a query with cancellation support."""
|
|
80
91
|
# Create cancellation token
|
|
@@ -101,6 +112,9 @@ class InteractiveSession:
|
|
|
101
112
|
"""Run the interactive session loop."""
|
|
102
113
|
self.show_welcome_message()
|
|
103
114
|
|
|
115
|
+
# Initialize table cache
|
|
116
|
+
await self._update_table_cache()
|
|
117
|
+
|
|
104
118
|
while True:
|
|
105
119
|
try:
|
|
106
120
|
user_query = await questionary.text(
|
|
@@ -108,7 +122,9 @@ class InteractiveSession:
|
|
|
108
122
|
qmark="",
|
|
109
123
|
multiline=True,
|
|
110
124
|
instruction="",
|
|
111
|
-
completer=
|
|
125
|
+
completer=CompositeCompleter(
|
|
126
|
+
SlashCommandCompleter(), self.table_completer
|
|
127
|
+
),
|
|
112
128
|
).ask_async()
|
|
113
129
|
|
|
114
130
|
if not user_query:
|
sqlsaber/cli/streaming.py
CHANGED
|
@@ -110,6 +110,14 @@ class StreamingQueryHandler:
|
|
|
110
110
|
if explanation_started:
|
|
111
111
|
self.display.show_newline() # Empty line for better readability
|
|
112
112
|
|
|
113
|
+
# Display the last assistant response as markdown
|
|
114
|
+
if hasattr(agent, "conversation_history") and agent.conversation_history:
|
|
115
|
+
last_message = agent.conversation_history[-1]
|
|
116
|
+
if last_message.get("role") == "assistant" and last_message.get(
|
|
117
|
+
"content"
|
|
118
|
+
):
|
|
119
|
+
self.display.show_markdown_response(last_message["content"])
|
|
120
|
+
|
|
113
121
|
def _stop_status(self, status):
|
|
114
122
|
"""Safely stop a status spinner."""
|
|
115
123
|
try:
|
sqlsaber/database/schema.py
CHANGED
|
@@ -683,6 +683,13 @@ class SchemaManager:
|
|
|
683
683
|
|
|
684
684
|
async def list_tables(self) -> Dict[str, Any]:
|
|
685
685
|
"""Get a list of all tables with basic information like row counts."""
|
|
686
|
+
# Check cache first
|
|
687
|
+
cache_key = "list_tables"
|
|
688
|
+
cached_data = self._get_cached_tables(cache_key)
|
|
689
|
+
if cached_data is not None:
|
|
690
|
+
return cached_data
|
|
691
|
+
|
|
692
|
+
# Fetch from database if not cached
|
|
686
693
|
tables = await self.introspector.list_tables_info(self.db)
|
|
687
694
|
|
|
688
695
|
# Format the result
|
|
@@ -699,4 +706,14 @@ class SchemaManager:
|
|
|
699
706
|
}
|
|
700
707
|
)
|
|
701
708
|
|
|
709
|
+
# Cache the result
|
|
710
|
+
self._schema_cache[cache_key] = (time.time(), result)
|
|
702
711
|
return result
|
|
712
|
+
|
|
713
|
+
def _get_cached_tables(self, cache_key: str) -> Optional[Dict[str, Any]]:
|
|
714
|
+
"""Get table list from cache if available and not expired."""
|
|
715
|
+
if cache_key in self._schema_cache:
|
|
716
|
+
cached_time, cached_data = self._schema_cache[cache_key]
|
|
717
|
+
if time.time() - cached_time < self.cache_ttl:
|
|
718
|
+
return cached_data
|
|
719
|
+
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlsaber
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: SQLSaber - Agentic SQL assistant like Claude Code
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -212,23 +212,24 @@ The MCP server uses your existing SQLSaber database configurations, so make sure
|
|
|
212
212
|
|
|
213
213
|
## How It Works
|
|
214
214
|
|
|
215
|
-
SQLSaber uses
|
|
215
|
+
SQLSaber uses a multi-step process to gather the right context, provide it to the model, and execute SQL queries to get the right answers:
|
|
216
|
+
|
|
217
|
+

|
|
216
218
|
|
|
217
219
|
### 🔍 Discovery Phase
|
|
218
220
|
|
|
219
221
|
1. **List Tables Tool**: Quickly discovers available tables with row counts
|
|
220
|
-
2. **Pattern Matching**: Identifies relevant tables based on your query
|
|
222
|
+
2. **Pattern Matching**: Identifies relevant tables based on your query
|
|
221
223
|
|
|
222
224
|
### 📋 Schema Analysis
|
|
223
225
|
|
|
224
|
-
3. **Smart Introspection**: Analyzes only the specific table structures needed for your query
|
|
225
|
-
4. **Selective Loading**: Fetches schema information only for relevant tables
|
|
226
|
+
3. **Smart Schema Introspection**: Analyzes only the specific table structures needed for your query
|
|
226
227
|
|
|
227
228
|
### ⚡ Execution Phase
|
|
228
229
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
4. **SQL Generation**: Creates optimized SQL queries based on natural language input
|
|
231
|
+
5. **Safe Execution**: Runs read-only queries with built-in protections against destructive operations
|
|
232
|
+
6. **Result Formatting**: Presents results with explanations in tables and optionally, visualizes using plots
|
|
232
233
|
|
|
233
234
|
## Contributing
|
|
234
235
|
|
|
@@ -7,19 +7,20 @@ sqlsaber/agents/mcp.py,sha256=FKtXgDrPZ2-xqUYCw2baI5JzrWekXaC5fjkYW1_Mg50,827
|
|
|
7
7
|
sqlsaber/agents/streaming.py,sha256=_EO390-FHUrL1fRCNfibtE9QuJz3LGQygbwG3CB2ViY,533
|
|
8
8
|
sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
|
|
9
9
|
sqlsaber/cli/commands.py,sha256=Dw24W0jij-8t1lpk99C4PBTgzFSag6vU-FZcjAYGG54,5074
|
|
10
|
+
sqlsaber/cli/completers.py,sha256=JWOCKAm0Prpy_O2QJsf_VbPWfy2lQQh6KutyG8FU4us,6462
|
|
10
11
|
sqlsaber/cli/database.py,sha256=DUfyvNBDp47oFM_VAC_hXHQy_qyE7JbXtowflJpwwH8,12643
|
|
11
|
-
sqlsaber/cli/display.py,sha256=
|
|
12
|
-
sqlsaber/cli/interactive.py,sha256=
|
|
12
|
+
sqlsaber/cli/display.py,sha256=NIBWHUrX_8ZhDu6iW9v4fzx0zncnXa5WdQ9wfTrjKIM,10017
|
|
13
|
+
sqlsaber/cli/interactive.py,sha256=FvgtT45U-yblhbRImKqJ4jgBRNs0u7NhE2PcgoVUaVA,7429
|
|
13
14
|
sqlsaber/cli/memory.py,sha256=LW4ZF2V6Gw6hviUFGZ4ym9ostFCwucgBTIMZ3EANO-I,7671
|
|
14
15
|
sqlsaber/cli/models.py,sha256=3IcXeeU15IQvemSv-V-RQzVytJ3wuQ4YmWk89nTDcSE,7813
|
|
15
|
-
sqlsaber/cli/streaming.py,sha256=
|
|
16
|
+
sqlsaber/cli/streaming.py,sha256=DfwygmjEzAh9hZGKjrW9kS1A7MG5W9Ky_kCTzxziODQ,4970
|
|
16
17
|
sqlsaber/config/__init__.py,sha256=olwC45k8Nc61yK0WmPUk7XHdbsZH9HuUAbwnmKe3IgA,100
|
|
17
18
|
sqlsaber/config/api_keys.py,sha256=kLdoExF_My9ojmdhO5Ca7-ZeowsO0v1GVa_QT5jjUPo,3658
|
|
18
19
|
sqlsaber/config/database.py,sha256=vKFOxPjVakjQhj1uoLcfzhS9ZFr6Z2F5b4MmYALQZoA,11421
|
|
19
20
|
sqlsaber/config/settings.py,sha256=zjQ7nS3ybcCb88Ea0tmwJox5-q0ettChZw89ZqRVpX8,3975
|
|
20
21
|
sqlsaber/database/__init__.py,sha256=a_gtKRJnZVO8-fEZI7g3Z8YnGa6Nio-5Y50PgVp07ss,176
|
|
21
22
|
sqlsaber/database/connection.py,sha256=s8GSFZebB8be8sVUr-N0x88-20YfkfljJFRyfoB1gH0,15154
|
|
22
|
-
sqlsaber/database/schema.py,sha256=
|
|
23
|
+
sqlsaber/database/schema.py,sha256=3CfkyhxgD6SmiUoz7MQPlQLrrA007HOQLnGCvvsdJx0,28647
|
|
23
24
|
sqlsaber/mcp/__init__.py,sha256=COdWq7wauPBp5Ew8tfZItFzbcLDSEkHBJSMhxzy8C9c,112
|
|
24
25
|
sqlsaber/mcp/mcp.py,sha256=ACm1P1TnicjOptQgeLNhXg5xgZf4MYq2kqdfVdj6wh0,4477
|
|
25
26
|
sqlsaber/memory/__init__.py,sha256=GiWkU6f6YYVV0EvvXDmFWe_CxarmDCql05t70MkTEWs,63
|
|
@@ -28,8 +29,8 @@ sqlsaber/memory/storage.py,sha256=DvZBsSPaAfk_DqrNEn86uMD-TQsWUI6rQLfNw6PSCB8,57
|
|
|
28
29
|
sqlsaber/models/__init__.py,sha256=RJ7p3WtuSwwpFQ1Iw4_DHV2zzCtHqIzsjJzxv8kUjUE,287
|
|
29
30
|
sqlsaber/models/events.py,sha256=q2FackB60J9-7vegYIjzElLwKebIh7nxnV5AFoZc67c,752
|
|
30
31
|
sqlsaber/models/types.py,sha256=3U_30n91EB3IglBTHipwiW4MqmmaA2qfshfraMZyPps,896
|
|
31
|
-
sqlsaber-0.
|
|
32
|
-
sqlsaber-0.
|
|
33
|
-
sqlsaber-0.
|
|
34
|
-
sqlsaber-0.
|
|
35
|
-
sqlsaber-0.
|
|
32
|
+
sqlsaber-0.7.0.dist-info/METADATA,sha256=tUV3WHkVZEXissVrKAaOooaZyn7e_NmMV_e-nNaoLVE,5986
|
|
33
|
+
sqlsaber-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
34
|
+
sqlsaber-0.7.0.dist-info/entry_points.txt,sha256=jmFo96Ylm0zIKXJBwhv_P5wQ7SXP9qdaBcnTp8iCEe8,195
|
|
35
|
+
sqlsaber-0.7.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
36
|
+
sqlsaber-0.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|