sqlsaber 0.30.2__py3-none-any.whl → 0.32.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/auth.py +15 -1
- sqlsaber/cli/commands.py +74 -0
- sqlsaber/cli/database.py +39 -0
- sqlsaber/cli/interactive.py +8 -2
- sqlsaber/cli/memory.py +26 -0
- sqlsaber/cli/models.py +19 -0
- sqlsaber/cli/streaming.py +5 -0
- sqlsaber/cli/theme.py +8 -0
- sqlsaber/cli/threads.py +17 -0
- sqlsaber/config/logging.py +196 -0
- sqlsaber/config/oauth_flow.py +22 -10
- sqlsaber/config/oauth_tokens.py +15 -6
- sqlsaber/threads/storage.py +31 -17
- {sqlsaber-0.30.2.dist-info → sqlsaber-0.32.0.dist-info}/METADATA +2 -1
- {sqlsaber-0.30.2.dist-info → sqlsaber-0.32.0.dist-info}/RECORD +18 -17
- {sqlsaber-0.30.2.dist-info → sqlsaber-0.32.0.dist-info}/WHEEL +0 -0
- {sqlsaber-0.30.2.dist-info → sqlsaber-0.32.0.dist-info}/entry_points.txt +0 -0
- {sqlsaber-0.30.2.dist-info → sqlsaber-0.32.0.dist-info}/licenses/LICENSE +0 -0
sqlsaber/config/oauth_tokens.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"""OAuth token management for SQLSaber."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
import logging
|
|
5
4
|
from datetime import datetime, timedelta, timezone
|
|
6
5
|
from typing import Any
|
|
7
6
|
|
|
8
7
|
import keyring
|
|
9
8
|
|
|
9
|
+
from sqlsaber.config.logging import get_logger
|
|
10
10
|
from sqlsaber.theme.manager import create_console
|
|
11
11
|
|
|
12
12
|
console = create_console()
|
|
13
|
-
logger =
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class OAuthToken:
|
|
@@ -99,6 +99,7 @@ class OAuthTokenManager:
|
|
|
99
99
|
f"OAuth token for {provider} has expired and needs refresh",
|
|
100
100
|
style="muted",
|
|
101
101
|
)
|
|
102
|
+
logger.info("oauth.token.expired", provider=provider)
|
|
102
103
|
return token # Return anyway for refresh attempt
|
|
103
104
|
|
|
104
105
|
if token.expires_soon():
|
|
@@ -106,11 +107,14 @@ class OAuthTokenManager:
|
|
|
106
107
|
f"OAuth token for {provider} expires soon, consider refreshing",
|
|
107
108
|
style="muted",
|
|
108
109
|
)
|
|
110
|
+
logger.info("oauth.token.expires_soon", provider=provider)
|
|
109
111
|
|
|
110
112
|
return token
|
|
111
113
|
|
|
112
114
|
except Exception as e:
|
|
113
|
-
logger.warning(
|
|
115
|
+
logger.warning(
|
|
116
|
+
"oauth.token.retrieve_failed", provider=provider, error=str(e)
|
|
117
|
+
)
|
|
114
118
|
return None
|
|
115
119
|
|
|
116
120
|
def store_oauth_token(self, provider: str, token: OAuthToken) -> bool:
|
|
@@ -121,9 +125,10 @@ class OAuthTokenManager:
|
|
|
121
125
|
token_data = json.dumps(token.to_dict())
|
|
122
126
|
keyring.set_password(service_name, provider, token_data)
|
|
123
127
|
console.print(f"OAuth token for {provider} stored securely", style="green")
|
|
128
|
+
logger.info("oauth.token.stored", provider=provider)
|
|
124
129
|
return True
|
|
125
130
|
except Exception as e:
|
|
126
|
-
logger.error(
|
|
131
|
+
logger.error("oauth.token.store_failed", provider=provider, error=str(e))
|
|
127
132
|
console.print(
|
|
128
133
|
f"Warning: Could not store OAuth token in keyring: {e}",
|
|
129
134
|
style="warning",
|
|
@@ -149,7 +154,10 @@ class OAuthTokenManager:
|
|
|
149
154
|
token_type=existing_token.token_type,
|
|
150
155
|
)
|
|
151
156
|
|
|
152
|
-
|
|
157
|
+
success = self.store_oauth_token(provider, updated_token)
|
|
158
|
+
if success:
|
|
159
|
+
logger.info("oauth.token.updated", provider=provider)
|
|
160
|
+
return success
|
|
153
161
|
|
|
154
162
|
def remove_oauth_token(self, provider: str) -> bool:
|
|
155
163
|
"""Remove OAuth token from storage."""
|
|
@@ -158,9 +166,10 @@ class OAuthTokenManager:
|
|
|
158
166
|
try:
|
|
159
167
|
keyring.delete_password(service_name, provider)
|
|
160
168
|
console.print(f"OAuth token for {provider} removed", style="green")
|
|
169
|
+
logger.info("oauth.token.removed", provider=provider)
|
|
161
170
|
return True
|
|
162
171
|
except Exception as e:
|
|
163
|
-
logger.error(
|
|
172
|
+
logger.error("oauth.token.remove_failed", provider=provider, error=str(e))
|
|
164
173
|
console.print(
|
|
165
174
|
f"Warning: Could not remove OAuth token: {e}", style="warning"
|
|
166
175
|
)
|
sqlsaber/threads/storage.py
CHANGED
|
@@ -9,7 +9,6 @@ recommended approach of serializing ModelMessage[] with ModelMessagesTypeAdapter
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import asyncio
|
|
12
|
-
import logging
|
|
13
12
|
import time
|
|
14
13
|
import uuid
|
|
15
14
|
from pathlib import Path
|
|
@@ -19,7 +18,9 @@ import aiosqlite
|
|
|
19
18
|
import platformdirs
|
|
20
19
|
from pydantic_ai.messages import ModelMessage, ModelMessagesTypeAdapter
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
from sqlsaber.config.logging import get_logger
|
|
22
|
+
|
|
23
|
+
logger = get_logger(__name__)
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
SCHEMA_SQL = """
|
|
@@ -79,9 +80,9 @@ class ThreadStorage:
|
|
|
79
80
|
await db.executescript(SCHEMA_SQL)
|
|
80
81
|
await db.commit()
|
|
81
82
|
self._initialized = True
|
|
82
|
-
logger.
|
|
83
|
+
logger.info("threads.db.init", path=str(self.db_path))
|
|
83
84
|
except Exception as e: # pragma: no cover - best-effort persistence
|
|
84
|
-
logger.warning("
|
|
85
|
+
logger.warning("threads.db.init_failed", error=str(e))
|
|
85
86
|
|
|
86
87
|
async def save_snapshot(
|
|
87
88
|
self,
|
|
@@ -116,10 +117,10 @@ class ThreadStorage:
|
|
|
116
117
|
),
|
|
117
118
|
)
|
|
118
119
|
await db.commit()
|
|
119
|
-
logger.
|
|
120
|
+
logger.info("threads.create", thread_id=thread_id)
|
|
120
121
|
return thread_id
|
|
121
122
|
except Exception as e: # pragma: no cover
|
|
122
|
-
logger.warning("
|
|
123
|
+
logger.warning("threads.create_failed", error=str(e))
|
|
123
124
|
return thread_id
|
|
124
125
|
else:
|
|
125
126
|
try:
|
|
@@ -140,10 +141,12 @@ class ThreadStorage:
|
|
|
140
141
|
),
|
|
141
142
|
)
|
|
142
143
|
await db.commit()
|
|
143
|
-
logger.
|
|
144
|
+
logger.info("threads.update_snapshot", thread_id=thread_id)
|
|
144
145
|
return thread_id
|
|
145
146
|
except Exception as e: # pragma: no cover
|
|
146
|
-
logger.warning(
|
|
147
|
+
logger.warning(
|
|
148
|
+
"threads.update_snapshot_failed", thread_id=thread_id, error=str(e)
|
|
149
|
+
)
|
|
147
150
|
return thread_id
|
|
148
151
|
|
|
149
152
|
async def save_metadata(
|
|
@@ -167,9 +170,12 @@ class ThreadStorage:
|
|
|
167
170
|
(title, model_name, thread_id),
|
|
168
171
|
)
|
|
169
172
|
await db.commit()
|
|
173
|
+
logger.info("threads.update_metadata", thread_id=thread_id)
|
|
170
174
|
return True
|
|
171
175
|
except Exception as e: # pragma: no cover
|
|
172
|
-
logger.warning(
|
|
176
|
+
logger.warning(
|
|
177
|
+
"threads.update_metadata_failed", thread_id=thread_id, error=str(e)
|
|
178
|
+
)
|
|
173
179
|
return False
|
|
174
180
|
|
|
175
181
|
async def end_thread(self, thread_id: str) -> bool:
|
|
@@ -181,9 +187,10 @@ class ThreadStorage:
|
|
|
181
187
|
(time.time(), time.time(), thread_id),
|
|
182
188
|
)
|
|
183
189
|
await db.commit()
|
|
190
|
+
logger.info("threads.end", thread_id=thread_id)
|
|
184
191
|
return True
|
|
185
192
|
except Exception as e: # pragma: no cover
|
|
186
|
-
logger.warning("
|
|
193
|
+
logger.warning("threads.end_failed", thread_id=thread_id, error=str(e))
|
|
187
194
|
return False
|
|
188
195
|
|
|
189
196
|
async def get_thread(self, thread_id: str) -> Thread | None:
|
|
@@ -211,7 +218,7 @@ class ThreadStorage:
|
|
|
211
218
|
model_name=row[6],
|
|
212
219
|
)
|
|
213
220
|
except Exception as e: # pragma: no cover
|
|
214
|
-
logger.warning("
|
|
221
|
+
logger.warning("threads.get_failed", thread_id=thread_id, error=str(e))
|
|
215
222
|
return None
|
|
216
223
|
|
|
217
224
|
async def get_thread_messages(self, thread_id: str) -> list[ModelMessage]:
|
|
@@ -229,7 +236,9 @@ class ThreadStorage:
|
|
|
229
236
|
messages_blob: bytes = row[0]
|
|
230
237
|
return ModelMessagesTypeAdapter.validate_json(messages_blob)
|
|
231
238
|
except Exception as e: # pragma: no cover
|
|
232
|
-
logger.warning(
|
|
239
|
+
logger.warning(
|
|
240
|
+
"threads.get_messages_failed", thread_id=thread_id, error=str(e)
|
|
241
|
+
)
|
|
233
242
|
return []
|
|
234
243
|
|
|
235
244
|
async def list_threads(
|
|
@@ -265,7 +274,7 @@ class ThreadStorage:
|
|
|
265
274
|
)
|
|
266
275
|
return threads
|
|
267
276
|
except Exception as e: # pragma: no cover
|
|
268
|
-
logger.warning("
|
|
277
|
+
logger.warning("threads.list_failed", error=str(e))
|
|
269
278
|
return []
|
|
270
279
|
|
|
271
280
|
async def delete_thread(self, thread_id: str) -> bool:
|
|
@@ -274,9 +283,12 @@ class ThreadStorage:
|
|
|
274
283
|
async with self._lock, aiosqlite.connect(self.db_path) as db:
|
|
275
284
|
cur = await db.execute("DELETE FROM threads WHERE id = ?", (thread_id,))
|
|
276
285
|
await db.commit()
|
|
277
|
-
|
|
286
|
+
deleted = cur.rowcount > 0
|
|
287
|
+
if deleted:
|
|
288
|
+
logger.info("threads.delete", thread_id=thread_id)
|
|
289
|
+
return deleted
|
|
278
290
|
except Exception as e: # pragma: no cover
|
|
279
|
-
logger.warning("
|
|
291
|
+
logger.warning("threads.delete_failed", thread_id=thread_id, error=str(e))
|
|
280
292
|
return False
|
|
281
293
|
|
|
282
294
|
async def prune_threads(self, older_than_days: int = 30) -> int:
|
|
@@ -297,7 +309,9 @@ class ThreadStorage:
|
|
|
297
309
|
(cutoff,),
|
|
298
310
|
)
|
|
299
311
|
await db.commit()
|
|
300
|
-
|
|
312
|
+
deleted = cur.rowcount or 0
|
|
313
|
+
logger.info("threads.prune", days=older_than_days, deleted=deleted)
|
|
314
|
+
return deleted
|
|
301
315
|
except Exception as e: # pragma: no cover
|
|
302
|
-
logger.warning("
|
|
316
|
+
logger.warning("threads.prune_failed", days=older_than_days, error=str(e))
|
|
303
317
|
return 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlsaber
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.32.0
|
|
4
4
|
Summary: SQLsaber - Open-source agentic SQL assistant
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -18,6 +18,7 @@ Requires-Dist: pydantic-ai
|
|
|
18
18
|
Requires-Dist: questionary>=2.1.0
|
|
19
19
|
Requires-Dist: rich>=13.7.0
|
|
20
20
|
Requires-Dist: sqlglot[rs]>=27.20.0
|
|
21
|
+
Requires-Dist: structlog>=25.4.0
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
|
|
23
24
|
# SQLsaber
|
|
@@ -9,24 +9,25 @@ sqlsaber/application/db_setup.py,sha256=ZSgR9rJJVHttIjsbYQS9GEIyzkM09k5RLrVGdegr
|
|
|
9
9
|
sqlsaber/application/model_selection.py,sha256=fSC06MZNKinHDR-csMFVYYJFyK8MydKf6pStof74Jp0,3191
|
|
10
10
|
sqlsaber/application/prompts.py,sha256=4rMGcWpYJbNWPMzqVWseUMx0nwvXOkWS6GaTAJ5mhfc,3473
|
|
11
11
|
sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
|
|
12
|
-
sqlsaber/cli/auth.py,sha256=
|
|
13
|
-
sqlsaber/cli/commands.py,sha256=
|
|
12
|
+
sqlsaber/cli/auth.py,sha256=8BsAkOYarLSpGipYi2CCUqyAv7EbOCqVXKLmdrX_g2c,6966
|
|
13
|
+
sqlsaber/cli/commands.py,sha256=WocWlLrxA5kM8URfvIvWFtc0ocfgKWAwoYTxVNZhmM4,10962
|
|
14
14
|
sqlsaber/cli/completers.py,sha256=g-hLDq5fiBx7gg8Bte1Lq8GU-ZxCYVs4dcPsmHPIcK4,6574
|
|
15
|
-
sqlsaber/cli/database.py,sha256=
|
|
15
|
+
sqlsaber/cli/database.py,sha256=BBGj0eyduh5DDXNLZLDtWfY9kWpeT_ZX0J9R9INZyyU,12421
|
|
16
16
|
sqlsaber/cli/display.py,sha256=WB5JCumhXadziDEX1EZHG3vN1Chol5FNAaTXHieqFK0,17892
|
|
17
|
-
sqlsaber/cli/interactive.py,sha256=
|
|
18
|
-
sqlsaber/cli/memory.py,sha256=
|
|
19
|
-
sqlsaber/cli/models.py,sha256=
|
|
17
|
+
sqlsaber/cli/interactive.py,sha256=XSl_W1NOozFfxf3On5r6w53qMNULyttHnH4vR4iQGus,14140
|
|
18
|
+
sqlsaber/cli/memory.py,sha256=kAY5LLFueIF30gJ8ibfrFw42rOyy5wajeJGS4h5XQw4,9475
|
|
19
|
+
sqlsaber/cli/models.py,sha256=aVHazP_fiT-Mj9AtCdjliDtq3E3fJrhgP4oF5p4CuwI,9593
|
|
20
20
|
sqlsaber/cli/onboarding.py,sha256=iBGT-W-OJFRvQoEpuHYyO1c9Mym5c97eIefRvxGHtTg,11265
|
|
21
|
-
sqlsaber/cli/streaming.py,sha256=
|
|
22
|
-
sqlsaber/cli/theme.py,sha256=
|
|
23
|
-
sqlsaber/cli/threads.py,sha256=
|
|
21
|
+
sqlsaber/cli/streaming.py,sha256=jicSDLWQ3efitpdc2y4QsasHcEW8ogZ4lHcWmftq9Ao,6763
|
|
22
|
+
sqlsaber/cli/theme.py,sha256=D6HIt7rmF00B5ZOCV5lXKzPICE4uppHdraOdVs7k5Nw,4672
|
|
23
|
+
sqlsaber/cli/threads.py,sha256=zYvs1epmRRuQxOofF85eXk1_YHS6co7oq_F33DdNdf0,14643
|
|
24
24
|
sqlsaber/config/__init__.py,sha256=olwC45k8Nc61yK0WmPUk7XHdbsZH9HuUAbwnmKe3IgA,100
|
|
25
25
|
sqlsaber/config/api_keys.py,sha256=dJ7cCSFOM6CRLHxEVgKJXGIOd_wQkRuQO4W88-8ng_w,3672
|
|
26
26
|
sqlsaber/config/auth.py,sha256=b5qB2h1doXyO9Bn8z0CcL8LAR2jF431gGXBGKLgTmtQ,2756
|
|
27
27
|
sqlsaber/config/database.py,sha256=Yec6_0wdzq-ADblMNnbgvouYCimYOY_DWHT9oweaISc,11449
|
|
28
|
-
sqlsaber/config/
|
|
29
|
-
sqlsaber/config/
|
|
28
|
+
sqlsaber/config/logging.py,sha256=V-DGhKm7XYPtcA6bXL-aQQyTTlyHi2WHTqTTzu2xipw,6174
|
|
29
|
+
sqlsaber/config/oauth_flow.py,sha256=cDfaJjqr4spNuzxbAlzuJfk6SEe1ojSRAkoOWlvQYy0,11037
|
|
30
|
+
sqlsaber/config/oauth_tokens.py,sha256=KCC2u3lOjdh0M-rd0K1rW0PWk58w7mqpodAhlPVp9NE,6424
|
|
30
31
|
sqlsaber/config/providers.py,sha256=JFjeJv1K5Q93zWSlWq3hAvgch1TlgoF0qFa0KJROkKY,2957
|
|
31
32
|
sqlsaber/config/settings.py,sha256=-nIBNt9E0tCRGd14bk4x-bNAwO12sbsjRsN8fFannK4,6449
|
|
32
33
|
sqlsaber/database/__init__.py,sha256=Gi9N_NOkD459WRWXDg3hSuGoBs3xWbMDRBvsTVmnGAg,2025
|
|
@@ -44,14 +45,14 @@ sqlsaber/memory/storage.py,sha256=ne8szLlGj5NELheqLnI7zu21V8YS4rtpYGGC7tOmi-s,57
|
|
|
44
45
|
sqlsaber/theme/__init__.py,sha256=qCICX1Cg4B6yCbZ1UrerxglWxcqldRFVSRrSs73na_8,188
|
|
45
46
|
sqlsaber/theme/manager.py,sha256=TPourIKGU-UzHtImgexgtazpuDaFhqUYtVauMblgGAQ,6480
|
|
46
47
|
sqlsaber/threads/__init__.py,sha256=Hh3dIG1tuC8fXprREUpslCIgPYz8_6o7aRLx4yNeO48,139
|
|
47
|
-
sqlsaber/threads/storage.py,sha256=
|
|
48
|
+
sqlsaber/threads/storage.py,sha256=gs-BMjGbow7KOF3dOfbNS64B8TNfxdbktV9K2PyiszI,11801
|
|
48
49
|
sqlsaber/tools/__init__.py,sha256=O6eqkMk8mkhYDniQD1eYgAElOjiHz03I2bGARdgkDkk,421
|
|
49
50
|
sqlsaber/tools/base.py,sha256=NKEEooliPKTJj_Pomwte_wW0Xd9Z5kXNfVdCRfTppuw,883
|
|
50
51
|
sqlsaber/tools/registry.py,sha256=XmBzERq0LJXtg3BZ-r8cEyt8J54NUekgUlTJ_EdSYMk,2204
|
|
51
52
|
sqlsaber/tools/sql_guard.py,sha256=dTDwcZP-N4xPGzcr7MQtKUxKrlDzlc1irr9aH5a4wvk,6182
|
|
52
53
|
sqlsaber/tools/sql_tools.py,sha256=eo-NTxiXGHMopAjujvDDjmv9hf5bQNbiy3nTpxoJ_E8,7369
|
|
53
|
-
sqlsaber-0.
|
|
54
|
-
sqlsaber-0.
|
|
55
|
-
sqlsaber-0.
|
|
56
|
-
sqlsaber-0.
|
|
57
|
-
sqlsaber-0.
|
|
54
|
+
sqlsaber-0.32.0.dist-info/METADATA,sha256=Ai3-BNq7KNZ1oA4W-39jp6YTHIE_9ZApz_z0vCkVPyI,5915
|
|
55
|
+
sqlsaber-0.32.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
56
|
+
sqlsaber-0.32.0.dist-info/entry_points.txt,sha256=tw1mB0fjlkXQiOsC0434X6nE-o1cFCuQwt2ZYHv_WAE,91
|
|
57
|
+
sqlsaber-0.32.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
58
|
+
sqlsaber-0.32.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|