sqlsaber 0.10.0__py3-none-any.whl → 0.12.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/__init__.py CHANGED
@@ -1,3 +1 @@
1
- """SQLSaber CLI - SQL like Claude Code."""
2
-
3
- __version__ = "0.1.0"
1
+ """SQLSaber CLI - Agentic SQL assistant like Claude Code but for SQL."""
sqlsaber/cli/commands.py CHANGED
@@ -43,7 +43,7 @@ def meta_handler(
43
43
  str | None,
44
44
  cyclopts.Parameter(
45
45
  ["--database", "-d"],
46
- help="Database connection name (uses default if not specified)",
46
+ help="Database connection name or direct file path for CSV/SQLite files (uses default if not specified)",
47
47
  ),
48
48
  ] = None,
49
49
  ):
@@ -54,6 +54,8 @@ def meta_handler(
54
54
  saber # Start interactive mode
55
55
  saber "show me all users" # Run a single query with default database
56
56
  saber -d mydb "show me users" # Run a query with specific database
57
+ saber -d data.csv "show me users" # Run a query with ad-hoc CSV file
58
+ saber -d data.db "show me users" # Run a query with ad-hoc SQLite file
57
59
  echo "show me all users" | saber # Read query from stdin
58
60
  cat query.txt | saber # Read query from file via stdin
59
61
  """
@@ -73,7 +75,7 @@ def query(
73
75
  str | None,
74
76
  cyclopts.Parameter(
75
77
  ["--database", "-d"],
76
- help="Database connection name (uses default if not specified)",
78
+ help="Database connection name or direct file path for CSV/SQLite files (uses default if not specified)",
77
79
  ),
78
80
  ] = None,
79
81
  ):
@@ -88,6 +90,8 @@ def query(
88
90
  Examples:
89
91
  saber # Start interactive mode
90
92
  saber "show me all users" # Run a single query
93
+ saber -d data.csv "show users" # Run a query with ad-hoc CSV file
94
+ saber -d data.db "show users" # Run a query with ad-hoc SQLite file
91
95
  echo "show me all users" | saber # Read query from stdin
92
96
  """
93
97
 
@@ -101,7 +105,7 @@ def query(
101
105
  # If stdin was empty, fall back to interactive mode
102
106
  actual_query = None
103
107
 
104
- # Get database configuration or handle direct CSV file
108
+ # Get database configuration or handle direct file paths
105
109
  if database:
106
110
  # Check if this is a direct CSV file path
107
111
  if database.endswith(".csv"):
@@ -110,6 +114,13 @@ def query(
110
114
  raise CLIError(f"CSV file '{database}' not found.")
111
115
  connection_string = f"csv:///{csv_path}"
112
116
  db_name = csv_path.stem
117
+ # Check if this is a direct SQLite file path
118
+ elif database.endswith((".db", ".sqlite", ".sqlite3")):
119
+ sqlite_path = Path(database).expanduser().resolve()
120
+ if not sqlite_path.exists():
121
+ raise CLIError(f"SQLite file '{database}' not found.")
122
+ connection_string = f"sqlite:///{sqlite_path}"
123
+ db_name = sqlite_path.stem
113
124
  else:
114
125
  # Look up configured database connection
115
126
  db_config = config_manager.get_database(database)
sqlsaber/cli/display.py CHANGED
@@ -125,7 +125,6 @@ class DisplayManager:
125
125
  {"name": "Schema", "style": "cyan"},
126
126
  {"name": "Table Name", "style": "white"},
127
127
  {"name": "Type", "style": "yellow"},
128
- {"name": "Row Count", "justify": "right", "style": "magenta"},
129
128
  ]
130
129
  table = self._create_table(columns)
131
130
 
@@ -134,12 +133,8 @@ class DisplayManager:
134
133
  schema = table_info.get("schema", "")
135
134
  name = table_info.get("name", "")
136
135
  table_type = table_info.get("type", "")
137
- row_count = table_info.get("row_count", 0)
138
136
 
139
- # Format row count with commas for readability
140
- formatted_count = f"{row_count:,}" if row_count else "0"
141
-
142
- table.add_row(schema, name, table_type, formatted_count)
137
+ table.add_row(schema, name, table_type)
143
138
 
144
139
  self.console.print(table)
145
140
 
@@ -42,7 +42,7 @@ class BaseSchemaIntrospector(ABC):
42
42
  pass
43
43
 
44
44
  @abstractmethod
45
- async def list_tables_info(self, connection) -> dict[str, Any]:
45
+ async def list_tables_info(self, connection) -> list[dict[str, Any]]:
46
46
  """Get list of tables with basic information."""
47
47
  pass
48
48
 
@@ -182,32 +182,31 @@ class PostgreSQLSchemaIntrospector(BaseSchemaIntrospector):
182
182
  """
183
183
  return await conn.fetch(pk_query)
184
184
 
185
- async def list_tables_info(self, connection) -> dict[str, Any]:
185
+ async def list_tables_info(self, connection) -> list[dict[str, Any]]:
186
186
  """Get list of tables with basic information for PostgreSQL."""
187
187
  pool = await connection.get_pool()
188
188
  async with pool.acquire() as conn:
189
- # Get tables with row counts
189
+ # Get tables without row counts for better performance
190
190
  tables_query = """
191
- WITH table_stats AS (
192
- SELECT
193
- schemaname,
194
- relname as tablename,
195
- n_live_tup as approximate_row_count
196
- FROM pg_stat_user_tables
197
- )
198
191
  SELECT
199
192
  t.table_schema,
200
193
  t.table_name,
201
- t.table_type,
202
- COALESCE(ts.approximate_row_count, 0) as row_count
194
+ t.table_type
203
195
  FROM information_schema.tables t
204
- LEFT JOIN table_stats ts
205
- ON t.table_schema = ts.schemaname
206
- AND t.table_name = ts.tablename
207
196
  WHERE t.table_schema NOT IN ('pg_catalog', 'information_schema')
208
197
  ORDER BY t.table_schema, t.table_name;
209
198
  """
210
- return await conn.fetch(tables_query)
199
+ records = await conn.fetch(tables_query)
200
+
201
+ # Convert asyncpg.Record objects to dictionaries
202
+ return [
203
+ {
204
+ "table_schema": record["table_schema"],
205
+ "table_name": record["table_name"],
206
+ "table_type": record["table_type"],
207
+ }
208
+ for record in records
209
+ ]
211
210
 
212
211
 
213
212
  class MySQLSchemaIntrospector(BaseSchemaIntrospector):
@@ -353,24 +352,33 @@ class MySQLSchemaIntrospector(BaseSchemaIntrospector):
353
352
  await cursor.execute(pk_query)
354
353
  return await cursor.fetchall()
355
354
 
356
- async def list_tables_info(self, connection) -> dict[str, Any]:
355
+ async def list_tables_info(self, connection) -> list[dict[str, Any]]:
357
356
  """Get list of tables with basic information for MySQL."""
358
357
  pool = await connection.get_pool()
359
358
  async with pool.acquire() as conn:
360
359
  async with conn.cursor() as cursor:
361
- # Get tables with row counts
360
+ # Get tables without row counts for better performance
362
361
  tables_query = """
363
362
  SELECT
364
363
  t.table_schema,
365
364
  t.table_name,
366
- t.table_type,
367
- COALESCE(t.table_rows, 0) as row_count
365
+ t.table_type
368
366
  FROM information_schema.tables t
369
367
  WHERE t.table_schema NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
370
368
  ORDER BY t.table_schema, t.table_name;
371
369
  """
372
370
  await cursor.execute(tables_query)
373
- return await cursor.fetchall()
371
+ rows = await cursor.fetchall()
372
+
373
+ # Convert rows to dictionaries
374
+ return [
375
+ {
376
+ "table_schema": row["table_schema"],
377
+ "table_name": row["table_name"],
378
+ "table_type": row["table_type"],
379
+ }
380
+ for row in rows
381
+ ]
374
382
 
375
383
 
376
384
  class SQLiteSchemaIntrospector(BaseSchemaIntrospector):
@@ -496,9 +504,9 @@ class SQLiteSchemaIntrospector(BaseSchemaIntrospector):
496
504
 
497
505
  return primary_keys
498
506
 
499
- async def list_tables_info(self, connection) -> dict[str, Any]:
507
+ async def list_tables_info(self, connection) -> list[dict[str, Any]]:
500
508
  """Get list of tables with basic information for SQLite."""
501
- # First get the table names
509
+ # Get table names without row counts for better performance
502
510
  tables_query = """
503
511
  SELECT
504
512
  'main' as table_schema,
@@ -512,34 +520,15 @@ class SQLiteSchemaIntrospector(BaseSchemaIntrospector):
512
520
 
513
521
  tables = await self._execute_query(connection, tables_query)
514
522
 
515
- # Now get row counts for each table
516
- result = []
517
- for table in tables:
518
- table_name = table["table_name"]
519
- table_type = table["table_type"]
520
-
521
- # Only count rows for tables, not views
522
- if table_type.lower() == "table":
523
- try:
524
- count_query = f"SELECT COUNT(*) as count FROM [{table_name}]"
525
- count_result = await self._execute_query(connection, count_query)
526
- row_count = count_result[0]["count"] if count_result else 0
527
- except Exception:
528
- # If count fails (e.g., table locked), default to 0
529
- row_count = 0
530
- else:
531
- # For views, we don't count rows as it could be expensive
532
- row_count = 0
533
-
534
- result.append(
535
- {
536
- "table_schema": table["table_schema"],
537
- "table_name": table_name,
538
- "table_type": table_type,
539
- "row_count": row_count,
540
- }
541
- )
542
- return result
523
+ # Convert to expected format
524
+ return [
525
+ {
526
+ "table_schema": table["table_schema"],
527
+ "table_name": table["table_name"],
528
+ "table_type": table["table_type"],
529
+ }
530
+ for table in tables
531
+ ]
543
532
 
544
533
 
545
534
  class SchemaManager:
@@ -682,7 +671,7 @@ class SchemaManager:
682
671
  )
683
672
 
684
673
  async def list_tables(self) -> dict[str, Any]:
685
- """Get a list of all tables with basic information like row counts."""
674
+ """Get a list of all tables with basic information."""
686
675
  # Check cache first
687
676
  cache_key = "list_tables"
688
677
  cached_data = self._get_cached_tables(cache_key)
@@ -702,7 +691,6 @@ class SchemaManager:
702
691
  "name": table["table_name"],
703
692
  "full_name": f"{table['table_schema']}.{table['table_name']}",
704
693
  "type": table["table_type"],
705
- "row_count": table["row_count"],
706
694
  }
