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.
- 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 +13 -1
- execsql/db/access.py +16 -12
- execsql/db/base.py +158 -90
- execsql/db/dsn.py +6 -5
- execsql/db/duckdb.py +2 -2
- execsql/db/firebird.py +23 -19
- execsql/db/mysql.py +8 -7
- execsql/db/oracle.py +11 -11
- execsql/db/postgres.py +28 -16
- execsql/db/sqlite.py +12 -11
- execsql/db/sqlserver.py +5 -3
- execsql/exceptions.py +7 -7
- execsql/exporters/base.py +6 -1
- execsql/exporters/delimited.py +44 -35
- execsql/exporters/duckdb.py +2 -2
- execsql/exporters/feather.py +6 -6
- execsql/exporters/html.py +83 -69
- execsql/exporters/json.py +50 -42
- execsql/exporters/latex.py +33 -27
- execsql/exporters/ods.py +4 -4
- execsql/exporters/parquet.py +2 -2
- execsql/exporters/pretty.py +11 -9
- execsql/exporters/raw.py +17 -13
- execsql/exporters/sqlite.py +2 -2
- execsql/exporters/templates.py +23 -15
- execsql/exporters/values.py +22 -20
- execsql/exporters/xls.py +4 -4
- execsql/exporters/xml.py +28 -13
- execsql/importers/base.py +4 -4
- execsql/importers/csv.py +6 -6
- execsql/importers/feather.py +4 -4
- execsql/importers/ods.py +4 -4
- execsql/importers/xls.py +4 -4
- execsql/metacommands/__init__.py +518 -67
- 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/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/py.typed +0 -0
- execsql/script.py +49 -5
- execsql/types.py +20 -20
- execsql/utils/fileio.py +15 -8
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/METADATA +6 -6
- execsql2-2.2.1.dist-info/RECORD +104 -0
- execsql2-2.1.2.dist-info/RECORD +0 -96
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/READ_ME.rst +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/WHEEL +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/entry_points.txt +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/licenses/NOTICE +0 -0
execsql/exporters/values.py
CHANGED
|
@@ -40,24 +40,26 @@ def export_values(
|
|
|
40
40
|
f = ef.open("wt")
|
|
41
41
|
else:
|
|
42
42
|
f = ZipWriter(zipfile, outfile, append)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
firstrow
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
try:
|
|
44
|
+
if desc is not None:
|
|
45
|
+
f.write(f"-- {desc}\n")
|
|
46
|
+
f.write(f"INSERT INTO !!target_table!!\n ({', '.join(hdrs)})\n")
|
|
47
|
+
f.write("VALUES\n")
|
|
48
|
+
firstrow = True
|
|
49
|
+
for r in rows:
|
|
50
|
+
if firstrow:
|
|
51
|
+
firstrow = False
|
|
52
|
+
else:
|
|
53
|
+
f.write(",\n")
|
|
54
|
+
quoted_row = [
|
|
55
|
+
f"'{v.replace(chr(39), chr(39) * 2)}'" if isinstance(v, str) else str(v) if v is not None else "NULL"
|
|
56
|
+
for v in r
|
|
57
|
+
]
|
|
58
|
+
f.write(f" ({', '.join(quoted_row)})")
|
|
59
|
+
f.write("\n ;\n")
|
|
60
|
+
finally:
|
|
61
|
+
if outfile.lower() != "stdout":
|
|
62
|
+
f.close()
|
|
61
63
|
|
|
62
64
|
|
|
63
65
|
def write_query_to_values(
|
|
@@ -72,6 +74,6 @@ def write_query_to_values(
|
|
|
72
74
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
73
75
|
except ErrInfo:
|
|
74
76
|
raise
|
|
75
|
-
except Exception:
|
|
76
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
77
|
+
except Exception as e:
|
|
78
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
77
79
|
export_values(outfile, hdrs, rows, append, desc, zipfile=zipfile)
|
execsql/exporters/xls.py
CHANGED
|
@@ -76,8 +76,8 @@ class XlsFile:
|
|
|
76
76
|
def sheet_data(self, sheetname: Any, junk_header_rows: int = 0) -> list:
|
|
77
77
|
try:
|
|
78
78
|
sheet = self.sheet_named(sheetname)
|
|
79
|
-
except Exception:
|
|
80
|
-
raise XlsFileError(f"There is no Excel worksheet named {sheetname} in {self.filename}.")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
raise XlsFileError(f"There is no Excel worksheet named {sheetname} in {self.filename}.") from e
|
|
81
81
|
|
|
82
82
|
# Don't rely on sheet.ncols and sheet.nrows, because Excel will count columns
|
|
83
83
|
# and rows that have ever been filled, even if they are now empty. Base the column count
|
|
@@ -211,8 +211,8 @@ class XlsxFile:
|
|
|
211
211
|
def sheet_data(self, sheetname: Any, junk_header_rows: int = 0) -> list:
|
|
212
212
|
try:
|
|
213
213
|
sheet = self.sheet_named(sheetname)
|
|
214
|
-
except Exception:
|
|
215
|
-
raise XlsxFileError(f"There is no Excel worksheet named {sheetname} in {self.filename}.")
|
|
214
|
+
except Exception as e:
|
|
215
|
+
raise XlsxFileError(f"There is no Excel worksheet named {sheetname} in {self.filename}.") from e
|
|
216
216
|
# Don't rely on sheet.max_column and sheet.max_row, because Excel will count columns
|
|
217
217
|
# and rows that have ever been filled, even if they are now empty. Base the column count
|
|
218
218
|
# 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
|
|
@@ -31,8 +33,8 @@ def write_query_to_xml(
|
|
|
31
33
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
32
34
|
except ErrInfo:
|
|
33
35
|
raise
|
|
34
|
-
except Exception:
|
|
35
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
36
|
+
except Exception as e:
|
|
37
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
36
38
|
if zipfile is None:
|
|
37
39
|
filewriter_close(outfile)
|
|
38
40
|
from execsql.utils.fileio import EncodedFile
|
|
@@ -47,14 +49,27 @@ def write_query_to_xml(
|
|
|
47
49
|
else:
|
|
48
50
|
f = ZipWriter(zipfile, outfile, append)
|
|
49
51
|
f.write(f"<?xml version='1.0' encoding='{conf.output_encoding}'?>\n")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
|
|
53
|
+
def _safe_xml_name(name: str) -> str:
|
|
54
|
+
"""Sanitize a string for use as an XML element name."""
|
|
55
|
+
# Replace characters that are invalid in XML names with underscores.
|
|
56
|
+
s = re.sub(r"[^\w.\-]", "_", str(name))
|
|
57
|
+
# XML names must start with a letter or underscore, not a digit or dot.
|
|
58
|
+
if s and not (s[0].isalpha() or s[0] == "_"):
|
|
59
|
+
s = "_" + s
|
|
60
|
+
return s or "_"
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
if desc is not None:
|
|
64
|
+
f.write(f"<!--{desc.replace('--', '- -')}-->\n")
|
|
65
|
+
safe_tablename = _safe_xml_name(tablename)
|
|
66
|
+
f.write(f"<{safe_tablename}>\n")
|
|
67
|
+
str_hdrs = [_safe_xml_name(h) for h in hdrs]
|
|
68
|
+
for row in rows:
|
|
69
|
+
f.write(" <row>\n")
|
|
70
|
+
for i, col in enumerate(str_hdrs):
|
|
71
|
+
f.write(f" <{col}>{xml_escape(str(row[i]) if row[i] is not None else '')}</{col}>\n")
|
|
72
|
+
f.write(" </row>\n")
|
|
73
|
+
f.write(f"</{safe_tablename}>\n")
|
|
74
|
+
finally:
|
|
75
|
+
f.close()
|
execsql/importers/base.py
CHANGED
|
@@ -85,13 +85,13 @@ def import_data_table(
|
|
|
85
85
|
# ...except for Firebird.
|
|
86
86
|
if db.type == dbt_firebird:
|
|
87
87
|
db.conn.commit()
|
|
88
|
-
except Exception:
|
|
88
|
+
except Exception as e:
|
|
89
89
|
raise ErrInfo(
|
|
90
90
|
type="db",
|
|
91
91
|
command_text=sql,
|
|
92
92
|
exception_msg=exception_info(),
|
|
93
93
|
other_msg=f"Could not create new table ({tablename}) for IMPORT metacommand",
|
|
94
|
-
)
|
|
94
|
+
) from e
|
|
95
95
|
table_cols = db.table_columns(tablename, schemaname)
|
|
96
96
|
if conf.import_common_cols_only:
|
|
97
97
|
import_cols = [col for col in hdrs if col.lower() in [tc.lower() for tc in table_cols]]
|
|
@@ -108,5 +108,5 @@ def import_data_table(
|
|
|
108
108
|
db.commit()
|
|
109
109
|
except ErrInfo:
|
|
110
110
|
raise
|
|
111
|
-
except Exception:
|
|
112
|
-
raise ErrInfo("db", "Call to populate_table when importing data", exception_msg=exception_info())
|
|
111
|
+
except Exception as e:
|
|
112
|
+
raise ErrInfo("db", "Call to populate_table when importing data", exception_msg=exception_info()) from e
|
execsql/importers/csv.py
CHANGED
|
@@ -60,13 +60,13 @@ def importtable(
|
|
|
60
60
|
# ...except for Firebird. Execute the commit directly via the connection so it is always done.
|
|
61
61
|
if db.type == dbt_firebird:
|
|
62
62
|
db.conn.commit()
|
|
63
|
-
except Exception:
|
|
63
|
+
except Exception as e:
|
|
64
64
|
raise ErrInfo(
|
|
65
65
|
type="db",
|
|
66
66
|
command_text=sql,
|
|
67
67
|
exception_msg=exception_info(),
|
|
68
68
|
other_msg=f"Could not create new table ({tablename}) for IMPORT metacommand",
|
|
69
|
-
)
|
|
69
|
+
) from e
|
|
70
70
|
else:
|
|
71
71
|
if schemaname is not None:
|
|
72
72
|
if not db.table_exists(tablename, schemaname):
|
|
@@ -85,13 +85,13 @@ def importtable(
|
|
|
85
85
|
db.commit()
|
|
86
86
|
except ErrInfo:
|
|
87
87
|
raise
|
|
88
|
-
except Exception:
|
|
88
|
+
except Exception as e:
|
|
89
89
|
fq_tablename = db.schema_qualified_table_name(schemaname, tablename)
|
|
90
90
|
raise ErrInfo(
|
|
91
91
|
"exception",
|
|
92
92
|
exception_msg=exception_info(),
|
|
93
93
|
other_msg=f"Can't import tabular file ({filename}) to table ({fq_tablename})",
|
|
94
|
-
)
|
|
94
|
+
) from e
|
|
95
95
|
inf.close()
|
|
96
96
|
|
|
97
97
|
|
|
@@ -121,10 +121,10 @@ def importfile(
|
|
|
121
121
|
db.commit()
|
|
122
122
|
except ErrInfo:
|
|
123
123
|
raise
|
|
124
|
-
except Exception:
|
|
124
|
+
except Exception as e:
|
|
125
125
|
fq_tablename = db.schema_qualified_table_name(schemaname, tablename)
|
|
126
126
|
raise ErrInfo(
|
|
127
127
|
"exception",
|
|
128
128
|
exception_msg=exception_info(),
|
|
129
129
|
other_msg=f"Can't import file ({filename}) to table ({fq_tablename})",
|
|
130
|
-
)
|
|
130
|
+
) from e
|
execsql/importers/feather.py
CHANGED
|
@@ -26,12 +26,12 @@ def import_feather(
|
|
|
26
26
|
|
|
27
27
|
try:
|
|
28
28
|
import polars as pl
|
|
29
|
-
except Exception:
|
|
29
|
+
except Exception as e:
|
|
30
30
|
raise ErrInfo(
|
|
31
31
|
"exception",
|
|
32
32
|
exception_msg=exception_info(),
|
|
33
33
|
other_msg="The polars Python library must be installed to import data from the Feather format.",
|
|
34
|
-
)
|
|
34
|
+
) from e
|
|
35
35
|
df = pl.read_ipc(filename)
|
|
36
36
|
hdrs = df.columns
|
|
37
37
|
data = [list(row) for row in df.rows()]
|
|
@@ -49,12 +49,12 @@ def import_parquet(
|
|
|
49
49
|
|
|
50
50
|
try:
|
|
51
51
|
import polars as pl
|
|
52
|
-
except Exception:
|
|
52
|
+
except Exception as e:
|
|
53
53
|
raise ErrInfo(
|
|
54
54
|
"exception",
|
|
55
55
|
exception_msg=exception_info(),
|
|
56
56
|
other_msg="The polars Python library must be installed to import data from the Parquet format.",
|
|
57
|
-
)
|
|
57
|
+
) from e
|
|
58
58
|
df = pl.read_parquet(filename)
|
|
59
59
|
hdrs = df.columns
|
|
60
60
|
data = [list(row) for row in df.rows()]
|
execsql/importers/ods.py
CHANGED
|
@@ -31,12 +31,12 @@ def ods_data(
|
|
|
31
31
|
wbk = OdsFile()
|
|
32
32
|
try:
|
|
33
33
|
wbk.open(filename)
|
|
34
|
-
except Exception:
|
|
35
|
-
raise ErrInfo(type="cmd", other_msg=f"{filename} is not a valid OpenDocument spreadsheet.")
|
|
34
|
+
except Exception as e:
|
|
35
|
+
raise ErrInfo(type="cmd", other_msg=f"{filename} is not a valid OpenDocument spreadsheet.") from e
|
|
36
36
|
try:
|
|
37
37
|
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}.")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
raise ErrInfo(type="cmd", other_msg=f"{sheetname} is not a worksheet in {filename}.") from e
|
|
40
40
|
colhdrs = alldata[0]
|
|
41
41
|
if any(x is None or len(x.strip()) == 0 for x in colhdrs):
|
|
42
42
|
if conf.del_empty_cols:
|
execsql/importers/xls.py
CHANGED
|
@@ -45,12 +45,12 @@ def xls_data(
|
|
|
45
45
|
raise ErrInfo(type="cmd", other_msg=f"{filename} is not a recognizable Excel spreadsheet name.")
|
|
46
46
|
try:
|
|
47
47
|
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.")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
raise ErrInfo(type="cmd", other_msg=f"{filename} is not a valid Excel spreadsheet.") from e
|
|
50
50
|
try:
|
|
51
51
|
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}.")
|
|
52
|
+
except Exception as e:
|
|
53
|
+
raise ErrInfo(type="cmd", other_msg=f"Error reading worksheet {sheetname} from {filename}.") from e
|
|
54
54
|
if len(alldata) == 0:
|
|
55
55
|
raise ErrInfo(type="cmd", other_msg=f"There are no data on worksheet {sheetname} of file {filename}.")
|
|
56
56
|
if ext3 == "lsx":
|