zaturn 0.1.7__py3-none-any.whl → 0.2.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.
- zaturn/mcp/__init__.py +97 -0
- zaturn/studio/__init__.py +5 -0
- zaturn/studio/agent_wrapper.py +131 -0
- zaturn/studio/app.py +288 -0
- zaturn/studio/static/fira_code.ttf +0 -0
- zaturn/studio/static/inter_ital_var.ttf +0 -0
- zaturn/studio/static/inter_var.ttf +0 -0
- zaturn/studio/static/js/htmx-multi-swap.js +44 -0
- zaturn/studio/static/js/htmx.min.js +1 -0
- zaturn/studio/static/logo.png +0 -0
- zaturn/studio/static/logo.svg +10 -0
- zaturn/studio/static/noto_emoji.ttf +0 -0
- zaturn/studio/storage.py +85 -0
- zaturn/studio/templates/_shell.html +38 -0
- zaturn/studio/templates/ai_message.html +4 -0
- zaturn/studio/templates/c_settings_updated.html +1 -0
- zaturn/studio/templates/c_source_card.html +19 -0
- zaturn/studio/templates/chat.html +22 -0
- zaturn/studio/templates/css/style.css +406 -0
- zaturn/studio/templates/function_call.html +7 -0
- zaturn/studio/templates/loader.html +1 -0
- zaturn/studio/templates/manage_sources.html +45 -0
- zaturn/studio/templates/nav.html +5 -0
- zaturn/studio/templates/new_conversation.html +13 -0
- zaturn/studio/templates/settings.html +29 -0
- zaturn/studio/templates/setup_prompt.html +6 -0
- zaturn/studio/templates/user_message.html +4 -0
- zaturn/tools/__init__.py +13 -0
- zaturn/{config.py → tools/config.py} +7 -9
- zaturn/tools/core.py +97 -0
- zaturn/{query_utils.py → tools/query_utils.py} +52 -2
- zaturn/tools/visualizations.py +267 -0
- zaturn-0.2.0.dist-info/METADATA +128 -0
- zaturn-0.2.0.dist-info/RECORD +39 -0
- {zaturn-0.1.7.dist-info → zaturn-0.2.0.dist-info}/WHEEL +1 -1
- zaturn-0.2.0.dist-info/entry_points.txt +3 -0
- zaturn/__init__.py +0 -14
- zaturn/core.py +0 -140
- zaturn/visualizations.py +0 -155
- zaturn-0.1.7.dist-info/METADATA +0 -185
- zaturn-0.1.7.dist-info/RECORD +0 -12
- zaturn-0.1.7.dist-info/entry_points.txt +0 -2
- /zaturn/{example_data → tools/example_data}/all_pokemon_data.csv +0 -0
- {zaturn-0.1.7.dist-info → zaturn-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {zaturn-0.1.7.dist-info → zaturn-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,406 @@
|
|
1
|
+
/* Fonts */
|
2
|
+
@font-face {
|
3
|
+
font-family: "Inter";
|
4
|
+
src: url('/static/inter_var.ttf');
|
5
|
+
}
|
6
|
+
|
7
|
+
@font-face {
|
8
|
+
font-family: "Inter";
|
9
|
+
font-style: italic;
|
10
|
+
src: url('/static/inter_ital_var.ttf');
|
11
|
+
}
|
12
|
+
|
13
|
+
@font-face {
|
14
|
+
font-family: "Fira Code";
|
15
|
+
src: url('/static/fira_code.ttf');
|
16
|
+
}
|
17
|
+
|
18
|
+
@font-face {
|
19
|
+
font-family: "Noto Emoji";
|
20
|
+
src: url('/static/noto_emoji.ttf');
|
21
|
+
}
|
22
|
+
|
23
|
+
/* Global Styles */
|
24
|
+
* {
|
25
|
+
box-sizing: border-box;
|
26
|
+
font-family: "Inter", "Noto Emoji", sans-serif;
|
27
|
+
margin: 0;
|
28
|
+
padding: 0;
|
29
|
+
}
|
30
|
+
|
31
|
+
:root {
|
32
|
+
--bg0: #ffffff;
|
33
|
+
--bg1: #e4e9f1;
|
34
|
+
--bg2: #dde3ee;
|
35
|
+
--t1: #336e80;
|
36
|
+
--t2: #5dd9ac;
|
37
|
+
--red: #d43a3a;
|
38
|
+
--fg2: #3a4d74;
|
39
|
+
--fg1: #182030;
|
40
|
+
--base-size: clamp(16px, 1.4vw, 32px);
|
41
|
+
--max-width: calc(var(--base-size)*36);
|
42
|
+
font-size: var(--base-size);
|
43
|
+
letter-spacing: 0.02rem;
|
44
|
+
}
|
45
|
+
|
46
|
+
body {
|
47
|
+
font-size: 1rem;
|
48
|
+
background: var(--bg0);
|
49
|
+
color: var(--fg1);
|
50
|
+
margin: 0;
|
51
|
+
padding: 0;
|
52
|
+
display: grid;
|
53
|
+
grid-template-columns: 1fr 3fr;
|
54
|
+
grid-template-rows: auto 1fr;
|
55
|
+
align-content: stretch;
|
56
|
+
height: 99vh;
|
57
|
+
line-height: 170%;
|
58
|
+
}
|
59
|
+
|
60
|
+
a {
|
61
|
+
color: var(--t1);
|
62
|
+
cursor: pointer;
|
63
|
+
}
|
64
|
+
|
65
|
+
header {
|
66
|
+
grid-area: 1 / 1 / 2 / 2;
|
67
|
+
padding: 0.5rem;
|
68
|
+
display: grid;
|
69
|
+
align-items: center;
|
70
|
+
grid-template-columns: 1fr auto auto auto;
|
71
|
+
gap: 0.5rem;
|
72
|
+
}
|
73
|
+
|
74
|
+
header a {
|
75
|
+
text-decoration: none;
|
76
|
+
}
|
77
|
+
|
78
|
+
header .logo {
|
79
|
+
width: 2rem;
|
80
|
+
height: 2rem;
|
81
|
+
}
|
82
|
+
|
83
|
+
#sidebar {
|
84
|
+
grid-area: 2 / 1 / 3 / 2;
|
85
|
+
background: var(--bg0);
|
86
|
+
height: 100%;
|
87
|
+
align-self: start;
|
88
|
+
padding: 0 0.5rem;
|
89
|
+
margin: 0.25rem 0;
|
90
|
+
display: grid;
|
91
|
+
align-content: start;
|
92
|
+
gap: 0.25rem;
|
93
|
+
overflow-y: auto;
|
94
|
+
}
|
95
|
+
|
96
|
+
#sidebar a {
|
97
|
+
text-decoration: none;
|
98
|
+
display: block;
|
99
|
+
padding: 0.6rem 0.25rem 0.4rem 0.25rem;
|
100
|
+
border-top: 1px solid var(--bg2);
|
101
|
+
font-size: 83%;
|
102
|
+
}
|
103
|
+
|
104
|
+
main {
|
105
|
+
grid-area: 1 / 2 / 3 / 3;
|
106
|
+
justify-items: center;
|
107
|
+
display: grid;
|
108
|
+
padding: 0.5rem;
|
109
|
+
height: 100vh;
|
110
|
+
overflow-y: scroll;
|
111
|
+
}
|
112
|
+
|
113
|
+
section {
|
114
|
+
display: inline-block;
|
115
|
+
width: 100%;
|
116
|
+
max-width: 120vh;
|
117
|
+
margin: 1rem 0;
|
118
|
+
padding: 0.5rem;
|
119
|
+
}
|
120
|
+
|
121
|
+
section h1 {
|
122
|
+
margin-bottom: 2rem;
|
123
|
+
}
|
124
|
+
|
125
|
+
.btn, button {
|
126
|
+
text-decoration: none;
|
127
|
+
background: var(--bg2);
|
128
|
+
display: inline-block;
|
129
|
+
padding: 0.5rem 1rem;
|
130
|
+
border-radius: 0.2rem;
|
131
|
+
color: var(--t1);
|
132
|
+
font-weight: bold;
|
133
|
+
cursor: pointer;
|
134
|
+
border: 1px solid var(--t1);
|
135
|
+
outline: none;
|
136
|
+
font-size: inherit;
|
137
|
+
}
|
138
|
+
|
139
|
+
.btn.danger, button.danger {
|
140
|
+
color: var(--red);
|
141
|
+
border: 1px solid var(--red);
|
142
|
+
}
|
143
|
+
|
144
|
+
.btn:hover, button:hover, .btn:focus, button:focus {
|
145
|
+
opacity: 0.8;
|
146
|
+
}
|
147
|
+
|
148
|
+
code {
|
149
|
+
font-family: "Fira Code", monospace;
|
150
|
+
font-size: 0.8rem;
|
151
|
+
}
|
152
|
+
|
153
|
+
.row-c2 {
|
154
|
+
display: grid;
|
155
|
+
grid-template-columns: 1fr 1fr;
|
156
|
+
gap: 1rem;
|
157
|
+
}
|
158
|
+
|
159
|
+
.alert {
|
160
|
+
background: var(--bg0);
|
161
|
+
padding: 0.5rem;
|
162
|
+
border: 1px solid var(--bg2);
|
163
|
+
}
|
164
|
+
|
165
|
+
|
166
|
+
/* General Forms */
|
167
|
+
form {
|
168
|
+
display: grid;
|
169
|
+
gap: 1.5rem;
|
170
|
+
background: var(--bg0);
|
171
|
+
padding: 1rem 0.5rem;
|
172
|
+
}
|
173
|
+
|
174
|
+
form label {
|
175
|
+
display: grid;
|
176
|
+
gap: 0.25rem;
|
177
|
+
}
|
178
|
+
|
179
|
+
form small {
|
180
|
+
font-size: 0.8rem;
|
181
|
+
font-weight: bold;
|
182
|
+
}
|
183
|
+
|
184
|
+
form input {
|
185
|
+
border: none;
|
186
|
+
outline: none;
|
187
|
+
font-family: "Fira Code", monospace;
|
188
|
+
border-bottom: 1px solid var(--fg1);
|
189
|
+
color: var(--fg1);
|
190
|
+
font-size: 0.8rem;
|
191
|
+
padding: 0.2rem;
|
192
|
+
}
|
193
|
+
|
194
|
+
form input:focus {
|
195
|
+
color: var(--t1);
|
196
|
+
border-bottom: 1px solid var(--t1);
|
197
|
+
}
|
198
|
+
|
199
|
+
form button {
|
200
|
+
justify-self: start;
|
201
|
+
}
|
202
|
+
|
203
|
+
form progress[value="0"] {
|
204
|
+
display: none;
|
205
|
+
}
|
206
|
+
|
207
|
+
form textarea {
|
208
|
+
font-size: inherit;
|
209
|
+
padding: 0.5rem;
|
210
|
+
border: none;
|
211
|
+
outline: none;
|
212
|
+
}
|
213
|
+
|
214
|
+
form.htmx-request {
|
215
|
+
display: none;
|
216
|
+
}
|
217
|
+
|
218
|
+
form ~ .loader {
|
219
|
+
display: none;
|
220
|
+
font-size: 1.2rem;
|
221
|
+
text-align: center;
|
222
|
+
padding: 2rem 0.5rem;
|
223
|
+
animation: loadanim 2s ease-in-out infinite;
|
224
|
+
}
|
225
|
+
|
226
|
+
form.htmx-request + .loader {
|
227
|
+
display: block;
|
228
|
+
}
|
229
|
+
|
230
|
+
@keyframes loadanim {
|
231
|
+
0% {opacity: 1;}
|
232
|
+
50% {opacity: 0.3;}
|
233
|
+
100% {opacity: 1;}
|
234
|
+
}
|
235
|
+
|
236
|
+
|
237
|
+
|
238
|
+
/*
|
239
|
+
SCREEN WISE CSS
|
240
|
+
*/
|
241
|
+
|
242
|
+
/* Setup Prompt */
|
243
|
+
|
244
|
+
#setup-prompt {
|
245
|
+
margin-top: 5rem;
|
246
|
+
}
|
247
|
+
|
248
|
+
/* Manage Sources */
|
249
|
+
|
250
|
+
#manage-sources .source-card {
|
251
|
+
display: grid;
|
252
|
+
grid-template-columns: 1fr auto auto;
|
253
|
+
padding: 1rem;
|
254
|
+
align-items: center;
|
255
|
+
gap: 1rem;
|
256
|
+
border-top: 1px solid var(--bg2);
|
257
|
+
}
|
258
|
+
|
259
|
+
#manage-sources .source-card form {
|
260
|
+
padding: 0;
|
261
|
+
display: inline-block;
|
262
|
+
background: none;
|
263
|
+
}
|
264
|
+
|
265
|
+
#manage-sources .source-card button {
|
266
|
+
font-size: 0.75rem;
|
267
|
+
padding: 0.25rem 0.5rem;
|
268
|
+
}
|
269
|
+
|
270
|
+
#manage-sources .source-card button.inactive {
|
271
|
+
filter: grayscale(100%);
|
272
|
+
}
|
273
|
+
|
274
|
+
#manage-sources .row-c2 {
|
275
|
+
margin-top: 1.5rem;
|
276
|
+
}
|
277
|
+
|
278
|
+
#manage-sources .row-c2 form {
|
279
|
+
border: 1px solid var(--bg2);
|
280
|
+
border-radius: 0.2rem;
|
281
|
+
}
|
282
|
+
|
283
|
+
|
284
|
+
/* New Conversation */
|
285
|
+
|
286
|
+
#new-conversation form {
|
287
|
+
border-top: 1px solid var(--bg2);
|
288
|
+
margin-top: 1rem;
|
289
|
+
}
|
290
|
+
|
291
|
+
|
292
|
+
/* Chat Display */
|
293
|
+
|
294
|
+
#chat {
|
295
|
+
border-radius: 0.2rem;
|
296
|
+
}
|
297
|
+
|
298
|
+
#chat>* {
|
299
|
+
margin: 0;
|
300
|
+
border-bottom: 1px solid var(--bg2);
|
301
|
+
padding: 1.5rem 0.5rem;
|
302
|
+
}
|
303
|
+
|
304
|
+
#chat ul {
|
305
|
+
list-style-position: inside;
|
306
|
+
}
|
307
|
+
|
308
|
+
#chat .user-message {
|
309
|
+
display: grid;
|
310
|
+
grid-template-columns: auto 1fr;
|
311
|
+
gap: 1rem;
|
312
|
+
padding-right: 1rem;
|
313
|
+
align-items: start;
|
314
|
+
}
|
315
|
+
|
316
|
+
#chat .user-message .sender {
|
317
|
+
color: var(--t1);
|
318
|
+
border: 1px solid var(--t1);
|
319
|
+
padding: 0.125rem 0.5rem;
|
320
|
+
font-size: 70%;
|
321
|
+
}
|
322
|
+
|
323
|
+
|
324
|
+
|
325
|
+
#chat .ai-message {
|
326
|
+
display: grid;
|
327
|
+
gap: 0.25rem;
|
328
|
+
}
|
329
|
+
|
330
|
+
#chat .ai-message .sender {
|
331
|
+
color: var(--t1);
|
332
|
+
font-size: 70%;
|
333
|
+
}
|
334
|
+
|
335
|
+
|
336
|
+
#chat .function-call {
|
337
|
+
display: grid;
|
338
|
+
gap: 0.75rem;
|
339
|
+
align-items: center;
|
340
|
+
}
|
341
|
+
|
342
|
+
#chat .function-name b {
|
343
|
+
font-size: 75%;
|
344
|
+
display: inline-block;
|
345
|
+
background: var(--t1);
|
346
|
+
padding: 0rem 0.5rem;
|
347
|
+
border-radius: 2rem;
|
348
|
+
color: var(--bg0);
|
349
|
+
margin-right: 0.5rem;
|
350
|
+
}
|
351
|
+
|
352
|
+
#chat .function-call pre {
|
353
|
+
overflow-x: auto;
|
354
|
+
}
|
355
|
+
|
356
|
+
#chat .function-name code {
|
357
|
+
color: var(--t1);
|
358
|
+
font-weight: 700;
|
359
|
+
}
|
360
|
+
|
361
|
+
#chat .function-call .args {
|
362
|
+
padding: 0.5rem;
|
363
|
+
background: var(--bg2);
|
364
|
+
border-radius: 0.2rem;
|
365
|
+
font-family: Fira Code, monospace;
|
366
|
+
font-size: 75%;
|
367
|
+
line-height: 180%;
|
368
|
+
}
|
369
|
+
|
370
|
+
#chat .function-output {
|
371
|
+
overflow-x: auto;
|
372
|
+
}
|
373
|
+
|
374
|
+
#chat table {
|
375
|
+
border-collapse: collapse;
|
376
|
+
border-radius: 0.25rem;
|
377
|
+
}
|
378
|
+
|
379
|
+
#chat table td, #chat table th {
|
380
|
+
border: 1px solid var(--bg2);
|
381
|
+
padding: 0.25rem 0.5rem;
|
382
|
+
font-family: Fira Code, monospace;
|
383
|
+
font-size: 75%;
|
384
|
+
}
|
385
|
+
|
386
|
+
#chat table th {
|
387
|
+
background: var(--t1);
|
388
|
+
border: 1px solid var(--t1);
|
389
|
+
color: var(--bg1);
|
390
|
+
font-weight: 700;
|
391
|
+
}
|
392
|
+
|
393
|
+
#chat table tr:nth-child(2n) td {
|
394
|
+
background: var(--bg1);
|
395
|
+
border: 1px solid var(--bg2);
|
396
|
+
}
|
397
|
+
|
398
|
+
@media print {
|
399
|
+
body {height: auto;}
|
400
|
+
header {display: none;}
|
401
|
+
#sidebar {display: none;}
|
402
|
+
main {grid-area: 1 / 1 / 3 / 3; height: auto; overflow-y: none;}
|
403
|
+
form {display: none;}
|
404
|
+
}
|
405
|
+
|
406
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<div class="function-call">
|
2
|
+
<pre class="function-name"><b>fn_call</b><code>{{msg['call_details']['name']}}</code></pre>
|
3
|
+
{% if msg['call_details']['arguments'] %}
|
4
|
+
<p class="args">{{msg['call_details']['arguments'] | safe}}</p>
|
5
|
+
{% endif %}
|
6
|
+
<div class="function-output">{{ msg['html'] | safe}}</div>
|
7
|
+
</div>
|
@@ -0,0 +1 @@
|
|
1
|
+
<p class="loader">💆♀️ Relax, Zaturn is running...</p>
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<section id="manage-sources">
|
2
|
+
{% if sources %}
|
3
|
+
<h1>🔗 Your Data Sources</h1>
|
4
|
+
{% for key in sources %}
|
5
|
+
{% with key=key, active=sources[key]['active'] %}
|
6
|
+
{% include('c_source_card.html') %}
|
7
|
+
{% endwith %}
|
8
|
+
{% endfor %}
|
9
|
+
{% else %}
|
10
|
+
<h1>➕ Add Some Data</h1>
|
11
|
+
{% endif %}
|
12
|
+
|
13
|
+
<div class="row-c2">
|
14
|
+
<form
|
15
|
+
id="datafile-form"
|
16
|
+
action="/upload_datafile"
|
17
|
+
enctype='multipart/form-data'
|
18
|
+
method="POST"
|
19
|
+
>
|
20
|
+
<label>
|
21
|
+
<small>Upload A File</small>
|
22
|
+
<input type="file" name="datafile" accept=".csv,.db,.sqlite,.sqlite3,.duckdb,.parquet,.pq" placeholder="" required>
|
23
|
+
<small><em>CSV / SQLite / DuckDB / Parquet</em></small>
|
24
|
+
</label>
|
25
|
+
<button>⬆ Upload</button>
|
26
|
+
<progress value='0' max='100'></progress>
|
27
|
+
</form>
|
28
|
+
|
29
|
+
<script>
|
30
|
+
htmx.on('#datafile-form', 'htmx:xhr:progress', function(evt) {
|
31
|
+
htmx.find('#datafile-form progress').setAttribute('value', evt.detail.loaded/evt.detail.total * 100);
|
32
|
+
});
|
33
|
+
</script>
|
34
|
+
|
35
|
+
<form action="/add_dataurl" method="POST">
|
36
|
+
<label>
|
37
|
+
<small>Or Link A Database Using URL</small>
|
38
|
+
<input type="text" name="db_url" required>
|
39
|
+
<small><em>PostgreSQL / MySQL / Clickhouse</em></small>
|
40
|
+
</label>
|
41
|
+
<button>🔗 Connect</button>
|
42
|
+
</form>
|
43
|
+
</div>
|
44
|
+
|
45
|
+
</section>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<section id="new-conversation">
|
2
|
+
<h1>Vamos! ᯓ★</h1>
|
3
|
+
<p>An AMA Session With Your Data</p>
|
4
|
+
<form action="/create_new_chat" method="POST">
|
5
|
+
<textarea
|
6
|
+
required
|
7
|
+
name="question"
|
8
|
+
placeholder="Type your question here...
E.g.: Explore the linked data and tell me something useful."
|
9
|
+
></textarea>
|
10
|
+
<button>➡</button>
|
11
|
+
</form>
|
12
|
+
{% include('loader.html') %}
|
13
|
+
</section>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<section id="settings">
|
2
|
+
<h1>
|
3
|
+
<a href="/">⬅</a>
|
4
|
+
Settings
|
5
|
+
</h1>
|
6
|
+
|
7
|
+
{% if updated %}
|
8
|
+
<p class="alert">
|
9
|
+
<code>{{updated}}</code>
|
10
|
+
✅ Settings Updated!
|
11
|
+
</p>
|
12
|
+
{% endif %}
|
13
|
+
|
14
|
+
<form action="/save_settings" method="post">
|
15
|
+
<label>
|
16
|
+
<small>OPENAI API KEY*</small>
|
17
|
+
<input type="text" name="api_key" value="{{current['api_key']}}" required>
|
18
|
+
</label>
|
19
|
+
<label>
|
20
|
+
<small>MODEL NAME*</small>
|
21
|
+
<input type="text" name="api_model" value="{{current['api_model']}}" required>
|
22
|
+
</label>
|
23
|
+
<label>
|
24
|
+
<small>OPENAI API ENDPOINT (Leave blank to use default)</small>
|
25
|
+
<input type="text" name="api_endpoint" value="{{current['api_endpoint']}}">
|
26
|
+
</label>
|
27
|
+
<button>💾 Save</button>
|
28
|
+
</form>
|
29
|
+
</section>
|
zaturn/tools/__init__.py
ADDED
@@ -6,12 +6,10 @@ import sys
|
|
6
6
|
|
7
7
|
# Basic Setup
|
8
8
|
USER_DATA_DIR = platformdirs.user_data_dir('zaturn', 'zaturn')
|
9
|
-
USER_CONFIG_DIR = platformdirs.user_config_dir('zaturn', 'zaturn')
|
10
9
|
QUERIES_DIR = os.path.join(USER_DATA_DIR, 'queries')
|
11
10
|
VISUALS_DIR = os.path.join(USER_DATA_DIR, 'visuals')
|
12
|
-
SOURCES_FILE = os.path.join(
|
11
|
+
SOURCES_FILE = os.path.join(USER_DATA_DIR, 'sources.txt')
|
13
12
|
|
14
|
-
os.makedirs(USER_CONFIG_DIR, exist_ok=True)
|
15
13
|
os.makedirs(QUERIES_DIR, exist_ok=True)
|
16
14
|
os.makedirs(VISUALS_DIR, exist_ok=True)
|
17
15
|
|
@@ -41,7 +39,7 @@ if not source_list:
|
|
41
39
|
source_list = [
|
42
40
|
pkg_resources.resource_filename(
|
43
41
|
'zaturn',
|
44
|
-
os.path.join('example_data', 'all_pokemon_data.csv')
|
42
|
+
os.path.join('mcp', 'example_data', 'all_pokemon_data.csv')
|
45
43
|
)
|
46
44
|
]
|
47
45
|
print("No data sources provided. Loading example dataset for demonstration.")
|
@@ -50,7 +48,7 @@ if not source_list:
|
|
50
48
|
print("zaturn_mcp sqlite:///path/to/mydata.db /path/to/my_file.csv")
|
51
49
|
print(f"\nNOTE: Sources in command line args will be ignored if sources are found in {SOURCES_FILE}")
|
52
50
|
|
53
|
-
|
51
|
+
CLI_SOURCES = {}
|
54
52
|
for s in source_list:
|
55
53
|
source = s.lower()
|
56
54
|
if source.startswith('sqlite://'):
|
@@ -72,14 +70,14 @@ for s in source_list:
|
|
72
70
|
elif source.endswith(".csv"):
|
73
71
|
source_type = "csv"
|
74
72
|
source_name = source.split('/')[-1].split('.')[0]
|
75
|
-
elif source.endswith(".parquet"):
|
73
|
+
elif source.endswith(".parquet") or source.endswith(".pq"):
|
76
74
|
source_type = "parquet"
|
77
75
|
source_name = source.split('/')[-1].split('.')[0]
|
78
76
|
else:
|
79
77
|
continue
|
80
78
|
|
81
79
|
source_id = f'{source_name}-{source_type}'
|
82
|
-
if source_id in
|
80
|
+
if source_id in CLI_SOURCES:
|
83
81
|
i = 2
|
84
82
|
while True:
|
85
83
|
source_id = f'{source_name}{i}-{source_type}'
|
@@ -87,11 +85,11 @@ for s in source_list:
|
|
87
85
|
break
|
88
86
|
i += 1
|
89
87
|
|
90
|
-
|
88
|
+
CLI_SOURCES[source_id] = {'url': s, 'type': source_type}
|
91
89
|
|
92
90
|
|
93
91
|
# Other Settings
|
94
|
-
|
92
|
+
CLI_RETURN_IMAGES = not args.noimg
|
95
93
|
|
96
94
|
|
97
95
|
|
zaturn/tools/core.py
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
from typing import Any, List, Union, Annotated
|
2
|
+
|
3
|
+
from pydantic import Field
|
4
|
+
|
5
|
+
from zaturn.tools import query_utils
|
6
|
+
|
7
|
+
|
8
|
+
class Core:
|
9
|
+
|
10
|
+
def __init__(self, data_sources):
|
11
|
+
self.data_sources = data_sources
|
12
|
+
self.tools = [
|
13
|
+
self.list_data_sources,
|
14
|
+
self.describe_table,
|
15
|
+
self.run_query,
|
16
|
+
]
|
17
|
+
|
18
|
+
|
19
|
+
def list_data_sources(self) -> str:
|
20
|
+
"""
|
21
|
+
List all available data sources.
|
22
|
+
Returns a list of unique source_ids to be used for other queries.
|
23
|
+
Source type is included in the source_id string.
|
24
|
+
While drafting SQL queries use appropriate syntax as per source type.
|
25
|
+
"""
|
26
|
+
try:
|
27
|
+
if not self.data_sources:
|
28
|
+
return "No data sources available. Add data sources."
|
29
|
+
|
30
|
+
result = "Available data sources:\n\n"
|
31
|
+
for source_id in self.data_sources:
|
32
|
+
tables = query_utils.list_tables(
|
33
|
+
self.data_sources[source_id]
|
34
|
+
)
|
35
|
+
if type(tables) is list:
|
36
|
+
tables = ', '.join(tables)
|
37
|
+
result += f"- {source_id}\nHas tables: {tables}\n"
|
38
|
+
|
39
|
+
return result
|
40
|
+
|
41
|
+
except Exception as e:
|
42
|
+
return str(e)
|
43
|
+
|
44
|
+
|
45
|
+
def describe_table(self,
|
46
|
+
source_id: Annotated[
|
47
|
+
str, Field(description='The data source')
|
48
|
+
],
|
49
|
+
table_name: Annotated[
|
50
|
+
str, Field(description='The table in the data source')
|
51
|
+
]
|
52
|
+
) -> str:
|
53
|
+
"""
|
54
|
+
Lists columns and their types in the specified table of specified data source.
|
55
|
+
"""
|
56
|
+
|
57
|
+
try:
|
58
|
+
source = self.data_sources.get(source_id)
|
59
|
+
if not source:
|
60
|
+
return f"Source {source_id} Not Found"
|
61
|
+
|
62
|
+
result = query_utils.describe_table(source, table_name)
|
63
|
+
return result.to_markdown(index=False)
|
64
|
+
|
65
|
+
except Exception as e:
|
66
|
+
return str(e)
|
67
|
+
|
68
|
+
|
69
|
+
def run_query(self,
|
70
|
+
source_id: Annotated[
|
71
|
+
str, Field(description='The data source to run the query on')
|
72
|
+
],
|
73
|
+
query: Annotated[
|
74
|
+
str, Field(description='SQL query to run on the data source')
|
75
|
+
]
|
76
|
+
) -> str:
|
77
|
+
"""
|
78
|
+
Run query against specified source
|
79
|
+
For both csv and parquet sources, use DuckDB SQL syntax
|
80
|
+
Use 'CSV' as the table name for csv sources.
|
81
|
+
Use 'PARQUET' as the table name for parquet sources.
|
82
|
+
|
83
|
+
This will return a dataframe with the results.
|
84
|
+
"""
|
85
|
+
|
86
|
+
try:
|
87
|
+
source = self.data_sources.get(source_id)
|
88
|
+
if not source:
|
89
|
+
return f"Source {source_id} Not Found"
|
90
|
+
|
91
|
+
df = query_utils.execute_query(source, query)
|
92
|
+
return df.to_markdown(index=False)
|
93
|
+
except Exception as e:
|
94
|
+
return str(e)
|
95
|
+
|
96
|
+
|
97
|
+
|