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
execsql/exporters/html.py CHANGED
@@ -11,6 +11,7 @@ CSS styling.
11
11
 
12
12
  import datetime
13
13
  import getpass
14
+ import html as html_mod
14
15
  import os
15
16
  import sys
16
17
  import tempfile
@@ -42,12 +43,12 @@ def export_html(
42
43
  f.write(f"<caption>{desc}</caption>\n")
43
44
  f.write("<thead><tr>")
44
45
  for h in hdrs:
45
- f.write(f"<th>{h}</th>")
46
+ f.write(f"<th>{html_mod.escape(str(h))}</th>")
46
47
  f.write("</tr></thead>\n<tbody>\n")
47
48
  for r in rows:
48
49
  f.write("<tr>")
49
50
  for v in r:
50
- f.write(f"<td>{v if v else ''}</td>")
51
+ f.write(f"<td>{html_mod.escape(str(v)) if v else ''}</td>")
51
52
  f.write("</tr>\n")
52
53
  f.write("</tbody>\n</table>\n")
53
54
 
@@ -67,42 +68,47 @@ def export_html(
67
68
  f = ef.open("wt")
68
69
  else:
69
70
  f = ZipWriter(zipfile, outfile, append)
70
- f.write('<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8" />\n')
71
- if querytext:
72
- descrip = f"Source: [{querytext}] with database {_state.dbs.current().name()} in script {str(Path(script).resolve())}, line {lno}"
73
- else:
74
- descrip = f"From database {_state.dbs.current().name()} in script {str(Path(script).resolve())}, line {lno}"
75
- f.write(f'<meta name="description" content="{descrip}" />\n')
76
- datecontent = datetime.datetime.now().strftime("%Y-%m-%d")
77
- f.write(f'<meta name="created" content="{datecontent}" />\n')
78
- f.write(f'<meta name="revised" content="{datecontent}" />\n')
79
- f.write(f'<meta name="author" content="{getpass.getuser()}" />\n')
80
- f.write("<title>Data Table</title>\n")
81
- if conf.css_file or conf.css_styles:
82
- if conf.css_file:
83
- f.write(f'<link rel="stylesheet" type="text/css" href="{conf.css_file}">')
84
- if conf.css_styles:
85
- f.write(f'<style type="text/css">\n{conf.css_styles}\n</style>')
86
- else:
87
- f.write('<style type="text/css">\n')
88
- f.write(
89
- 'table {font-family: "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Lucida Console", "Courier New", Courier, fixed; '
90
- + "border-top: 3px solid #814324; border-bottom: 3px solid #814324; "
91
- + "border-left: 2px solid #814324; border-right: 2px solid #814324; "
92
- + "border-collapse: collapse; }\n",
93
- )
94
- f.write("td {text-align: left; padding 0 10px; border-right: 1px dotted #814324; }\n")
95
- f.write(
96
- "th {padding: 2px 10px; text-align: center; border-bottom: 1px solid #814324; border-right: 1px dotted #814324;}\n",
97
- )
98
- f.write("tr.hdr {font-weight: bold;}\n")
99
- f.write("thead tr {border-bottom: 1px solid #814324; background-color: #F3F1E2; }\n")
100
- f.write("tbody tr { border-bottom: 1px dotted #814324; }\n")
101
- f.write("</style>")
102
- f.write("\n</head>\n<body>\n")
103
- write_table(f)
104
- f.write("</body>\n</html>\n")
105
- f.close()
71
+ try:
72
+ f.write('<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8" />\n')
73
+ if querytext:
74
+ descrip = f"Source: [{querytext}] with database {_state.dbs.current().name()} in script {str(Path(script).resolve())}, line {lno}"
75
+ else:
76
+ descrip = (
77
+ f"From database {_state.dbs.current().name()} in script {str(Path(script).resolve())}, line {lno}"
78
+ )
79
+ f.write(f'<meta name="description" content="{descrip}" />\n')
80
+ datecontent = datetime.datetime.now().strftime("%Y-%m-%d")
81
+ f.write(f'<meta name="created" content="{datecontent}" />\n')
82
+ f.write(f'<meta name="revised" content="{datecontent}" />\n')
83
+ f.write(f'<meta name="author" content="{getpass.getuser()}" />\n')
84
+ f.write("<title>Data Table</title>\n")
85
+ if conf.css_file or conf.css_styles:
86
+ if conf.css_file:
87
+ f.write(f'<link rel="stylesheet" type="text/css" href="{conf.css_file}">')
88
+ if conf.css_styles:
89
+ f.write(f'<style type="text/css">\n{conf.css_styles}\n</style>')
90
+ else:
91
+ f.write('<style type="text/css">\n')
92
+ f.write(
93
+ 'table {font-family: "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Lucida Console", "Courier New", Courier, fixed; '
94
+ + "border-top: 3px solid #814324; border-bottom: 3px solid #814324; "
95
+ + "border-left: 2px solid #814324; border-right: 2px solid #814324; "
96
+ + "border-collapse: collapse; }\n",
97
+ )
98
+ f.write("td {text-align: left; padding 0 10px; border-right: 1px dotted #814324; }\n")
99
+ f.write(
100
+ "th {padding: 2px 10px; text-align: center; border-bottom: 1px solid #814324; border-right: 1px dotted #814324;}\n",
101
+ )
102
+ f.write("tr.hdr {font-weight: bold;}\n")
103
+ f.write("thead tr {border-bottom: 1px solid #814324; background-color: #F3F1E2; }\n")
104
+ f.write("tbody tr { border-bottom: 1px dotted #814324; }\n")
105
+ f.write("</style>")
106
+ f.write("\n</head>\n<body>\n")
107
+ write_table(f)
108
+ f.write("</body>\n</html>\n")
109
+ finally:
110
+ if outfile.lower() != "stdout":
111
+ f.close()
106
112
  elif not zipfile and append:
107
113
  if outfile.lower() == "stdout":
108
114
  f = sys.stdout
@@ -112,8 +118,10 @@ def export_html(
112
118
 
113
119
  ef = EncodedFile(outfile, conf.output_encoding)
114
120
  f = ef.open("wt")
115
- write_table(f)
116
- f.close()
121
+ try:
122
+ write_table(f)
123
+ finally:
124
+ f.close()
117
125
  else:
118
126
  filewriter_close(outfile)
119
127
  from execsql.utils.fileio import EncodedFile
@@ -124,23 +132,25 @@ def export_html(
124
132
  os.close(tempf) # Close the fd from mkstemp; EncodedFile opens its own handle
125
133
  tf = EncodedFile(tempfname, conf.output_encoding)
126
134
  t = tf.open("wt")
127
- remainder = ""
128
- for line in f:
129
- bodypos = line.lower().find("</body>")
130
- if bodypos > -1:
131
- t.write(line[0:bodypos])
132
- t.write("\n")
133
- remainder = line[bodypos:]
134
- break
135
- else:
135
+ try:
136
+ remainder = ""
137
+ for line in f:
138
+ bodypos = line.lower().find("</body>")
139
+ if bodypos > -1:
140
+ t.write(line[0:bodypos])
141
+ t.write("\n")
142
+ remainder = line[bodypos:]
143
+ break
144
+ else:
145
+ t.write(line)
146
+ t.write("\n")
147
+ write_table(t)
148
+ t.write(remainder)
149
+ for line in f:
136
150
  t.write(line)
137
- t.write("\n")
138
- write_table(t)
139
- t.write(remainder)
140
- for line in f:
141
- t.write(line)
142
- t.close()
143
- f.close()
151
+ finally:
152
+ t.close()
153
+ f.close()
144
154
  os.unlink(outfile)
145
155
  os.rename(tempfname, outfile)
146
156
 
@@ -162,12 +172,12 @@ def export_cgi_html(
162
172
  f.write(f"<caption>{desc}</caption>\n")
163
173
  f.write("<thead><tr>")
164
174
  for h in hdrs:
165
- f.write(f"<th>{h}</th>")
175
+ f.write(f"<th>{html_mod.escape(str(h))}</th>")
166
176
  f.write("</tr></thead>\n<tbody>\n")
167
177
  for r in rows:
168
178
  f.write("<tr>")
169
179
  for v in r:
170
- f.write(f"<td>{v if v else ''}</td>")
180
+ f.write(f"<td>{html_mod.escape(str(v)) if v else ''}</td>")
171
181
  f.write("</tr>\n")
172
182
  f.write("</tbody>\n</table>\n")
173
183
 
@@ -184,10 +194,12 @@ def export_cgi_html(
184
194
  f = ef.open("wt")
185
195
  else:
186
196
  f = ZipWriter(zipfile, outfile, append)
187
- f.write("Content-Type: text/html\n\n")
188
- write_table(f)
189
- if outfile.lower() != "stdout":
190
- f.close()
197
+ try:
198
+ f.write("Content-Type: text/html\n\n")
199
+ write_table(f)
200
+ finally:
201
+ if outfile.lower() != "stdout":
202
+ f.close()
191
203
  else:
192
204
  if outfile == "stdout":
193
205
  f = sys.stdout
@@ -196,9 +208,11 @@ def export_cgi_html(
196
208
 
197
209
  ef = EncodedFile(outfile, conf.output_encoding)
198
210
  f = ef.open("a")
199
- write_table(f)
200
- if outfile.lower() != "stdout":
201
- f.close()
211
+ try:
212
+ write_table(f)
213
+ finally:
214
+ if outfile.lower() != "stdout":
215
+ f.close()
202
216
 
203
217
 
204
218
  def write_query_to_html(
@@ -213,8 +227,8 @@ def write_query_to_html(
213
227
  hdrs, rows = db.select_rowsource(select_stmt)
214
228
  except ErrInfo:
215
229
  raise
216
- except Exception:
217
- raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
230
+ except Exception as e:
231
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
218
232
  export_html(outfile, hdrs, rows, append, select_stmt, desc, zipfile=zipfile)
219
233
 
220
234
 
@@ -230,6 +244,6 @@ def write_query_to_cgi_html(
230
244
  hdrs, rows = db.select_rowsource(select_stmt)
231
245
  except ErrInfo:
232
246
  raise
233
- except Exception:
234
- raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
247
+ except Exception as e:
248
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
235
249
  export_cgi_html(outfile, hdrs, rows, append, select_stmt, desc, zipfile=zipfile)
execsql/exporters/json.py CHANGED
@@ -34,8 +34,8 @@ def write_query_to_json(
34
34
  hdrs, rows = db.select_rowsource(select_stmt)
35
35
  except ErrInfo:
36
36
  raise
37
- except Exception:
38
- raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
37
+ except Exception as e:
38
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
39
39
  if zipfile is None:
40
40
  filewriter_close(outfile)
41
41
  from execsql.utils.fileio import EncodedFile
@@ -48,20 +48,22 @@ def write_query_to_json(
48
48
  f = ef.open("wt")
49
49
  else:
50
50
  f = ZipWriter(zipfile, outfile, append)
51
- f.write("[")
52
- uhdrs = [str(h) for h in hdrs]
53
- first = True
54
- for row in rows:
55
- if first:
56
- f.write("\n")
57
- else:
58
- f.write(",\n")
59
- first = False
60
- dictdata = dict(zip(uhdrs, [str(v) if isinstance(v, str) else v for v in row]))
61
- jsondata = json.dumps(dictdata, separators=(",", ":"), default=str)
62
- f.write(str(jsondata))
63
- f.write("\n]\n")
64
- f.close()
51
+ try:
52
+ f.write("[")
53
+ uhdrs = [str(h) for h in hdrs]
54
+ first = True
55
+ for row in rows:
56
+ if first:
57
+ f.write("\n")
58
+ else:
59
+ f.write(",\n")
60
+ first = False
61
+ dictdata = dict(zip(uhdrs, [str(v) if isinstance(v, str) else v for v in row]))
62
+ jsondata = json.dumps(dictdata, separators=(",", ":"), default=str)
63
+ f.write(str(jsondata))
64
+ f.write("\n]\n")
65
+ finally:
66
+ f.close()
65
67
 
66
68
 
67
69
  def write_query_to_json_ts(
@@ -73,13 +75,16 @@ def write_query_to_json_ts(
73
75
  desc: str | None = None,
74
76
  zipfile: str | None = None,
75
77
  ) -> None:
78
+ global json
79
+ import json
80
+
76
81
  conf = _state.conf
77
82
  try:
78
83
  hdrs, rows = db.select_rowsource(select_stmt)
79
84
  except ErrInfo:
80
85
  raise
81
- except Exception:
82
- raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
86
+ except Exception as e:
87
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
83
88
  max_col_idx = len(hdrs) - 1
84
89
  if zipfile is None:
85
90
  filewriter_close(outfile)
@@ -93,27 +98,30 @@ def write_query_to_json_ts(
93
98
  f = ef.open("wt")
94
99
  else:
95
100
  f = ZipWriter(zipfile, outfile, append)
96
- f.write("{\n")
97
- if desc is not None:
98
- f.write(f' "description": "{desc}",\n')
99
- f.write(' "fields": [\n')
100
- if write_types:
101
- # Scan the data to determine data types.
102
- tbl_desc = DataTable(hdrs, rows)
103
- # Write the column descriptions to the header.
104
- # Iterate over hdrs instead of tbl_desc.cols to preserve column order.
105
- for i, h in enumerate(hdrs):
106
- qcomma = "," if i < max_col_idx else ""
107
- c = [col for col in tbl_desc.cols if col.name == h][0]
108
- f.write(
109
- f' {{\n "name": "{c.name}",\n "title": "{c.name.capitalize().replace("_", " ")}",\n "type": "{_state.to_json_type[c.dt[1]]}"\n }}{qcomma}\n',
110
- )
111
- else:
112
- # Write the column descriptions to the header.
113
- for i, h in enumerate(hdrs):
114
- qcomma = "," if i < max_col_idx else ""
115
- f.write(
116
- f' {{\n "name": "{h}",\n "title": "{h.capitalize().replace("_", " ")}"\n }}{qcomma}\n',
117
- )
118
- f.write(" ]\n}\n")
119
- f.close()
101
+ try:
102
+ f.write("{\n")
103
+ if desc is not None:
104
+ escaped_desc = json.dumps(desc)
105
+ f.write(f' "description": {escaped_desc},\n')
106
+ f.write(' "fields": [\n')
107
+ if write_types:
108
+ # Scan the data to determine data types.
109
+ tbl_desc = DataTable(hdrs, rows)
110
+ # Write the column descriptions to the header.
111
+ # Iterate over hdrs instead of tbl_desc.cols to preserve column order.
112
+ for i, h in enumerate(hdrs):
113
+ qcomma = "," if i < max_col_idx else ""
114
+ c = [col for col in tbl_desc.cols if col.name == h][0]
115
+ f.write(
116
+ f' {{\n "name": "{c.name}",\n "title": "{c.name.capitalize().replace("_", " ")}",\n "type": "{_state.to_json_type[c.dt[1]]}"\n }}{qcomma}\n',
117
+ )
118
+ else:
119
+ # Write the column descriptions to the header.
120
+ for i, h in enumerate(hdrs):
121
+ qcomma = "," if i < max_col_idx else ""
122
+ f.write(
123
+ f' {{\n "name": "{h}",\n "title": "{h.capitalize().replace("_", " ")}"\n }}{qcomma}\n',
124
+ )
125
+ f.write(" ]\n}\n")
126
+ finally:
127
+ f.close()
@@ -60,12 +60,14 @@ def export_latex(
60
60
  f = ef.open("wt")
61
61
  else:
62
62
  f = WriteableZipfile(zipfile).open(outfile, append)
63
- f.write("\\documentclass{article}\n")
64
- f.write("\\begin{document}\n")
65
- write_table(f)
66
- f.write("\\end{document}\n")
67
- if outfile.lower() != "stdout":
68
- f.close()
63
+ try:
64
+ f.write("\\documentclass{article}\n")
65
+ f.write("\\begin{document}\n")
66
+ write_table(f)
67
+ f.write("\\end{document}\n")
68
+ finally:
69
+ if outfile.lower() != "stdout":
70
+ f.close()
69
71
  else:
70
72
  if outfile.lower() == "stdout" or not Path(outfile).is_file():
71
73
  if outfile.lower() == "stdout":
@@ -75,9 +77,11 @@ def export_latex(
75
77
  else:
76
78
  ef = EncodedFile(outfile, conf.output_encoding)
77
79
  f = ef.open("wt")
78
- write_table(f)
79
- if outfile.lower() != "stdout":
80
- f.close()
80
+ try:
81
+ write_table(f)
82
+ finally:
83
+ if outfile.lower() != "stdout":
84
+ f.close()
81
85
  else:
82
86
  ef = EncodedFile(outfile, conf.output_encoding)
83
87
  f = ef.open("rt")
@@ -85,23 +89,25 @@ def export_latex(
85
89
  os.close(tempf) # Close the fd from mkstemp; EncodedFile opens its own handle
86
90
  tf = EncodedFile(tempfname, conf.output_encoding)
87
91
  t = tf.open("wt")
88
- remainder = ""
89
- for line in f:
90
- bodypos = line.lower().find("\\end{document}")
91
- if bodypos > -1:
92
- t.write(line[0:bodypos])
93
- t.write("\n")
94
- remainder = line[bodypos:]
95
- break
96
- else:
92
+ try:
93
+ remainder = ""
94
+ for line in f:
95
+ bodypos = line.lower().find("\\end{document}")
96
+ if bodypos > -1:
97
+ t.write(line[0:bodypos])
98
+ t.write("\n")
99
+ remainder = line[bodypos:]
100
+ break
101
+ else:
102
+ t.write(line)
103
+ t.write("\n")
104
+ write_table(t)
105
+ t.write(remainder)
106
+ for line in f:
97
107
  t.write(line)
98
- t.write("\n")
99
- write_table(t)
100
- t.write(remainder)
101
- for line in f:
102
- t.write(line)
103
- t.close()
104
- f.close()
108
+ finally:
109
+ t.close()
110
+ f.close()
105
111
  os.unlink(outfile)
106
112
  os.rename(tempfname, outfile)
107
113
 
@@ -120,6 +126,6 @@ def write_query_to_latex(
120
126
  hdrs, rows = db.select_rowsource(select_stmt)
121
127
  except ErrInfo:
122
128
  raise
123
- except Exception:
124
- raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
129
+ except Exception as e:
130
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
125
131
  export_latex(outfile, hdrs, rows, append, select_stmt, desc, zipfile=zipfile)
execsql/exporters/ods.py CHANGED
@@ -342,8 +342,8 @@ def write_query_to_ods(
342
342
  hdrs, rows = db.select_rowsource(select_stmt)
343
343
  except ErrInfo:
344
344
  raise
345
- except Exception:
346
- raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
345
+ except Exception as e:
346
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
347
347
  export_ods(outfile, hdrs, rows, append, select_stmt, sheetname, desc)
348
348
 
349
349
 
@@ -403,8 +403,8 @@ def write_queries_to_ods(
403
403
  hdrs, rows = db.select_rowsource(select_stmt)
404
404
  except ErrInfo:
405
405
  raise
406
- except Exception:
407
- raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
406
+ except Exception as e:
407
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
408
408
  # Add the data to a new sheet.
409
409
  tbl = wbk.new_sheet(sheet_name)
410
410
  wbk.add_row_to_sheet(hdrs, tbl, header=True)
@@ -17,12 +17,12 @@ from execsql.utils.fileio import filewriter_close
17
17
  def write_query_to_parquet(outfile: str, headers: list[str], rows: Any) -> None:
18
18
  try:
19
19
  import polars as pl
20
- except ImportError:
20
+ except ImportError as e:
21
21
  raise ErrInfo(
22
22
  "exception",
23
23
  exception_msg=exception_desc(),
24
24
  other_msg="The polars Python package must be installed to export data to the parquet format.",
25
- )
25
+ ) from e
26
26
  rows_list = list(rows)
27
27
  if rows_list:
28
28
  df = pl.DataFrame(rows_list, schema=headers, orient="row")
@@ -43,12 +43,12 @@ def prettyprint_rowset(
43
43
  if not isinstance(rows, list):
44
44
  try:
45
45
  rows = list(rows)
46
- except Exception:
46
+ except Exception as e:
47
47
  raise ErrInfo(
48
48
  "exception",
49
49
  exception_msg=exception_desc(),
50
50
  other_msg="Can't create a list in memory of the data to be displayed as formatted text.",
51
- )
51
+ ) from e
52
52
  rcols = range(len(colhdrs))
53
53
  rrows = range(len(rows))
54
54
  colwidths = [max(0, len(colhdrs[j]), *(len(as_ucode(rows[i][j])) for i in rrows)) for j in rcols]
@@ -76,13 +76,15 @@ def prettyprint_rowset(
76
76
  ofile = EncodedFile(output_dest, _state.conf.output_encoding).open("w")
77
77
  else:
78
78
  ofile = ZipWriter(zipfile, output_dest, append)
79
- if desc is not None:
80
- ofile.write(f"{desc}\n")
81
- for row in rows:
82
- ln = f"{margin}{row}\n"
83
- ofile.write(ln)
84
- if output_dest != "stdout":
85
- ofile.close()
79
+ try:
80
+ if desc is not None:
81
+ ofile.write(f"{desc}\n")
82
+ for row in rows:
83
+ ln = f"{margin}{row}\n"
84
+ ofile.write(ln)
85
+ finally:
86
+ if output_dest != "stdout":
87
+ ofile.close()
86
88
  return None
87
89
 
88
90
 
execsql/exporters/raw.py CHANGED
@@ -28,16 +28,18 @@ def write_query_raw(
28
28
  of = open(outfile, mode) # noqa: SIM115
29
29
  else:
30
30
  of = ZipWriter(zipfile, outfile, append)
31
- for row in rowsource:
32
- for col in row:
33
- if isinstance(col, bytearray):
34
- of.write(col)
35
- else:
36
- if isinstance(col, str):
37
- of.write(bytes(col, db_encoding))
31
+ try:
32
+ for row in rowsource:
33
+ for col in row:
34
+ if isinstance(col, bytearray):
35
+ of.write(col)
38
36
  else:
39
- of.write(bytes(str(col), db_encoding))
40
- of.close()
37
+ if isinstance(col, str):
38
+ of.write(bytes(col, db_encoding))
39
+ else:
40
+ of.write(bytes(str(col), db_encoding))
41
+ finally:
42
+ of.close()
41
43
 
42
44
 
43
45
  def write_query_b64(outfile: str, rowsource: Any, append: bool = False, zipfile: str | None = None) -> None:
@@ -50,7 +52,9 @@ def write_query_b64(outfile: str, rowsource: Any, append: bool = False, zipfile:
50
52
  of = open(outfile, mode) # noqa: SIM115
51
53
  else:
52
54
  of = ZipWriter(zipfile, outfile, append)
53
- for row in rowsource:
54
- for col in row:
55
- of.write(base64.standard_b64decode(col))
56
- of.close()
55
+ try:
56
+ for row in rowsource:
57
+ for col in row:
58
+ of.write(base64.standard_b64decode(col))
59
+ finally:
60
+ of.close()
@@ -77,6 +77,6 @@ def write_query_to_sqlite(
77
77
  hdrs, rows = db.select_rowsource(select_stmt)
78
78
  except ErrInfo:
79
79
  raise
80
- except Exception:
81
- raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
80
+ except Exception as e:
81
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
82
82
  export_sqlite(outfile, hdrs, rows, append, tablename)
@@ -29,8 +29,11 @@ class StrTemplateReport:
29
29
  from execsql.utils.fileio import EncodedFile
30
30
 
31
31
  inf = EncodedFile(self.infname, conf.script_encoding)
32
- self.template = string.Template(inf.open("r").read())
33
- inf.close()
32
+ fh = inf.open("r")
33
+ try:
34
+ self.template = string.Template(fh.read())
35
+ finally:
36
+ fh.close()
34
37
 
35
38
  def __repr__(self) -> str:
36
39
  return f"StrTemplateReport({self.infname})"
@@ -58,10 +61,12 @@ class StrTemplateReport:
58
61
  ofile = EncodedFile(output_dest, conf.output_encoding).open("w")
59
62
  else:
60
63
  ofile = ZipWriter(zipfile, output_dest, append)
61
- for dd in data_dict_rows:
62
- ofile.write(self.template.safe_substitute(dd))
63
- if output_dest != "stdout":
64
- ofile.close()
64
+ try:
65
+ for dd in data_dict_rows:
66
+ ofile.write(self.template.safe_substitute(dd))
67
+ finally:
68
+ if output_dest != "stdout":
69
+ ofile.close()
65
70
 
66
71
 
67
72
  class JinjaTemplateReport:
@@ -70,6 +75,7 @@ class JinjaTemplateReport:
70
75
  global jinja2
71
76
  try:
72
77
  import jinja2
78
+ from jinja2.sandbox import SandboxedEnvironment
73
79
  except ImportError:
74
80
  fatal_error(
75
81
  "The jinja2 library is required to produce reports with the Jinja2 templating system. See http://jinja.pocoo.org/",
@@ -79,11 +85,14 @@ class JinjaTemplateReport:
79
85
  from execsql.utils.fileio import EncodedFile
80
86
 
81
87
  inf = EncodedFile(template_file, conf.script_encoding)
82
- self.template = jinja2.Template(inf.open("r").read())
83
- inf.close()
88
+ fh = inf.open("r")
89
+ try:
90
+ self.template = SandboxedEnvironment().from_string(fh.read())
91
+ finally:
92
+ fh.close()
84
93
 
85
94
  def __repr__(self) -> str:
86
- return f"StrTemplateReport({self.infname})"
95
+ return f"JinjaTemplateReport({self.infname})"
87
96
 
88
97
  def write_report(
89
98
  self,
@@ -111,13 +120,12 @@ class JinjaTemplateReport:
111
120
  try:
112
121
  ofile.write(self.template.render(headers=headers, datatable=data_dict_rows))
113
122
  except jinja2.TemplateSyntaxError as e:
114
- raise ErrInfo("error", other_msg=e.message + f" on template line {e.lineno}")
123
+ raise ErrInfo("error", other_msg=e.message + f" on template line {e.lineno}") from e
115
124
  except jinja2.TemplateError as e:
116
- raise ErrInfo("error", other_msg=f"Jinja2 template error ({e.message})")
117
- except:
118
- raise
119
- if output_dest != "stdout":
120
- ofile.close()
125
+ raise ErrInfo("error", other_msg=f"Jinja2 template error ({e.message})") from e
126
+ finally:
127
+ if output_dest != "stdout":
128
+ ofile.close()
121
129
 
122
130
 
123
131
  def report_query(