execsql2 2.0.1__py3-none-any.whl → 2.1.2__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.
- execsql/cli.py +322 -108
- execsql/config.py +134 -114
- execsql/db/access.py +89 -65
- execsql/db/base.py +97 -68
- execsql/db/dsn.py +45 -29
- execsql/db/duckdb.py +4 -5
- execsql/db/factory.py +27 -27
- execsql/db/firebird.py +30 -18
- execsql/db/mysql.py +38 -14
- execsql/db/oracle.py +58 -33
- execsql/db/postgres.py +68 -28
- execsql/db/sqlite.py +36 -27
- execsql/db/sqlserver.py +45 -30
- execsql/exceptions.py +68 -64
- execsql/exporters/__init__.py +1 -1
- execsql/exporters/base.py +42 -17
- execsql/exporters/delimited.py +60 -59
- execsql/exporters/duckdb.py +8 -12
- execsql/exporters/feather.py +32 -24
- execsql/exporters/html.py +33 -30
- execsql/exporters/json.py +18 -17
- execsql/exporters/latex.py +11 -13
- execsql/exporters/ods.py +50 -46
- execsql/exporters/parquet.py +32 -0
- execsql/exporters/pretty.py +16 -15
- execsql/exporters/raw.py +9 -11
- execsql/exporters/sqlite.py +38 -38
- execsql/exporters/templates.py +15 -72
- execsql/exporters/values.py +13 -12
- execsql/exporters/xls.py +26 -26
- execsql/exporters/xml.py +12 -12
- execsql/exporters/zip.py +0 -3
- execsql/gui/__init__.py +2 -2
- execsql/gui/console.py +0 -1
- execsql/gui/desktop.py +6 -7
- execsql/gui/tui.py +8 -14
- execsql/importers/base.py +6 -9
- execsql/importers/csv.py +10 -17
- execsql/importers/feather.py +16 -22
- execsql/importers/ods.py +3 -4
- execsql/importers/xls.py +5 -6
- execsql/metacommands/__init__.py +8 -8
- execsql/metacommands/conditions.py +41 -33
- execsql/metacommands/connect.py +113 -99
- execsql/metacommands/control.py +38 -26
- execsql/metacommands/data.py +35 -33
- execsql/metacommands/debug.py +13 -9
- execsql/metacommands/io.py +288 -229
- execsql/metacommands/prompt.py +179 -157
- execsql/metacommands/script_ext.py +11 -9
- execsql/metacommands/system.py +44 -25
- execsql/models.py +9 -16
- execsql/parser.py +10 -10
- execsql/script.py +183 -157
- execsql/state.py +170 -208
- execsql/types.py +46 -81
- execsql/utils/auth.py +114 -14
- execsql/utils/crypto.py +31 -4
- execsql/utils/datetime.py +7 -7
- execsql/utils/errors.py +34 -29
- execsql/utils/fileio.py +90 -55
- execsql/utils/gui.py +22 -23
- execsql/utils/mail.py +15 -17
- execsql/utils/numeric.py +2 -3
- execsql/utils/regex.py +9 -12
- execsql/utils/strings.py +10 -12
- execsql/utils/timer.py +0 -2
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/execsql.conf +1 -1
- execsql2-2.1.2.dist-info/METADATA +300 -0
- execsql2-2.1.2.dist-info/RECORD +96 -0
- execsql2-2.0.1.dist-info/METADATA +0 -406
- execsql2-2.0.1.dist-info/RECORD +0 -95
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/READ_ME.rst +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/WHEEL +0 -0
- {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/entry_points.txt +0 -0
- {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/licenses/NOTICE +0 -0
execsql/exporters/json.py
CHANGED
|
@@ -8,13 +8,14 @@ Provides :func:`write_query_to_json` (standard JSON array of objects) and
|
|
|
8
8
|
both of which serialize a query result set to a file or stream.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
import io
|
|
13
|
-
import os
|
|
14
|
-
from typing import Any, Optional, List
|
|
11
|
+
from typing import Any
|
|
15
12
|
|
|
16
13
|
import execsql.state as _state
|
|
17
14
|
from execsql.exporters.zip import ZipWriter
|
|
15
|
+
from execsql.exceptions import ErrInfo
|
|
16
|
+
from execsql.models import DataTable
|
|
17
|
+
from execsql.utils.errors import exception_desc
|
|
18
|
+
from execsql.utils.fileio import filewriter_close
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def write_query_to_json(
|
|
@@ -22,8 +23,8 @@ def write_query_to_json(
|
|
|
22
23
|
db: Any,
|
|
23
24
|
outfile: str,
|
|
24
25
|
append: bool = False,
|
|
25
|
-
desc:
|
|
26
|
-
zipfile:
|
|
26
|
+
desc: str | None = None,
|
|
27
|
+
zipfile: str | None = None,
|
|
27
28
|
) -> None:
|
|
28
29
|
global json
|
|
29
30
|
import json
|
|
@@ -31,12 +32,12 @@ def write_query_to_json(
|
|
|
31
32
|
conf = _state.conf
|
|
32
33
|
try:
|
|
33
34
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
34
|
-
except
|
|
35
|
+
except ErrInfo:
|
|
35
36
|
raise
|
|
36
|
-
except:
|
|
37
|
-
raise
|
|
37
|
+
except Exception:
|
|
38
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
38
39
|
if zipfile is None:
|
|
39
|
-
|
|
40
|
+
filewriter_close(outfile)
|
|
40
41
|
from execsql.utils.fileio import EncodedFile
|
|
41
42
|
|
|
42
43
|
ef = EncodedFile(outfile, conf.output_encoding)
|
|
@@ -69,19 +70,19 @@ def write_query_to_json_ts(
|
|
|
69
70
|
outfile: str,
|
|
70
71
|
append: bool = False,
|
|
71
72
|
write_types: bool = True,
|
|
72
|
-
desc:
|
|
73
|
-
zipfile:
|
|
73
|
+
desc: str | None = None,
|
|
74
|
+
zipfile: str | None = None,
|
|
74
75
|
) -> None:
|
|
75
76
|
conf = _state.conf
|
|
76
77
|
try:
|
|
77
78
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
78
|
-
except
|
|
79
|
+
except ErrInfo:
|
|
79
80
|
raise
|
|
80
|
-
except:
|
|
81
|
-
raise
|
|
81
|
+
except Exception:
|
|
82
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
82
83
|
max_col_idx = len(hdrs) - 1
|
|
83
84
|
if zipfile is None:
|
|
84
|
-
|
|
85
|
+
filewriter_close(outfile)
|
|
85
86
|
from execsql.utils.fileio import EncodedFile
|
|
86
87
|
|
|
87
88
|
ef = EncodedFile(outfile, conf.output_encoding)
|
|
@@ -98,7 +99,7 @@ def write_query_to_json_ts(
|
|
|
98
99
|
f.write(' "fields": [\n')
|
|
99
100
|
if write_types:
|
|
100
101
|
# Scan the data to determine data types.
|
|
101
|
-
tbl_desc =
|
|
102
|
+
tbl_desc = DataTable(hdrs, rows)
|
|
102
103
|
# Write the column descriptions to the header.
|
|
103
104
|
# Iterate over hdrs instead of tbl_desc.cols to preserve column order.
|
|
104
105
|
for i, h in enumerate(hdrs):
|
execsql/exporters/latex.py
CHANGED
|
@@ -8,11 +8,10 @@ set to a LaTeX ``tabular`` environment suitable for inclusion in a
|
|
|
8
8
|
``.tex`` document.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
import io
|
|
12
11
|
import os
|
|
13
|
-
import re
|
|
14
12
|
import tempfile
|
|
15
|
-
from
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
16
15
|
|
|
17
16
|
from execsql.exceptions import ErrInfo
|
|
18
17
|
from execsql.exporters.zip import WriteableZipfile
|
|
@@ -21,15 +20,14 @@ import execsql.state as _state
|
|
|
21
20
|
|
|
22
21
|
def export_latex(
|
|
23
22
|
outfile: str,
|
|
24
|
-
hdrs:
|
|
23
|
+
hdrs: list[str],
|
|
25
24
|
rows: Any,
|
|
26
25
|
append: bool = False,
|
|
27
|
-
querytext:
|
|
28
|
-
desc:
|
|
29
|
-
zipfile:
|
|
26
|
+
querytext: str | None = None,
|
|
27
|
+
desc: str | None = None,
|
|
28
|
+
zipfile: Any | None = None,
|
|
30
29
|
) -> None:
|
|
31
30
|
from execsql.utils.fileio import EncodedFile
|
|
32
|
-
from execsql.utils.errors import exception_info
|
|
33
31
|
|
|
34
32
|
def write_table(f: Any) -> None:
|
|
35
33
|
f.write("\\begin{center}\n")
|
|
@@ -69,7 +67,7 @@ def export_latex(
|
|
|
69
67
|
if outfile.lower() != "stdout":
|
|
70
68
|
f.close()
|
|
71
69
|
else:
|
|
72
|
-
if outfile.lower() == "stdout" or not
|
|
70
|
+
if outfile.lower() == "stdout" or not Path(outfile).is_file():
|
|
73
71
|
if outfile.lower() == "stdout":
|
|
74
72
|
import sys
|
|
75
73
|
|
|
@@ -113,15 +111,15 @@ def write_query_to_latex(
|
|
|
113
111
|
db: Any,
|
|
114
112
|
outfile: str,
|
|
115
113
|
append: bool = False,
|
|
116
|
-
desc:
|
|
117
|
-
zipfile:
|
|
114
|
+
desc: str | None = None,
|
|
115
|
+
zipfile: Any | None = None,
|
|
118
116
|
) -> None:
|
|
119
|
-
from execsql.utils.errors import
|
|
117
|
+
from execsql.utils.errors import exception_desc
|
|
120
118
|
|
|
121
119
|
try:
|
|
122
120
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
123
121
|
except ErrInfo:
|
|
124
122
|
raise
|
|
125
123
|
except Exception:
|
|
126
|
-
raise ErrInfo("db", select_stmt, exception_msg=
|
|
124
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
127
125
|
export_latex(outfile, hdrs, rows, append, select_stmt, desc, zipfile=zipfile)
|
execsql/exporters/ods.py
CHANGED
|
@@ -11,13 +11,17 @@ Provides :func:`write_query_to_ods` (single-sheet export),
|
|
|
11
11
|
|
|
12
12
|
import datetime
|
|
13
13
|
import getpass
|
|
14
|
-
import io
|
|
15
14
|
import os
|
|
16
|
-
import
|
|
17
|
-
from typing import Any
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
18
17
|
|
|
19
18
|
import execsql.state as _state
|
|
20
19
|
from execsql.exceptions import OdsFileError
|
|
20
|
+
from execsql.exceptions import ErrInfo
|
|
21
|
+
from execsql.script import current_script_line
|
|
22
|
+
from execsql.utils.errors import exception_desc, fatal_error
|
|
23
|
+
from execsql.utils.fileio import filewriter_close
|
|
24
|
+
from execsql.utils.strings import unquoted
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
class OdsFile:
|
|
@@ -27,20 +31,21 @@ class OdsFile:
|
|
|
27
31
|
def __init__(self) -> None:
|
|
28
32
|
global of
|
|
29
33
|
try:
|
|
34
|
+
import of as of
|
|
30
35
|
import of.opendocument
|
|
31
36
|
import of.table
|
|
32
37
|
import of.text
|
|
33
38
|
import of.number
|
|
34
39
|
import of.style
|
|
35
|
-
except:
|
|
36
|
-
|
|
40
|
+
except ImportError:
|
|
41
|
+
fatal_error("The odfpy library is needed to create OpenDocument spreadsheets.")
|
|
37
42
|
self.filename = None
|
|
38
43
|
self.wbk = None
|
|
39
44
|
self.cell_style_names = []
|
|
40
45
|
|
|
41
46
|
def open(self, filename: str) -> None:
|
|
42
47
|
self.filename = filename
|
|
43
|
-
if
|
|
48
|
+
if Path(filename).is_file():
|
|
44
49
|
self.wbk = of.opendocument.load(filename)
|
|
45
50
|
# Get a list of all cell style names used, so as not to re-define them.
|
|
46
51
|
for sty in self.wbk.automaticstyles.childNodes:
|
|
@@ -50,8 +55,8 @@ class OdsFile:
|
|
|
50
55
|
name = sty.getAttribute("name")
|
|
51
56
|
if name not in self.cell_style_names:
|
|
52
57
|
self.cell_style_names.append(name)
|
|
53
|
-
except:
|
|
54
|
-
pass
|
|
58
|
+
except Exception:
|
|
59
|
+
pass # Skip nodes without expected attributes.
|
|
55
60
|
else:
|
|
56
61
|
self.wbk = of.opendocument.OpenDocumentSpreadsheet()
|
|
57
62
|
|
|
@@ -119,7 +124,7 @@ class OdsFile:
|
|
|
119
124
|
self.wbk.automaticstyles.addElement(dts)
|
|
120
125
|
self.cell_style_names.append(st_name)
|
|
121
126
|
|
|
122
|
-
def sheetnames(self) ->
|
|
127
|
+
def sheetnames(self) -> list[str]:
|
|
123
128
|
# Returns a list of the worksheet names in the specified ODS spreadsheet.
|
|
124
129
|
return [sheet.getAttribute("name") for sheet in self.wbk.spreadsheet.getElementsByType(of.table.Table)]
|
|
125
130
|
|
|
@@ -133,7 +138,7 @@ class OdsFile:
|
|
|
133
138
|
sheet_no = int(sheetname)
|
|
134
139
|
if sheet_no < 1:
|
|
135
140
|
sheet_no = None
|
|
136
|
-
except:
|
|
141
|
+
except (ValueError, TypeError):
|
|
137
142
|
sheet_no = None
|
|
138
143
|
if sheet_no is not None:
|
|
139
144
|
for i, sheet in enumerate(self.wbk.spreadsheet.getElementsByType(of.table.Table)):
|
|
@@ -147,7 +152,7 @@ class OdsFile:
|
|
|
147
152
|
return sheet
|
|
148
153
|
return None
|
|
149
154
|
|
|
150
|
-
def sheet_data(self, sheetname: Any, junk_header_rows: int = 0) ->
|
|
155
|
+
def sheet_data(self, sheetname: Any, junk_header_rows: int = 0) -> list:
|
|
151
156
|
sheet = self.sheet_named(sheetname)
|
|
152
157
|
if not sheet:
|
|
153
158
|
raise OdsFileError(f"There is no sheet named {sheetname}")
|
|
@@ -166,19 +171,19 @@ class OdsFile:
|
|
|
166
171
|
repeat = spanned
|
|
167
172
|
ps = cell.getElementsByType(of.text.P)
|
|
168
173
|
if len(ps) == 0:
|
|
169
|
-
for
|
|
174
|
+
for _rr in range(int(repeat)):
|
|
170
175
|
p_content.append(None)
|
|
171
176
|
else:
|
|
172
177
|
for p in ps:
|
|
173
178
|
pval = str(p)
|
|
174
179
|
if len(pval) == 0:
|
|
175
|
-
for
|
|
180
|
+
for _rr in range(int(repeat)):
|
|
176
181
|
p_content.append(None)
|
|
177
182
|
else:
|
|
178
|
-
for
|
|
183
|
+
for _rr in range(int(repeat)):
|
|
179
184
|
p_content.append(pval)
|
|
180
185
|
if len(p_content) == 0:
|
|
181
|
-
for
|
|
186
|
+
for _rr in range(int(repeat)):
|
|
182
187
|
rowdata.append(None)
|
|
183
188
|
elif p_content[0] != "#":
|
|
184
189
|
rowdata.extend(p_content)
|
|
@@ -206,7 +211,7 @@ class OdsFile:
|
|
|
206
211
|
if isinstance(item, bool):
|
|
207
212
|
# Booleans must be evaluated before numbers.
|
|
208
213
|
tc = of.table.TableCell(valuetype="boolean", value=1 if item else 0, stylename=style_name)
|
|
209
|
-
elif isinstance(item, float
|
|
214
|
+
elif isinstance(item, (float, int)):
|
|
210
215
|
tc = of.table.TableCell(valuetype="float", value=item, stylename=style_name)
|
|
211
216
|
elif isinstance(item, datetime.datetime):
|
|
212
217
|
self.define_iso_datetime_style()
|
|
@@ -245,9 +250,8 @@ class OdsFile:
|
|
|
245
250
|
self.wbk.spreadsheet.addElement(of_table)
|
|
246
251
|
|
|
247
252
|
def save_close(self) -> None:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
ofile.close()
|
|
253
|
+
with open(self.filename, "wb") as ofile:
|
|
254
|
+
self.wbk.write(ofile)
|
|
251
255
|
self.filename = None
|
|
252
256
|
self.wbk = None
|
|
253
257
|
|
|
@@ -258,16 +262,16 @@ class OdsFile:
|
|
|
258
262
|
|
|
259
263
|
def export_ods(
|
|
260
264
|
outfile: str,
|
|
261
|
-
hdrs:
|
|
265
|
+
hdrs: list[str],
|
|
262
266
|
rows: Any,
|
|
263
267
|
append: bool = False,
|
|
264
|
-
querytext:
|
|
265
|
-
sheetname:
|
|
266
|
-
desc:
|
|
268
|
+
querytext: str | None = None,
|
|
269
|
+
sheetname: str | None = None,
|
|
270
|
+
desc: str | None = None,
|
|
267
271
|
) -> None:
|
|
268
272
|
# If not given, determine the worksheet name to use. The pattern is "Sheetx", where x is
|
|
269
273
|
# the first integer for which there is not already a sheet name.
|
|
270
|
-
if append and
|
|
274
|
+
if append and Path(outfile).is_file():
|
|
271
275
|
wbk = OdsFile()
|
|
272
276
|
wbk.open(outfile)
|
|
273
277
|
sheet_names = wbk.sheetnames()
|
|
@@ -282,8 +286,8 @@ def export_ods(
|
|
|
282
286
|
wbk.close()
|
|
283
287
|
else:
|
|
284
288
|
sheet_name = sheetname or "Sheet1"
|
|
285
|
-
if
|
|
286
|
-
|
|
289
|
+
if Path(outfile).is_file():
|
|
290
|
+
filewriter_close(outfile)
|
|
287
291
|
os.unlink(outfile)
|
|
288
292
|
wbk = OdsFile()
|
|
289
293
|
wbk.open(outfile)
|
|
@@ -307,11 +311,11 @@ def export_ods(
|
|
|
307
311
|
# Add information to the "Datasheets" sheet.
|
|
308
312
|
datasheetlist = wbk.sheet_named(datasheet_name)
|
|
309
313
|
if datasheetlist:
|
|
310
|
-
script, lno =
|
|
314
|
+
script, lno = current_script_line()
|
|
311
315
|
if querytext:
|
|
312
|
-
src = f"{querytext} with database {_state.dbs.current().name()}, with script {
|
|
316
|
+
src = f"{querytext} with database {_state.dbs.current().name()}, with script {str(Path(script).resolve())}, line {lno}"
|
|
313
317
|
else:
|
|
314
|
-
src = f"From database {_state.dbs.current().name()}, with script {
|
|
318
|
+
src = f"From database {_state.dbs.current().name()}, with script {str(Path(script).resolve())}, line {lno}"
|
|
315
319
|
wbk.add_row_to_sheet(
|
|
316
320
|
(
|
|
317
321
|
sheet_name,
|
|
@@ -331,15 +335,15 @@ def write_query_to_ods(
|
|
|
331
335
|
db: Any,
|
|
332
336
|
outfile: str,
|
|
333
337
|
append: bool = False,
|
|
334
|
-
sheetname:
|
|
335
|
-
desc:
|
|
338
|
+
sheetname: str | None = None,
|
|
339
|
+
desc: str | None = None,
|
|
336
340
|
) -> None:
|
|
337
341
|
try:
|
|
338
342
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
339
|
-
except
|
|
343
|
+
except ErrInfo:
|
|
340
344
|
raise
|
|
341
|
-
except:
|
|
342
|
-
raise
|
|
345
|
+
except Exception:
|
|
346
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
343
347
|
export_ods(outfile, hdrs, rows, append, select_stmt, sheetname, desc)
|
|
344
348
|
|
|
345
349
|
|
|
@@ -349,7 +353,7 @@ def write_queries_to_ods(
|
|
|
349
353
|
outfile: str,
|
|
350
354
|
append: bool = False,
|
|
351
355
|
tee: bool = False,
|
|
352
|
-
desc:
|
|
356
|
+
desc: str | None = None,
|
|
353
357
|
) -> None:
|
|
354
358
|
from execsql.exporters.pretty import prettyprint_query
|
|
355
359
|
from execsql.exporters.base import ExportRecord
|
|
@@ -358,8 +362,8 @@ def write_queries_to_ods(
|
|
|
358
362
|
if desc is not None:
|
|
359
363
|
descriptions = [d.strip() for d in desc.split(",")]
|
|
360
364
|
one_desc = len(descriptions) != len(tables)
|
|
361
|
-
if
|
|
362
|
-
|
|
365
|
+
if Path(outfile).is_file() and not append:
|
|
366
|
+
filewriter_close(outfile)
|
|
363
367
|
os.unlink(outfile)
|
|
364
368
|
wbk = OdsFile()
|
|
365
369
|
wbk.open(outfile)
|
|
@@ -377,13 +381,13 @@ def write_queries_to_ods(
|
|
|
377
381
|
if "." in t:
|
|
378
382
|
st = t.split(".")
|
|
379
383
|
if len(st) != 2:
|
|
380
|
-
raise
|
|
384
|
+
raise ErrInfo("cmd", other_msg=f"Unrecognized table specification in <{t}>")
|
|
381
385
|
if len(st) == 1:
|
|
382
|
-
tblname =
|
|
386
|
+
tblname = unquoted(st[0])
|
|
383
387
|
else:
|
|
384
|
-
tblname =
|
|
388
|
+
tblname = unquoted(st[1])
|
|
385
389
|
else:
|
|
386
|
-
tblname =
|
|
390
|
+
tblname = unquoted(t)
|
|
387
391
|
# Get next sheet number for sheet name
|
|
388
392
|
sheet_names = wbk.sheetnames()
|
|
389
393
|
sheet_name = tblname
|
|
@@ -397,10 +401,10 @@ def write_queries_to_ods(
|
|
|
397
401
|
select_stmt = f"select * from {t};"
|
|
398
402
|
try:
|
|
399
403
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
400
|
-
except
|
|
404
|
+
except ErrInfo:
|
|
401
405
|
raise
|
|
402
|
-
except:
|
|
403
|
-
raise
|
|
406
|
+
except Exception:
|
|
407
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
404
408
|
# Add the data to a new sheet.
|
|
405
409
|
tbl = wbk.new_sheet(sheet_name)
|
|
406
410
|
wbk.add_row_to_sheet(hdrs, tbl, header=True)
|
|
@@ -418,8 +422,8 @@ def write_queries_to_ods(
|
|
|
418
422
|
d = descriptions[i]
|
|
419
423
|
datasheetlist = wbk.sheet_named(inventory_name)
|
|
420
424
|
if datasheetlist:
|
|
421
|
-
script, lno =
|
|
422
|
-
src = f"From database {_state.dbs.current().name()}, with script {
|
|
425
|
+
script, lno = current_script_line()
|
|
426
|
+
src = f"From database {_state.dbs.current().name()}, with script {str(Path(script).resolve())}, line {lno}"
|
|
423
427
|
wbk.add_row_to_sheet(
|
|
424
428
|
(
|
|
425
429
|
sheet_name,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Apache Parquet export for execsql.
|
|
5
|
+
|
|
6
|
+
Provides :func:`write_query_to_parquet` (Parquet format via ``polars``).
|
|
7
|
+
Used by ``EXPORT … FORMAT parquet``. Polars is an optional dependency.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from execsql.exceptions import ErrInfo
|
|
13
|
+
from execsql.utils.errors import exception_desc
|
|
14
|
+
from execsql.utils.fileio import filewriter_close
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def write_query_to_parquet(outfile: str, headers: list[str], rows: Any) -> None:
|
|
18
|
+
try:
|
|
19
|
+
import polars as pl
|
|
20
|
+
except ImportError:
|
|
21
|
+
raise ErrInfo(
|
|
22
|
+
"exception",
|
|
23
|
+
exception_msg=exception_desc(),
|
|
24
|
+
other_msg="The polars Python package must be installed to export data to the parquet format.",
|
|
25
|
+
)
|
|
26
|
+
rows_list = list(rows)
|
|
27
|
+
if rows_list:
|
|
28
|
+
df = pl.DataFrame(rows_list, schema=headers, orient="row")
|
|
29
|
+
else:
|
|
30
|
+
df = pl.DataFrame({h: [] for h in headers})
|
|
31
|
+
filewriter_close(outfile)
|
|
32
|
+
df.write_parquet(outfile)
|
execsql/exporters/pretty.py
CHANGED
|
@@ -8,44 +8,45 @@ format a query result set as a fixed-width human-readable text table
|
|
|
8
8
|
(column-aligned, with a header row and separator).
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
import os
|
|
13
|
-
from typing import Any, Optional, List
|
|
11
|
+
from typing import Any
|
|
14
12
|
|
|
15
13
|
import execsql.state as _state
|
|
16
14
|
from execsql.exporters.zip import ZipWriter
|
|
15
|
+
from execsql.exceptions import ErrInfo
|
|
16
|
+
from execsql.utils.errors import exception_desc
|
|
17
|
+
from execsql.utils.fileio import filewriter_close
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def prettyprint_rowset(
|
|
20
|
-
colhdrs:
|
|
21
|
+
colhdrs: list[str],
|
|
21
22
|
rows: Any,
|
|
22
23
|
output_dest: str,
|
|
23
24
|
append: bool = False,
|
|
24
25
|
and_val: str = "",
|
|
25
|
-
desc:
|
|
26
|
-
zipfile:
|
|
26
|
+
desc: str | None = None,
|
|
27
|
+
zipfile: str | None = None,
|
|
27
28
|
) -> None:
|
|
28
29
|
# Adapted from the pp() function by Aaron Watters,
|
|
29
30
|
# posted to gadfly-rdbms@egroups.com 1999-01-18.
|
|
30
31
|
def as_ucode(s):
|
|
31
32
|
if s is None:
|
|
32
33
|
return and_val
|
|
33
|
-
if isinstance(s,
|
|
34
|
+
if isinstance(s, str):
|
|
34
35
|
return s
|
|
35
36
|
if type(s) in (type(memoryview(b"")), bytes, bytearray):
|
|
36
37
|
return f"Binary data ({len(s)} bytes)"
|
|
37
38
|
else:
|
|
38
|
-
if
|
|
39
|
+
if isinstance(s, bytes):
|
|
39
40
|
return s.decode(_state.dbs.current().encoding)
|
|
40
41
|
return str(s)
|
|
41
42
|
|
|
42
|
-
if
|
|
43
|
+
if not isinstance(rows, list):
|
|
43
44
|
try:
|
|
44
45
|
rows = list(rows)
|
|
45
|
-
except:
|
|
46
|
-
raise
|
|
46
|
+
except Exception:
|
|
47
|
+
raise ErrInfo(
|
|
47
48
|
"exception",
|
|
48
|
-
exception_msg=
|
|
49
|
+
exception_msg=exception_desc(),
|
|
49
50
|
other_msg="Can't create a list in memory of the data to be displayed as formatted text.",
|
|
50
51
|
)
|
|
51
52
|
rcols = range(len(colhdrs))
|
|
@@ -68,7 +69,7 @@ def prettyprint_rowset(
|
|
|
68
69
|
if zipfile is None:
|
|
69
70
|
from execsql.utils.fileio import EncodedFile
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
filewriter_close(output_dest)
|
|
72
73
|
if append:
|
|
73
74
|
ofile = EncodedFile(output_dest, _state.conf.output_encoding).open("a")
|
|
74
75
|
else:
|
|
@@ -91,8 +92,8 @@ def prettyprint_query(
|
|
|
91
92
|
outfile: str,
|
|
92
93
|
append: bool = False,
|
|
93
94
|
and_val: str = "",
|
|
94
|
-
desc:
|
|
95
|
-
zipfile:
|
|
95
|
+
desc: str | None = None,
|
|
96
|
+
zipfile: str | None = None,
|
|
96
97
|
) -> None:
|
|
97
98
|
_state.status.sql_error = False
|
|
98
99
|
names, rows = db.select_data(select_stmt)
|
execsql/exporters/raw.py
CHANGED
|
@@ -9,12 +9,10 @@ used by the ``EXPORT … FORMAT raw`` and ``FORMAT b64`` metacommand
|
|
|
9
9
|
variants.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
import
|
|
13
|
-
import os
|
|
14
|
-
from typing import Any, Optional
|
|
12
|
+
from typing import Any
|
|
15
13
|
|
|
16
|
-
import execsql.state as _state
|
|
17
14
|
from execsql.exporters.zip import ZipWriter
|
|
15
|
+
from execsql.utils.fileio import filewriter_close
|
|
18
16
|
|
|
19
17
|
|
|
20
18
|
def write_query_raw(
|
|
@@ -22,17 +20,17 @@ def write_query_raw(
|
|
|
22
20
|
rowsource: Any,
|
|
23
21
|
db_encoding: str,
|
|
24
22
|
append: bool = False,
|
|
25
|
-
zipfile:
|
|
23
|
+
zipfile: str | None = None,
|
|
26
24
|
) -> None:
|
|
27
25
|
if zipfile is None:
|
|
28
|
-
|
|
26
|
+
filewriter_close(outfile)
|
|
29
27
|
mode = "wb" if not append else "ab"
|
|
30
|
-
of =
|
|
28
|
+
of = open(outfile, mode) # noqa: SIM115
|
|
31
29
|
else:
|
|
32
30
|
of = ZipWriter(zipfile, outfile, append)
|
|
33
31
|
for row in rowsource:
|
|
34
32
|
for col in row:
|
|
35
|
-
if
|
|
33
|
+
if isinstance(col, bytearray):
|
|
36
34
|
of.write(col)
|
|
37
35
|
else:
|
|
38
36
|
if isinstance(col, str):
|
|
@@ -42,14 +40,14 @@ def write_query_raw(
|
|
|
42
40
|
of.close()
|
|
43
41
|
|
|
44
42
|
|
|
45
|
-
def write_query_b64(outfile: str, rowsource: Any, append: bool = False, zipfile:
|
|
43
|
+
def write_query_b64(outfile: str, rowsource: Any, append: bool = False, zipfile: str | None = None) -> None:
|
|
46
44
|
global base64
|
|
47
45
|
import base64
|
|
48
46
|
|
|
49
47
|
if zipfile is None:
|
|
50
|
-
|
|
48
|
+
filewriter_close(outfile)
|
|
51
49
|
mode = "wb" if not append else "ab"
|
|
52
|
-
of =
|
|
50
|
+
of = open(outfile, mode) # noqa: SIM115
|
|
53
51
|
else:
|
|
54
52
|
of = ZipWriter(zipfile, outfile, append)
|
|
55
53
|
for row in rowsource:
|
execsql/exporters/sqlite.py
CHANGED
|
@@ -8,16 +8,16 @@ to a table in an SQLite database file. Used by ``EXPORT … FORMAT sqlite``.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import math
|
|
11
|
-
import
|
|
12
|
-
from typing import Any
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
13
|
|
|
14
14
|
from execsql.exceptions import ErrInfo
|
|
15
|
-
|
|
15
|
+
from execsql.types import dbt_sqlite
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def export_sqlite(
|
|
19
19
|
outfile: str,
|
|
20
|
-
hdrs:
|
|
20
|
+
hdrs: list[str],
|
|
21
21
|
rows: Any,
|
|
22
22
|
append: bool,
|
|
23
23
|
tablename: str,
|
|
@@ -25,43 +25,43 @@ def export_sqlite(
|
|
|
25
25
|
import sqlite3
|
|
26
26
|
|
|
27
27
|
from execsql.models import DataTable
|
|
28
|
-
from execsql.utils.errors import exception_info
|
|
29
28
|
|
|
30
29
|
chunksize = 10000
|
|
31
|
-
pre_exist =
|
|
30
|
+
pre_exist = Path(outfile).is_file()
|
|
32
31
|
sdb = sqlite3.connect(outfile)
|
|
33
|
-
|
|
32
|
+
try:
|
|
33
|
+
if pre_exist:
|
|
34
|
+
curs = sdb.cursor()
|
|
35
|
+
res = curs.execute(
|
|
36
|
+
f"select name from sqlite_master where type='table' and name='{tablename}';",
|
|
37
|
+
)
|
|
38
|
+
rv = res.fetchone()
|
|
39
|
+
if not (rv is None or rv[0] == 0):
|
|
40
|
+
if append:
|
|
41
|
+
raise ErrInfo(type="error", other_msg=f"The table {tablename} already exists in {outfile}.")
|
|
42
|
+
else:
|
|
43
|
+
curs.execute(f"drop table {tablename};")
|
|
44
|
+
curs.close()
|
|
45
|
+
# Construct and run the CREATE TABLE statement
|
|
46
|
+
rowdata = list(rows)
|
|
47
|
+
tablespec = DataTable(hdrs, rowdata)
|
|
48
|
+
sql = tablespec.create_table(dbt_sqlite, schemaname=None, tablename=tablename)
|
|
34
49
|
curs = sdb.cursor()
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
curs.execute(sql)
|
|
51
|
+
# Export all rows of data
|
|
52
|
+
columns = [dbt_sqlite.quoted(col) for col in hdrs]
|
|
53
|
+
colspec = ",".join(columns)
|
|
54
|
+
paramspec = ",".join(("?",) * len(columns))
|
|
55
|
+
sql = f"insert into {tablename} ({colspec}) values ({paramspec});"
|
|
56
|
+
n_chunks = math.ceil(len(rowdata) / chunksize)
|
|
57
|
+
for i in range(n_chunks):
|
|
58
|
+
start = i * chunksize
|
|
59
|
+
end = start + chunksize
|
|
60
|
+
curs.executemany(sql, rowdata[start:end])
|
|
61
|
+
sdb.commit()
|
|
44
62
|
curs.close()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
tablespec = DataTable(hdrs, rowdata)
|
|
48
|
-
dbt_sqlite = _state.dbt_sqlite
|
|
49
|
-
sql = tablespec.create_table(dbt_sqlite, schemaname=None, tablename=tablename)
|
|
50
|
-
curs = sdb.cursor()
|
|
51
|
-
curs.execute(sql)
|
|
52
|
-
# Export all rows of data
|
|
53
|
-
columns = [dbt_sqlite.quoted(col) for col in hdrs]
|
|
54
|
-
colspec = ",".join(columns)
|
|
55
|
-
paramspec = ",".join(("?",) * len(columns))
|
|
56
|
-
sql = f"insert into {tablename} ({colspec}) values ({paramspec});"
|
|
57
|
-
n_chunks = math.ceil(len(rowdata) / chunksize)
|
|
58
|
-
for i in range(n_chunks):
|
|
59
|
-
start = i * chunksize
|
|
60
|
-
end = start + chunksize
|
|
61
|
-
curs.executemany(sql, rowdata[start:end])
|
|
62
|
-
sdb.commit()
|
|
63
|
-
curs.close()
|
|
64
|
-
sdb.close()
|
|
63
|
+
finally:
|
|
64
|
+
sdb.close()
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
def write_query_to_sqlite(
|
|
@@ -71,12 +71,12 @@ def write_query_to_sqlite(
|
|
|
71
71
|
append: bool,
|
|
72
72
|
tablename: str,
|
|
73
73
|
) -> None:
|
|
74
|
-
from execsql.utils.errors import
|
|
74
|
+
from execsql.utils.errors import exception_desc
|
|
75
75
|
|
|
76
76
|
try:
|
|
77
77
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
78
78
|
except ErrInfo:
|
|
79
79
|
raise
|
|
80
80
|
except Exception:
|
|
81
|
-
raise ErrInfo("db", select_stmt, exception_msg=
|
|
81
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
82
82
|
export_sqlite(outfile, hdrs, rows, append, tablename)
|