execsql2 2.1.2__py3-none-any.whl → 2.4.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.
- execsql/cli/__init__.py +436 -0
- execsql/cli/dsn.py +86 -0
- execsql/cli/help.py +140 -0
- execsql/{cli.py → cli/run.py} +14 -589
- execsql/config.py +65 -1
- execsql/db/access.py +27 -15
- execsql/db/base.py +328 -215
- execsql/db/dsn.py +10 -5
- execsql/db/duckdb.py +6 -2
- execsql/db/factory.py +21 -0
- execsql/db/firebird.py +27 -19
- execsql/db/mysql.py +12 -7
- execsql/db/oracle.py +15 -11
- execsql/db/postgres.py +31 -16
- execsql/db/sqlite.py +15 -11
- execsql/db/sqlserver.py +16 -5
- execsql/exceptions.py +25 -7
- execsql/exporters/base.py +12 -1
- execsql/exporters/delimited.py +80 -35
- execsql/exporters/duckdb.py +6 -2
- execsql/exporters/feather.py +10 -6
- execsql/exporters/html.py +89 -69
- execsql/exporters/json.py +52 -45
- execsql/exporters/latex.py +37 -27
- execsql/exporters/ods.py +32 -11
- execsql/exporters/parquet.py +5 -2
- execsql/exporters/pretty.py +16 -9
- execsql/exporters/raw.py +22 -16
- execsql/exporters/sqlite.py +6 -2
- execsql/exporters/templates.py +39 -21
- execsql/exporters/values.py +26 -20
- execsql/exporters/xls.py +30 -11
- execsql/exporters/xml.py +31 -13
- execsql/exporters/zip.py +15 -0
- execsql/importers/base.py +6 -4
- execsql/importers/csv.py +8 -6
- execsql/importers/feather.py +6 -4
- execsql/importers/ods.py +6 -4
- execsql/importers/xls.py +6 -4
- execsql/metacommands/__init__.py +208 -1548
- execsql/metacommands/conditions.py +101 -27
- execsql/metacommands/control.py +8 -4
- execsql/metacommands/data.py +6 -6
- execsql/metacommands/debug.py +6 -2
- execsql/metacommands/dispatch.py +2011 -0
- execsql/metacommands/io.py +67 -1310
- execsql/metacommands/io_export.py +442 -0
- execsql/metacommands/io_fileops.py +287 -0
- execsql/metacommands/io_import.py +398 -0
- execsql/metacommands/io_write.py +248 -0
- execsql/metacommands/prompt.py +22 -66
- execsql/metacommands/system.py +7 -2
- execsql/models.py +7 -0
- execsql/parser.py +10 -0
- execsql/py.typed +0 -0
- execsql/script/__init__.py +95 -0
- execsql/script/control.py +162 -0
- execsql/{script.py → script/engine.py} +184 -402
- execsql/script/variables.py +281 -0
- execsql/types.py +49 -20
- execsql/utils/auth.py +2 -0
- execsql/utils/crypto.py +4 -6
- execsql/utils/datetime.py +1 -0
- execsql/utils/errors.py +11 -0
- execsql/utils/fileio.py +33 -8
- execsql/utils/gui.py +46 -0
- execsql/utils/mail.py +7 -17
- execsql/utils/numeric.py +2 -0
- execsql/utils/regex.py +9 -0
- execsql/utils/strings.py +16 -0
- execsql/utils/timer.py +2 -0
- execsql2-2.4.0.data/data/execsql2_extras/README.md +65 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/execsql.conf +1 -1
- {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/METADATA +13 -6
- execsql2-2.4.0.dist-info/RECORD +108 -0
- execsql2-2.1.2.data/data/execsql2_extras/READ_ME.rst +0 -127
- execsql2-2.1.2.dist-info/RECORD +0 -96
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/WHEEL +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/licenses/NOTICE +0 -0
execsql/exporters/xls.py
CHANGED
|
@@ -16,8 +16,12 @@ from typing import Any
|
|
|
16
16
|
from execsql.exceptions import XlsFileError, XlsxFileError
|
|
17
17
|
from execsql.utils.errors import fatal_error
|
|
18
18
|
|
|
19
|
+
__all__ = ["XlsFile", "XlsxFile"]
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
class XlsFile:
|
|
23
|
+
"""Read-only wrapper around ``xlrd`` for importing legacy ``.xls`` spreadsheets."""
|
|
24
|
+
|
|
21
25
|
def __repr__(self) -> str:
|
|
22
26
|
return "XlsFile()"
|
|
23
27
|
|
|
@@ -29,9 +33,11 @@ class XlsFile:
|
|
|
29
33
|
self.log_msgs.append(msg)
|
|
30
34
|
|
|
31
35
|
def __init__(self) -> None:
|
|
36
|
+
"""Import xlrd and initialise file state; raises a fatal error if xlrd is absent."""
|
|
32
37
|
try:
|
|
33
|
-
global xlrd
|
|
34
38
|
import xlrd
|
|
39
|
+
|
|
40
|
+
self._xlrd = xlrd
|
|
35
41
|
except ImportError:
|
|
36
42
|
fatal_error("The xlrd library is needed to read Excel (.xls) spreadsheets.")
|
|
37
43
|
self.filename = None
|
|
@@ -41,20 +47,23 @@ class XlsFile:
|
|
|
41
47
|
self.errlog = self.XlsLog()
|
|
42
48
|
|
|
43
49
|
def open(self, filename: str, encoding: str | None = None, read_only: bool = False) -> None:
|
|
50
|
+
"""Open an existing ``.xls`` file for reading; raises XlsFileError if absent."""
|
|
44
51
|
self.filename = filename
|
|
45
52
|
self.encoding = encoding
|
|
46
53
|
self.read_only = read_only
|
|
47
54
|
if Path(filename).is_file():
|
|
48
55
|
# The 'read_only' argument is not used, but is present for compatibility with XlsxFile.open().
|
|
49
|
-
self.wbk =
|
|
56
|
+
self.wbk = self._xlrd.open_workbook(filename, logfile=self.errlog, encoding_override=self.encoding)
|
|
50
57
|
self.datemode = self.wbk.datemode
|
|
51
58
|
else:
|
|
52
59
|
raise XlsFileError(f"There is no Excel file {self.filename}.")
|
|
53
60
|
|
|
54
61
|
def sheetnames(self) -> Any:
|
|
62
|
+
"""Return the list of sheet objects in the open workbook."""
|
|
55
63
|
return self.wbk.sheets()
|
|
56
64
|
|
|
57
65
|
def sheet_named(self, sheetname: Any) -> Any:
|
|
66
|
+
"""Return the sheet matching a name or 1-based integer, raising XlsFileError if absent."""
|
|
58
67
|
# Return the sheet with the matching name. If the name is actually an integer,
|
|
59
68
|
# return that sheet number.
|
|
60
69
|
if isinstance(sheetname, int):
|
|
@@ -74,10 +83,11 @@ class XlsFile:
|
|
|
74
83
|
return sheet
|
|
75
84
|
|
|
76
85
|
def sheet_data(self, sheetname: Any, junk_header_rows: int = 0) -> list:
|
|
86
|
+
"""Return all row data from the named sheet, optionally skipping leading junk rows."""
|
|
77
87
|
try:
|
|
78
88
|
sheet = self.sheet_named(sheetname)
|
|
79
|
-
except Exception:
|
|
80
|
-
raise XlsFileError(f"There is no Excel worksheet named {sheetname} in {self.filename}.")
|
|
89
|
+
except Exception as e:
|
|
90
|
+
raise XlsFileError(f"There is no Excel worksheet named {sheetname} in {self.filename}.") from e
|
|
81
91
|
|
|
82
92
|
# Don't rely on sheet.ncols and sheet.nrows, because Excel will count columns
|
|
83
93
|
# and rows that have ever been filled, even if they are now empty. Base the column count
|
|
@@ -109,7 +119,7 @@ class XlsFile:
|
|
|
109
119
|
datarow.append(c.value)
|
|
110
120
|
elif c.ctype == 3:
|
|
111
121
|
# date
|
|
112
|
-
dt =
|
|
122
|
+
dt = self._xlrd.xldate_as_tuple(c.value, self.datemode)
|
|
113
123
|
# Convert to time or datetime
|
|
114
124
|
if not any(dt[:3]):
|
|
115
125
|
# No date values
|
|
@@ -121,7 +131,7 @@ class XlsFile:
|
|
|
121
131
|
datarow.append(bool(c.value))
|
|
122
132
|
elif c.ctype == 5:
|
|
123
133
|
# Error code
|
|
124
|
-
datarow.append(
|
|
134
|
+
datarow.append(self._xlrd.error_text_from_code(c.value))
|
|
125
135
|
elif c.ctype == 6:
|
|
126
136
|
# blank
|
|
127
137
|
datarow.append(None)
|
|
@@ -145,6 +155,8 @@ class XlsFile:
|
|
|
145
155
|
|
|
146
156
|
|
|
147
157
|
class XlsxFile:
|
|
158
|
+
"""Read/write wrapper around ``openpyxl`` for ``.xlsx`` spreadsheets."""
|
|
159
|
+
|
|
148
160
|
def __repr__(self) -> str:
|
|
149
161
|
return "XlsxFile()"
|
|
150
162
|
|
|
@@ -156,9 +168,11 @@ class XlsxFile:
|
|
|
156
168
|
self.log_msgs.append(msg)
|
|
157
169
|
|
|
158
170
|
def __init__(self) -> None:
|
|
171
|
+
"""Import openpyxl and initialise file state; raises a fatal error if openpyxl is absent."""
|
|
159
172
|
try:
|
|
160
|
-
global openpyxl
|
|
161
173
|
import openpyxl
|
|
174
|
+
|
|
175
|
+
self._openpyxl = openpyxl
|
|
162
176
|
except ImportError:
|
|
163
177
|
fatal_error("The openpyxl library is needed to read Excel (.xlsx) spreadsheets.")
|
|
164
178
|
self.filename = None
|
|
@@ -168,18 +182,20 @@ class XlsxFile:
|
|
|
168
182
|
self.errlog = self.XlsxLog()
|
|
169
183
|
|
|
170
184
|
def open(self, filename: str, encoding: str | None = None, read_only: bool = False) -> None:
|
|
185
|
+
"""Open an existing ``.xlsx`` file for reading; raises XlsxFileError if absent."""
|
|
171
186
|
self.filename = filename
|
|
172
187
|
self.encoding = encoding
|
|
173
188
|
self.read_only = read_only
|
|
174
189
|
if Path(filename).is_file():
|
|
175
190
|
if read_only:
|
|
176
|
-
self.wbk =
|
|
191
|
+
self.wbk = self._openpyxl.load_workbook(filename, read_only=True)
|
|
177
192
|
else:
|
|
178
|
-
self.wbk =
|
|
193
|
+
self.wbk = self._openpyxl.load_workbook(filename)
|
|
179
194
|
else:
|
|
180
195
|
raise XlsxFileError(f"There is no Excel file {self.filename}.")
|
|
181
196
|
|
|
182
197
|
def close(self) -> None:
|
|
198
|
+
"""Close the open workbook and reset all state attributes."""
|
|
183
199
|
if self.wbk is not None:
|
|
184
200
|
self.wbk.close()
|
|
185
201
|
self.wbk = None
|
|
@@ -187,9 +203,11 @@ class XlsxFile:
|
|
|
187
203
|
self.encoding = None
|
|
188
204
|
|
|
189
205
|
def sheetnames(self) -> list[str]:
|
|
206
|
+
"""Return the list of worksheet names in the open workbook."""
|
|
190
207
|
return self.wbk.sheetnames
|
|
191
208
|
|
|
192
209
|
def sheet_named(self, sheetname: Any) -> Any:
|
|
210
|
+
"""Return the sheet matching a name or 1-based integer index."""
|
|
193
211
|
# Return the sheet with the matching name. If the name is actually an integer,
|
|
194
212
|
# return that sheet number.
|
|
195
213
|
if isinstance(sheetname, int):
|
|
@@ -209,10 +227,11 @@ class XlsxFile:
|
|
|
209
227
|
return sheet
|
|
210
228
|
|
|
211
229
|
def sheet_data(self, sheetname: Any, junk_header_rows: int = 0) -> list:
|
|
230
|
+
"""Return all row data from the named sheet, optionally skipping leading junk rows."""
|
|
212
231
|
try:
|
|
213
232
|
sheet = self.sheet_named(sheetname)
|
|
214
|
-
except Exception:
|
|
215
|
-
raise XlsxFileError(f"There is no Excel worksheet named {sheetname} in {self.filename}.")
|
|
233
|
+
except Exception as e:
|
|
234
|
+
raise XlsxFileError(f"There is no Excel worksheet named {sheetname} in {self.filename}.") from e
|
|
216
235
|
# Don't rely on sheet.max_column and sheet.max_row, because Excel will count columns
|
|
217
236
|
# and rows that have ever been filled, even if they are now empty. Base the column count
|
|
218
237
|
# on the number of contiguous non-empty cells in the first row, and process the data up to nrows until
|
execsql/exporters/xml.py
CHANGED
|
@@ -8,7 +8,9 @@ to a well-formed XML file with one element per row and column values as
|
|
|
8
8
|
child elements or attributes.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
import re
|
|
11
12
|
from typing import Any
|
|
13
|
+
from xml.sax.saxutils import escape as xml_escape
|
|
12
14
|
|
|
13
15
|
import execsql.state as _state
|
|
14
16
|
from execsql.exporters.zip import ZipWriter
|
|
@@ -16,6 +18,8 @@ from execsql.exceptions import ErrInfo
|
|
|
16
18
|
from execsql.utils.errors import exception_desc
|
|
17
19
|
from execsql.utils.fileio import filewriter_close
|
|
18
20
|
|
|
21
|
+
__all__ = ["write_query_to_xml"]
|
|
22
|
+
|
|
19
23
|
|
|
20
24
|
def write_query_to_xml(
|
|
21
25
|
select_stmt: str,
|
|
@@ -26,13 +30,14 @@ def write_query_to_xml(
|
|
|
26
30
|
desc: str | None = None,
|
|
27
31
|
zipfile: str | None = None,
|
|
28
32
|
) -> None:
|
|
33
|
+
"""Execute a SELECT and write the result set as a well-formed XML document."""
|
|
29
34
|
conf = _state.conf
|
|
30
35
|
try:
|
|
31
36
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
32
37
|
except ErrInfo:
|
|
33
38
|
raise
|
|
34
|
-
except Exception:
|
|
35
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
39
|
+
except Exception as e:
|
|
40
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
36
41
|
if zipfile is None:
|
|
37
42
|
filewriter_close(outfile)
|
|
38
43
|
from execsql.utils.fileio import EncodedFile
|
|
@@ -47,14 +52,27 @@ def write_query_to_xml(
|
|
|
47
52
|
else:
|
|
48
53
|
f = ZipWriter(zipfile, outfile, append)
|
|
49
54
|
f.write(f"<?xml version='1.0' encoding='{conf.output_encoding}'?>\n")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
|
|
56
|
+
def _safe_xml_name(name: str) -> str:
|
|
57
|
+
"""Sanitize a string for use as an XML element name."""
|
|
58
|
+
# Replace characters that are invalid in XML names with underscores.
|
|
59
|
+
s = re.sub(r"[^\w.\-]", "_", str(name))
|
|
60
|
+
# XML names must start with a letter or underscore, not a digit or dot.
|
|
61
|
+
if s and not (s[0].isalpha() or s[0] == "_"):
|
|
62
|
+
s = "_" + s
|
|
63
|
+
return s or "_"
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
if desc is not None:
|
|
67
|
+
f.write(f"<!--{desc.replace('--', '- -')}-->\n")
|
|
68
|
+
safe_tablename = _safe_xml_name(tablename)
|
|
69
|
+
f.write(f"<{safe_tablename}>\n")
|
|
70
|
+
str_hdrs = [_safe_xml_name(h) for h in hdrs]
|
|
71
|
+
for row in rows:
|
|
72
|
+
f.write(" <row>\n")
|
|
73
|
+
for i, col in enumerate(str_hdrs):
|
|
74
|
+
f.write(f" <{col}>{xml_escape(str(row[i]) if row[i] is not None else '')}</{col}>\n")
|
|
75
|
+
f.write(" </row>\n")
|
|
76
|
+
f.write(f"</{safe_tablename}>\n")
|
|
77
|
+
finally:
|
|
78
|
+
f.close()
|
execsql/exporters/zip.py
CHANGED
|
@@ -15,9 +15,14 @@ import zipfile
|
|
|
15
15
|
|
|
16
16
|
import execsql.state as _state
|
|
17
17
|
|
|
18
|
+
__all__ = ["WriteableZipfile", "ZipWriter"]
|
|
19
|
+
|
|
18
20
|
|
|
19
21
|
class WriteableZipfile:
|
|
22
|
+
"""Thin ZipFile wrapper that accepts chunked string writes via an internal buffer."""
|
|
23
|
+
|
|
20
24
|
def __init__(self, zipfile_name: str, append: bool = False) -> None:
|
|
25
|
+
"""Open (or create) a ZIP archive and allocate a write buffer."""
|
|
21
26
|
conf = _state.conf
|
|
22
27
|
self.bufsize = conf.zip_buffer_mb * 1024 * 1000
|
|
23
28
|
self.buf = memoryview(bytearray(self.bufsize))
|
|
@@ -31,6 +36,7 @@ class WriteableZipfile:
|
|
|
31
36
|
self.close()
|
|
32
37
|
|
|
33
38
|
def member_file(self, member_filename: str) -> None:
|
|
39
|
+
"""Create a new member entry in the archive and open it for writing."""
|
|
34
40
|
# Creates a ZipInfo object (file) within the zipfile and opens it for writing.
|
|
35
41
|
self.current_zinfo = zipfile.ZipInfo(
|
|
36
42
|
filename=member_filename,
|
|
@@ -49,6 +55,7 @@ class WriteableZipfile:
|
|
|
49
55
|
self.current_handle = self.zf.open(self.current_zinfo, mode="w")
|
|
50
56
|
|
|
51
57
|
def zip_buffer(self) -> None:
|
|
58
|
+
"""Flush any buffered bytes to the currently open zip member file."""
|
|
52
59
|
# Writes the buffer contents, if any, to the zip member file.
|
|
53
60
|
if self.buflen > 0 and self.current_handle is not None:
|
|
54
61
|
with self.zf._lock:
|
|
@@ -57,6 +64,7 @@ class WriteableZipfile:
|
|
|
57
64
|
self.buflen = 0
|
|
58
65
|
|
|
59
66
|
def write(self, str_data: str) -> None:
|
|
67
|
+
"""Buffer a UTF-8-encoded string for writing to the currently open member."""
|
|
60
68
|
# Writes the given text to the currently open member.
|
|
61
69
|
# Convert from string to bytes.
|
|
62
70
|
data = str_data.encode("utf-8")
|
|
@@ -67,26 +75,33 @@ class WriteableZipfile:
|
|
|
67
75
|
self.buflen = self.buflen + datalen
|
|
68
76
|
|
|
69
77
|
def close_member(self) -> None:
|
|
78
|
+
"""Flush the buffer and close the currently open member file handle."""
|
|
70
79
|
if self.current_handle is not None:
|
|
71
80
|
self.zip_buffer()
|
|
72
81
|
self.current_handle.close()
|
|
73
82
|
self.current_handle = None
|
|
74
83
|
|
|
75
84
|
def close(self) -> None:
|
|
85
|
+
"""Close the open member (flushing the buffer) and finalise the ZIP archive."""
|
|
76
86
|
self.close_member()
|
|
77
87
|
self.zf.close()
|
|
78
88
|
|
|
79
89
|
|
|
80
90
|
class ZipWriter:
|
|
91
|
+
"""High-level write-only interface used by EXPORT metacommands to stream output into a ZIP archive."""
|
|
92
|
+
|
|
81
93
|
def __init__(self, zip_fname: str, member_fname: str, append: bool = False) -> None:
|
|
94
|
+
"""Open the archive at ``zip_fname`` and begin a new member file named ``member_fname``."""
|
|
82
95
|
self.zip_fname = zip_fname
|
|
83
96
|
self.member_fname = member_fname
|
|
84
97
|
self.zwriter = WriteableZipfile(self.zip_fname, append)
|
|
85
98
|
self.member = self.zwriter.member_file(member_fname)
|
|
86
99
|
|
|
87
100
|
def write(self, str_data: str) -> None:
|
|
101
|
+
"""Write a string to the current zip member."""
|
|
88
102
|
self.zwriter.write(str_data)
|
|
89
103
|
|
|
90
104
|
def close(self) -> None:
|
|
105
|
+
"""Close the zip member and finalise the archive."""
|
|
91
106
|
self.zwriter.close()
|
|
92
107
|
self.zwriter = None
|
execsql/importers/base.py
CHANGED
|
@@ -17,6 +17,8 @@ from execsql.db.base import Database
|
|
|
17
17
|
import execsql.state as _state
|
|
18
18
|
from execsql.types import dbt_firebird
|
|
19
19
|
|
|
20
|
+
__all__ = ["import_data_table"]
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
def import_data_table(
|
|
22
24
|
db: Database,
|
|
@@ -85,13 +87,13 @@ def import_data_table(
|
|
|
85
87
|
# ...except for Firebird.
|
|
86
88
|
if db.type == dbt_firebird:
|
|
87
89
|
db.conn.commit()
|
|
88
|
-
except Exception:
|
|
90
|
+
except Exception as e:
|
|
89
91
|
raise ErrInfo(
|
|
90
92
|
type="db",
|
|
91
93
|
command_text=sql,
|
|
92
94
|
exception_msg=exception_info(),
|
|
93
95
|
other_msg=f"Could not create new table ({tablename}) for IMPORT metacommand",
|
|
94
|
-
)
|
|
96
|
+
) from e
|
|
95
97
|
table_cols = db.table_columns(tablename, schemaname)
|
|
96
98
|
if conf.import_common_cols_only:
|
|
97
99
|
import_cols = [col for col in hdrs if col.lower() in [tc.lower() for tc in table_cols]]
|
|
@@ -108,5 +110,5 @@ def import_data_table(
|
|
|
108
110
|
db.commit()
|
|
109
111
|
except ErrInfo:
|
|
110
112
|
raise
|
|
111
|
-
except Exception:
|
|
112
|
-
raise ErrInfo("db", "Call to populate_table when importing data", exception_msg=exception_info())
|
|
113
|
+
except Exception as e:
|
|
114
|
+
raise ErrInfo("db", "Call to populate_table when importing data", exception_msg=exception_info()) from e
|
execsql/importers/csv.py
CHANGED
|
@@ -17,6 +17,8 @@ from execsql.db.base import Database
|
|
|
17
17
|
import execsql.state as _state
|
|
18
18
|
from execsql.types import dbt_firebird
|
|
19
19
|
|
|
20
|
+
__all__ = ["importfile", "importtable"]
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
def importtable(
|
|
22
24
|
db: Database,
|
|
@@ -60,13 +62,13 @@ def importtable(
|
|
|
60
62
|
# ...except for Firebird. Execute the commit directly via the connection so it is always done.
|
|
61
63
|
if db.type == dbt_firebird:
|
|
62
64
|
db.conn.commit()
|
|
63
|
-
except Exception:
|
|
65
|
+
except Exception as e:
|
|
64
66
|
raise ErrInfo(
|
|
65
67
|
type="db",
|
|
66
68
|
command_text=sql,
|
|
67
69
|
exception_msg=exception_info(),
|
|
68
70
|
other_msg=f"Could not create new table ({tablename}) for IMPORT metacommand",
|
|
69
|
-
)
|
|
71
|
+
) from e
|
|
70
72
|
else:
|
|
71
73
|
if schemaname is not None:
|
|
72
74
|
if not db.table_exists(tablename, schemaname):
|
|
@@ -85,13 +87,13 @@ def importtable(
|
|
|
85
87
|
db.commit()
|
|
86
88
|
except ErrInfo:
|
|
87
89
|
raise
|
|
88
|
-
except Exception:
|
|
90
|
+
except Exception as e:
|
|
89
91
|
fq_tablename = db.schema_qualified_table_name(schemaname, tablename)
|
|
90
92
|
raise ErrInfo(
|
|
91
93
|
"exception",
|
|
92
94
|
exception_msg=exception_info(),
|
|
93
95
|
other_msg=f"Can't import tabular file ({filename}) to table ({fq_tablename})",
|
|
94
|
-
)
|
|
96
|
+
) from e
|
|
95
97
|
inf.close()
|
|
96
98
|
|
|
97
99
|
|
|
@@ -121,10 +123,10 @@ def importfile(
|
|
|
121
123
|
db.commit()
|
|
122
124
|
except ErrInfo:
|
|
123
125
|
raise
|
|
124
|
-
except Exception:
|
|
126
|
+
except Exception as e:
|
|
125
127
|
fq_tablename = db.schema_qualified_table_name(schemaname, tablename)
|
|
126
128
|
raise ErrInfo(
|
|
127
129
|
"exception",
|
|
128
130
|
exception_msg=exception_info(),
|
|
129
131
|
other_msg=f"Can't import file ({filename}) to table ({fq_tablename})",
|
|
130
|
-
)
|
|
132
|
+
) from e
|
execsql/importers/feather.py
CHANGED
|
@@ -14,6 +14,8 @@ from execsql.exceptions import ErrInfo
|
|
|
14
14
|
from execsql.db.base import Database
|
|
15
15
|
from execsql.importers.base import import_data_table
|
|
16
16
|
|
|
17
|
+
__all__ = ["import_feather", "import_parquet"]
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
def import_feather(
|
|
19
21
|
db: Database,
|
|
@@ -26,12 +28,12 @@ def import_feather(
|
|
|
26
28
|
|
|
27
29
|
try:
|
|
28
30
|
import polars as pl
|
|
29
|
-
except Exception:
|
|
31
|
+
except Exception as e:
|
|
30
32
|
raise ErrInfo(
|
|
31
33
|
"exception",
|
|
32
34
|
exception_msg=exception_info(),
|
|
33
35
|
other_msg="The polars Python library must be installed to import data from the Feather format.",
|
|
34
|
-
)
|
|
36
|
+
) from e
|
|
35
37
|
df = pl.read_ipc(filename)
|
|
36
38
|
hdrs = df.columns
|
|
37
39
|
data = [list(row) for row in df.rows()]
|
|
@@ -49,12 +51,12 @@ def import_parquet(
|
|
|
49
51
|
|
|
50
52
|
try:
|
|
51
53
|
import polars as pl
|
|
52
|
-
except Exception:
|
|
54
|
+
except Exception as e:
|
|
53
55
|
raise ErrInfo(
|
|
54
56
|
"exception",
|
|
55
57
|
exception_msg=exception_info(),
|
|
56
58
|
other_msg="The polars Python library must be installed to import data from the Parquet format.",
|
|
57
|
-
)
|
|
59
|
+
) from e
|
|
58
60
|
df = pl.read_parquet(filename)
|
|
59
61
|
hdrs = df.columns
|
|
60
62
|
data = [list(row) for row in df.rows()]
|
execsql/importers/ods.py
CHANGED
|
@@ -17,6 +17,8 @@ from execsql.exporters.ods import OdsFile
|
|
|
17
17
|
from execsql.importers.base import import_data_table
|
|
18
18
|
import execsql.state as _state
|
|
19
19
|
|
|
20
|
+
__all__ = ["importods", "ods_data"]
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
def ods_data(
|
|
22
24
|
filename: str,
|
|
@@ -31,12 +33,12 @@ def ods_data(
|
|
|
31
33
|
wbk = OdsFile()
|
|
32
34
|
try:
|
|
33
35
|
wbk.open(filename)
|
|
34
|
-
except Exception:
|
|
35
|
-
raise ErrInfo(type="cmd", other_msg=f"{filename} is not a valid OpenDocument spreadsheet.")
|
|
36
|
+
except Exception as e:
|
|
37
|
+
raise ErrInfo(type="cmd", other_msg=f"{filename} is not a valid OpenDocument spreadsheet.") from e
|
|
36
38
|
try:
|
|
37
39
|
alldata = wbk.sheet_data(sheetname, junk_header_rows)
|
|
38
|
-
except Exception:
|
|
39
|
-
raise ErrInfo(type="cmd", other_msg=f"{sheetname} is not a worksheet in {filename}.")
|
|
40
|
+
except Exception as e:
|
|
41
|
+
raise ErrInfo(type="cmd", other_msg=f"{sheetname} is not a worksheet in {filename}.") from e
|
|
40
42
|
colhdrs = alldata[0]
|
|
41
43
|
if any(x is None or len(x.strip()) == 0 for x in colhdrs):
|
|
42
44
|
if conf.del_empty_cols:
|
execsql/importers/xls.py
CHANGED
|
@@ -16,6 +16,8 @@ from execsql.db.base import Database
|
|
|
16
16
|
from execsql.importers.base import import_data_table
|
|
17
17
|
import execsql.state as _state
|
|
18
18
|
|
|
19
|
+
__all__ = ["importxls", "xls_data"]
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
def xls_data(
|
|
21
23
|
filename: str,
|
|
@@ -45,12 +47,12 @@ def xls_data(
|
|
|
45
47
|
raise ErrInfo(type="cmd", other_msg=f"{filename} is not a recognizable Excel spreadsheet name.")
|
|
46
48
|
try:
|
|
47
49
|
wbk.open(filename, encoding, read_only=True)
|
|
48
|
-
except Exception:
|
|
49
|
-
raise ErrInfo(type="cmd", other_msg=f"{filename} is not a valid Excel spreadsheet.")
|
|
50
|
+
except Exception as e:
|
|
51
|
+
raise ErrInfo(type="cmd", other_msg=f"{filename} is not a valid Excel spreadsheet.") from e
|
|
50
52
|
try:
|
|
51
53
|
alldata = wbk.sheet_data(sheetname, junk_header_rows)
|
|
52
|
-
except Exception:
|
|
53
|
-
raise ErrInfo(type="cmd", other_msg=f"Error reading worksheet {sheetname} from {filename}.")
|
|
54
|
+
except Exception as e:
|
|
55
|
+
raise ErrInfo(type="cmd", other_msg=f"Error reading worksheet {sheetname} from {filename}.") from e
|
|
54
56
|
if len(alldata) == 0:
|
|
55
57
|
raise ErrInfo(type="cmd", other_msg=f"There are no data on worksheet {sheetname} of file {filename}.")
|
|
56
58
|
if ext3 == "lsx":
|