execsql2 2.21.2__py3-none-any.whl → 2.22.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 +21 -4
- execsql/cli/run.py +37 -17
- execsql/config.py +37 -22
- execsql/data/execsql.conf.template +6 -0
- execsql/db/access.py +6 -5
- execsql/db/base.py +23 -5
- execsql/db/dsn.py +2 -0
- execsql/db/duckdb.py +1 -1
- execsql/db/mysql.py +6 -8
- execsql/db/sqlite.py +1 -1
- execsql/exceptions.py +14 -11
- execsql/exporters/base.py +1 -1
- execsql/exporters/delimited.py +22 -16
- execsql/exporters/duckdb.py +6 -1
- execsql/exporters/feather.py +1 -1
- execsql/exporters/json.py +5 -3
- execsql/exporters/latex.py +2 -2
- execsql/exporters/markdown.py +1 -0
- execsql/exporters/ods.py +7 -3
- execsql/exporters/raw.py +8 -8
- execsql/exporters/sqlite.py +11 -1
- execsql/exporters/templates.py +2 -1
- execsql/exporters/xls.py +10 -10
- execsql/exporters/xml.py +1 -0
- execsql/exporters/yaml.py +1 -0
- execsql/exporters/zip.py +9 -7
- execsql/format.py +6 -6
- execsql/gui/console.py +3 -3
- execsql/gui/desktop.py +28 -10
- execsql/gui/tui.py +8 -6
- execsql/importers/base.py +14 -8
- execsql/importers/csv.py +8 -3
- execsql/importers/xls.py +1 -0
- execsql/metacommands/conditions.py +14 -14
- execsql/metacommands/connect.py +2 -2
- execsql/metacommands/control.py +1 -1
- execsql/metacommands/data.py +2 -1
- execsql/metacommands/io_export.py +4 -0
- execsql/metacommands/io_fileops.py +85 -39
- execsql/metacommands/io_import.py +1 -0
- execsql/metacommands/io_write.py +1 -1
- execsql/metacommands/prompt.py +24 -22
- execsql/metacommands/system.py +14 -1
- execsql/models.py +56 -17
- execsql/parser.py +8 -8
- execsql/plugins.py +1 -1
- execsql/script/engine.py +4 -4
- execsql/script/executor.py +67 -28
- execsql/script/parser.py +13 -12
- execsql/script/variables.py +4 -2
- execsql/state.pyi +122 -0
- execsql/types.py +31 -26
- execsql/utils/auth.py +6 -2
- execsql/utils/datetime.py +3 -3
- execsql/utils/errors.py +7 -5
- execsql/utils/fileio.py +77 -31
- execsql/utils/gui.py +15 -11
- execsql/utils/mail.py +9 -2
- execsql/utils/numeric.py +1 -1
- execsql/utils/regex.py +9 -9
- {execsql2-2.21.2.dist-info → execsql2-2.22.0.dist-info}/METADATA +3 -3
- execsql2-2.22.0.dist-info/RECORD +123 -0
- execsql2-2.21.2.dist-info/RECORD +0 -122
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.21.2.data → execsql2-2.22.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.21.2.dist-info → execsql2-2.22.0.dist-info}/WHEEL +0 -0
- {execsql2-2.21.2.dist-info → execsql2-2.22.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.21.2.dist-info → execsql2-2.22.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.21.2.dist-info → execsql2-2.22.0.dist-info}/licenses/NOTICE +0 -0
execsql/utils/fileio.py
CHANGED
|
@@ -27,13 +27,15 @@ import io
|
|
|
27
27
|
import multiprocessing
|
|
28
28
|
import os
|
|
29
29
|
import queue
|
|
30
|
+
import re
|
|
30
31
|
from pathlib import Path
|
|
31
32
|
import stat
|
|
32
33
|
import sys
|
|
33
34
|
import tempfile
|
|
34
35
|
import time
|
|
36
|
+
from collections.abc import Callable
|
|
35
37
|
from encodings.aliases import aliases as codec_dict
|
|
36
|
-
from typing import Any
|
|
38
|
+
from typing import Any, cast
|
|
37
39
|
|
|
38
40
|
from execsql.exceptions import ErrInfo
|
|
39
41
|
|
|
@@ -207,11 +209,11 @@ class FileWriter(multiprocessing.Process):
|
|
|
207
209
|
self.open_timeout = open_timeout
|
|
208
210
|
self.encoding = encoding
|
|
209
211
|
self.openmode = "a"
|
|
210
|
-
self.handle = None
|
|
212
|
+
self.handle: io.TextIOWrapper | None = None
|
|
211
213
|
self.status = self.STATUS_UNOPENED
|
|
212
|
-
self.open_start_time = None
|
|
214
|
+
self.open_start_time: float | None = None
|
|
213
215
|
self.fail_message_written = False
|
|
214
|
-
self.output_queue: collections.deque = collections.deque()
|
|
216
|
+
self.output_queue: collections.deque[str] = collections.deque()
|
|
215
217
|
self.close_after_write = False
|
|
216
218
|
|
|
217
219
|
def __del__(self) -> None:
|
|
@@ -221,6 +223,7 @@ class FileWriter(multiprocessing.Process):
|
|
|
221
223
|
pass # Best-effort cleanup at interpreter shutdown.
|
|
222
224
|
|
|
223
225
|
def write_queue(self) -> None:
|
|
226
|
+
assert self.handle is not None
|
|
224
227
|
while len(self.output_queue) > 0:
|
|
225
228
|
m = self.output_queue.pop()
|
|
226
229
|
self.handle.write(m)
|
|
@@ -238,11 +241,14 @@ class FileWriter(multiprocessing.Process):
|
|
|
238
241
|
self.open_start_time = time.time()
|
|
239
242
|
if time.time() - self.open_start_time < self.open_timeout:
|
|
240
243
|
try:
|
|
241
|
-
self.handle =
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
self.handle = cast(
|
|
245
|
+
io.TextIOWrapper,
|
|
246
|
+
open( # noqa: SIM115
|
|
247
|
+
file=self.filename,
|
|
248
|
+
mode=self.openmode,
|
|
249
|
+
encoding=self.encoding,
|
|
250
|
+
errors="backslashreplace",
|
|
251
|
+
),
|
|
246
252
|
)
|
|
247
253
|
except Exception:
|
|
248
254
|
self.status = self.STATUS_WAITING
|
|
@@ -298,10 +304,10 @@ class FileWriter(multiprocessing.Process):
|
|
|
298
304
|
self.return_msg_queue = return_msg_queue
|
|
299
305
|
self.file_encoding = file_encoding
|
|
300
306
|
self.open_timeout = open_timeout
|
|
301
|
-
self.files: dict = {}
|
|
307
|
+
self.files: dict[str, FileWriter.FileControl] = {}
|
|
302
308
|
self.active = True
|
|
303
309
|
# Functions in execvec must be in the same order as the CMD enums.
|
|
304
|
-
self.execvec = (
|
|
310
|
+
self.execvec: tuple[Callable[..., None], ...] = (
|
|
305
311
|
self.write,
|
|
306
312
|
self.close_if_open,
|
|
307
313
|
self.close_all,
|
|
@@ -424,7 +430,7 @@ def filewriter_filestatus(filename: str) -> int:
|
|
|
424
430
|
if not _writer_alive():
|
|
425
431
|
return FileWriter.FileControl.STATUS_CLOSED
|
|
426
432
|
fw_input.put((FileWriter.CMD_GET_STATUS, (filename,)))
|
|
427
|
-
return fw_output.get()
|
|
433
|
+
return cast(int, fw_output.get())
|
|
428
434
|
|
|
429
435
|
|
|
430
436
|
def filewriter_write(filename: str, message: str) -> None:
|
|
@@ -527,19 +533,23 @@ class EncodedFile:
|
|
|
527
533
|
|
|
528
534
|
if Path(filename).exists():
|
|
529
535
|
self.encoding, self.bom_length = detect_by_bom(filename, file_encoding)
|
|
530
|
-
self.fo = None
|
|
536
|
+
self.fo: io.TextIOWrapper | None = None
|
|
531
537
|
|
|
532
538
|
def open(self, mode: str = "r") -> io.TextIOWrapper:
|
|
533
539
|
import execsql.state as _state
|
|
534
540
|
|
|
535
541
|
conf = _state.conf
|
|
536
|
-
self.fo =
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
+
self.fo = cast(
|
|
543
|
+
io.TextIOWrapper,
|
|
544
|
+
open( # noqa: SIM115
|
|
545
|
+
file=self.filename,
|
|
546
|
+
mode=mode,
|
|
547
|
+
encoding=self.encoding,
|
|
548
|
+
errors=conf.enc_err_disposition,
|
|
549
|
+
newline=None,
|
|
550
|
+
),
|
|
542
551
|
)
|
|
552
|
+
assert self.fo is not None
|
|
543
553
|
return self.fo
|
|
544
554
|
|
|
545
555
|
def close(self) -> None:
|
|
@@ -557,6 +567,7 @@ class EncodedFile:
|
|
|
557
567
|
# before entering the ``with`` block.
|
|
558
568
|
if self.fo is None:
|
|
559
569
|
self.open("r")
|
|
570
|
+
assert self.fo is not None
|
|
560
571
|
return self.fo
|
|
561
572
|
|
|
562
573
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
@@ -565,7 +576,15 @@ class EncodedFile:
|
|
|
565
576
|
|
|
566
577
|
class Logger:
|
|
567
578
|
# A custom logger for execsql that writes several different types of messages to a log file.
|
|
568
|
-
log_file = None
|
|
579
|
+
log_file: io.TextIOWrapper | None = None
|
|
580
|
+
_COMMON_SECRET_PATTERNS = (
|
|
581
|
+
re.compile(r"(?i)(://[^/\s:@]+:)([^@\s]+)(@)"),
|
|
582
|
+
re.compile(r"(?i)\b(password|passwd|token|secret|api[_-]?key|private[_-]?key|webhook)(\s*[=:]\s*)([^\s,;]+)"),
|
|
583
|
+
re.compile(r"\bsk-(?:live|test)-[A-Za-z0-9_-]+\b"),
|
|
584
|
+
re.compile(r"\bAKIA[A-Z0-9]{12,}\b"),
|
|
585
|
+
re.compile(r"\bgh[pousr]_[A-Za-z0-9_]{20,}\b"),
|
|
586
|
+
re.compile(r"\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b"),
|
|
587
|
+
)
|
|
569
588
|
|
|
570
589
|
def __repr__(self) -> str:
|
|
571
590
|
return f"Logger({self.script_file_name!r}, {self.db_name!r}, {self.server_name!r}, {self.cmdline_options!r}, {self.log_file_name!r})"
|
|
@@ -587,6 +606,7 @@ class Logger:
|
|
|
587
606
|
self.db_name = db_name
|
|
588
607
|
self.server_name = server_name
|
|
589
608
|
self.cmdline_options = cmdline_options
|
|
609
|
+
self._redaction_values: list[str] = []
|
|
590
610
|
if log_file_name:
|
|
591
611
|
self.log_file_name = log_file_name
|
|
592
612
|
else:
|
|
@@ -630,6 +650,9 @@ class Logger:
|
|
|
630
650
|
self.run_start = _now
|
|
631
651
|
self.run_id = _now.strftime("%Y%m%d_%H%M_%S_") + f"{_now.microsecond // 1000:03d}"
|
|
632
652
|
self.user = getpass.getuser()
|
|
653
|
+
sz: int
|
|
654
|
+
dt: str
|
|
655
|
+
abs_script: str
|
|
633
656
|
if script_file_name and Path(script_file_name).is_file():
|
|
634
657
|
sz, dt = file_size_date(script_file_name)
|
|
635
658
|
abs_script = str(Path(script_file_name).resolve())
|
|
@@ -653,9 +676,9 @@ class Logger:
|
|
|
653
676
|
self.seq_no = 0
|
|
654
677
|
atexit.register(self.close)
|
|
655
678
|
self.exit_type = "unknown"
|
|
656
|
-
self.exit_scriptfile = None
|
|
657
|
-
self.exit_lno = None
|
|
658
|
-
self.exit_description = None
|
|
679
|
+
self.exit_scriptfile: str | None = None
|
|
680
|
+
self.exit_lno: int | None = None
|
|
681
|
+
self.exit_description: str | None = None
|
|
659
682
|
atexit.register(self.log_exit)
|
|
660
683
|
|
|
661
684
|
def _ts(self) -> str:
|
|
@@ -663,6 +686,29 @@ class Logger:
|
|
|
663
686
|
|
|
664
687
|
return _datetime.datetime.now().isoformat(timespec="seconds")
|
|
665
688
|
|
|
689
|
+
def add_redaction_value(self, value: str | None) -> None:
|
|
690
|
+
if value is None:
|
|
691
|
+
return
|
|
692
|
+
value = str(value)
|
|
693
|
+
if not value or value in self._redaction_values:
|
|
694
|
+
return
|
|
695
|
+
self._redaction_values.append(value)
|
|
696
|
+
|
|
697
|
+
def _redact(self, msg: str | None) -> str | None:
|
|
698
|
+
if msg is None:
|
|
699
|
+
return None
|
|
700
|
+
redacted = str(msg)
|
|
701
|
+
for value in sorted(self._redaction_values, key=len, reverse=True):
|
|
702
|
+
redacted = redacted.replace(value, "***")
|
|
703
|
+
for pattern in self._COMMON_SECRET_PATTERNS:
|
|
704
|
+
if pattern.pattern.startswith("(?i)(://"):
|
|
705
|
+
redacted = pattern.sub(r"\1***\3", redacted)
|
|
706
|
+
elif "password|passwd|token" in pattern.pattern:
|
|
707
|
+
redacted = pattern.sub(r"\1\2***", redacted)
|
|
708
|
+
else:
|
|
709
|
+
redacted = pattern.sub("***", redacted)
|
|
710
|
+
return redacted
|
|
711
|
+
|
|
666
712
|
def _rotate_if_needed(self) -> None:
|
|
667
713
|
try:
|
|
668
714
|
import execsql.state as _state
|
|
@@ -701,32 +747,32 @@ class Logger:
|
|
|
701
747
|
self.writelog(msg)
|
|
702
748
|
|
|
703
749
|
def log_status_exception(self, msg: str | None) -> None:
|
|
704
|
-
msg = None if not msg else msg.replace("\n", "")
|
|
750
|
+
msg = self._redact(None if not msg else msg.replace("\n", ""))
|
|
705
751
|
self.seq_no += 1
|
|
706
752
|
wmsg = f"status\t{self.run_id}\t{self.seq_no}\t{self._ts()}\texception\t{msg or ''}\n"
|
|
707
753
|
self.writelog(wmsg)
|
|
708
754
|
|
|
709
755
|
def log_status_error(self, msg: str | None) -> None:
|
|
710
|
-
msg = None if not msg else msg.replace("\n", "")
|
|
756
|
+
msg = self._redact(None if not msg else msg.replace("\n", ""))
|
|
711
757
|
self.seq_no += 1
|
|
712
758
|
wmsg = f"status\t{self.run_id}\t{self.seq_no}\t{self._ts()}\terror\t{msg or ''}\n"
|
|
713
759
|
self.writelog(wmsg)
|
|
714
760
|
|
|
715
761
|
def log_status_info(self, msg: str | None) -> None:
|
|
716
|
-
msg = None if not msg else msg.replace("\n", "")
|
|
762
|
+
msg = self._redact(None if not msg else msg.replace("\n", ""))
|
|
717
763
|
self.seq_no += 1
|
|
718
764
|
wmsg = f"status\t{self.run_id}\t{self.seq_no}\t{self._ts()}\tinfo\t{msg or ''}\n"
|
|
719
765
|
self.writelog(wmsg)
|
|
720
766
|
|
|
721
767
|
def log_status_warning(self, msg: str | None) -> None:
|
|
722
|
-
msg = None if not msg else msg.replace("\n", "")
|
|
768
|
+
msg = self._redact(None if not msg else msg.replace("\n", ""))
|
|
723
769
|
self.seq_no += 1
|
|
724
770
|
wmsg = f"status\t{self.run_id}\t{self.seq_no}\t{self._ts()}\twarning\t{msg or ''}\n"
|
|
725
771
|
self.writelog(wmsg)
|
|
726
772
|
|
|
727
773
|
def log_sql_query(self, sql: str, db_name: str, line_no: int | None = None) -> None:
|
|
728
774
|
"""Log an executed SQL statement for audit purposes."""
|
|
729
|
-
cleaned = sql.replace("\n", " ").replace("\t", " ")
|
|
775
|
+
cleaned = self._redact(sql.replace("\n", " ").replace("\t", " ")) or ""
|
|
730
776
|
if len(cleaned) > 2000:
|
|
731
777
|
cleaned = cleaned[:2000] + "..."
|
|
732
778
|
self.seq_no += 1
|
|
@@ -735,7 +781,7 @@ class Logger:
|
|
|
735
781
|
self.writelog(wmsg)
|
|
736
782
|
|
|
737
783
|
def log_user_msg(self, msg: str | None) -> None:
|
|
738
|
-
msg = None if not msg else msg.replace("\n", "")
|
|
784
|
+
msg = self._redact(None if not msg else msg.replace("\n", ""))
|
|
739
785
|
if msg != "":
|
|
740
786
|
self.seq_no += 1
|
|
741
787
|
wmsg = f"user_msg\t{self.run_id}\t{self.seq_no}\t{self._ts()}\tinfo\t{msg}\n"
|
|
@@ -760,14 +806,14 @@ class Logger:
|
|
|
760
806
|
self.exit_type = "exception"
|
|
761
807
|
self.exit_scriptfile = None
|
|
762
808
|
self.exit_lno = None
|
|
763
|
-
self.exit_description = msg.replace("\n", "")
|
|
809
|
+
self.exit_description = self._redact(msg.replace("\n", ""))
|
|
764
810
|
|
|
765
811
|
def log_exit_error(self, msg: str | None) -> None:
|
|
766
812
|
# Save values to be used by exit() function triggered on program exit
|
|
767
813
|
self.exit_type = "error"
|
|
768
814
|
self.exit_scriptfile = None
|
|
769
815
|
self.exit_lno = None
|
|
770
|
-
self.exit_description = None if not msg else msg.replace("\n", "")
|
|
816
|
+
self.exit_description = self._redact(None if not msg else msg.replace("\n", ""))
|
|
771
817
|
|
|
772
818
|
def log_exit(self) -> None:
|
|
773
819
|
import datetime as _datetime
|
execsql/utils/gui.py
CHANGED
|
@@ -19,7 +19,7 @@ their bodies to avoid circular imports.
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
import sys
|
|
22
|
-
from typing import Any
|
|
22
|
+
from typing import Any, cast
|
|
23
23
|
|
|
24
24
|
__all__ = [
|
|
25
25
|
# Constants
|
|
@@ -221,7 +221,7 @@ def gui_console_isrunning() -> bool:
|
|
|
221
221
|
clicking the Tkinter window's close button).
|
|
222
222
|
"""
|
|
223
223
|
if _active_backend is not None:
|
|
224
|
-
return _active_backend.query_console({}).get("console_running", False)
|
|
224
|
+
return cast(bool, _active_backend.query_console({}).get("console_running", False))
|
|
225
225
|
return _console_running
|
|
226
226
|
|
|
227
227
|
|
|
@@ -245,6 +245,7 @@ def enable_gui() -> None:
|
|
|
245
245
|
# Already initialised — nothing to do.
|
|
246
246
|
if _active_backend is not None:
|
|
247
247
|
return
|
|
248
|
+
backend: Any
|
|
248
249
|
|
|
249
250
|
framework = _state.conf.gui_framework if _state.conf else "tkinter"
|
|
250
251
|
|
|
@@ -457,11 +458,12 @@ def _apply_connect_result(alias: str, result: dict) -> None:
|
|
|
457
458
|
)
|
|
458
459
|
|
|
459
460
|
db_type = result.get("db_type", "l")
|
|
460
|
-
server = result.get("server")
|
|
461
|
-
database = result.get("database")
|
|
462
|
-
db_file = result.get("db_file")
|
|
463
|
-
username = result.get("username")
|
|
461
|
+
server = str(result.get("server") or "")
|
|
462
|
+
database = str(result.get("database") or "")
|
|
463
|
+
db_file = str(result.get("db_file") or "")
|
|
464
|
+
username = str(result.get("username") or "")
|
|
464
465
|
|
|
466
|
+
db: Any
|
|
465
467
|
if db_type == "p":
|
|
466
468
|
db = db_Postgres(server, database, user=username, pw_needed=True)
|
|
467
469
|
elif db_type == "s":
|
|
@@ -613,7 +615,7 @@ def pause(
|
|
|
613
615
|
old_settings = termios.tcgetattr(fd)
|
|
614
616
|
|
|
615
617
|
timer_handler: TimerHandler | None = None
|
|
616
|
-
old_alarm = signal.SIG_DFL
|
|
618
|
+
old_alarm: Any = signal.SIG_DFL
|
|
617
619
|
|
|
618
620
|
try:
|
|
619
621
|
tty.setraw(fd)
|
|
@@ -667,6 +669,8 @@ def pause_win(
|
|
|
667
669
|
"""
|
|
668
670
|
try:
|
|
669
671
|
import msvcrt
|
|
672
|
+
|
|
673
|
+
msvcrt_mod: Any = msvcrt
|
|
670
674
|
except ImportError:
|
|
671
675
|
# Not on Windows — delegate to the POSIX implementation.
|
|
672
676
|
return pause(text, action=action, countdown=countdown, timeunit=timeunit)
|
|
@@ -690,8 +694,8 @@ def pause_win(
|
|
|
690
694
|
"{:8.1f} |{}{}|\r".format(remaining, "+" * bar_left, "-" * (bar_len - bar_left)),
|
|
691
695
|
)
|
|
692
696
|
sys.stdout.flush()
|
|
693
|
-
if
|
|
694
|
-
ch =
|
|
697
|
+
if msvcrt_mod.kbhit():
|
|
698
|
+
ch = msvcrt_mod.getwch()
|
|
695
699
|
if ch in ("\r", "\n"):
|
|
696
700
|
_clear_progress_line()
|
|
697
701
|
return 0
|
|
@@ -703,8 +707,8 @@ def pause_win(
|
|
|
703
707
|
sys.stderr.write("Press Enter to continue, Esc to quit...\n")
|
|
704
708
|
sys.stderr.flush()
|
|
705
709
|
while True:
|
|
706
|
-
if
|
|
707
|
-
ch =
|
|
710
|
+
if msvcrt_mod.kbhit():
|
|
711
|
+
ch = msvcrt_mod.getwch()
|
|
708
712
|
if ch in ("\r", "\n"):
|
|
709
713
|
return 0
|
|
710
714
|
if ch == "\x1b":
|
execsql/utils/mail.py
CHANGED
|
@@ -17,6 +17,7 @@ from email.mime.base import MIMEBase
|
|
|
17
17
|
from email.mime.multipart import MIMEMultipart
|
|
18
18
|
from email.mime.text import MIMEText
|
|
19
19
|
from pathlib import Path
|
|
20
|
+
from typing import cast
|
|
20
21
|
|
|
21
22
|
import execsql.state as _state
|
|
22
23
|
from execsql.exceptions import ErrInfo
|
|
@@ -48,6 +49,7 @@ class Mailer:
|
|
|
48
49
|
self.close()
|
|
49
50
|
|
|
50
51
|
def __init__(self) -> None:
|
|
52
|
+
self.smtpconn: smtplib.SMTP
|
|
51
53
|
conf = _state.conf
|
|
52
54
|
if conf.smtp_host is None:
|
|
53
55
|
raise ErrInfo(type="error", other_msg="Can't send email; the email host is not configured.")
|
|
@@ -138,14 +140,16 @@ class MailSpec:
|
|
|
138
140
|
self.sent = False
|
|
139
141
|
|
|
140
142
|
@staticmethod
|
|
141
|
-
def _expand(text: str) -> str:
|
|
143
|
+
def _expand(text: str | None) -> str | None:
|
|
142
144
|
"""Expand local and global substitution variables in *text*."""
|
|
145
|
+
if text is None:
|
|
146
|
+
return None
|
|
143
147
|
result = text
|
|
144
148
|
localvars = _state.current_localvars()
|
|
145
149
|
if localvars is not None:
|
|
146
150
|
result, _ = localvars.substitute_all(result)
|
|
147
151
|
result, _ = _state.subvars.substitute_all(result)
|
|
148
|
-
return result
|
|
152
|
+
return cast(str, result)
|
|
149
153
|
|
|
150
154
|
def send(self) -> None:
|
|
151
155
|
if self.repeatable or not self.sent:
|
|
@@ -153,6 +157,9 @@ class MailSpec:
|
|
|
153
157
|
send_from = self._expand(self.send_from)
|
|
154
158
|
send_to = self._expand(self.send_to)
|
|
155
159
|
subject = self._expand(self.subject)
|
|
160
|
+
assert send_from is not None
|
|
161
|
+
assert send_to is not None
|
|
162
|
+
assert subject is not None
|
|
156
163
|
msg_content = self._expand(self.msg_content)
|
|
157
164
|
content_filename = self._expand(self.content_filename)
|
|
158
165
|
attach_filename = self._expand(self.attach_filename)
|
execsql/utils/numeric.py
CHANGED
|
@@ -45,7 +45,7 @@ def leading_zero_num(dataval: Any) -> bool:
|
|
|
45
45
|
|
|
46
46
|
def as_numeric(strval: Any) -> int | float | None:
|
|
47
47
|
# Converts the given value to an int, a float, or None.
|
|
48
|
-
if
|
|
48
|
+
if isinstance(strval, int | float):
|
|
49
49
|
return strval
|
|
50
50
|
if not isinstance(strval, str):
|
|
51
51
|
strval = str(strval)
|
execsql/utils/regex.py
CHANGED
|
@@ -24,7 +24,7 @@ __all__ = [
|
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def ins_rxs(rx_list: tuple, fragment1: object, fragment2: object) -> tuple:
|
|
27
|
+
def ins_rxs(rx_list: tuple[str, ...], fragment1: object, fragment2: object) -> tuple[str, ...]:
|
|
28
28
|
# Returns a tuple of all strings consisting of elements of the 'rx_list' tuple
|
|
29
29
|
# inserted between 'fragment1' and 'fragment2'. The fragments may themselves
|
|
30
30
|
# be tuples.
|
|
@@ -34,7 +34,7 @@ def ins_rxs(rx_list: tuple, fragment1: object, fragment2: object) -> tuple:
|
|
|
34
34
|
fragment2 = ("",)
|
|
35
35
|
if not isinstance(fragment2, tuple):
|
|
36
36
|
fragment2 = (fragment2,)
|
|
37
|
-
rv = []
|
|
37
|
+
rv: list[str] = []
|
|
38
38
|
for te in rx_list:
|
|
39
39
|
for f1 in fragment1:
|
|
40
40
|
for f2 in fragment2:
|
|
@@ -42,12 +42,12 @@ def ins_rxs(rx_list: tuple, fragment1: object, fragment2: object) -> tuple:
|
|
|
42
42
|
return tuple(rv)
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def ins_quoted_rx(fragment1: object, fragment2: object, rx: str) -> tuple:
|
|
45
|
+
def ins_quoted_rx(fragment1: object, fragment2: object, rx: str) -> tuple[str, ...]:
|
|
46
46
|
return ins_rxs((rx, rf'"{rx}"'), fragment1, fragment2)
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def ins_schema_rxs(fragment1: object, fragment2: object, suffix: str | None = None) -> tuple:
|
|
50
|
-
schema_exprs = (
|
|
49
|
+
def ins_schema_rxs(fragment1: object, fragment2: object, suffix: str | None = None) -> tuple[str, ...]:
|
|
50
|
+
schema_exprs: tuple[str, ...] = (
|
|
51
51
|
r'"(?P<schema>[A-Za-z0-9_\- ]+)"',
|
|
52
52
|
r"(?P<schema>[A-Za-z0-9_\-]+)",
|
|
53
53
|
r"\[(?P<schema>[A-Za-z0-9_\- ]+)\]",
|
|
@@ -57,8 +57,8 @@ def ins_schema_rxs(fragment1: object, fragment2: object, suffix: str | None = No
|
|
|
57
57
|
return ins_rxs(schema_exprs, fragment1, fragment2)
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
def ins_table_rxs(fragment1: object, fragment2: object, suffix: str | None = None) -> tuple:
|
|
61
|
-
tbl_exprs = (
|
|
60
|
+
def ins_table_rxs(fragment1: object, fragment2: object, suffix: str | None = None) -> tuple[str, ...]:
|
|
61
|
+
tbl_exprs: tuple[str, ...] = (
|
|
62
62
|
r'(?:"(?P<schema>[A-Za-z0-9_\- ]+)"\.)?"(?P<table>[A-Za-z0-9_\-\# ]+)"',
|
|
63
63
|
r"(?:(?P<schema>[A-Za-z0-9_\-]+)\.)?(?P<table>[A-Za-z0-9_\-\#]+)",
|
|
64
64
|
r'(?:"(?P<schema>[A-Za-z0-9_\- ]+)"\.)?(?P<table>[A-Za-z0-9_\-\#]+)',
|
|
@@ -75,7 +75,7 @@ def ins_table_rxs(fragment1: object, fragment2: object, suffix: str | None = Non
|
|
|
75
75
|
return ins_rxs(tbl_exprs, fragment1, fragment2)
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
def ins_table_list_rxs(fragment1: object, fragment2: object) -> tuple:
|
|
78
|
+
def ins_table_list_rxs(fragment1: object, fragment2: object) -> tuple[str, ...]:
|
|
79
79
|
tbl_exprs = (
|
|
80
80
|
r'(?:(?P<tables>(?:"[A-Za-z0-9_\- ]+"\.)?"[A-Za-z0-9_\-\# ]+"(?:\s*,\s*(?:"[A-Za-z0-9_\- ]+"\.)?"[A-Za-z0-9_\-\# ]+")*))',
|
|
81
81
|
r"(?:(?P<tables>(?:[A-Za-z0-9_\-]+\.)?[A-Za-z0-9_\-\#]+(?:\s*,\s*(?:[A-Za-z0-9_\-]+\.)?[A-Za-z0-9_\-\#]+)*))",
|
|
@@ -83,7 +83,7 @@ def ins_table_list_rxs(fragment1: object, fragment2: object) -> tuple:
|
|
|
83
83
|
return ins_rxs(tbl_exprs, fragment1, fragment2)
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
def ins_fn_rxs(fragment1: object, fragment2: object, symbolicname: str = "filename") -> tuple:
|
|
86
|
+
def ins_fn_rxs(fragment1: object, fragment2: object, symbolicname: str = "filename") -> tuple[str, ...]:
|
|
87
87
|
if os.name == "posix":
|
|
88
88
|
fns = (
|
|
89
89
|
rf"(?P<{symbolicname}>[\w\.\-\\\/\'~`!@#$^&()+={{}}\[\]:;,]*[\w\.\-\\\/\'~`!@#$^&(+={{}}\[\]:;,])",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.22.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
|
|
6
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
@@ -173,7 +173,7 @@ Optional extras install database drivers and feature bundles:
|
|
|
173
173
|
|
|
174
174
|
```bash
|
|
175
175
|
# Database drivers
|
|
176
|
-
pip install execsql2[postgres] # PostgreSQL (
|
|
176
|
+
pip install execsql2[postgres] # PostgreSQL (psycopg3 / psycopg[binary])
|
|
177
177
|
pip install execsql2[mysql] # MySQL / MariaDB (pymysql)
|
|
178
178
|
pip install execsql2[mssql] # SQL Server (pyodbc)
|
|
179
179
|
pip install execsql2[duckdb] # DuckDB
|
|
@@ -403,7 +403,7 @@ execsql-format --no-sql --in-place scripts/
|
|
|
403
403
|
```yaml
|
|
404
404
|
repos:
|
|
405
405
|
- repo: https://github.com/geocoug/execsql
|
|
406
|
-
rev: v2.
|
|
406
|
+
rev: v2.22.0
|
|
407
407
|
hooks:
|
|
408
408
|
- id: execsql-format
|
|
409
409
|
```
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
execsql/__init__.py,sha256=ZeVnpEWu8M2bn3qLmVM72NhWRueUGNbsX8UAPTermo0,901
|
|
2
|
+
execsql/__main__.py,sha256=HdbK-SAhyUmfB6xINY5AzxdMSxGzWSGEG_2dv42Jn64,315
|
|
3
|
+
execsql/api.py,sha256=v_M46MkVcuGPkIG6xp5VD4XNfwMg7HG45OKxdAwrAEs,21732
|
|
4
|
+
execsql/config.py,sha256=gxv7SDjiVrpR05rU_rSjCBd_PP3RmwsyhOyPWOqfAGQ,34123
|
|
5
|
+
execsql/exceptions.py,sha256=GlphAiR8iZu4UR4U50WgSqEUMd9_8AeQJ_hhPcJOeQU,9381
|
|
6
|
+
execsql/format.py,sha256=ON8Jy5BSen4UI4SGY5cHKRwsvR0ZoLhH0BdblMwydYE,33670
|
|
7
|
+
execsql/models.py,sha256=-DWTVrPNwM-n1h3e3rXMoBv9-wpWVdZahyy4RB7fRRE,15502
|
|
8
|
+
execsql/parser.py,sha256=aOxZ7Mzgvk7G4-nvy9Bgz7xlaZm6oo81cffDeBk_LCk,15597
|
|
9
|
+
execsql/plugins.py,sha256=l82VWa5NpvOQWD6vcO-WKP-vtn0wp9__wWDFAbwxZg0,12838
|
|
10
|
+
execsql/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
execsql/state.py,sha256=T6UoXXxAkUP-4KKQpfFAlI3WMzm2xUi3LSplJDuRLY0,21965
|
|
12
|
+
execsql/state.pyi,sha256=ES5xdoId-ALCGnLIQvQIGBZljZRB6aOOEMRLKXjO9hA,3770
|
|
13
|
+
execsql/types.py,sha256=eKMF2kb5LHrEwKsiFpy6z_fEH05yC0Cpsg98Ea4SpVA,32231
|
|
14
|
+
execsql/cli/__init__.py,sha256=aJknKKIGxYCXpny0cyHXfqJsJ95dBtlEXXhPASFG8GQ,23114
|
|
15
|
+
execsql/cli/dsn.py,sha256=svaZtrUXFRL2W5G6FRRiKtR6kehOp7urrVhIx_642Z8,2820
|
|
16
|
+
execsql/cli/help.py,sha256=ThwdZuMIfLPxLAPpGWwXFY_UfyWvYOCjdlBNK20Vzd8,5718
|
|
17
|
+
execsql/cli/lint.py,sha256=YqKzFNUhyb_Th69hYgKk1ZZVjCsZfJMIiUGqp06JwNs,17236
|
|
18
|
+
execsql/cli/run.py,sha256=Yb-Ri3cds0st1hUn4_w-PHMphTsArHhC4fx919SW9gU,39390
|
|
19
|
+
execsql/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
execsql/data/execsql.conf.template,sha256=w_rJ1mx3FOWi_7qWLGcxsf3HTR5nSMsbKAzlfcJh_tM,10959
|
|
21
|
+
execsql/db/__init__.py,sha256=jTbuafuKOqYtXFR1wvCOoKK5Lr3l1uErfaIbIr6UywI,1063
|
|
22
|
+
execsql/db/access.py,sha256=h2zAMr3mQrm6vFlOYaN7iaG19K7gitXDuonllelqbcE,19539
|
|
23
|
+
execsql/db/base.py,sha256=0K6mSCqFcheg_T91VOwEnagqqP_eLL2dP5VN4yDz36Q,35414
|
|
24
|
+
execsql/db/dsn.py,sha256=P17FwrjEkmhYiPCWZSRJuHYIQu_BCkumiXLkwZEMb4o,6334
|
|
25
|
+
execsql/db/duckdb.py,sha256=YxtjWSzkbJEzZcfoYKfsA9QRrRL08A31GYzkolJiIw0,3229
|
|
26
|
+
execsql/db/factory.py,sha256=C_cyi3GjNx0opuwc6maFFeCSkjfOfoUjuf68YT4VAHE,5377
|
|
27
|
+
execsql/db/firebird.py,sha256=cV5wUzQHoz7yugHdNxAn8IW7fL3v1riIsGLykCZhZwA,9480
|
|
28
|
+
execsql/db/mysql.py,sha256=hv_OqoJBVIaCh5e2lMTVrgBTNljAaVnxwlAoQYff8c4,18026
|
|
29
|
+
execsql/db/oracle.py,sha256=1_odb5xmlm8vjdJdQXz7SHm9dzIbZ5sxP_IxTklH3kA,12018
|
|
30
|
+
execsql/db/postgres.py,sha256=LIZgx4T_UPZvOk_mn0l832ShNKgzculdOrVrFO7Rj8I,22045
|
|
31
|
+
execsql/db/sqlite.py,sha256=mx56IDwLKAXdep--4C8j15qK22JkJjworSJkfnx1ZFw,11551
|
|
32
|
+
execsql/db/sqlserver.py,sha256=j2ViLoBWzizgaL0u6V4iHfjinrlJ4rpBD3XZiKKdwSU,8968
|
|
33
|
+
execsql/debug/__init__.py,sha256=j6EGUR0dHzUhWN1mHHtf1-Lhjq3Sb1V-vmnq2Ztgj1M,178
|
|
34
|
+
execsql/debug/repl.py,sha256=JObeoEXh15qyk4Q3WQCCqMcBtojlIcu_Xg-ZRDZJi5Q,25491
|
|
35
|
+
execsql/exporters/__init__.py,sha256=-Cnji-OgodJV8ftcDcOyTof0kQMy9J5kKVC8GVFpc3o,670
|
|
36
|
+
execsql/exporters/base.py,sha256=_GoO0YqwlTxrjogxHI3CxxRR64sMgIEjRogt0KCqzoY,6402
|
|
37
|
+
execsql/exporters/delimited.py,sha256=vd9Q74TzeeWSPU03EboqqCBOWwOOeFVL544sPXINI0w,32755
|
|
38
|
+
execsql/exporters/duckdb.py,sha256=912TMdYaSVD-kb-_oebTBHQp8FvYMbWTDEDD80w5Z50,3455
|
|
39
|
+
execsql/exporters/feather.py,sha256=tah48aep0cQRikZcmADXUPTzFddgDUT78Ndw_l0Hsj4,4178
|
|
40
|
+
execsql/exporters/html.py,sha256=BPTGYODiC5_5zaQsVkZ9QVAl67yfCWFTsjK0D-QOJkM,9717
|
|
41
|
+
execsql/exporters/json.py,sha256=wWGT6zjvIdWlO5FmXbaEw1xOhy23f1YY6bm5uQD_NS0,4393
|
|
42
|
+
execsql/exporters/latex.py,sha256=TiHH9EO4eF2AgdUyjpbHNZhvKhIJl8Ew6NoO1z0YbB0,4484
|
|
43
|
+
execsql/exporters/markdown.py,sha256=MOErBwkMHM552JE0QuNkCRyHpxTEI3Pw7o9bZOe3RE8,4447
|
|
44
|
+
execsql/exporters/ods.py,sha256=oenUeZuC34Urlh7rI-PXIWOc-cuKMfUU4HfYj4xzvkQ,19521
|
|
45
|
+
execsql/exporters/parquet.py,sha256=mbCo2Wlit1lkXmyo5EyLd_ATIHSICS49bouiBT2kdAs,1121
|
|
46
|
+
execsql/exporters/pretty.py,sha256=tG7Yr4lwFMs3a0Jrsq6qsNNkE8Q7sMePr61SS5QMaTs,3447
|
|
47
|
+
execsql/exporters/protocol.py,sha256=BxATgz0xKHbB2FpZBeNg7wZfIiCohhD1awlr3JCec0c,4372
|
|
48
|
+
execsql/exporters/raw.py,sha256=9o3BMt7UYmnjbP8OEzmM2kNKfwwERi2yDcuNfUoEfuQ,2530
|
|
49
|
+
execsql/exporters/sqlite.py,sha256=QjP_W90S-hecNqp2A-Vy58DD8MZitME1sTq4lqIS43w,3401
|
|
50
|
+
execsql/exporters/templates.py,sha256=wXvN6HwOHbro6t306Dq7WcnxwGdJMF1veXry9hwuI3k,6016
|
|
51
|
+
execsql/exporters/values.py,sha256=HIyud31aux_dbCphfKHEGeZB9fkIPE5PoGXQz817XIE,2520
|
|
52
|
+
execsql/exporters/xls.py,sha256=iZ_N7hneyfaDKbO1XdYv6doJCFPqJWEJql5rduoNyRU,11197
|
|
53
|
+
execsql/exporters/xlsx.py,sha256=0f-Gdp8DyB3muUdH6tyiGFKGx9UymZ4fL3ja6H0sBjI,11853
|
|
54
|
+
execsql/exporters/xml.py,sha256=t1PSr3i8xvTCWzd9jaxxAy5NcRpl6sqjnZALafKCqpM,2575
|
|
55
|
+
execsql/exporters/yaml.py,sha256=R61Rs_0rd5G3bg344L4hC84GM79IfYXaDO2tkF-BluY,3121
|
|
56
|
+
execsql/exporters/zip.py,sha256=341z4Z4MPtEkXJEtfiMSiuJigERh5_f8RDbNMBbt8KM,5078
|
|
57
|
+
execsql/gui/__init__.py,sha256=oCb-cyhLZzVpWJ4WU5HbqEDBrV-lm0ytEwxemrOZyqs,2048
|
|
58
|
+
execsql/gui/base.py,sha256=15wCaSLvSb21hToCczru46ghmd56ryMpEOOZ0R_dE3U,12740
|
|
59
|
+
execsql/gui/console.py,sha256=hgFnxPP7lLolKgmKCBTSMXIt4uU2L4C32XcA82ytr2U,19438
|
|
60
|
+
execsql/gui/desktop.py,sha256=922OdzjV4OGNDIesOxKOmlsrNp1aJ8St8cVwED0H2sE,59521
|
|
61
|
+
execsql/gui/tui.py,sha256=yXKBqbbJDNC6a25sUKpvy3BR10U2LhLSuGAxkw6ADcQ,65358
|
|
62
|
+
execsql/importers/__init__.py,sha256=zZwdQxMaValCNqUrVdvaA7XPU3J8NmqVJ4uolNWY2iU,299
|
|
63
|
+
execsql/importers/base.py,sha256=3Iq_Sr4cCXKHpjjJYkhZX6uhsIIRAdmvAvTdC4mFlWo,4326
|
|
64
|
+
execsql/importers/csv.py,sha256=Kec1VCycRdfstNoxIXsEsSFqQprZp_edpNiMZm43eTI,4914
|
|
65
|
+
execsql/importers/feather.py,sha256=g2B69d2uv9vmnXcmjFyTVsMP40LYEzFYkhk3gD26mGw,1900
|
|
66
|
+
execsql/importers/json.py,sha256=fL47h09Zzx4Qw3TTQRQJbdPdD3qVMiU-kK9C7j1IQzg,5534
|
|
67
|
+
execsql/importers/ods.py,sha256=VsxIkr7opBamB0PbSFmMWt7G11w8lLUH1k_kRwz28zw,2847
|
|
68
|
+
execsql/importers/xls.py,sha256=D7Ul42gIRYZZC5QxD35ft_wmqQv2f1jNMSbnqlzV0ZA,3851
|
|
69
|
+
execsql/metacommands/__init__.py,sha256=H_D1Z5kN6zfYtxatejQhQhQV1tlVZ7rcdFZ_9uhXu2Q,13010
|
|
70
|
+
execsql/metacommands/conditions.py,sha256=2bfdVV_LCl91quG4SRLur9Jk60NsIOStHlBCeJJ-Djc,30068
|
|
71
|
+
execsql/metacommands/connect.py,sha256=SPk4h9yiUgTD3w1gNOT_oYx8VShggsO357by5Hoy6OI,15344
|
|
72
|
+
execsql/metacommands/control.py,sha256=EAURAsBjEZm3YwOJG0Gr3oI2H5_8p3Cu8-AguhBD_ds,8007
|
|
73
|
+
execsql/metacommands/data.py,sha256=Rp18lHUKSTkwTFRjOQ7bDgZd42LNLVoTS3Zc_bcNEWY,12199
|
|
74
|
+
execsql/metacommands/debug.py,sha256=MeVXAob8ItEg2QzuSUkKDaQCEABnH6u0XcAwJzw36CE,13015
|
|
75
|
+
execsql/metacommands/dispatch.py,sha256=t7x0xWHJA0PeCrYf7jYeSMJgf0yxcZ_xh-_YAtBPHLw,87192
|
|
76
|
+
execsql/metacommands/io.py,sha256=vlGBje5sgnqeilooMdhJDgSRIhysHy5_7LrKtik9Xjs,3011
|
|
77
|
+
execsql/metacommands/io_export.py,sha256=YamxQwURRjZRpCG2z8W3QurncCmEmAyLFqDkxtHo5dY,14434
|
|
78
|
+
execsql/metacommands/io_fileops.py,sha256=l1ZaTMzvsXa88zaQp-scWvUy4TMmN4WVVhr4V6-b-tE,11645
|
|
79
|
+
execsql/metacommands/io_import.py,sha256=bIFQMTEW9Rc5FB0VAR-04eve7FL0g0gBKtdoGR_jXrI,15719
|
|
80
|
+
execsql/metacommands/io_write.py,sha256=yXDkMbQ3haDARVATVUYZprakyJHSyk1NywdKkFLpQ14,12783
|
|
81
|
+
execsql/metacommands/prompt.py,sha256=7pPURC11qBFcKEI7IdhrttbiBKTc1txjH59QN3dvFyQ,37865
|
|
82
|
+
execsql/metacommands/script_ext.py,sha256=CsUbyq8QFPnnvfX_k7fPlYNvvRw1xLmAWER4Exo5pwc,3658
|
|
83
|
+
execsql/metacommands/system.py,sha256=6X2qmMpGUHDzyEluEotXt0Ks9jKQTpc2x0eoYNgN12M,7997
|
|
84
|
+
execsql/metacommands/upsert.py,sha256=wzoMpM8g49pEvU9GkHZ62fPvqV3w1UIUfxVA7HAsS_o,20317
|
|
85
|
+
execsql/script/__init__.py,sha256=eGJPBDWj42aaId2lX_quSrqoKrvGwGElIrGDNCyoV1Y,3547
|
|
86
|
+
execsql/script/ast.py,sha256=TQ4_7Lfw1F8_k6ycdvMZdzwNafrZiljSrthVRWUsuIk,20585
|
|
87
|
+
execsql/script/control.py,sha256=WqLy-HLPqHG3vEzYpKMiIJsD7LpORjyQuUWzFzcGz4w,2327
|
|
88
|
+
execsql/script/engine.py,sha256=KbSB5aXkuDlazh3rRP4vRym_bVTDKmChLQ_OFf3-N8Q,21016
|
|
89
|
+
execsql/script/executor.py,sha256=64dYw8QuNcHgvnJX1PwTK01UvDgOOYcI1IKceBL4PO4,36948
|
|
90
|
+
execsql/script/parser.py,sha256=VhFTJK8kODNRWgIDc3LjYjA3_SMPFATkpjVl82dYnrM,35179
|
|
91
|
+
execsql/script/variables.py,sha256=_hklqCeqPbdOT646Yg3ZcpmPke38_8bukMG3eZMTKZw,16384
|
|
92
|
+
execsql/utils/__init__.py,sha256=0uR6JwVJQRX3vceByNBduCAf5dd5assKjeqJUWvpZoA,278
|
|
93
|
+
execsql/utils/auth.py,sha256=8Wixkx5nayEBG7RT4WTUQpCiElIKS3yOTV_6_-hHTRs,8889
|
|
94
|
+
execsql/utils/crypto.py,sha256=KlT6lPk-v0-qQQfAMfWvT9Q6kRdCD3_5JVaLajNocKM,3051
|
|
95
|
+
execsql/utils/datetime.py,sha256=8BrD24KWeMoouJZiFaXK4HF7plc-O_xrzIw12qOEyp8,3629
|
|
96
|
+
execsql/utils/errors.py,sha256=bDWooUmGdN8xqUt6csq466wPqKPLHHSi7U4hsmrFZxA,8577
|
|
97
|
+
execsql/utils/fileio.py,sha256=q1d1121GRilGSIpEF7Srgft7oF_latVUEghNdkAZbsw,33171
|
|
98
|
+
execsql/utils/gui.py,sha256=T22ThhkedzQz9kBidxZwIArt1umIvrgrSUwsPWAwvug,23548
|
|
99
|
+
execsql/utils/mail.py,sha256=SXpTBDx0WIPl2NSjkjmnoChHYVEMyZhIKU4QiJFFZNY,6011
|
|
100
|
+
execsql/utils/numeric.py,sha256=_7bi5R6pHjMCkOPne_Sh5nUj-B8e9g6WFmjptFHaunA,1573
|
|
101
|
+
execsql/utils/regex.py,sha256=B3mSBhRRx_DpUnX_t9IoYd05KvWAebiLMCZqQAmEwcw,3885
|
|
102
|
+
execsql/utils/strings.py,sha256=UQNjpRCEFa1UO6feU-M-9e24wWAvizs_iu_4fFusLxo,8516
|
|
103
|
+
execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
|
|
104
|
+
execsql2-2.22.0.data/data/execsql2_extras/README.md,sha256=vX4NTL095dUoA_hesyRMGYBorEZ_Y_tJ9qrd-MVV09I,5032
|
|
105
|
+
execsql2-2.22.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
|
|
106
|
+
execsql2-2.22.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
|
|
107
|
+
execsql2-2.22.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
|
|
108
|
+
execsql2-2.22.0.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
|
|
109
|
+
execsql2-2.22.0.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
|
|
110
|
+
execsql2-2.22.0.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
|
|
111
|
+
execsql2-2.22.0.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
|
|
112
|
+
execsql2-2.22.0.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
|
|
113
|
+
execsql2-2.22.0.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
|
|
114
|
+
execsql2-2.22.0.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
|
|
115
|
+
execsql2-2.22.0.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
|
|
116
|
+
execsql2-2.22.0.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
|
|
117
|
+
execsql2-2.22.0.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
|
|
118
|
+
execsql2-2.22.0.dist-info/METADATA,sha256=aYtTGfrjV6YMqEipLcyiyWymmhwBnx1rF6DGcY87AB8,22623
|
|
119
|
+
execsql2-2.22.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
120
|
+
execsql2-2.22.0.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
|
|
121
|
+
execsql2-2.22.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
|
|
122
|
+
execsql2-2.22.0.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
|
|
123
|
+
execsql2-2.22.0.dist-info/RECORD,,
|