707
695
  )
708
696
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlsaber
3
- Version: 0.10.0
3
+ Version: 0.12.0
4
4
  Summary: SQLSaber - Agentic SQL assistant like Claude Code
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -21,6 +21,15 @@ Description-Content-Type: text/markdown
21
21
 
22
22
  # SQLSaber
23
23
 
24
+ ```
25
+ ███████ ██████ ██ ███████ █████ ██████ ███████ ██████
26
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
27
+ ███████ ██ ██ ██ ███████ ███████ ██████ █████ ██████
28
+ ██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
29
+ ███████ ██████ ███████ ███████ ██ ██ ██████ ███████ ██ ██
30
+ ▀▀
31
+ ```
32
+
24
33
  > Use the agent Luke!
25
34
 
26
35
  SQLSaber is an agentic SQL assistant. Think Claude Code but for SQL.
@@ -1,4 +1,4 @@
1
- sqlsaber/__init__.py,sha256=QCFi8xTVMohelfi7zOV1-6oLCcGoiXoOcKQY-HNBCk8,66
1
+ sqlsaber/__init__.py,sha256=HjS8ULtP4MGpnTL7njVY45NKV9Fi4e_yeYuY-hyXWQc,73
2
2
  sqlsaber/__main__.py,sha256=RIHxWeWh2QvLfah-2OkhI5IJxojWfy4fXpMnVEJYvxw,78
