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 +14 -36
- sqlsaber/database/resolver.py +96 -0
- {sqlsaber-0.12.0.dist-info → sqlsaber-0.13.0.dist-info}/METADATA +1 -1
- {sqlsaber-0.12.0.dist-info → sqlsaber-0.13.0.dist-info}/RECORD +7 -6
- {sqlsaber-0.12.0.dist-info → sqlsaber-0.13.0.dist-info}/WHEEL +0 -0
- {sqlsaber-0.12.0.dist-info → sqlsaber-0.13.0.dist-info}/entry_points.txt +0 -0
- {sqlsaber-0.12.0.dist-info → sqlsaber-0.13.0.dist-info}/licenses/LICENSE +0 -0
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
|
|
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
|
|
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
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
+
)
|
|
@@ -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=
|
|
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.
|
|
43
|
-
sqlsaber-0.
|
|
44
|
-
sqlsaber-0.
|
|
45
|
-
sqlsaber-0.
|
|
46
|
-
sqlsaber-0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|