sqlsaber 0.12.0__py3-none-any.whl → 0.13.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
@@ -2,7 +2,6 @@
2
2
 
3
3
  import asyncio
4
4
  import sys
5
- from pathlib import Path
6
5
  from typing import Annotated
7
6
 
8
7
  import cyclopts
@@ -17,6 +16,7 @@ from sqlsaber.cli.models import create_models_app
17
16
  from sqlsaber.cli.streaming import StreamingQueryHandler
18
17
  from sqlsaber.config.database import DatabaseConfigManager
19
18
  from sqlsaber.database.connection import DatabaseConnection
19
+ from sqlsaber.database.resolver import DatabaseResolutionError, resolve_database
20
20
 
21
21
 
22
22
  class CLIError(Exception):
@@ -43,7 +43,7 @@ def meta_handler(
43
43
  str | None,
44
44
  cyclopts.Parameter(
45
45
  ["--database", "-d"],
46
- help="Database connection name or direct file path for CSV/SQLite files (uses default if not specified)",
46
+ help="Database connection name, file path (CSV/SQLite), or connection string (postgresql://, mysql://) (uses default if not specified)",
47
47
  ),
48
48
  ] = None,
49
49
  ):
@@ -56,6 +56,8 @@ def meta_handler(
56
56
  saber -d mydb "show me users" # Run a query with specific database
57
57
  saber -d data.csv "show me users" # Run a query with ad-hoc CSV file
58
58
  saber -d data.db "show me users" # Run a query with ad-hoc SQLite file
59
+ saber -d "postgresql://user:pass@host:5432/db" "show users" # PostgreSQL connection string
60
+ saber -d "mysql://user:pass@host:3306/db" "show users" # MySQL connection string
59
61
  echo "show me all users" | saber # Read query from stdin
60
62
  cat query.txt | saber # Read query from file via stdin
61
63
  """
@@ -75,7 +77,7 @@ def query(
75
77
  str | None,
76
78
  cyclopts.Parameter(
77
79
  ["--database", "-d"],
78
- help="Database connection name or direct file path for CSV/SQLite files (uses default if not specified)",
80
+ help="Database connection name, file path (CSV/SQLite), or connection string (postgresql://, mysql://) (uses default if not specified)",
79
81
  ),
80
82
  ] = None,
81
83
  ):
@@ -92,6 +94,8 @@ def query(
92
94
  saber "show me all users" # Run a single query
93
95
  saber -d data.csv "show users" # Run a query with ad-hoc CSV file
94
96
  saber -d data.db "show users" # Run a query with ad-hoc SQLite file
97
+ saber -d "postgresql://user:pass@host:5432/db" "show users" # PostgreSQL connection string
98
+ saber -d "mysql://user:pass@host:3306/db" "show users" # MySQL connection string
95
99
  echo "show me all users" | saber # Read query from stdin
96
100
  """
97
101
 
@@ -105,39 +109,13 @@ def query(
105
109
  # If stdin was empty, fall back to interactive mode
106
110
  actual_query = None
107
111
 
108
- # Get database configuration or handle direct file paths
109
- if database:
110
- # Check if this is a direct CSV file path
111
- if database.endswith(".csv"):
112
- csv_path = Path(database).expanduser().resolve()
113
- if not csv_path.exists():
114
- raise CLIError(f"CSV file '{database}' not found.")
115
- connection_string = f"csv:///{csv_path}"
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
124
- else:
125
- # Look up configured database connection
126
- db_config = config_manager.get_database(database)
127
- if not db_config:
128
- raise CLIError(
129
- f"Database connection '{database}' not found. Use 'sqlsaber db list' to see available connections."
130
- )
131
- connection_string = db_config.to_connection_string()
132
- db_name = db_config.name
133
- else:
134
- db_config = config_manager.get_default_database()
135
- if not db_config:
136
- raise CLIError(
137
- "No database connections configured. Use 'sqlsaber db add <name>' to add a database connection."
138
- )
139
- connection_string = db_config.to_connection_string()
140
- db_name = db_config.name
112
+ # Resolve database from CLI input
113
+ try:
114
+ resolved = resolve_database(database, config_manager)
115
+ connection_string = resolved.connection_string
116
+ db_name = resolved.name
117
+ except DatabaseResolutionError as e:
118
+ raise CLIError(str(e))
141
119
 
142
120
  # Create database connection
143
121
  try:
@@ -0,0 +1,96 @@
1
+ """Database connection resolution from CLI input."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from urllib.parse import urlparse
8
+
9
+ from sqlsaber.config.database import DatabaseConfig, DatabaseConfigManager
10
+
11
+
12
+ class DatabaseResolutionError(Exception):
13
+ """Exception raised when database resolution fails."""
14
+
15
+ pass
16
+
17
+
18
+ @dataclass
19
+ class ResolvedDatabase:
20
+ """Result of database resolution containing canonical connection info."""
21
+
22
+ name: str # Human-readable name for display/logging
23
+ connection_string: str # Canonical connection string for DatabaseConnection factory
24
+
25
+
26
+ SUPPORTED_SCHEMES = {"postgresql", "mysql", "sqlite", "csv"}
27
+
28
+
29
+ def _is_connection_string(s: str) -> bool:
30
+ """Check if string looks like a connection string with supported scheme."""
31
+ try:
32
+ scheme = urlparse(s).scheme
33
+ return scheme in SUPPORTED_SCHEMES
34
+ except Exception:
35
+ return False
36
+
37
+
38
+ def resolve_database(
39
+ spec: str | None, config_mgr: DatabaseConfigManager
40
+ ) -> ResolvedDatabase:
41
+ """Turn user CLI input into resolved database connection info.
42
+
43
+ Args:
44
+ spec: User input - None (default), configured name, connection string, or file path
45
+ config_mgr: Database configuration manager for looking up configured connections
46
+
47
+ Returns:
48
+ ResolvedDatabase with name and canonical connection string
49
+
50
+ Raises:
51
+ DatabaseResolutionError: If the spec cannot be resolved to a valid database connection
52
+ """
53
+ if spec is None:
54
+ db_cfg = config_mgr.get_default_database()
55
+ if not db_cfg:
56
+ raise DatabaseResolutionError(
57
+ "No database connections configured. "
58
+ "Use 'sqlsaber db add <name>' to add one."
59
+ )
60
+ return ResolvedDatabase(
61
+ name=db_cfg.name,
62
+ connection_string=db_cfg.to_connection_string(),
63
+ )
64
+
65
+ # 1. Connection string?
66
+ if _is_connection_string(spec):
67
+ scheme = urlparse(spec).scheme
68
+ if scheme in {"postgresql", "mysql"}:
69
+ db_name = urlparse(spec).path.lstrip("/") or "database"
70
+ elif scheme in {"sqlite", "csv"}:
71
+ db_name = Path(urlparse(spec).path).stem
72
+ else: # should not happen because of SUPPORTED_SCHEMES
73
+ db_name = "database"
74
+ return ResolvedDatabase(name=db_name, connection_string=spec)
75
+
76
+ # 2. Raw file path?
77
+ path = Path(spec).expanduser().resolve()
78
+ if path.suffix.lower() == ".csv":
79
+ if not path.exists():
80
+ raise DatabaseResolutionError(f"CSV file '{spec}' not found.")
81
+ return ResolvedDatabase(name=path.stem, connection_string=f"csv:///{path}")
82
+ if path.suffix.lower() in {".db", ".sqlite", ".sqlite3"}:
83
+ if not path.exists():
84
+ raise DatabaseResolutionError(f"SQLite file '{spec}' not found.")
85
+ return ResolvedDatabase(name=path.stem, connection_string=f"sqlite:///{path}")
86
+
87
+ # 3. Must be a configured name
88
+ db_cfg: DatabaseConfig | None = config_mgr.get_database(spec)
89
+ if not db_cfg:
90
+ raise DatabaseResolutionError(
91
+ f"Database connection '{spec}' not found. "
92
+ "Use 'sqlsaber db list' to see available connections."
93
+ )
94
+ return ResolvedDatabase(
95
+ name=db_cfg.name, connection_string=db_cfg.to_connection_string()
96
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlsaber
3
- Version: 0.12.0
3
+ Version: 0.13.0
4
4
  Summary: SQLSaber - Agentic SQL assistant like Claude Code
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -7,7 +7,7 @@ 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=rhbfpXNrWLQbiEEqn4GDwE175vhOWrfPiTRP32Ui7NM,7156
10
+ sqlsaber/cli/commands.py,sha256=VX7pqQnf-85A9zkjXqzytVNeCG8KO0mB2TyIEzB4sh8,6241
11
11
  sqlsaber/cli/completers.py,sha256=HsUPjaZweLSeYCWkAcgMl8FylQ1xjWBWYTEL_9F6xfU,6430
12
12
  sqlsaber/cli/database.py,sha256=tJ8rqGrafZpg3VgDmSiq7eZoPscoGAW3XLTYGoQw8LE,12910
13
13
  sqlsaber/cli/display.py,sha256=HtXwPe3VPUh2EJpyvpJVWyisCanu9O7w-rkqq7Y4UaY,9791
@@ -30,6 +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/resolver.py,sha256=RPXF5EoKzvQDDLmPGNHYd2uG_oNICH8qvUjBp6iXmNY,3348
33
34
  sqlsaber/database/schema.py,sha256=anlxjr_4-gEWlEjQgZ5h4KGMfbS0-aQH1VwhXIw41Q4,27951
34
35
  sqlsaber/mcp/__init__.py,sha256=COdWq7wauPBp5Ew8tfZItFzbcLDSEkHBJSMhxzy8C9c,112
35
36
  sqlsaber/mcp/mcp.py,sha256=YH4crygqb5_Y94nsns6d-26FZCTlDPOh3tf-ghihzDM,4440
@@ -39,8 +40,8 @@ sqlsaber/memory/storage.py,sha256=ne8szLlGj5NELheqLnI7zu21V8YS4rtpYGGC7tOmi-s,57
39
40
  sqlsaber/models/__init__.py,sha256=RJ7p3WtuSwwpFQ1Iw4_DHV2zzCtHqIzsjJzxv8kUjUE,287
40
41
  sqlsaber/models/events.py,sha256=89SXKb5GGpH01yTr2kPEBhzp9xv35RFIYuFdAZSIPoE,721
41
42
  sqlsaber/models/types.py,sha256=w-zk81V2dtveuteej36_o1fDK3So428j3P2rAejU62U,862
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,,
43
+ sqlsaber-0.13.0.dist-info/METADATA,sha256=WWlxSquzdf4e1KW1goid6R-WKBIzVKQ_K0M_N5O8hw0,6877
44
+ sqlsaber-0.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ sqlsaber-0.13.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
46
+ sqlsaber-0.13.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
47
+ sqlsaber-0.13.0.dist-info/RECORD,,