execsql2 2.24.0__py3-none-any.whl → 2.25.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/api.py +169 -160
- execsql/cli/__init__.py +2 -0
- execsql/cli/commands/lint.py +2 -10
- execsql/cli/commands/run.py +35 -27
- execsql/cli/dsn.py +11 -4
- execsql/cli/lint.py +271 -28
- execsql/cli/options.py +1 -1
- execsql/cli/run.py +56 -19
- execsql/config.py +5 -19
- execsql/data/execsql.conf.template +14 -4
- execsql/db/access.py +29 -8
- execsql/db/base.py +40 -8
- execsql/db/dsn.py +13 -4
- execsql/db/duckdb.py +3 -1
- execsql/db/factory.py +11 -2
- execsql/db/firebird.py +21 -7
- execsql/db/mysql.py +37 -16
- execsql/db/oracle.py +49 -16
- execsql/db/postgres.py +30 -6
- execsql/db/sqlite.py +7 -9
- execsql/db/sqlserver.py +25 -8
- execsql/exporters/delimited.py +87 -28
- execsql/exporters/xlsx.py +24 -9
- execsql/exporters/zip.py +1 -2
- execsql/format.py +29 -2
- execsql/gui/desktop.py +1 -3
- execsql/gui/tui.py +2 -2
- execsql/importers/base.py +35 -1
- execsql/importers/csv.py +3 -1
- execsql/manifest.py +33 -22
- execsql/metacommands/conditions.py +6 -7
- execsql/metacommands/data.py +4 -1
- execsql/metacommands/debug.py +2 -4
- execsql/metacommands/io_fileops.py +62 -37
- execsql/metacommands/io_import.py +0 -1
- execsql/metacommands/prompt.py +2 -0
- execsql/metacommands/upsert.py +5 -2
- execsql/models.py +3 -2
- execsql/parser.py +14 -5
- execsql/plugins.py +24 -0
- execsql/script/engine.py +40 -11
- execsql/script/executor.py +9 -5
- execsql/script/parser.py +167 -118
- execsql/state.py +23 -9
- execsql/state.pyi +8 -0
- execsql/types.py +22 -13
- execsql/utils/auth.py +169 -24
- execsql/utils/datetime.py +55 -17
- execsql/utils/errors.py +71 -9
- execsql/utils/fileio.py +404 -117
- execsql/utils/gui.py +3 -0
- execsql/utils/mail.py +37 -12
- {execsql2-2.24.0.dist-info → execsql2-2.25.0.dist-info}/METADATA +18 -12
- {execsql2-2.24.0.dist-info → execsql2-2.25.0.dist-info}/RECORD +72 -72
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.24.0.data → execsql2-2.25.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.24.0.dist-info → execsql2-2.25.0.dist-info}/WHEEL +0 -0
- {execsql2-2.24.0.dist-info → execsql2-2.25.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.24.0.dist-info → execsql2-2.25.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.24.0.dist-info → execsql2-2.25.0.dist-info}/licenses/NOTICE +0 -0
execsql/utils/errors.py
CHANGED
|
@@ -8,8 +8,10 @@ throughout the codebase:
|
|
|
8
8
|
|
|
9
9
|
- :func:`exception_info` — extracts type, value, and source location
|
|
10
10
|
from the current exception.
|
|
11
|
-
- :func:`exception_desc` —
|
|
12
|
-
|
|
11
|
+
- :func:`exception_desc` — ``Type: message`` for the exception being
|
|
12
|
+
handled, as shown on screen.
|
|
13
|
+
- :func:`error_origin` / :func:`logged_error` — where in execsql an error
|
|
14
|
+
was raised, added to the message written to the log.
|
|
13
15
|
- :func:`write_warning` — writes a non-fatal warning message.
|
|
14
16
|
- :func:`exit_now` — terminates execution after optional halt hooks.
|
|
15
17
|
- :func:`fatal_error` — logs a fatal error and calls :func:`exit_now`.
|
|
@@ -27,7 +29,9 @@ from execsql.exceptions import ErrInfo
|
|
|
27
29
|
|
|
28
30
|
__all__ = [
|
|
29
31
|
"exception_info",
|
|
32
|
+
"error_origin",
|
|
30
33
|
"exception_desc",
|
|
34
|
+
"logged_error",
|
|
31
35
|
"exit_now",
|
|
32
36
|
"fatal_error",
|
|
33
37
|
"stamp_errinfo",
|
|
@@ -67,8 +71,35 @@ def exception_info() -> tuple:
|
|
|
67
71
|
|
|
68
72
|
|
|
69
73
|
def exception_desc() -> str:
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
"""``Type: message`` for the exception being handled, as shown to the person running the script.
|
|
75
|
+
|
|
76
|
+
Where in execsql it was raised is for the log: see :func:`error_origin`.
|
|
77
|
+
"""
|
|
78
|
+
exc_type, exc_strval, _filename, _lineno, _linetext = exception_info()
|
|
79
|
+
return f"{exc_type}: {exc_strval}"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def error_origin(exc: BaseException) -> str | None:
|
|
83
|
+
"""Where in the execsql package the error behind *exc* was raised, as ``execsql/db/base.py:254``.
|
|
84
|
+
|
|
85
|
+
Follows *exc* to the exception it was raised from, and returns the last
|
|
86
|
+
frame of that traceback inside the package, or ``None`` when there is none.
|
|
87
|
+
"""
|
|
88
|
+
package = Path(__file__).resolve().parents[1]
|
|
89
|
+
cause = exc.__cause__ or exc.__context__ or exc
|
|
90
|
+
origin = None
|
|
91
|
+
for frame in traceback.extract_tb(cause.__traceback__):
|
|
92
|
+
path = Path(frame.filename).resolve()
|
|
93
|
+
if path.is_relative_to(package):
|
|
94
|
+
origin = f"{path.relative_to(package.parent).as_posix()}:{frame.lineno}"
|
|
95
|
+
return origin
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def logged_error(errinfo: ErrInfo) -> str:
|
|
99
|
+
"""The error message for the log: what the screen shows, plus :func:`error_origin` when known."""
|
|
100
|
+
origin = error_origin(errinfo)
|
|
101
|
+
message = errinfo.errmsg()
|
|
102
|
+
return f"{message}\n Raised at: {origin}" if origin else message
|
|
72
103
|
|
|
73
104
|
|
|
74
105
|
def stamp_errinfo(errinfo: ErrInfo) -> ErrInfo:
|
|
@@ -124,18 +155,38 @@ def _run_deferred_script(spec: Any) -> None:
|
|
|
124
155
|
)
|
|
125
156
|
|
|
126
157
|
|
|
158
|
+
#: How long a halt waits for an output file that another process has
|
|
159
|
+
#: locked, before its pending output is counted as lost.
|
|
160
|
+
HALT_FLUSH_SECONDS = 10.0
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _flush_output_at_halt() -> ErrInfo | None:
|
|
164
|
+
"""Write out what the run's files are still waiting for; the loss, if any, as an error."""
|
|
165
|
+
from execsql.utils.fileio import filewriter_close_all_after_write
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
filewriter_close_all_after_write(max_wait=HALT_FLUSH_SECONDS)
|
|
169
|
+
except ErrInfo as lost:
|
|
170
|
+
# The loss belongs to no particular statement.
|
|
171
|
+
lost.script_file = ""
|
|
172
|
+
return lost
|
|
173
|
+
except Exception:
|
|
174
|
+
pass # A failed flush must not hide the error that halted the run.
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
|
|
127
178
|
def exit_now(exit_status: int, errinfo: ErrInfo | None, logmsg: str | None = None) -> None:
|
|
128
179
|
# An error in input typed at a BREAKPOINT prompt ends
|
|
129
180
|
# that input, not the run: the prompt reports it and reads the next one.
|
|
130
181
|
# HALT and a canceled prompt carry no error and still end the run.
|
|
131
182
|
if errinfo is not None and _state.prompt_input is not None:
|
|
132
183
|
raise errinfo
|
|
133
|
-
|
|
184
|
+
_state.halt_error = errinfo
|
|
134
185
|
if errinfo is not None:
|
|
135
186
|
stamp_errinfo(errinfo)
|
|
136
187
|
if _state.subvars is not None:
|
|
137
188
|
_state.subvars.add_substitution("$ERROR_MESSAGE", errinfo.errmsg())
|
|
138
|
-
|
|
189
|
+
errinfo.write()
|
|
139
190
|
if _state.err_halt_writespec is not None:
|
|
140
191
|
try:
|
|
141
192
|
_state.err_halt_writespec.write()
|
|
@@ -182,14 +233,23 @@ def exit_now(exit_status: int, errinfo: ErrInfo | None, logmsg: str | None = Non
|
|
|
182
233
|
cancelexec = _state.cancel_halt_exec
|
|
183
234
|
_state.cancel_halt_exec = None
|
|
184
235
|
_run_deferred_script(cancelexec)
|
|
236
|
+
# After the ON ERROR_HALT / ON CANCEL_HALT actions, which may write files
|
|
237
|
+
# themselves; before the writer is stopped, which discards what waits.
|
|
238
|
+
lost_output = _flush_output_at_halt()
|
|
239
|
+
if lost_output is not None:
|
|
240
|
+
_state.halt_output_error = lost_output
|
|
241
|
+
lost_output.write()
|
|
242
|
+
exit_status = exit_status or 1
|
|
185
243
|
if exit_status > 0 and _state.exec_log:
|
|
186
244
|
if logmsg:
|
|
187
245
|
_state.exec_log.log_exit_error(logmsg)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
246
|
+
elif errinfo is not None:
|
|
247
|
+
_state.exec_log.log_exit_error(logged_error(errinfo))
|
|
248
|
+
if lost_output is not None:
|
|
249
|
+
_state.exec_log.log_status_error(lost_output.errmsg())
|
|
191
250
|
if _state.exec_log is not None:
|
|
192
251
|
_state.exec_log.log_status_info(f"{_state.cmds_run} commands run")
|
|
252
|
+
_state.exec_log.log_exit()
|
|
193
253
|
_state.exec_log.close()
|
|
194
254
|
from execsql.utils.fileio import filewriter_end
|
|
195
255
|
|
|
@@ -198,6 +258,8 @@ def exit_now(exit_status: int, errinfo: ErrInfo | None, logmsg: str | None = Non
|
|
|
198
258
|
from execsql import manifest as _manifest
|
|
199
259
|
|
|
200
260
|
if (manifest := _manifest.current()) is not None:
|
|
261
|
+
if lost_output is not None:
|
|
262
|
+
manifest.add_error(lost_output)
|
|
201
263
|
manifest.finish(exit_status, errinfo)
|
|
202
264
|
sys.exit(exit_status)
|
|
203
265
|
|