3
3
  sqlsaber/agents/__init__.py,sha256=LWeSeEUE4BhkyAYFF3TE-fx8TtLud3oyEtyB8ojFJgo,167
4
4
  sqlsaber/agents/anthropic.py,sha256=CBHneR5NJhu155d0-D1mSGOcTH7kmbXZSLv2mVQotSM,22128
@@ -7,10 +7,10 @@ sqlsaber/agents/mcp.py,sha256=FKtXgDrPZ2-xqUYCw2baI5JzrWekXaC5fjkYW1_Mg50,827
7
7
  sqlsaber/agents/streaming.py,sha256=LaSeMTlxuJFRArJVqDly5-_KgcePiCCKPKfMxfB4oGs,521
8
8
  sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
9
9
  sqlsaber/cli/auth.py,sha256=1yvawtS5NGj9dWMRZ8I5T6sqBiRqZpPsjEPrJSQBJAs,4979
10
- sqlsaber/cli/commands.py,sha256=84KpBtrcoprWVuQjlGuzR6D34Ep2fCf8G1pmxrThswE,6329
10
+ sqlsaber/cli/commands.py,sha256=rhbfpXNrWLQbiEEqn4GDwE175vhOWrfPiTRP32Ui7NM,7156
11
11
  sqlsaber/cli/completers.py,sha256=HsUPjaZweLSeYCWkAcgMl8FylQ1xjWBWYTEL_9F6xfU,6430
