execsql2 2.34.0__py3-none-any.whl → 2.37.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 +17 -8
- execsql/cli/lint.py +54 -6
- execsql/cli/run.py +5 -10
- execsql/config.py +2 -0
- execsql/data/execsql.conf.template +17 -7
- execsql/db/base.py +12 -2
- execsql/db/duckdb.py +8 -1
- execsql/debug/repl.py +2 -2
- execsql/metacommands/connect.py +10 -9
- execsql/metacommands/data.py +7 -1
- execsql/metacommands/dispatch.py +7 -0
- execsql/metacommands/io_fileops.py +6 -3
- execsql/metacommands/io_write.py +3 -1
- execsql/metacommands/reference.py +1 -0
- execsql/metacommands/script_ext.py +13 -10
- execsql/metacommands/system.py +13 -6
- execsql/models.py +30 -4
- execsql/script/ast.py +21 -0
- execsql/script/control.py +63 -3
- execsql/script/executor.py +379 -229
- execsql/script/parser.py +102 -52
- execsql/state.py +23 -0
- execsql/utils/errors.py +24 -6
- execsql/utils/sigterm.py +154 -0
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/METADATA +2 -2
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/RECORD +43 -42
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/WHEEL +0 -0
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/licenses/NOTICE +0 -0
execsql/script/parser.py
CHANGED
|
@@ -25,7 +25,8 @@ Usage::
|
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
27
|
import re
|
|
28
|
-
from collections.abc import Iterable
|
|
28
|
+
from collections.abc import Callable, Iterable
|
|
29
|
+
from dataclasses import replace
|
|
29
30
|
from pathlib import Path
|
|
30
31
|
from typing import cast
|
|
31
32
|
|
|
@@ -96,7 +97,7 @@ _INCLUDE_RX = re.compile(
|
|
|
96
97
|
)
|
|
97
98
|
|
|
98
99
|
|
|
99
|
-
def
|
|
100
|
+
def strip_quotes(s: str) -> str:
|
|
100
101
|
"""Strip a matching pair of surrounding quotes from *s*."""
|
|
101
102
|
if len(s) >= 2 and s[0] in ('"', "'") and s[-1] == s[0]:
|
|
102
103
|
return s[1:-1]
|
|
@@ -126,6 +127,94 @@ _EXEC_SCRIPT_RX = re.compile(
|
|
|
126
127
|
re.I,
|
|
127
128
|
)
|
|
128
129
|
|
|
130
|
+
|
|
131
|
+
def directive_node(command: str, span: SourceSpan) -> IncludeDirective | None:
|
|
132
|
+
"""The :class:`IncludeDirective` for an INCLUDE or EXECUTE SCRIPT *command*, or ``None`` for any other.
|
|
133
|
+
|
|
134
|
+
*command* is the metacommand without ``-- !x!``.
|
|
135
|
+
"""
|
|
136
|
+
m = _INCLUDE_RX.match(command)
|
|
137
|
+
if m:
|
|
138
|
+
return IncludeDirective(
|
|
139
|
+
span=span,
|
|
140
|
+
target=strip_quotes(m.group("target").strip()),
|
|
141
|
+
if_exists=m.group("exists") is not None,
|
|
142
|
+
command=command.strip(),
|
|
143
|
+
)
|
|
144
|
+
m = _EXEC_SCRIPT_RX.match(command)
|
|
145
|
+
if m:
|
|
146
|
+
return IncludeDirective(
|
|
147
|
+
span=span,
|
|
148
|
+
target=m.group("script_id"),
|
|
149
|
+
is_execute_script=True,
|
|
150
|
+
if_exists=m.group("exists") is not None,
|
|
151
|
+
arguments=m.group("argexp"),
|
|
152
|
+
loop_type=m.group("looptype").upper() if m.group("looptype") else None,
|
|
153
|
+
loop_condition=m.group("loopcond"),
|
|
154
|
+
command=command.strip(),
|
|
155
|
+
)
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def substitute_directive(node: IncludeDirective, substitute: Callable[[str], str]) -> IncludeDirective:
|
|
160
|
+
"""*node* with *substitute* applied once to each part that is substituted when it runs.
|
|
161
|
+
|
|
162
|
+
Those parts are INCLUDE's file and EXECUTE SCRIPT's script name and
|
|
163
|
+
arguments. For a node parsed from a metacommand, ``command`` becomes
|
|
164
|
+
the metacommand with them substituted. The metacommand is not
|
|
165
|
+
parsed again, so a value with a line break in it stays one value (an
|
|
166
|
+
unquoted argument still ends at a comma or a closing parenthesis, as it
|
|
167
|
+
always has). A ``WHILE`` / ``UNTIL`` condition is left as written: it
|
|
168
|
+
is substituted on each pass of the loop.
|
|
169
|
+
"""
|
|
170
|
+
m = (_EXEC_SCRIPT_RX if node.is_execute_script else _INCLUDE_RX).match(node.command)
|
|
171
|
+
if m is None:
|
|
172
|
+
# Built without the text of a metacommand.
|
|
173
|
+
target = substitute(node.target)
|
|
174
|
+
return replace(
|
|
175
|
+
node,
|
|
176
|
+
target=target if node.is_execute_script else strip_quotes(target.strip()),
|
|
177
|
+
arguments=substitute(node.arguments) if node.arguments is not None else None,
|
|
178
|
+
)
|
|
179
|
+
pieces: list[str] = []
|
|
180
|
+
values: dict[str, str] = {}
|
|
181
|
+
last = 0
|
|
182
|
+
for group in ("script_id", "argexp") if node.is_execute_script else ("target",):
|
|
183
|
+
if m.group(group) is None:
|
|
184
|
+
continue
|
|
185
|
+
start, end = m.span(group)
|
|
186
|
+
values[group] = substitute(node.command[start:end])
|
|
187
|
+
pieces += [node.command[last:start], values[group]]
|
|
188
|
+
last = end
|
|
189
|
+
pieces.append(node.command[last:])
|
|
190
|
+
command = "".join(pieces)
|
|
191
|
+
if node.is_execute_script:
|
|
192
|
+
return replace(node, target=values["script_id"], arguments=values.get("argexp"), command=command)
|
|
193
|
+
return replace(node, target=strip_quotes(values["target"].strip()), command=command)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def metacommand_node(command: str, span: SourceSpan) -> IfBlock | IncludeDirective | MetaCommandStatement:
|
|
197
|
+
"""The node for a metacommand that opens or closes no block, however it is reached.
|
|
198
|
+
|
|
199
|
+
The same on a line of its own, inside an inline ``IF (…) { … }`` and
|
|
200
|
+
when added with ``EXTEND SCRIPT … WITH METACOMMAND``: an inline ``IF``
|
|
201
|
+
is an :class:`IfBlock`, INCLUDE and EXECUTE SCRIPT an
|
|
202
|
+
:class:`IncludeDirective`, and any other a :class:`MetaCommandStatement`
|
|
203
|
+
for the dispatch table. A metacommand whose text a substitution
|
|
204
|
+
variable supplies (``-- !x! !!cmd!!``) is classified here as written,
|
|
205
|
+
before substitution, so it always goes to the dispatch table (#113).
|
|
206
|
+
"""
|
|
207
|
+
m = _IF_INLINE_RX.match(command)
|
|
208
|
+
if m:
|
|
209
|
+
return IfBlock(
|
|
210
|
+
span=span,
|
|
211
|
+
condition=m.group("cond").strip(),
|
|
212
|
+
body=[metacommand_node(m.group("cmd").strip(), span)],
|
|
213
|
+
command=command.strip(),
|
|
214
|
+
)
|
|
215
|
+
return directive_node(command, span) or MetaCommandStatement(span=span, command=command)
|
|
216
|
+
|
|
217
|
+
|
|
129
218
|
# A parameter default value is either a double-quoted string, a single-quoted
|
|
130
219
|
# string (both may contain spaces, commas, and other special characters), or
|
|
131
220
|
# a bare run of non-whitespace characters. Unquoted values must not begin
|
|
@@ -197,7 +286,7 @@ def _parse_param_defs(
|
|
|
197
286
|
)
|
|
198
287
|
name, default = m.group(1), m.group(2)
|
|
199
288
|
if default is not None:
|
|
200
|
-
default =
|
|
289
|
+
default = strip_quotes(default)
|
|
201
290
|
if seen_optional is None:
|
|
202
291
|
seen_optional = name
|
|
203
292
|
elif seen_optional is not None:
|
|
@@ -512,7 +601,7 @@ def _parse_lines(lines: Iterable[str], source_name: str, errors: list[ErrInfo] |
|
|
|
512
601
|
in_sql_block = True
|
|
513
602
|
block_stack.append(
|
|
514
603
|
_BlockFrame(
|
|
515
|
-
SqlBlock(span=SourceSpan(source_name, file_lineno)),
|
|
604
|
+
SqlBlock(span=SourceSpan(source_name, file_lineno), command=cmd_text),
|
|
516
605
|
kind="sqlblock",
|
|
517
606
|
start_line=file_lineno,
|
|
518
607
|
source=source_name,
|
|
@@ -576,6 +665,7 @@ def _parse_lines(lines: Iterable[str], source_name: str, errors: list[ErrInfo] |
|
|
|
576
665
|
span=SourceSpan(source_name, file_lineno),
|
|
577
666
|
name=name,
|
|
578
667
|
param_defs=param_defs,
|
|
668
|
+
command=cmd_text,
|
|
579
669
|
),
|
|
580
670
|
kind="script",
|
|
581
671
|
start_line=file_lineno,
|
|
@@ -630,6 +720,7 @@ def _parse_lines(lines: Iterable[str], source_name: str, errors: list[ErrInfo] |
|
|
|
630
720
|
IfBlock(
|
|
631
721
|
span=SourceSpan(source_name, file_lineno),
|
|
632
722
|
condition=m.group("cond").strip(),
|
|
723
|
+
command=cmd_text,
|
|
633
724
|
),
|
|
634
725
|
kind="if",
|
|
635
726
|
start_line=file_lineno,
|
|
@@ -639,18 +730,8 @@ def _parse_lines(lines: Iterable[str], source_name: str, errors: list[ErrInfo] |
|
|
|
639
730
|
continue
|
|
640
731
|
|
|
641
732
|
# -- IF (inline form): IF (cond) { cmd } --
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
inner = MetaCommandStatement(
|
|
645
|
-
span=SourceSpan(source_name, file_lineno),
|
|
646
|
-
command=m.group("cmd").strip(),
|
|
647
|
-
)
|
|
648
|
-
if_node = IfBlock(
|
|
649
|
-
span=SourceSpan(source_name, file_lineno),
|
|
650
|
-
condition=m.group("cond").strip(),
|
|
651
|
-
body=[inner],
|
|
652
|
-
)
|
|
653
|
-
_current_body().append(if_node)
|
|
733
|
+
if _IF_INLINE_RX.match(cmd_text):
|
|
734
|
+
_current_body().append(metacommand_node(cmd_text, SourceSpan(source_name, file_lineno)))
|
|
654
735
|
continue
|
|
655
736
|
|
|
656
737
|
# -- ELSEIF --
|
|
@@ -673,6 +754,7 @@ def _parse_lines(lines: Iterable[str], source_name: str, errors: list[ErrInfo] |
|
|
|
673
754
|
ElseIfClause(
|
|
674
755
|
condition=m.group("cond").strip(),
|
|
675
756
|
span=SourceSpan(source_name, file_lineno),
|
|
757
|
+
command=cmd_text,
|
|
676
758
|
),
|
|
677
759
|
)
|
|
678
760
|
continue
|
|
@@ -767,6 +849,7 @@ def _parse_lines(lines: Iterable[str], source_name: str, errors: list[ErrInfo] |
|
|
|
767
849
|
span=SourceSpan(source_name, file_lineno),
|
|
768
850
|
loop_type=m.group("looptype").upper(),
|
|
769
851
|
condition=m.group("loopcond").strip(),
|
|
852
|
+
command=cmd_text,
|
|
770
853
|
),
|
|
771
854
|
kind="loop",
|
|
772
855
|
start_line=file_lineno,
|
|
@@ -793,7 +876,7 @@ def _parse_lines(lines: Iterable[str], source_name: str, errors: list[ErrInfo] |
|
|
|
793
876
|
if m:
|
|
794
877
|
block_stack.append(
|
|
795
878
|
_BlockFrame(
|
|
796
|
-
BatchBlock(span=SourceSpan(source_name, file_lineno)),
|
|
879
|
+
BatchBlock(span=SourceSpan(source_name, file_lineno), command=cmd_text),
|
|
797
880
|
kind="batch",
|
|
798
881
|
start_line=file_lineno,
|
|
799
882
|
source=source_name,
|
|
@@ -814,41 +897,8 @@ def _parse_lines(lines: Iterable[str], source_name: str, errors: list[ErrInfo] |
|
|
|
814
897
|
_close_block(closed, file_lineno)
|
|
815
898
|
continue
|
|
816
899
|
|
|
817
|
-
# -- INCLUDE --
|
|
818
|
-
|
|
819
|
-
if m:
|
|
820
|
-
_current_body().append(
|
|
821
|
-
IncludeDirective(
|
|
822
|
-
span=SourceSpan(source_name, file_lineno),
|
|
823
|
-
target=_strip_quotes(m.group("target").strip()),
|
|
824
|
-
if_exists=m.group("exists") is not None,
|
|
825
|
-
),
|
|
826
|
-
)
|
|
827
|
-
continue
|
|
828
|
-
|
|
829
|
-
# -- EXECUTE SCRIPT / RUN SCRIPT --
|
|
830
|
-
m = _EXEC_SCRIPT_RX.match(cmd_text)
|
|
831
|
-
if m:
|
|
832
|
-
_current_body().append(
|
|
833
|
-
IncludeDirective(
|
|
834
|
-
span=SourceSpan(source_name, file_lineno),
|
|
835
|
-
target=m.group("script_id"),
|
|
836
|
-
is_execute_script=True,
|
|
837
|
-
if_exists=m.group("exists") is not None,
|
|
838
|
-
arguments=m.group("argexp"),
|
|
839
|
-
loop_type=m.group("looptype").upper() if m.group("looptype") else None,
|
|
840
|
-
loop_condition=m.group("loopcond"),
|
|
841
|
-
),
|
|
842
|
-
)
|
|
843
|
-
continue
|
|
844
|
-
|
|
845
|
-
# -- All other metacommands (flat) --
|
|
846
|
-
_current_body().append(
|
|
847
|
-
MetaCommandStatement(
|
|
848
|
-
span=SourceSpan(source_name, file_lineno),
|
|
849
|
-
command=cmd_text,
|
|
850
|
-
),
|
|
851
|
-
)
|
|
900
|
+
# -- INCLUDE, EXECUTE SCRIPT and all other metacommands (flat) --
|
|
901
|
+
_current_body().append(metacommand_node(cmd_text, SourceSpan(source_name, file_lineno)))
|
|
852
902
|
continue
|
|
853
903
|
|
|
854
904
|
# --- SQL line ---
|
execsql/state.py
CHANGED
|
@@ -467,6 +467,29 @@ class _StateModule(types.ModuleType):
|
|
|
467
467
|
return sorted(set(super().__dir__()) | _CONTEXT_ATTRS)
|
|
468
468
|
|
|
469
469
|
|
|
470
|
+
def _context_property(name: str) -> property:
|
|
471
|
+
"""A property on :class:`_StateModule` that reads *name* from the active context.
|
|
472
|
+
|
|
473
|
+
Without it every ``_state.conf`` read failed the module's own lookup and
|
|
474
|
+
reached :meth:`_StateModule.__getattr__` through an ``AttributeError``.
|
|
475
|
+
Building that error is cheap in a script, but on Python 3.13 it checks
|
|
476
|
+
whether the module shadows a standard-library one, which takes ~12 µs
|
|
477
|
+
when ``sys.path[0]`` is ``''`` (``python -c``, stdin, the REPL,
|
|
478
|
+
notebooks). Writes and deletes still go through ``__setattr__`` and
|
|
479
|
+
``__delattr__``.
|
|
480
|
+
"""
|
|
481
|
+
|
|
482
|
+
def get(self: _StateModule) -> Any:
|
|
483
|
+
return getattr(_get_ctx(), name)
|
|
484
|
+
|
|
485
|
+
return property(get)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
for _name in _CONTEXT_ATTRS:
|
|
489
|
+
setattr(_StateModule, _name, _context_property(_name))
|
|
490
|
+
del _name
|
|
491
|
+
|
|
492
|
+
|
|
470
493
|
# ---------------------------------------------------------------------------
|
|
471
494
|
# Utility functions
|
|
472
495
|
# ---------------------------------------------------------------------------
|
execsql/utils/errors.py
CHANGED
|
@@ -121,7 +121,8 @@ def stamp_errinfo(errinfo: ErrInfo) -> ErrInfo:
|
|
|
121
121
|
errinfo.script_file = lc.source
|
|
122
122
|
errinfo.script_line_no = lc.line_no
|
|
123
123
|
if errinfo.cmd is None:
|
|
124
|
-
|
|
124
|
+
# The metacommand as written, without "-- !x!", as upstream reported it.
|
|
125
|
+
errinfo.cmd = getattr(lc.command, "statement", None)
|
|
125
126
|
errinfo.cmdtype = lc.command_type
|
|
126
127
|
return errinfo
|
|
127
128
|
|
|
@@ -129,10 +130,14 @@ def stamp_errinfo(errinfo: ErrInfo) -> ErrInfo:
|
|
|
129
130
|
def _run_deferred_script(spec: Any) -> None:
|
|
130
131
|
"""Execute a deferred ScriptExecSpec (ON ERROR_HALT / ON CANCEL_HALT EXECUTE SCRIPT).
|
|
131
132
|
|
|
132
|
-
Executes the target script natively through the AST executor.
|
|
133
|
+
Executes the target script natively through the AST executor. Its
|
|
134
|
+
arguments are substituted now, when the halt fires, so a deferred
|
|
135
|
+
variable (``!{$ERROR_MESSAGE}!``) holds the error that fired it.
|
|
133
136
|
"""
|
|
134
137
|
from execsql.script.ast import IncludeDirective, SourceSpan
|
|
135
|
-
from execsql.script.
|
|
138
|
+
from execsql.script.engine import substitute_vars
|
|
139
|
+
from execsql.script.executor import _execute_script_native, _script_args, _stack_localvars
|
|
140
|
+
from execsql.script.parser import substitute_directive
|
|
136
141
|
from execsql.state import get_context
|
|
137
142
|
|
|
138
143
|
script_id = spec.script_id.lower() if hasattr(spec, "script_id") else None
|
|
@@ -145,8 +150,16 @@ def _run_deferred_script(spec: Any) -> None:
|
|
|
145
150
|
arguments=spec.arg_exp,
|
|
146
151
|
loop_type=spec.looptype,
|
|
147
152
|
loop_condition=spec.loopcond,
|
|
153
|
+
command=(
|
|
154
|
+
f"EXECUTE SCRIPT {spec.script_id}"
|
|
155
|
+
+ (f" WITH ARGUMENTS ({spec.arg_exp})" if spec.arg_exp else "")
|
|
156
|
+
+ (f" {spec.looptype} ({spec.loopcond})" if spec.looptype else "")
|
|
157
|
+
),
|
|
148
158
|
)
|
|
149
|
-
|
|
159
|
+
localvars = _stack_localvars(ctx)
|
|
160
|
+
node = substitute_directive(node, lambda text: substitute_vars(text, localvars, ctx=ctx))
|
|
161
|
+
script_block = ctx.ast_scripts[script_id]
|
|
162
|
+
_execute_script_native(ctx, node, script_block, _script_args(node, script_block))
|
|
150
163
|
else:
|
|
151
164
|
raise ErrInfo(
|
|
152
165
|
type="error",
|
|
@@ -199,6 +212,11 @@ def exit_now(exit_status: int, errinfo: ErrInfo | None, logmsg: str | None = Non
|
|
|
199
212
|
except Exception:
|
|
200
213
|
if _state.exec_log is not None:
|
|
201
214
|
_state.exec_log.log_status_error("Failed to write the ON CANCEL_HALT WRITE message.")
|
|
215
|
+
# A halt inside BEGIN BATCH rolls the batch back: after the error is
|
|
216
|
+
# shown, in case a lost connection makes the rollback wait, and before
|
|
217
|
+
# the ON ERROR_HALT / ON CANCEL_HALT script, whose own SQL then commits.
|
|
218
|
+
if _state.status is not None:
|
|
219
|
+
_state.status.batch.abandon_all()
|
|
202
220
|
# Defer import to avoid circular dependencies
|
|
203
221
|
from execsql.utils.gui import gui_console_isrunning, gui_console_wait_user, gui_console_off
|
|
204
222
|
|
|
@@ -274,8 +292,8 @@ def write_warning(warning_msg: str, *, always: bool = False) -> None:
|
|
|
274
292
|
warning_msg: The warning text to emit.
|
|
275
293
|
always: When ``True``, always write to stderr regardless of the
|
|
276
294
|
``conf.write_warnings`` setting. Use this for structural warnings
|
|
277
|
-
(e.g. IF-level mismatch
|
|
278
|
-
|
|
295
|
+
(e.g. IF-level mismatch) that should always be visible. When
|
|
296
|
+
``False`` (default), stderr output is gated by
|
|
279
297
|
``conf.write_warnings``.
|
|
280
298
|
"""
|
|
281
299
|
if _state.exec_log is not None:
|
execsql/utils/sigterm.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
SIGTERM handling for the ``execsql`` command.
|
|
5
|
+
|
|
6
|
+
:func:`terminate` is the handler the console entry point installs: it kills
|
|
7
|
+
the commands ``SYSTEM_CMD`` is waiting on, then ends the run through
|
|
8
|
+
``SystemExit`` (exit status 143), so exit handlers run and the log is
|
|
9
|
+
written. :func:`command` starts such a command; :func:`sigterm_held` holds
|
|
10
|
+
a SIGTERM back for the few lines where raising would leave something
|
|
11
|
+
behind, and acts on it as the block ends.
|
|
12
|
+
|
|
13
|
+
Python runs signal handlers on the main thread only, between bytecodes. A
|
|
14
|
+
hold therefore counts only on the main thread: a SIGTERM raised there in the
|
|
15
|
+
middle of ``Popen()`` (which forks the command before it returns) would
|
|
16
|
+
leave the command running with nothing to stop it. The Textual console runs
|
|
17
|
+
the script on another thread, which the handler never interrupts; that
|
|
18
|
+
thread stops at its next command instead (see :func:`command`). It is a
|
|
19
|
+
daemon thread, which may never run again once the main thread exits, so the
|
|
20
|
+
handler waits for a command that thread is starting and kills it itself.
|
|
21
|
+
|
|
22
|
+
The library API (``execsql.run()``) does not install the handler, so there
|
|
23
|
+
:func:`sigterm_held` holds nothing back and :func:`command` only stops its
|
|
24
|
+
command when the block is left.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import subprocess
|
|
28
|
+
import threading
|
|
29
|
+
from collections.abc import Iterator
|
|
30
|
+
from contextlib import contextmanager
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
#: Depth of nested :func:`sigterm_held` blocks on the main thread.
|
|
34
|
+
_held = 0
|
|
35
|
+
#: The signal number of a SIGTERM that arrived while held.
|
|
36
|
+
_pending: int | None = None
|
|
37
|
+
#: The signal number of the SIGTERM that ended the run.
|
|
38
|
+
_terminated: int | None = None
|
|
39
|
+
#: Commands started by :func:`command` and not yet stopped or finished.
|
|
40
|
+
_commands: list[subprocess.Popen] = []
|
|
41
|
+
#: Held while a command is started and added to ``_commands``. Reentrant: a
|
|
42
|
+
#: second SIGTERM can land while the handler holds it.
|
|
43
|
+
_starting = threading.RLock()
|
|
44
|
+
#: How long the handler waits for a command that is starting (seconds).
|
|
45
|
+
_START_WAIT = 5.0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def terminate(signum: int, _frame: Any) -> None:
|
|
49
|
+
"""Kill the running commands, record a ``terminated`` exit and raise ``SystemExit(128 + signum)``.
|
|
50
|
+
|
|
51
|
+
Inside a :func:`sigterm_held` block the signal is only noted; the block's
|
|
52
|
+
end acts on it.
|
|
53
|
+
"""
|
|
54
|
+
global _pending
|
|
55
|
+
if _held:
|
|
56
|
+
_pending = signum
|
|
57
|
+
return
|
|
58
|
+
_exit(signum)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _exit(signum: int) -> None:
|
|
62
|
+
global _pending, _terminated
|
|
63
|
+
import execsql.state as _state
|
|
64
|
+
|
|
65
|
+
_pending = None
|
|
66
|
+
# Set first, so a thread about to start a command sees it and starts none.
|
|
67
|
+
_terminated = signum
|
|
68
|
+
# Wait for a command another thread is starting to be listed: that thread
|
|
69
|
+
# (a daemon) may never run again once this one exits. Past the wait, a
|
|
70
|
+
# command still starting is left to that thread.
|
|
71
|
+
listed = _starting.acquire(timeout=_START_WAIT)
|
|
72
|
+
try:
|
|
73
|
+
for proc in list(_commands):
|
|
74
|
+
# Kill only: the handler may have interrupted Popen.wait() on this
|
|
75
|
+
# command, which holds a lock a second wait() would block on
|
|
76
|
+
# forever. The command() block reaps it as it is left.
|
|
77
|
+
_kill(proc)
|
|
78
|
+
finally:
|
|
79
|
+
if listed:
|
|
80
|
+
_starting.release()
|
|
81
|
+
if _state.exec_log is not None:
|
|
82
|
+
_state.exec_log.log_exit_terminated()
|
|
83
|
+
raise SystemExit(128 + signum)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _kill(proc: subprocess.Popen) -> bool:
|
|
87
|
+
"""Kill *proc* unless it has finished; False if the OS refuses (a command running as another user)."""
|
|
88
|
+
try:
|
|
89
|
+
proc.kill()
|
|
90
|
+
except OSError:
|
|
91
|
+
return False
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _end_if_terminated() -> None:
|
|
96
|
+
"""Raise the run's ``SystemExit`` once a SIGTERM has ended it (for a thread the handler does not interrupt)."""
|
|
97
|
+
if _terminated is not None:
|
|
98
|
+
raise SystemExit(128 + _terminated)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@contextmanager
|
|
102
|
+
def sigterm_held() -> Iterator[None]:
|
|
103
|
+
"""Hold back a SIGTERM until the block ends, then end the run with it.
|
|
104
|
+
|
|
105
|
+
Keep the block short: a run asked to stop waits for it. An exception
|
|
106
|
+
the block raises gives way to the held SIGTERM. Off the main thread it
|
|
107
|
+
holds nothing, since the handler never interrupts that thread.
|
|
108
|
+
"""
|
|
109
|
+
global _held
|
|
110
|
+
if threading.current_thread() is not threading.main_thread():
|
|
111
|
+
yield
|
|
112
|
+
return
|
|
113
|
+
_held += 1
|
|
114
|
+
try:
|
|
115
|
+
yield
|
|
116
|
+
finally:
|
|
117
|
+
_held -= 1
|
|
118
|
+
if not _held and _pending is not None:
|
|
119
|
+
_exit(_pending)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@contextmanager
|
|
123
|
+
def command(args: list[str]) -> Iterator[subprocess.Popen]:
|
|
124
|
+
"""Start *args* as a command that is stopped when the block ends or the run is terminated.
|
|
125
|
+
|
|
126
|
+
Leaving the block, however it is left (the command finished, a timeout,
|
|
127
|
+
Ctrl-C, a SIGTERM), kills the command if it is still running and reaps
|
|
128
|
+
it. A SIGTERM at any point after the command starts kills it before the
|
|
129
|
+
run's ``SystemExit`` is raised. On a thread other than the main one,
|
|
130
|
+
which the handler does not interrupt, the block raises that
|
|
131
|
+
``SystemExit`` itself once the SIGTERM has ended the run, and no command
|
|
132
|
+
starts after it.
|
|
133
|
+
"""
|
|
134
|
+
proc = None
|
|
135
|
+
try:
|
|
136
|
+
with sigterm_held(), _starting:
|
|
137
|
+
_end_if_terminated()
|
|
138
|
+
proc = subprocess.Popen(args)
|
|
139
|
+
_commands.append(proc)
|
|
140
|
+
# The handler killed the command if it ended the run while it started.
|
|
141
|
+
_end_if_terminated()
|
|
142
|
+
yield proc
|
|
143
|
+
finally:
|
|
144
|
+
if proc is not None:
|
|
145
|
+
with sigterm_held():
|
|
146
|
+
# Killed before it is unlisted, so the handler kills it if this
|
|
147
|
+
# thread never gets that far.
|
|
148
|
+
killed = _kill(proc)
|
|
149
|
+
# Ctrl-C can land between Popen() returning and the append.
|
|
150
|
+
if proc in _commands:
|
|
151
|
+
_commands.remove(proc)
|
|
152
|
+
if killed:
|
|
153
|
+
proc.wait()
|
|
154
|
+
_end_if_terminated()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.37.0
|
|
4
4
|
Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
|
|
5
5
|
Project-URL: Homepage, https://execsql2.readthedocs.io/en/stable/
|
|
6
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
@@ -441,7 +441,7 @@ execsql format --no-sql --in-place scripts/
|
|
|
441
441
|
```yaml
|
|
442
442
|
repos:
|
|
443
443
|
- repo: https://github.com/geocoug/execsql
|
|
444
|
-
rev: v2.
|
|
444
|
+
rev: v2.37.0
|
|
445
445
|
hooks:
|
|
446
446
|
- id: execsql-format
|
|
447
447
|
- id: execsql-lint
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
execsql/__init__.py,sha256=ZeVnpEWu8M2bn3qLmVM72NhWRueUGNbsX8UAPTermo0,901
|
|
2
2
|
execsql/__main__.py,sha256=Nnh7_QgjYTTzd1Kog2thl7wzNpoxfnZ3YaurSOMzE_U,270
|
|
3
|
-
execsql/api.py,sha256=
|
|
4
|
-
execsql/config.py,sha256=
|
|
3
|
+
execsql/api.py,sha256=ElUu7NOZLbpmSTQOCP0ePeyiSYQhGAAZHMS_3p2zlU0,27349
|
|
4
|
+
execsql/config.py,sha256=wTtugbLNplxgva4P8uD8jKYs8N0MywHPTvjwHTsYFDU,40528
|
|
5
5
|
execsql/config_validation.py,sha256=a4ZszyJFGTNgs1jkvXE3yMCaYFtF1Uqih2ojMDVh1v8,10556
|
|
6
6
|
execsql/exceptions.py,sha256=aHavdblmP7m5ZLxyyKKmPSwwbdmP962j4ShP1Gd9r64,9651
|
|
7
7
|
execsql/format.py,sha256=z-KZ8VY1muz8S6iaErZX9E_7wE_VtjEcUB7e_KyhfyI,59565
|
|
8
8
|
execsql/interactive.py,sha256=7vm7NAlcarBJJOIffcLOcJqH9ZCInvHB_CmC2sQsXf8,8241
|
|
9
9
|
execsql/manifest.py,sha256=eJp39R8bYuFuJiGaBbDsk_lua13uJHXPOdwWdUfE-CQ,9027
|
|
10
|
-
execsql/models.py,sha256=
|
|
10
|
+
execsql/models.py,sha256=8AnxILNwFZjDX8xXvv5usgOcTDsOIwkfZJjeYbXREe8,18374
|
|
11
11
|
execsql/parser.py,sha256=bw26e1lD-PZscV11VtBdqSLkjpcYfNLQC2EYfYD5UwU,16012
|
|
12
12
|
execsql/plugins.py,sha256=RykMlIjTyWcnQc9ygjLErS3wdcsZSsG9QTIvg4DwfAI,13626
|
|
13
13
|
execsql/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
execsql/state.py,sha256=
|
|
14
|
+
execsql/state.py,sha256=oAtaiX9fD0y7cwO6Is9mM9MjaSUe-AmnjuYJ3g3r3kA,24294
|
|
15
15
|
execsql/state.pyi,sha256=ZDHZ1DPKYu4Qs4bcJT31XNv4GOt6O4s_zH4UHCowovw,4344
|
|
16
16
|
execsql/types.py,sha256=HiIhc8wqm9Trt8WYP40VU93uuBSPo1uPP3z-sMsUI1o,33719
|
|
17
17
|
execsql/cli/__init__.py,sha256=FHg2lm2gt4L-XBnlVORn3qWHZ4739Ao-uwBIURfVIjg,2798
|
|
@@ -19,9 +19,9 @@ execsql/cli/application.py,sha256=tsSYE8cerJxY6y73GM_Xr1WBwc6A0_al8y969B6lPP8,12
|
|
|
19
19
|
execsql/cli/dispatch.py,sha256=deyN2QQYvdzMSqGGa3CSMpaqdLNeMgc5Uke0mUbl764,2704
|
|
20
20
|
execsql/cli/dsn.py,sha256=Jqr0CBtj6L3BY25Ej-U13xHV85phX1TFTVpjB7A-KIs,4815
|
|
21
21
|
execsql/cli/help.py,sha256=bEy6ssVjyIw73JD_UPLXnCtnCifdVCKJr_cydAAVxhk,8220
|
|
22
|
-
execsql/cli/lint.py,sha256=
|
|
22
|
+
execsql/cli/lint.py,sha256=5rNSVbHbuhMo5xvPaNq0tGIGl7i0xNou6Sb8ZfnLNKc,50262
|
|
23
23
|
execsql/cli/options.py,sha256=Srhf1gEOB5yy_7PxkTKBv07xK8Qzw8H-z7PUUnItpwE,2916
|
|
24
|
-
execsql/cli/run.py,sha256=
|
|
24
|
+
execsql/cli/run.py,sha256=L1NdYlEKsy7E5-B67C9UqH3cJRstT9HUiAtUW6Qw828,42845
|
|
25
25
|
execsql/cli/commands/__init__.py,sha256=FVe_xF7w98krP-USxqf-BIcflA1ctMYVsV8p0i6cChA,572
|
|
26
26
|
execsql/cli/commands/config.py,sha256=V7CGGkhSCqeJIlAXxAYyG_xNhnsORiwqth2XWEVPzss,8213
|
|
27
27
|
execsql/cli/commands/format.py,sha256=3awfNmCc6kImeyXKZo1Jt1qXbEuWk86ZllqEOY5vxzw,4456
|
|
@@ -31,13 +31,13 @@ execsql/cli/commands/listing.py,sha256=thtkJ-ILGd0SPs57oVIMkkCGpXsfS0hQxueMc2ccR
|
|
|
31
31
|
execsql/cli/commands/lsp.py,sha256=8xIrqA4dnapdlLqbGGb53YtFTnsX2YqpLYvrrl-Z6pE,4051
|
|
32
32
|
execsql/cli/commands/run.py,sha256=sGFDku66CazJrorr0mmrhUI3QDvQVSMEYlU8G1Pd5fQ,16492
|
|
33
33
|
execsql/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
-
execsql/data/execsql.conf.template,sha256=
|
|
34
|
+
execsql/data/execsql.conf.template,sha256=kEUUg1x2TcJ356Zl-AvHqItAZzcG3hBBa8-pmqdSn5U,13292
|
|
35
35
|
execsql/data/script_header.sql,sha256=a7WKScekNgJoxVEmMRTkWv8NsAzW1jJ4vJfGy9HVy0g,349
|
|
36
36
|
execsql/db/__init__.py,sha256=jTbuafuKOqYtXFR1wvCOoKK5Lr3l1uErfaIbIr6UywI,1063
|
|
37
37
|
execsql/db/access.py,sha256=fxiC3rtoOJzrNXNxau-w7oh1fd9tyh2_PMliBbFc164,21427
|
|
38
|
-
execsql/db/base.py,sha256=
|
|
38
|
+
execsql/db/base.py,sha256=BoCOp0hBa50jpCkyQ6W6GK_u9pMV5D0FuHx-vWzjFZ4,48090
|
|
39
39
|
execsql/db/dsn.py,sha256=Evrc6MUllJVFDn0jZB_iks-aKCsXuNF9XYjAkttqgGQ,6995
|
|
40
|
-
execsql/db/duckdb.py,sha256=
|
|
40
|
+
execsql/db/duckdb.py,sha256=QmIPVOVgltC-jCwQlu8LllkrQjhJ7gjjyNOXeKQNAHg,14262
|
|
41
41
|
execsql/db/factory.py,sha256=Uz7Xcbff6ZZbtLM55FhHCwGdo4KKfqU_1kyjvOHn-HY,5691
|
|
42
42
|
execsql/db/firebird.py,sha256=CoNnzFtOFAkp8OJByXXXclitCPAAjM23eFJGfy3C7IE,10171
|
|
43
43
|
execsql/db/mysql.py,sha256=X-brnIFiQaOwVbY0uqPrC4esclhBv4sg-gHW3EVxHNw,26923
|
|
@@ -49,7 +49,7 @@ execsql/db/sqlserver.py,sha256=WIVvTp1MNKaUZn912U7ErcjSqhaLTqnZ2hfv_Gcso74,11601
|
|
|
49
49
|
execsql/db/target_types.py,sha256=68nSyYY0mj6sgS26zeOYcFuUJA_ik08-0BeNsSM7UrE,8563
|
|
50
50
|
execsql/db/tiers.py,sha256=PH2sUYwlYqcANGVKXprWQKVIlM6fpWU9q74djn8oFwc,2924
|
|
51
51
|
execsql/debug/__init__.py,sha256=j6EGUR0dHzUhWN1mHHtf1-Lhjq3Sb1V-vmnq2Ztgj1M,178
|
|
52
|
-
execsql/debug/repl.py,sha256=
|
|
52
|
+
execsql/debug/repl.py,sha256=NE3HfMCcDIlGsL_qNsKUfU_B_ZInjqGm-z89aZB2sAA,20785
|
|
53
53
|
execsql/exporters/__init__.py,sha256=-Cnji-OgodJV8ftcDcOyTof0kQMy9J5kKVC8GVFpc3o,670
|
|
54
54
|
execsql/exporters/base.py,sha256=_GoO0YqwlTxrjogxHI3CxxRR64sMgIEjRogt0KCqzoY,6402
|
|
55
55
|
execsql/exporters/delimited.py,sha256=N0cP_CNlb_LLCUqbHmTQsOGufWnD_ddGdeFnXPE8Erc,35606
|
|
@@ -99,59 +99,60 @@ execsql/lsp/rename.py,sha256=fYk1gDSWWhqyoIGZO5hVIHPYYdGKNjgZS0mijFDFpac,3756
|
|
|
99
99
|
execsql/lsp/server.py,sha256=gTjk1agZbygdbcowa3jkL45WJ37sxhJ58oiLB7BM6Bw,19893
|
|
100
100
|
execsql/metacommands/__init__.py,sha256=aMf-3IRjMpNFSUI-IMAcvL-kXZHq5qF6W_p3UawdQwM,13115
|
|
101
101
|
execsql/metacommands/conditions.py,sha256=4DsMXr6vnfbtcKqgnxYV4UK4NmAfE_hpfgkFjlH0bjo,29844
|
|
102
|
-
execsql/metacommands/connect.py,sha256=
|
|
102
|
+
execsql/metacommands/connect.py,sha256=rXUd78La8JeP0bqDgEPtEKPhH95RAtQGNcXSurXaqBo,15636
|
|
103
103
|
execsql/metacommands/control.py,sha256=9swW6X8jXDUpu5cHiAnQVx1ahFyWxlx7mjg4dqYfX5Y,9355
|
|
104
|
-
execsql/metacommands/data.py,sha256=
|
|
104
|
+
execsql/metacommands/data.py,sha256=9zxyg3_kV4jRuagUNtSlhhlvH2TiPP0gdNAaXBXdhI0,12493
|
|
105
105
|
execsql/metacommands/debug.py,sha256=mKRT7uLwnw34Xl46fNDI_zH4MsPpZyihoLlonapbXuU,12981
|
|
106
|
-
execsql/metacommands/dispatch.py,sha256=
|
|
106
|
+
execsql/metacommands/dispatch.py,sha256=m7p2L_HtooKfSv58MHbDmYJJ7Xe1UEpr6mnHdvABViI,87655
|
|
107
107
|
execsql/metacommands/effects.py,sha256=cRSo48R4nRJzkRR3OGHLhJzofcdAaNIobaxmn1N9TGw,3813
|
|
108
108
|
execsql/metacommands/io.py,sha256=vlGBje5sgnqeilooMdhJDgSRIhysHy5_7LrKtik9Xjs,3011
|
|
109
109
|
execsql/metacommands/io_export.py,sha256=_1o5T3Ejqq_0N1hYwaRPgDktn8nZT8sQLMyXPRgXEec,14587
|
|
110
|
-
execsql/metacommands/io_fileops.py,sha256=
|
|
110
|
+
execsql/metacommands/io_fileops.py,sha256=VlBscDcJ9RBShwmk9Jky9MWZbM6lA24_G4oymlehK-A,13550
|
|
111
111
|
execsql/metacommands/io_import.py,sha256=gJICeK1MOfG6D73I7m8zBCalvr_ZJzD-QBKOpKYeYXo,15684
|
|
112
|
-
execsql/metacommands/io_write.py,sha256=
|
|
112
|
+
execsql/metacommands/io_write.py,sha256=hKnDUB6jYQc38Y8Ou_1e8tuESnDiSf6e2iAN7FVtDSY,12889
|
|
113
113
|
execsql/metacommands/prompt.py,sha256=PlXhtBYCembG_1SSCrwpQR10mGF3_jXodu4aCSHlSCM,37950
|
|
114
|
-
execsql/metacommands/reference.py,sha256=
|
|
115
|
-
execsql/metacommands/script_ext.py,sha256=
|
|
116
|
-
execsql/metacommands/system.py,sha256=
|
|
114
|
+
execsql/metacommands/reference.py,sha256=9Q7DSiPEjXyD9HQkkqiToRN59TrZ8_6Gz9VVVv9WSjw,54369
|
|
115
|
+
execsql/metacommands/script_ext.py,sha256=dtEjvl_gjPeAmsGZdp1evqGuz8lErKsuajMFGuPsEbE,3917
|
|
116
|
+
execsql/metacommands/system.py,sha256=0noc1u-KlO6gE0KqJDScDeX0gIlG5ciaJplxOt-EDyo,8517
|
|
117
117
|
execsql/metacommands/upsert.py,sha256=hcYGonmh2sdijbqwNynn1-iajMnLp7x5vbRbtfjaf8c,21461
|
|
118
118
|
execsql/script/__init__.py,sha256=I7hVO6wm3iOocxRRGEcFHhjdtL1moKbUYLB-wKbjPdk,3267
|
|
119
|
-
execsql/script/ast.py,sha256=
|
|
120
|
-
execsql/script/control.py,sha256
|
|
119
|
+
execsql/script/ast.py,sha256=KexrVh6dE4nHCB5CbC9odiPdyWw4rnUqD8bOsYdyNXg,21417
|
|
120
|
+
execsql/script/control.py,sha256=-wyfeVR--Kst9hU3rmRkoQ-cPU4g23kCkod4r0vRKxE,4762
|
|
121
121
|
execsql/script/engine.py,sha256=h1pUSXGqwJASLJizqzd9LBMn_U5Vuqc1UbaZk1xUinw,18468
|
|
122
|
-
execsql/script/executor.py,sha256=
|
|
123
|
-
execsql/script/parser.py,sha256=
|
|
122
|
+
execsql/script/executor.py,sha256=TRRpycxyGch179imtiiUiWmCl8X3uWham_bG0fnvYjE,46799
|
|
123
|
+
execsql/script/parser.py,sha256=wogBw31kPddNox3fz5KPS-y3He163h_T-ES2ZfZsrCI,39695
|
|
124
124
|
execsql/script/variables.py,sha256=XHRnViFp0zPy-skGpYbpckkYAcczqpw1lqTVLR9eZvo,17735
|
|
125
125
|
execsql/utils/__init__.py,sha256=0uR6JwVJQRX3vceByNBduCAf5dd5assKjeqJUWvpZoA,278
|
|
126
126
|
execsql/utils/auth.py,sha256=0LYKdNMhodq9R0B8WyKp69a-xlP9wOR6Ua9Lgo2aJnQ,14868
|
|
127
127
|
execsql/utils/color.py,sha256=goQqhnLg8yERpFolQikA9zYSVcuAm0unKTJiWTbQm-U,634
|
|
128
128
|
execsql/utils/crypto.py,sha256=KlT6lPk-v0-qQQfAMfWvT9Q6kRdCD3_5JVaLajNocKM,3051
|
|
129
129
|
execsql/utils/datetime.py,sha256=9ZizCFBYut5gC4gU3Cmegfddv1HLoU5xVQWm9thy5EI,6127
|
|
130
|
-
execsql/utils/errors.py,sha256=
|
|
130
|
+
execsql/utils/errors.py,sha256=NspFIQXAcv3AWtXhcqV8K8r9kCeywW9whIj1uXiUXxc,12770
|
|
131
131
|
execsql/utils/fileio.py,sha256=IqIEhun9aOnr1oBCWK2z2VAPVo95bZ54cVglLW4rV9M,48208
|
|
132
132
|
execsql/utils/gui.py,sha256=1iiupP9AsLdR6ST0gcLE143o6y6YGUFlNv_pdVPxfCE,23943
|
|
133
133
|
execsql/utils/mail.py,sha256=4RiKtXa_sqij_YGdYmjQ3PG0u3G9qjs-9caQwBErtBw,7278
|
|
134
134
|
execsql/utils/numeric.py,sha256=_7bi5R6pHjMCkOPne_Sh5nUj-B8e9g6WFmjptFHaunA,1573
|
|
135
135
|
execsql/utils/paths.py,sha256=7IIHT6vqXTfs6Ltfrzm_I2ZV4pU8IuedOLkHo6a1_K8,1175
|
|
136
136
|
execsql/utils/regex.py,sha256=DGzPBl0Kt4--RbwPhoZI6Bm6N_CBUvI0DbqE4EF6qp8,3718
|
|
137
|
+
execsql/utils/sigterm.py,sha256=_oK-Hj8vHiKTgR04mNud7BMX90Fb1TDHb9eT7f-auAI,5842
|
|
137
138
|
execsql/utils/strings.py,sha256=UQNjpRCEFa1UO6feU-M-9e24wWAvizs_iu_4fFusLxo,8516
|
|
138
139
|
execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
|
|
139
|
-
execsql2-2.
|
|
140
|
-
execsql2-2.
|
|
141
|
-
execsql2-2.
|
|
142
|
-
execsql2-2.
|
|
143
|
-
execsql2-2.
|
|
144
|
-
execsql2-2.
|
|
145
|
-
execsql2-2.
|
|
146
|
-
execsql2-2.
|
|
147
|
-
execsql2-2.
|
|
148
|
-
execsql2-2.
|
|
149
|
-
execsql2-2.
|
|
150
|
-
execsql2-2.
|
|
151
|
-
execsql2-2.
|
|
152
|
-
execsql2-2.
|
|
153
|
-
execsql2-2.
|
|
154
|
-
execsql2-2.
|
|
155
|
-
execsql2-2.
|
|
156
|
-
execsql2-2.
|
|
157
|
-
execsql2-2.
|
|
140
|
+
execsql2-2.37.0.data/data/execsql2_extras/README.md,sha256=6f5bWiBlKRn8s6GVrU423-HFrU1Jwbk2AabxraLu3AY,5398
|
|
141
|
+
execsql2-2.37.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
|
|
142
|
+
execsql2-2.37.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
|
|
143
|
+
execsql2-2.37.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
|
|
144
|
+
execsql2-2.37.0.data/data/execsql2_extras/md_compare.sql,sha256=ARYEcOlxWLxfw-KCKDeJRdCqnuCb9PVwGaHtITR2sYU,26535
|
|
145
|
+
execsql2-2.37.0.data/data/execsql2_extras/md_glossary.sql,sha256=gu2PDTm295YvxR7cDp25Mf-xXHly0BvlMudoNOrep5Q,10931
|
|
146
|
+
execsql2-2.37.0.data/data/execsql2_extras/md_upsert.sql,sha256=YNUTFS6yB4bZXs-wRDn6dN8jL5wIumzZyE2uo5arIos,113407
|
|
147
|
+
execsql2-2.37.0.data/data/execsql2_extras/pg_compare.sql,sha256=WcupGxQoWtMwHVPuy42xCpP4gAV4Ttb1W-_hwh-3xb4,24529
|
|
148
|
+
execsql2-2.37.0.data/data/execsql2_extras/pg_glossary.sql,sha256=CfQz2HZj2nMvJfYfQrAmNYdx0uRrOIWsCXwVo5gKv_k,9953
|
|
149
|
+
execsql2-2.37.0.data/data/execsql2_extras/script_template.sql,sha256=s5i0HULxp49N2Cpd24iALleM_dBq5oLuN3Arx_4Z7-o,11140
|
|
150
|
+
execsql2-2.37.0.data/data/execsql2_extras/ss_compare.sql,sha256=PpU-vPyqqC1faBoo5zwVZlZHYKkT79QZisWVlk4o-ss,26415
|
|
151
|
+
execsql2-2.37.0.data/data/execsql2_extras/ss_glossary.sql,sha256=_oTiZHpadPXWrNs8VoRe9PZgX9XdK490NMVWVTVtjl4,13110
|
|
152
|
+
execsql2-2.37.0.data/data/execsql2_extras/ss_upsert.sql,sha256=Dvst877h4KHn8ayYF9EXI_OxqLQ89wyfcnGYYT4szro,118304
|
|
153
|
+
execsql2-2.37.0.dist-info/METADATA,sha256=JgJFZpUBKXM92MDCnvSW34G7J9P5U0_A77eqr-fGNsI,26939
|
|
154
|
+
execsql2-2.37.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
155
|
+
execsql2-2.37.0.dist-info/entry_points.txt,sha256=jOf53-4IVGjEZeNYUOF1GGaiPNfVZcNKxP3vDFKEyMA,58
|
|
156
|
+
execsql2-2.37.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
|
|
157
|
+
execsql2-2.37.0.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
|
|
158
|
+
execsql2-2.37.0.dist-info/RECORD,,
|