execsql2 2.1.2__py3-none-any.whl → 2.2.1__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.
Files changed (75) hide show
  1. execsql/cli/__init__.py +436 -0
  2. execsql/cli/dsn.py +86 -0
  3. execsql/cli/help.py +140 -0
  4. execsql/{cli.py → cli/run.py} +14 -589
  5. execsql/config.py +13 -1
  6. execsql/db/access.py +16 -12
  7. execsql/db/base.py +158 -90
  8. execsql/db/dsn.py +6 -5
  9. execsql/db/duckdb.py +2 -2
  10. execsql/db/firebird.py +23 -19
  11. execsql/db/mysql.py +8 -7
  12. execsql/db/oracle.py +11 -11
  13. execsql/db/postgres.py +28 -16
  14. execsql/db/sqlite.py +12 -11
  15. execsql/db/sqlserver.py +5 -3
  16. execsql/exceptions.py +7 -7
  17. execsql/exporters/base.py +6 -1
  18. execsql/exporters/delimited.py +44 -35
  19. execsql/exporters/duckdb.py +2 -2
  20. execsql/exporters/feather.py +6 -6
  21. execsql/exporters/html.py +83 -69
  22. execsql/exporters/json.py +50 -42
  23. execsql/exporters/latex.py +33 -27
  24. execsql/exporters/ods.py +4 -4
  25. execsql/exporters/parquet.py +2 -2
  26. execsql/exporters/pretty.py +11 -9
  27. execsql/exporters/raw.py +17 -13
  28. execsql/exporters/sqlite.py +2 -2
  29. execsql/exporters/templates.py +23 -15
  30. execsql/exporters/values.py +22 -20
  31. execsql/exporters/xls.py +4 -4
  32. execsql/exporters/xml.py +28 -13
  33. execsql/importers/base.py +4 -4
  34. execsql/importers/csv.py +6 -6
  35. execsql/importers/feather.py +4 -4
  36. execsql/importers/ods.py +4 -4
  37. execsql/importers/xls.py +4 -4
  38. execsql/metacommands/__init__.py +518 -67
  39. execsql/metacommands/conditions.py +101 -27
  40. execsql/metacommands/control.py +8 -4
  41. execsql/metacommands/data.py +6 -6
  42. execsql/metacommands/debug.py +6 -2
  43. execsql/metacommands/io.py +67 -1310
  44. execsql/metacommands/io_export.py +442 -0
  45. execsql/metacommands/io_fileops.py +287 -0
  46. execsql/metacommands/io_import.py +398 -0
  47. execsql/metacommands/io_write.py +248 -0
  48. execsql/metacommands/prompt.py +22 -66
  49. execsql/metacommands/system.py +7 -2
  50. execsql/py.typed +0 -0
  51. execsql/script.py +49 -5
  52. execsql/types.py +20 -20
  53. execsql/utils/fileio.py +15 -8
  54. {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/METADATA +6 -6
  55. execsql2-2.2.1.dist-info/RECORD +104 -0
  56. execsql2-2.1.2.dist-info/RECORD +0 -96
  57. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/READ_ME.rst +0 -0
  58. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  59. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  60. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/execsql.conf +0 -0
  61. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
  62. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_compare.sql +0 -0
  63. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_glossary.sql +0 -0
  64. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_upsert.sql +0 -0
  65. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_compare.sql +0 -0
  66. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  67. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  68. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/script_template.sql +0 -0
  69. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_compare.sql +0 -0
  70. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  71. {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  72. {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/WHEEL +0 -0
  73. {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/entry_points.txt +0 -0
  74. {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/licenses/LICENSE.txt +0 -0
  75. {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,442 @@
1
+ """Export metacommand handlers.
2
+
3
+ Implements ``x_export``, ``x_export_query``, template-based exports,
4
+ ODS multi-sheet export, and export metadata operations.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import execsql.state as _state
13
+ from execsql.exceptions import ErrInfo
14
+ from execsql.exporters.base import ExportRecord
15
+ from execsql.exporters.delimited import write_delimited_file
16
+ from execsql.exporters.duckdb import write_query_to_duckdb
17
+ from execsql.exporters.feather import write_query_to_feather, write_query_to_hdf5
18
+ from execsql.exporters.html import write_query_to_cgi_html, write_query_to_html
19
+ from execsql.exporters.json import write_query_to_json, write_query_to_json_ts
20
+ from execsql.exporters.latex import write_query_to_latex
21
+ from execsql.exporters.ods import write_queries_to_ods, write_query_to_ods
22
+ from execsql.exporters.parquet import write_query_to_parquet
23
+ from execsql.exporters.pretty import prettyprint_query, prettyprint_rowset
24
+ from execsql.exporters.raw import write_query_b64, write_query_raw
25
+ from execsql.exporters.sqlite import write_query_to_sqlite
26
+ from execsql.exporters.templates import report_query
27
+ from execsql.exporters.values import write_query_to_values
28
+ from execsql.exporters.xml import write_query_to_xml
29
+ from execsql.importers.base import import_data_table
30
+ from execsql.script import current_script_line
31
+ from execsql.utils.errors import exception_desc
32
+ from execsql.utils.fileio import check_dir
33
+
34
+
35
+ def _apply_output_dir(path: str) -> str:
36
+ """Prepend the configured --output-dir to *path* if it is a relative path.
37
+
38
+ If ``conf.export_output_dir`` is set and *path* is not absolute (and not
39
+ ``stdout``), the base directory is joined to *path* so that all EXPORT
40
+ output lands in the same directory without requiring scripts to hard-code
41
+ absolute paths.
42
+ """
43
+ output_dir = getattr(_state.conf, "export_output_dir", None)
44
+ if not output_dir:
45
+ return path
46
+ if path.lower() == "stdout":
47
+ return path
48
+ if Path(path).is_absolute():
49
+ return path
50
+ # Windows drive-letter paths are also absolute
51
+ if len(path) > 1 and path[1] == ":":
52
+ return path
53
+ return str(Path(output_dir) / path)
54
+
55
+
56
+ def x_export(**kwargs: Any) -> None:
57
+ schema = kwargs["schema"]
58
+ table = kwargs["table"]
59
+ queryname = _state.dbs.current().schema_qualified_table_name(schema, table)
60
+ select_stmt = f"select * from {queryname};"
61
+ outfile = _apply_output_dir(kwargs["filename"])
62
+ description = kwargs["description"]
63
+ tee = kwargs["tee"]
64
+ tee = bool(tee)
65
+ append = kwargs["append"]
66
+ append = bool(append)
67
+ filefmt = kwargs["format"].lower()
68
+ zipfilename = _apply_output_dir(kwargs["zipfilename"]) if kwargs["zipfilename"] else None
69
+ if zipfilename is not None:
70
+ if outfile.lower() == "stdout":
71
+ raise ErrInfo("error", other_msg="Cannot write stdout to a zipfile.")
72
+ elif len(outfile) > 1 and outfile[1] == ":":
73
+ raise ErrInfo("error", other_msg="Cannot use a drive letter for a file path within a zipfile.")
74
+ if filefmt == "duckdb":
75
+ raise ErrInfo("error", other_msg="Cannot export to the DuckDB format within a zipfile.")
76
+ if filefmt == "sqlite":
77
+ raise ErrInfo("error", other_msg="Cannot export to the SQLite format within a zipfile.")
78
+ if filefmt == "latex":
79
+ raise ErrInfo("error", other_msg="Cannot export to the LaTeX format within a zipfile.")
80
+ if filefmt == "feather":
81
+ raise ErrInfo("error", other_msg="Cannot export to the feather format within a zipfile.")
82
+ if filefmt == "parquet":
83
+ raise ErrInfo("error", other_msg="Cannot export to the parquet format within a zipfile.")
84
+ if filefmt == "hdf5":
85
+ raise ErrInfo("error", other_msg="Cannot export to the HDF5 format within a zipfile.")
86
+ if filefmt == "ods":
87
+ raise ErrInfo("error", other_msg="Cannot export to an ODS workbook within a zipfile.")
88
+ notype = bool(kwargs.get("notype"))
89
+ if zipfilename is not None:
90
+ check_dir(zipfilename)
91
+ else:
92
+ check_dir(outfile)
93
+ if tee and outfile.lower() != "stdout":
94
+ prettyprint_query(select_stmt, _state.dbs.current(), "stdout", False, desc=description)
95
+ if filefmt in ("txt", "text"):
96
+ prettyprint_query(
97
+ select_stmt,
98
+ _state.dbs.current(),
99
+ outfile,
100
+ append,
101
+ desc=description,
102
+ zipfile=zipfilename,
103
+ )
104
+ elif filefmt in ("txt-and", "text-and", "txt-and", "text-and"):
105
+ prettyprint_query(
106
+ select_stmt,
107
+ _state.dbs.current(),
108
+ outfile,
109
+ append,
110
+ and_val="AND",
111
+ desc=description,
112
+ zipfile=zipfilename,
113
+ )
114
+ elif filefmt == "ods":
115
+ write_query_to_ods(
116
+ select_stmt,
117
+ _state.dbs.current(),
118
+ outfile,
119
+ append,
120
+ sheetname=queryname,
121
+ desc=description,
122
+ )
123
+ elif filefmt == "duckdb":
124
+ write_query_to_duckdb(select_stmt, _state.dbs.current(), outfile, append, tablename=queryname)
125
+ elif filefmt == "sqlite":
126
+ write_query_to_sqlite(select_stmt, _state.dbs.current(), outfile, append, tablename=queryname)
127
+ elif filefmt == "xml":
128
+ write_query_to_xml(
129
+ select_stmt,
130
+ table,
131
+ _state.dbs.current(),
132
+ outfile,
133
+ append,
134
+ desc=description,
135
+ zipfile=zipfilename,
136
+ )
137
+ elif filefmt == "json":
138
+ write_query_to_json(
139
+ select_stmt,
140
+ _state.dbs.current(),
141
+ outfile,
142
+ append,
143
+ desc=description,
144
+ zipfile=zipfilename,
145
+ )
146
+ elif filefmt in ("json_ts", "json_tableschema"):
147
+ write_query_to_json_ts(
148
+ select_stmt,
149
+ _state.dbs.current(),
150
+ outfile,
151
+ append,
152
+ not notype,
153
+ desc=description,
154
+ zipfile=zipfilename,
155
+ )
156
+ elif filefmt == "values":
157
+ write_query_to_values(
158
+ select_stmt,
159
+ _state.dbs.current(),
160
+ outfile,
161
+ append,
162
+ desc=description,
163
+ zipfile=zipfilename,
164
+ )
165
+ elif filefmt == "html":
166
+ write_query_to_html(
167
+ select_stmt,
168
+ _state.dbs.current(),
169
+ outfile,
170
+ append,
171
+ desc=description,
172
+ zipfile=zipfilename,
173
+ )
174
+ elif filefmt == "cgi-html":
175
+ write_query_to_cgi_html(
176
+ select_stmt,
177
+ _state.dbs.current(),
178
+ outfile,
179
+ append,
180
+ desc=description,
181
+ zipfile=zipfilename,
182
+ )
183
+ elif filefmt == "latex":
184
+ write_query_to_latex(
185
+ select_stmt,
186
+ _state.dbs.current(),
187
+ outfile,
188
+ append,
189
+ desc=description,
190
+ zipfile=zipfilename,
191
+ )
192
+ elif filefmt == "hdf5":
193
+ write_query_to_hdf5(table, select_stmt, _state.dbs.current(), outfile, append, desc=description)
194
+ else:
195
+ try:
196
+ hdrs, rows = _state.dbs.current().select_rowsource(select_stmt)
197
+ except ErrInfo:
198
+ raise
199
+ except Exception as e:
200
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
201
+ if filefmt == "raw":
202
+ write_query_raw(outfile, rows, _state.dbs.current().encoding, append, zipfile=zipfilename)
203
+ elif filefmt == "b64":
204
+ write_query_b64(outfile, rows, append)
205
+ elif filefmt == "feather":
206
+ write_query_to_feather(outfile, hdrs, rows)
207
+ elif filefmt == "parquet":
208
+ write_query_to_parquet(outfile, hdrs, rows)
209
+ else:
210
+ write_delimited_file(outfile, filefmt, hdrs, rows, _state.conf.output_encoding, append, zipfilename)
211
+ _state.export_metadata.add(ExportRecord(queryname, outfile, zipfilename, description))
212
+ return None
213
+
214
+
215
+ def x_export_query(**kwargs: Any) -> None:
216
+ select_stmt = kwargs["query"]
217
+ outfile = kwargs["filename"]
218
+ description = kwargs["description"]
219
+ tee = kwargs["tee"]
220
+ tee = bool(tee)
221
+ append = kwargs["append"]
222
+ append = bool(append)
223
+ filefmt = kwargs["format"].lower()
224
+ zipfilename = kwargs["zipfilename"]
225
+ if zipfilename is not None:
226
+ if outfile == "stdout":
227
+ raise ErrInfo("error", other_msg="Cannot write stdout to a zipfile.")
228
+ elif len(outfile) > 1 and outfile[1] == ":":
229
+ raise ErrInfo("error", other_msg="Cannot use a drive letter for a file path within a zipfile.")
230
+ if filefmt == "latex":
231
+ raise ErrInfo("error", other_msg="Cannot export to the LaTeX format within a zipfile.")
232
+ if filefmt == "feather":
233
+ raise ErrInfo("error", other_msg="Cannot export to the feather format within a zipfile.")
234
+ if filefmt == "parquet":
235
+ raise ErrInfo("error", other_msg="Cannot export to the parquet format within a zipfile.")
236
+ if filefmt == "hdf5":
237
+ raise ErrInfo("error", other_msg="Cannot export to the HDF5 format within a zipfile.")
238
+ if filefmt == "ods":
239
+ raise ErrInfo("error", other_msg="Cannot export to an ODS workbook within a zipfile.")
240
+ notype = bool(kwargs.get("notype"))
241
+ check_dir(outfile)
242
+ if tee and outfile.lower() != "stdout":
243
+ prettyprint_query(select_stmt, _state.dbs.current(), "stdout", False, desc=description)
244
+ if filefmt in ("txt", "text"):
245
+ prettyprint_query(
246
+ select_stmt,
247
+ _state.dbs.current(),
248
+ outfile,
249
+ append,
250
+ desc=description,
251
+ zipfile=zipfilename,
252
+ )
253
+ elif filefmt in ("txt-and", "text-and", "txt-and", "text-and"):
254
+ prettyprint_query(
255
+ select_stmt,
256
+ _state.dbs.current(),
257
+ outfile,
258
+ append,
259
+ and_val="AND",
260
+ desc=description,
261
+ zipfile=zipfilename,
262
+ )
263
+ elif filefmt == "ods":
264
+ script_name, lno = current_script_line()
265
+ write_query_to_ods(
266
+ select_stmt,
267
+ _state.dbs.current(),
268
+ outfile,
269
+ append,
270
+ sheetname=f"Query_{lno}",
271
+ desc=description,
272
+ )
273
+ elif filefmt == "json":
274
+ write_query_to_json(
275
+ select_stmt,
276
+ _state.dbs.current(),
277
+ outfile,
278
+ append,
279
+ desc=description,
280
+ zipfile=zipfilename,
281
+ )
282
+ elif filefmt in ("json_ts", "json_tableschema"):
283
+ write_query_to_json_ts(
284
+ select_stmt,
285
+ _state.dbs.current(),
286
+ outfile,
287
+ append,
288
+ not notype,
289
+ desc=description,
290
+ zipfile=zipfilename,
291
+ )
292
+ elif filefmt == "values":
293
+ write_query_to_values(
294
+ select_stmt,
295
+ _state.dbs.current(),
296
+ outfile,
297
+ append,
298
+ desc=description,
299
+ zipfile=zipfilename,
300
+ )
301
+ elif filefmt == "html":
302
+ write_query_to_html(
303
+ select_stmt,
304
+ _state.dbs.current(),
305
+ outfile,
306
+ append,
307
+ desc=description,
308
+ zipfile=zipfilename,
309
+ )
310
+ elif filefmt == "cgi-html":
311
+ write_query_to_cgi_html(
312
+ select_stmt,
313
+ _state.dbs.current(),
314
+ outfile,
315
+ append,
316
+ desc=description,
317
+ zipfile=zipfilename,
318
+ )
319
+ elif filefmt == "latex":
320
+ write_query_to_latex(
321
+ select_stmt,
322
+ _state.dbs.current(),
323
+ outfile,
324
+ append,
325
+ desc=description,
326
+ zipfile=zipfilename,
327
+ )
328
+ else:
329
+ try:
330
+ hdrs, rows = _state.dbs.current().select_rowsource(select_stmt)
331
+ except ErrInfo:
332
+ raise
333
+ except Exception as e:
334
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
335
+ if filefmt == "raw":
336
+ write_query_raw(outfile, rows, _state.dbs.current().encoding, append, zipfile=zipfilename)
337
+ elif filefmt == "b64":
338
+ write_query_b64(outfile, rows, append, zipfile=zipfilename)
339
+ elif filefmt == "feather":
340
+ write_query_to_feather(outfile, hdrs, rows)
341
+ elif filefmt == "parquet":
342
+ write_query_to_parquet(outfile, hdrs, rows)
343
+ else:
344
+ write_delimited_file(
345
+ outfile,
346
+ filefmt,
347
+ hdrs,
348
+ rows,
349
+ _state.conf.output_encoding,
350
+ append,
351
+ zipfile=zipfilename,
352
+ )
353
+ _state.export_metadata.add(ExportRecord(select_stmt, outfile, zipfilename, description))
354
+ return None
355
+
356
+
357
+ def x_export_query_with_template(**kwargs: Any) -> None:
358
+ select_stmt = kwargs["query"]
359
+ outfile = kwargs["filename"]
360
+ template_file = kwargs["template"]
361
+ tee = kwargs["tee"]
362
+ tee = bool(tee)
363
+ append = kwargs["append"]
364
+ append = bool(append)
365
+ zipfilename = kwargs["zipfilename"]
366
+ check_dir(outfile)
367
+ if tee and outfile.lower() != "stdout":
368
+ prettyprint_query(select_stmt, _state.dbs.current(), "stdout", False)
369
+ report_query(select_stmt, _state.dbs.current(), outfile, template_file, append, zipfile=zipfilename)
370
+ _state.export_metadata.add(ExportRecord(select_stmt, outfile, zipfilename))
371
+ return None
372
+
373
+
374
+ def x_export_with_template(**kwargs: Any) -> None:
375
+ schema = kwargs["schema"]
376
+ table = kwargs["table"]
377
+ queryname = _state.dbs.current().schema_qualified_table_name(schema, table)
378
+ select_stmt = f"select * from {queryname};"
379
+ outfile = kwargs["filename"]
380
+ template_file = kwargs["template"]
381
+ tee = kwargs["tee"]
382
+ tee = bool(tee)
383
+ append = kwargs["append"]
384
+ append = bool(append)
385
+ zipfilename = kwargs["zipfilename"]
386
+ check_dir(outfile)
387
+ if tee and outfile.lower() != "stdout":
388
+ prettyprint_query(select_stmt, _state.dbs.current(), "stdout", False)
389
+ report_query(select_stmt, _state.dbs.current(), outfile, template_file, append, zipfile=zipfilename)
390
+ _state.export_metadata.add(ExportRecord(queryname, outfile, zipfilename))
391
+ return None
392
+
393
+
394
+ def x_export_ods_multiple(**kwargs: Any) -> None:
395
+ table_list = kwargs["tables"]
396
+ outfile = kwargs["filename"]
397
+ description = kwargs["description"]
398
+ tee = kwargs["tee"]
399
+ tee = bool(tee)
400
+ append = kwargs["append"]
401
+ append = append is not None
402
+ check_dir(outfile)
403
+ write_queries_to_ods(table_list, _state.dbs.current(), outfile, append, tee, desc=description)
404
+
405
+
406
+ def x_export_metadata(**kwargs: Any) -> None:
407
+ outfile = kwargs["filename"]
408
+ append = kwargs["append"] is not None
409
+ xall = kwargs["all"] is not None
410
+ zipfilename = kwargs["zipfilename"]
411
+ filefmt = kwargs["format"].lower()
412
+ if xall:
413
+ hdrs, rows = _state.export_metadata.get_all()
414
+ else:
415
+ hdrs, rows = _state.export_metadata.get()
416
+ if outfile.lower() != "stdout":
417
+ check_dir(outfile)
418
+ if filefmt in ("txt", "text"):
419
+ prettyprint_rowset(hdrs, rows, outfile, append, and_val="", zipfile=zipfilename)
420
+ else:
421
+ write_delimited_file(outfile, filefmt, hdrs, rows, _state.conf.output_encoding, append, zipfilename)
422
+
423
+
424
+ def x_export_metadata_table(**kwargs: Any) -> None:
425
+ xall = kwargs["all"] is not None
426
+ schemaname = kwargs["schema"]
427
+ tablename = kwargs["table"]
428
+ newstr = kwargs["new"]
429
+ if newstr:
430
+ is_new = 1 + ["new", "replacement"].index(newstr.lower())
431
+ else:
432
+ is_new = 0
433
+ if xall:
434
+ hdrs, rows = _state.export_metadata.get_all()
435
+ else:
436
+ hdrs, rows = _state.export_metadata.get()
437
+ import_data_table(_state.dbs.current(), schemaname, tablename, is_new, hdrs, rows)
438
+
439
+
440
+ def x_export_row_buffer(**kwargs: Any) -> None:
441
+ rows = kwargs["rows"]
442
+ _state.conf.export_row_buffer = int(rows)