12
12
  sqlsaber/cli/database.py,sha256=tJ8rqGrafZpg3VgDmSiq7eZoPscoGAW3XLTYGoQw8LE,12910
13
- sqlsaber/cli/display.py,sha256=wC-xYmmD21XyAkpRdMW7Ch2Mn5SlM1X34pbpka2ZIX8,10083
13
+ sqlsaber/cli/display.py,sha256=HtXwPe3VPUh2EJpyvpJVWyisCanu9O7w-rkqq7Y4UaY,9791
14
14
  sqlsaber/cli/interactive.py,sha256=NjrhzXt6YK4WL17DMkJTY5dA1v9QMxLdkI1XgPKK8YA,8017
15
15
  sqlsaber/cli/memory.py,sha256=OufHFJFwV0_GGn7LvKRTJikkWhV1IwNIUDOxFPHXOaQ,7794
16
16
  sqlsaber/cli/models.py,sha256=HByezaeKqj65GzB_FmWuugjkgTq2Pvab_mzNZnHxya0,7690
@@ -30,7 +30,7 @@ sqlsaber/config/oauth_tokens.py,sha256=C9z35hyx-PvSAYdC1LNf3rg9_wsEIY56hkEczelba
30
30
  sqlsaber/config/settings.py,sha256=gKhGlErzsBk39RoRSy1b8pb-bN2K7HIaPaBgbJDhY4M,4753
31
31
  sqlsaber/database/__init__.py,sha256=a_gtKRJnZVO8-fEZI7g3Z8YnGa6Nio-5Y50PgVp07ss,176
32
32
  sqlsaber/database/connection.py,sha256=sZVGNMzMwiM11GrsLLPwR8A5ugzJ5O0TCdkrt0KVRuI,15123
33
- sqlsaber/database/schema.py,sha256=B4emtbaNiqjz6aGBUQYYwARsTMqBilvWSurNg_zKu9U,28600
33
+ sqlsaber/database/schema.py,sha256=anlxjr_4-gEWlEjQgZ5h4KGMfbS0-aQH1VwhXIw41Q4,27951
34
34
  sqlsaber/mcp/__init__.py,sha256=COdWq7wauPBp5Ew8tfZItFzbcLDSEkHBJSMhxzy8C9c,112
35
35
  sqlsaber/mcp/mcp.py,sha256=YH4crygqb5_Y94nsns6d-26FZCTlDPOh3tf-ghihzDM,4440
36
36
  sqlsaber/memory/__init__.py,sha256=GiWkU6f6YYVV0EvvXDmFWe_CxarmDCql05t70MkTEWs,63
@@ -39,8 +39,8 @@ sqlsaber/memory/storage.py,sha256=ne8szLlGj5NELheqLnI7zu21V8YS4rtpYGGC7tOmi-s,57
39
39
  sqlsaber/models/__init__.py,sha256=RJ7p3WtuSwwpFQ1Iw4_DHV2zzCtHqIzsjJzxv8kUjUE,287
40
40
  sqlsaber/models/events.py,sha256=89SXKb5GGpH01yTr2kPEBhzp9xv35RFIYuFdAZSIPoE,721
41
41
  sqlsaber/models/types.py,sha256=w-zk81V2dtveuteej36_o1fDK3So428j3P2rAejU62U,862
42
- sqlsaber-0.10.0.dist-info/METADATA,sha256=mnHp2BteZvCNwoG3mUW-ulbBFxvQa4i5JkCffUHCEhc,6150
43
- sqlsaber-0.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
44
- sqlsaber-0.10.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
45
- sqlsaber-0.10.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
46
- sqlsaber-0.10.0.dist-info/RECORD,,
42
+ sqlsaber-0.12.0.dist-info/METADATA,sha256=k_OjB22bXlyVvH_nPTEIBjLwwzmdsAntYs6_rl7LSzg,6877
43
+ sqlsaber-0.12.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
44
+ sqlsaber-0.12.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
45
+ sqlsaber-0.12.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
46
+ sqlsaber-0.12.0.dist-info/RECORD,,