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.
Files changed (94) 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 +65 -1
  6. execsql/db/access.py +27 -15
  7. execsql/db/base.py +328 -215
  8. execsql/db/dsn.py +10 -5
  9. execsql/db/duckdb.py +6 -2
  10. execsql/db/factory.py +21 -0
  11. execsql/db/firebird.py +27 -19
  12. execsql/db/mysql.py +12 -7
  13. execsql/db/oracle.py +15 -11
  14. execsql/db/postgres.py +31 -16
  15. execsql/db/sqlite.py +15 -11
  16. execsql/db/sqlserver.py +16 -5
  17. execsql/exceptions.py +25 -7
  18. execsql/exporters/base.py +12 -1
  19. execsql/exporters/delimited.py +80 -35
  20. execsql/exporters/duckdb.py +6 -2
  21. execsql/exporters/feather.py +10 -6
  22. execsql/exporters/html.py +89 -69
  23. execsql/exporters/json.py +52 -45
  24. execsql/exporters/latex.py +37 -27
  25. execsql/exporters/ods.py +32 -11
  26. execsql/exporters/parquet.py +5 -2
  27. execsql/exporters/pretty.py +16 -9
  28. execsql/exporters/raw.py +22 -16
  29. execsql/exporters/sqlite.py +6 -2
  30. execsql/exporters/templates.py +39 -21
  31. execsql/exporters/values.py +26 -20
  32. execsql/exporters/xls.py +30 -11
  33. execsql/exporters/xml.py +31 -13
  34. execsql/exporters/zip.py +15 -0
  35. execsql/importers/base.py +6 -4
  36. execsql/importers/csv.py +8 -6
  37. execsql/importers/feather.py +6 -4
  38. execsql/importers/ods.py +6 -4
  39. execsql/importers/xls.py +6 -4
  40. execsql/metacommands/__init__.py +208 -1548
  41. execsql/metacommands/conditions.py +101 -27
  42. execsql/metacommands/control.py +8 -4
  43. execsql/metacommands/data.py +6 -6
  44. execsql/metacommands/debug.py +6 -2
  45. execsql/metacommands/dispatch.py +2011 -0
  46. execsql/metacommands/io.py +67 -1310
  47. execsql/metacommands/io_export.py +442 -0
  48. execsql/metacommands/io_fileops.py +287 -0
  49. execsql/metacommands/io_import.py +398 -0
  50. execsql/metacommands/io_write.py +248 -0
  51. execsql/metacommands/prompt.py +22 -66
  52. execsql/metacommands/system.py +7 -2
  53. execsql/models.py +7 -0
  54. execsql/parser.py +10 -0
  55. execsql/py.typed +0 -0
  56. execsql/script/__init__.py +95 -0
  57. execsql/script/control.py +162 -0
  58. execsql/{script.py → script/engine.py} +184 -402
  59. execsql/script/variables.py +281 -0
  60. execsql/types.py +49 -20
  61. execsql/utils/auth.py +2 -0
  62. execsql/utils/crypto.py +4 -6
  63. execsql/utils/datetime.py +1 -0
  64. execsql/utils/errors.py +11 -0
  65. execsql/utils/fileio.py +33 -8
  66. execsql/utils/gui.py +46 -0
  67. execsql/utils/mail.py +7 -17
  68. execsql/utils/numeric.py +2 -0
  69. execsql/utils/regex.py +9 -0
  70. execsql/utils/strings.py +16 -0
  71. execsql/utils/timer.py +2 -0
  72. execsql2-2.4.0.data/data/execsql2_extras/README.md +65 -0
  73. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/execsql.conf +1 -1
  74. {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/METADATA +13 -6
  75. execsql2-2.4.0.dist-info/RECORD +108 -0
  76. execsql2-2.1.2.data/data/execsql2_extras/READ_ME.rst +0 -127
  77. execsql2-2.1.2.dist-info/RECORD +0 -96
  78. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  79. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  80. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
  81. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/md_compare.sql +0 -0
  82. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
  83. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
  84. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
  85. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  86. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  87. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/script_template.sql +0 -0
  88. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
  89. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  90. {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  91. {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/WHEEL +0 -0
  92. {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/entry_points.txt +0 -0
  93. {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/licenses/LICENSE.txt +0 -0
  94. {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,248 @@
1
+ """WRITE metacommand handlers.
2
+
3
+ Implements ``x_write``, ``x_write_create_table`` (CSV, ODS, XLS, alias),
4
+ ``x_write_prefix``, ``x_write_suffix``, and ``x_writescript``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import execsql.state as _state
13
+ from execsql.exceptions import ErrInfo
14
+ from execsql.exporters.delimited import CsvFile
15
+ from execsql.importers.ods import ods_data
16
+ from execsql.importers.xls import xls_data
17
+ from execsql.models import DataTable
18
+ from execsql.script import substitute_vars
19
+ from execsql.utils.errors import exception_desc
20
+ from execsql.utils.fileio import check_dir, filewriter_close, filewriter_open_as_new, filewriter_write
21
+ from execsql.utils.gui import ConsoleUIError
22
+
23
+
24
+ def x_write(**kwargs: Any) -> None:
25
+ msg = f"{kwargs['text']}\n"
26
+ tee = kwargs["tee"]
27
+ tee = bool(tee)
28
+ outf = kwargs["filename"]
29
+ if _state.conf.write_prefix is not None:
30
+ msg = substitute_vars(_state.conf.write_prefix) + " " + msg
31
+ if _state.conf.write_suffix is not None:
32
+ msg = msg[:-1] + " " + substitute_vars(_state.conf.write_suffix) + "\n"
33
+ if outf:
34
+ check_dir(outf)
35
+ filewriter_write(outf, msg)
36
+ if (not outf) or tee:
37
+ try:
38
+ _state.output.write(msg)
39
+ except TypeError as e:
40
+ raise ErrInfo(
41
+ type="other",
42
+ command_text=kwargs["metacommandline"],
43
+ other_msg="TypeError in 'write' metacommand.",
44
+ ) from e
45
+ except ConsoleUIError as e:
46
+ _state.output.reset()
47
+ _state.exec_log.log_status_info(f"Console UI write failed (message {{{e.value}}}); output reset to stdout.")
48
+ _state.output.write(msg.encode(_state.conf.output_encoding))
49
+ if _state.conf.tee_write_log:
50
+ _state.exec_log.log_user_msg(msg)
51
+ return None
52
+
53
+
54
+ def x_write_create_table(**kwargs: Any) -> None:
55
+ filename = kwargs["filename"]
56
+ if not Path(filename).exists():
57
+ raise ErrInfo(
58
+ type="cmd",
59
+ command_text=kwargs["metacommandline"],
60
+ other_msg="Input file does not exist",
61
+ )
62
+ quotechar = kwargs["quotechar"]
63
+ delimchar = kwargs["delimchar"]
64
+ encoding = kwargs["encoding"]
65
+ if delimchar:
66
+ if delimchar.lower() == "tab":
67
+ delimchar = chr(9)
68
+ elif delimchar.lower() in ("unitsep", "us"):
69
+ delimchar = chr(31)
70
+ junk_hdrs = kwargs["skip"]
71
+ if not junk_hdrs:
72
+ junk_hdrs = 0
73
+ else:
74
+ junk_hdrs = int(junk_hdrs)
75
+ enc = encoding if encoding else _state.conf.import_encoding
76
+ inf = CsvFile(filename, enc, junk_header_lines=junk_hdrs)
77
+ if quotechar and delimchar:
78
+ inf.lineformat(delimchar, quotechar, None)
79
+ inf.evaluate_column_types()
80
+ sql = inf.create_table(_state.dbs.current().type, kwargs["schema"], kwargs["table"], pretty=True)
81
+ inf.close()
82
+ comment = kwargs["comment"]
83
+ outfile = kwargs["outfile"]
84
+
85
+ def write(txt: str) -> None:
86
+ if outfile is None or outfile == "stdout":
87
+ _state.output.write(txt)
88
+ else:
89
+ filewriter_write(outfile, txt)
90
+
91
+ if outfile:
92
+ check_dir(outfile)
93
+ if comment:
94
+ write(f"-- {comment}\n")
95
+ write(f"{sql}\n")
96
+
97
+
98
+ def x_write_create_table_ods(**kwargs: Any) -> None:
99
+ schemaname = kwargs["schema"]
100
+ tablename = kwargs["table"]
101
+ filename = kwargs["filename"]
102
+ sheetname = kwargs["sheet"]
103
+ hdr_rows = kwargs["skip"]
104
+ if not hdr_rows:
105
+ hdr_rows = 0
106
+ else:
107
+ hdr_rows = int(hdr_rows)
108
+ comment = kwargs["comment"]
109
+ outfile = kwargs["outfile"]
110
+ if not Path(filename).exists():
111
+ raise ErrInfo(
112
+ type="cmd",
113
+ command_text=kwargs["metacommandline"],
114
+ other_msg="Input file does not exist",
115
+ )
116
+ hdrs, data = ods_data(filename, sheetname, hdr_rows)
117
+ tablespec = DataTable(hdrs, data)
118
+ sql = tablespec.create_table(_state.dbs.current().type, schemaname, tablename, pretty=True)
119
+ if outfile:
120
+ if comment:
121
+ filewriter_write(outfile, f"-- {comment}\n")
122
+ filewriter_write(outfile, sql)
123
+ filewriter_close(outfile)
124
+ else:
125
+ if comment:
126
+ _state.output.write(f"-- {comment}\n")
127
+ _state.output.write(f"{sql}\n")
128
+
129
+
130
+ def x_write_create_table_xls(**kwargs: Any) -> None:
131
+ schemaname = kwargs["schema"]
132
+ tablename = kwargs["table"]
133
+ filename = kwargs["filename"]
134
+ sheetname = kwargs["sheet"]
135
+ junk_hdrs = kwargs["skip"]
136
+ encoding = kwargs["encoding"]
137
+ enc = encoding if encoding else _state.conf.import_encoding
138
+ if not junk_hdrs:
139
+ junk_hdrs = 0
140
+ else:
141
+ junk_hdrs = int(junk_hdrs)
142
+ comment = kwargs["comment"]
143
+ outfile = kwargs["outfile"]
144
+ if not Path(filename).exists():
145
+ raise ErrInfo(
146
+ type="cmd",
147
+ command_text=kwargs["metacommandline"],
148
+ other_msg="Input file does not exist",
149
+ )
150
+ hdrs, data = xls_data(filename, sheetname, junk_hdrs, enc)
151
+ tablespec = DataTable(hdrs, data)
152
+ sql = tablespec.create_table(_state.dbs.current().type, schemaname, tablename, pretty=True)
153
+ if outfile:
154
+ if comment:
155
+ filewriter_write(outfile, f"-- {comment}\n")
156
+ filewriter_write(outfile, sql)
157
+ filewriter_close(outfile)
158
+ else:
159
+ if comment:
160
+ _state.output.write(f"-- {comment}\n")
161
+ _state.output.write(f"{sql}\n")
162
+
163
+
164
+ def x_write_create_table_alias(**kwargs: Any) -> None:
165
+ alias = kwargs["alias"].lower()
166
+ schema = kwargs["schema"]
167
+ table = kwargs["table"]
168
+ comment = kwargs["comment"]
169
+ outfile = kwargs["filename"]
170
+ if alias not in _state.dbs.aliases():
171
+ raise ErrInfo(
172
+ type="cmd",
173
+ command_text=kwargs["metacommandline"],
174
+ other_msg=f"Unrecognized database alias: {alias}.",
175
+ )
176
+ db = _state.dbs.aliased_as(alias)
177
+ tbl = db.schema_qualified_table_name(schema, table)
178
+ try:
179
+ if not db.table_exists(table, schema):
180
+ raise ErrInfo(
181
+ type="cmd",
182
+ command_text=kwargs["metacommandline"],
183
+ other_msg=f"Table {tbl} does not exist",
184
+ )
185
+ except Exception:
186
+ pass # Best-effort check; some adapters lack information_schema.
187
+ select_stmt = f"select * from {tbl};"
188
+ try:
189
+ hdrs, rows = db.select_rowsource(select_stmt)
190
+ except ErrInfo:
191
+ raise
192
+ except Exception as e:
193
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
194
+ tablespec = DataTable(hdrs, rows)
195
+ sql = tablespec.create_table(_state.dbs.current().type, kwargs["schema1"], kwargs["table1"], pretty=True)
196
+ if outfile:
197
+ if comment:
198
+ filewriter_write(outfile, f"-- {comment}\n")
199
+ filewriter_write(outfile, sql)
200
+ filewriter_close(outfile)
201
+ else:
202
+ if comment:
203
+ _state.output.write(f"-- {comment}\n")
204
+ _state.output.write(f"{sql}\n")
205
+
206
+
207
+ def x_write_prefix(**kwargs: Any) -> None:
208
+ pf = kwargs["prefix"]
209
+ if pf.lower() == "clear":
210
+ _state.conf.write_prefix = None
211
+ else:
212
+ _state.conf.write_prefix = pf
213
+ return None
214
+
215
+
216
+ def x_write_suffix(**kwargs: Any) -> None:
217
+ sf = kwargs["suffix"]
218
+ if sf.lower() == "clear":
219
+ _state.conf.write_suffix = None
220
+ else:
221
+ _state.conf.write_suffix = sf
222
+ return None
223
+
224
+
225
+ def x_writescript(**kwargs: Any) -> None:
226
+ script_id = kwargs["script_id"]
227
+ output_dest = kwargs["filename"]
228
+ append = kwargs["append"]
229
+
230
+ def write(txt: str) -> None:
231
+ if output_dest is None or output_dest == "stdout":
232
+ _state.output.write(txt)
233
+ else:
234
+ filewriter_write(output_dest, txt)
235
+
236
+ if output_dest is not None and output_dest != "stdout":
237
+ check_dir(output_dest)
238
+ if not append:
239
+ filewriter_open_as_new(output_dest)
240
+ script = _state.savedscripts[script_id]
241
+ if script.paramnames is not None and len(script.paramnames) > 0:
242
+ write(f"BEGIN SCRIPT {script_id} ({', '.join(script.paramnames)})\n")
243
+ else:
244
+ write(f"BEGIN SCRIPT {script_id}\n")
245
+ lines = [c.commandline() for c in script.cmdlist]
246
+ for line in lines:
247
+ write(f"{line}\n")
248
+ write(f"END SCRIPT {script_id}\n")
@@ -29,7 +29,6 @@ from typing import Any
29
29
  import execsql.state as _state
30
30
  from execsql.script import current_script_line
31
31
  from execsql.utils.errors import exception_desc, exit_now
32
- from execsql.utils.fileio import EncodedFile, check_dir
33
32
  from execsql.utils.gui import (
34
33
  ActionSpec,
35
34
  EntrySpec,
@@ -38,7 +37,6 @@ from execsql.utils.gui import (
38
37
  GUI_DIRECTORY,
39
38
  GUI_DISPLAY,
40
39
  GUI_ENTRY,
41
- GUI_HALT,
42
40
  GUI_MAP,
43
41
  GUI_MSG,
44
42
  GUI_OPENFILE,
@@ -210,12 +208,12 @@ def x_prompt_entryform(**kwargs: Any) -> None:
210
208
  initial_value = str(str(v["initial_value"]).lower() in ("yes", "true", "on", "1"))
211
209
  else:
212
210
  initial_value = str(v["initial_value"])
213
- except Exception:
211
+ except Exception as e:
214
212
  raise ErrInfo(
215
213
  type="cmd",
216
214
  command_text=kwargs["metacommandline"],
217
215
  other_msg=f"The initial value of {v['initial_value']} can't be used.",
218
- )
216
+ ) from e
219
217
  if "lookup_table" in colhdrs:
220
218
  lt = v["lookup_table"]
221
219
  if lt:
@@ -226,23 +224,23 @@ def x_prompt_entryform(**kwargs: Any) -> None:
226
224
  if entry_width:
227
225
  try:
228
226
  entry_width = int(entry_width)
229
- except Exception:
227
+ except Exception as e:
230
228
  raise ErrInfo(
231
229
  type="cmd",
232
230
  command_text=kwargs["metacommandline"],
233
231
  other_msg=f"Entry width {entry_width} is not an integer",
234
- )
232
+ ) from e
235
233
  if "height" in colhdrs:
236
234
  entry_height = v.get("height")
237
235
  if entry_height:
238
236
  try:
239
237
  entry_height = int(entry_height)
240
- except Exception:
238
+ except Exception as e:
241
239
  raise ErrInfo(
242
240
  type="cmd",
243
241
  command_text=kwargs["metacommandline"],
244
242
  other_msg=f"Entry height {entry_height} is not an integer",
245
- )
243
+ ) from e
246
244
  if entry_height < 1:
247
245
  entry_height = 1
248
246
  if "form_column" in colhdrs:
@@ -250,12 +248,12 @@ def x_prompt_entryform(**kwargs: Any) -> None:
250
248
  if entry_col:
251
249
  try:
252
250
  entry_col = int(entry_col)
253
- except Exception:
251
+ except Exception as e:
254
252
  raise ErrInfo(
255
253
  type="cmd",
256
254
  command_text=kwargs["metacommandline"],
257
255
  other_msg=f"Entry column {entry_col} is not an integer",
258
- )
256
+ ) from e
259
257
  if entry_col < 1:
260
258
  entry_col = 1
261
259
  subvarset = _state.subvars if subvar[0] != "~" else _state.commandliststack[-1].localvars
@@ -363,15 +361,15 @@ def prompt_compare(button_list: list, **kwargs: Any) -> Any:
363
361
  if alias1 is not None:
364
362
  try:
365
363
  db1 = _state.dbs.aliased_as(alias1)
366
- except Exception:
367
- raise ErrInfo(type="error", other_msg=badaliasmsg % alias1)
364
+ except Exception as e:
365
+ raise ErrInfo(type="error", other_msg=badaliasmsg % alias1) from e
368
366
  else:
369
367
  db1 = _state.dbs.current()
370
368
  if alias2 is not None:
371
369
  try:
372
370
  db2 = _state.dbs.aliased_as(alias2)
373
- except Exception:
374
- raise ErrInfo(type="error", other_msg=badaliasmsg % alias2)
371
+ except Exception as e:
372
+ raise ErrInfo(type="error", other_msg=badaliasmsg % alias2) from e
375
373
  else:
376
374
  db2 = _state.dbs.current()
377
375
  sq_name1 = db1.schema_qualified_table_name(schema1, table1)
@@ -666,8 +664,8 @@ def x_prompt_savefile(**kwargs: Any) -> None:
666
664
  subvarset5.add_substitution(sub_name5, Path(fn).stem)
667
665
  except (ErrInfo, SystemExit):
668
666
  raise
669
- except Exception:
670
- raise ErrInfo(type="exception", exception_msg=exception_desc())
667
+ except Exception as e:
668
+ raise ErrInfo(type="exception", exception_msg=exception_desc()) from e
671
669
  return None
672
670
 
673
671
 
@@ -733,8 +731,8 @@ def x_prompt_openfile(**kwargs: Any) -> None:
733
731
  subvarset5.add_substitution(sub_name5, Path(fn).stem)
734
732
  except (ErrInfo, SystemExit):
735
733
  raise
736
- except Exception:
737
- raise ErrInfo(type="exception", exception_msg=exception_desc())
734
+ except Exception as e:
735
+ raise ErrInfo(type="exception", exception_msg=exception_desc()) from e
738
736
  return None
739
737
 
740
738
 
@@ -769,8 +767,8 @@ def x_prompt_directory(**kwargs: Any) -> None:
769
767
  )
770
768
  except (ErrInfo, SystemExit):
771
769
  raise
772
- except Exception:
773
- raise ErrInfo(type="exception", exception_msg=exception_desc())
770
+ except Exception as e:
771
+ raise ErrInfo(type="exception", exception_msg=exception_desc()) from e
774
772
  return None
775
773
 
776
774
 
@@ -787,15 +785,15 @@ def prompt_select_rows(button_list: list, **kwargs: Any) -> Any:
787
785
  if alias1 is not None:
788
786
  try:
789
787
  db1 = _state.dbs.aliased_as(alias1)
790
- except Exception:
791
- raise ErrInfo(type="error", other_msg=badaliasmsg % alias1)
788
+ except Exception as e:
789
+ raise ErrInfo(type="error", other_msg=badaliasmsg % alias1) from e
792
790
  else:
793
791
  db1 = _state.dbs.current()
794
792
  if alias2 is not None:
795
793
  try:
796
794
  db2 = _state.dbs.aliased_as(alias2)
797
- except Exception:
798
- raise ErrInfo(type="error", other_msg=badaliasmsg % alias2)
795
+ except Exception as e:
796
+ raise ErrInfo(type="error", other_msg=badaliasmsg % alias2) from e
799
797
  else:
800
798
  db2 = _state.dbs.current()
801
799
  sq_name1 = db1.schema_qualified_table_name(schema1, table1)
@@ -942,48 +940,6 @@ def x_pause(**kwargs: Any) -> None:
942
940
  return None
943
941
 
944
942
 
945
- def x_halt_msg(**kwargs: Any) -> None:
946
- errmsg = kwargs["errmsg"]
947
- tee = kwargs["tee"]
948
- tee = bool(tee)
949
- outf = kwargs["filename"]
950
- errlevel = kwargs["errorlevel"]
951
- if errlevel:
952
- errlevel = int(errlevel)
953
- else:
954
- errlevel = 3
955
- conf = _state.conf
956
- if outf:
957
- check_dir(outf)
958
- of = EncodedFile(outf, conf.output_encoding).open("a")
959
- of.write(f"{errmsg}\n")
960
- of.close()
961
- schema = kwargs.get("schema")
962
- table = kwargs.get("table")
963
- if table:
964
- db = _state.dbs.current()
965
- db_obj = db.schema_qualified_table_name(schema, table)
966
- sql = f"select * from {db_obj};"
967
- headers, rows = db.select_data(sql)
968
- else:
969
- headers, rows = None, None
970
- enable_gui()
971
- return_queue = _queue.Queue()
972
- gui_args = {
973
- "title": "HALT",
974
- "message": errmsg,
975
- "button_list": [("OK", 1, "<Return>")],
976
- "no_cancel": True,
977
- "column_headers": headers,
978
- "rowset": rows,
979
- "help_url": None,
980
- }
981
- _state.gui_manager_queue.put(GuiSpec(GUI_HALT, gui_args, return_queue))
982
- return_queue.get(block=True)
983
- _state.exec_log.log_exit_halt(*current_script_line(), msg=errmsg)
984
- exit_now(errlevel, None)
985
-
986
-
987
943
  def x_msg(**kwargs: Any) -> None:
988
944
  message = kwargs["message"]
989
945
  current_script_line()
@@ -89,6 +89,11 @@ def x_log_datavars(**kwargs: Any) -> None:
89
89
  _state.conf.log_datavars = setting in ("yes", "on", "true", "1")
90
90
 
91
91
 
92
+ def x_log_sql(**kwargs: Any) -> None:
93
+ setting = kwargs["setting"].lower()
94
+ _state.conf.log_sql = setting in ("yes", "on", "true", "1")
95
+
96
+
92
97
  def x_console(**kwargs: Any) -> None:
93
98
  onoff = kwargs["onoff"].lower()
94
99
  if onoff == "on":
@@ -251,6 +256,6 @@ def x_execute(**kwargs: Any) -> None:
251
256
  db.commit()
252
257
  except ErrInfo:
253
258
  raise
254
- except Exception:
255
- raise ErrInfo("db", command_text=sql, exception_msg=exception_desc())
259
+ except Exception as e:
260
+ raise ErrInfo("db", command_text=sql, exception_msg=exception_desc()) from e
256
261
  return None
execsql/models.py CHANGED
@@ -40,6 +40,13 @@ from execsql.types import (
40
40
  DT_Varchar,
41
41
  )
42
42
 
43
+ __all__ = [
44
+ "Column",
45
+ "DataTable",
46
+ "JsonDatatype",
47
+ "to_json_type",
48
+ ]
49
+
43
50
 
44
51
  class Column:
45
52
  # Column objects are used to compile information about the data types that a set of data
execsql/parser.py CHANGED
@@ -27,6 +27,16 @@ from typing import Any
27
27
 
28
28
  from execsql.exceptions import CondParserError, NumericParserError
29
29
 
30
+ __all__ = [
31
+ "SourceString",
32
+ "CondTokens",
33
+ "NumTokens",
34
+ "CondAstNode",
35
+ "NumericAstNode",
36
+ "CondParser",
37
+ "NumericParser",
38
+ ]
39
+
30
40
 
31
41
  class SourceString:
32
42
  def __init__(self, source_string: str) -> None:
execsql/py.typed ADDED
File without changes
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Core script-execution engine for execsql.
5
+
6
+ This module contains the data structures and functions that load, parse, and
7
+ drive execution of execsql ``.sql`` script files. It is the heart of the
8
+ runtime.
9
+
10
+ Key classes:
11
+
12
+ - :class:`BatchLevels` — tracks which databases are used in nested BEGIN/END
13
+ BATCH blocks for commit/rollback handling.
14
+ - :class:`IfItem` / :class:`IfLevels` — stack-based IF/ELSE/ENDIF nesting.
15
+ - :class:`CounterVars` — named integer counters (``@NAME``).
16
+ - :class:`SubVarSet` — global ``!!$VAR!!`` substitution-variable store, plus
17
+ ``&ENV``, ``@COUNTER``, ``~LOCAL``, and ``#ARG`` prefixes.
18
+ - :class:`LocalSubVarSet` / :class:`ScriptArgSubVarSet` — per-script-scope
19
+ variable overlays.
20
+ - :class:`MetaCommand` — one entry in the metacommand dispatch table (regex +
21
+ handler function + flags).
22
+ - :class:`MetaCommandList` — ordered list of :class:`MetaCommand` entries;
23
+ ``get_match()`` finds the first matching entry for a given line.
24
+ - :class:`SqlStmt` — wraps a single SQL string; ``run()`` executes it via the
25
+ active database connection.
26
+ - :class:`MetacommandStmt` — wraps a metacommand line; ``run()`` dispatches
27
+ through :attr:`execsql.state.metacommandlist`.
28
+ - :class:`ScriptCmd` — pairs a statement with its source-file location.
29
+ - :class:`CommandList` — ordered list of :class:`ScriptCmd` objects plus an
30
+ execution cursor; ``run_next()`` drives one step of execution.
31
+ - :class:`CommandListWhileLoop` / :class:`CommandListUntilLoop` — loop
32
+ variants of :class:`CommandList` that re-evaluate a condition each pass.
33
+ - :class:`ScriptFile` — reads and tokenises a ``.sql`` file into
34
+ :class:`ScriptCmd` objects.
35
+ - :class:`ScriptExecSpec` — specification for deferred script execution.
36
+
37
+ Key functions:
38
+
39
+ - :func:`set_system_vars` — populates built-in ``$VARNAME`` system variables.
40
+ - :func:`substitute_vars` — performs ``!!$VAR!!`` and ``!{$var}!`` expansion.
41
+ - :func:`runscripts` — central execution loop; pops the top
42
+ :class:`CommandList` from ``_state.commandliststack`` and drives
43
+ ``run_next()`` until the stack is empty.
44
+ - :func:`current_script_line` — returns the source location of the currently
45
+ executing command.
46
+ - :func:`read_sqlfile` — parses a SQL script file into a new
47
+ :class:`CommandList` and pushes it onto ``_state.commandliststack``.
48
+ """
49
+
50
+ from execsql.script.control import BatchLevels, IfItem, IfLevels
51
+ from execsql.script.engine import (
52
+ CommandList,
53
+ CommandListUntilLoop,
54
+ CommandListWhileLoop,
55
+ MetaCommand,
56
+ MetaCommandList,
57
+ MetacommandStmt,
58
+ ScriptCmd,
59
+ ScriptExecSpec,
60
+ ScriptFile,
61
+ SqlStmt,
62
+ current_script_line,
63
+ read_sqlfile,
64
+ read_sqlstring,
65
+ runscripts,
66
+ set_system_vars,
67
+ substitute_vars,
68
+ )
69
+ from execsql.script.variables import CounterVars, LocalSubVarSet, ScriptArgSubVarSet, SubVarSet
70
+
71
+ __all__ = [
72
+ "BatchLevels",
73
+ "IfItem",
74
+ "IfLevels",
75
+ "CounterVars",
76
+ "SubVarSet",
77
+ "LocalSubVarSet",
78
+ "ScriptArgSubVarSet",
79
+ "MetaCommand",
80
+ "MetaCommandList",
81
+ "SqlStmt",
82
+ "MetacommandStmt",
83
+ "ScriptCmd",
84
+ "CommandList",
85
+ "CommandListWhileLoop",
86
+ "CommandListUntilLoop",
87
+ "ScriptFile",
88
+ "ScriptExecSpec",
89
+ "set_system_vars",
90
+ "substitute_vars",
91
+ "runscripts",
92
+ "current_script_line",
93
+ "read_sqlfile",
94
+ "read_sqlstring",
95
+ ]