execsql2 2.21.2__py3-none-any.whl → 2.22.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. execsql/api.py +29 -4
  2. execsql/cli/__init__.py +1 -1
  3. execsql/cli/run.py +37 -17
  4. execsql/config.py +40 -22
  5. execsql/data/execsql.conf.template +6 -0
  6. execsql/db/access.py +6 -5
  7. execsql/db/base.py +23 -5
  8. execsql/db/dsn.py +2 -0
  9. execsql/db/duckdb.py +1 -1
  10. execsql/db/mysql.py +6 -8
  11. execsql/db/sqlite.py +1 -1
  12. execsql/exceptions.py +14 -11
  13. execsql/exporters/base.py +1 -1
  14. execsql/exporters/delimited.py +22 -16
  15. execsql/exporters/duckdb.py +6 -1
  16. execsql/exporters/feather.py +1 -1
  17. execsql/exporters/json.py +5 -3
  18. execsql/exporters/latex.py +2 -2
  19. execsql/exporters/markdown.py +1 -0
  20. execsql/exporters/ods.py +7 -3
  21. execsql/exporters/raw.py +8 -8
  22. execsql/exporters/sqlite.py +11 -1
  23. execsql/exporters/templates.py +2 -1
  24. execsql/exporters/xls.py +10 -10
  25. execsql/exporters/xml.py +1 -0
  26. execsql/exporters/yaml.py +1 -0
  27. execsql/exporters/zip.py +9 -7
  28. execsql/format.py +6 -6
  29. execsql/gui/console.py +3 -3
  30. execsql/gui/desktop.py +28 -10
  31. execsql/gui/tui.py +8 -6
  32. execsql/importers/base.py +14 -8
  33. execsql/importers/csv.py +8 -3
  34. execsql/importers/xls.py +1 -0
  35. execsql/metacommands/conditions.py +14 -14
  36. execsql/metacommands/connect.py +2 -2
  37. execsql/metacommands/control.py +1 -1
  38. execsql/metacommands/data.py +2 -1
  39. execsql/metacommands/io_export.py +4 -0
  40. execsql/metacommands/io_fileops.py +85 -39
  41. execsql/metacommands/io_import.py +1 -0
  42. execsql/metacommands/io_write.py +1 -1
  43. execsql/metacommands/prompt.py +24 -22
  44. execsql/metacommands/system.py +14 -1
  45. execsql/models.py +56 -17
  46. execsql/parser.py +8 -8
  47. execsql/plugins.py +1 -1
  48. execsql/script/engine.py +4 -4
  49. execsql/script/executor.py +69 -28
  50. execsql/script/parser.py +13 -12
  51. execsql/script/variables.py +4 -2
  52. execsql/state.pyi +122 -0
  53. execsql/types.py +31 -26
  54. execsql/utils/auth.py +6 -2
  55. execsql/utils/datetime.py +3 -3
  56. execsql/utils/errors.py +7 -5
  57. execsql/utils/fileio.py +80 -32
  58. execsql/utils/gui.py +15 -11
  59. execsql/utils/mail.py +9 -2
  60. execsql/utils/numeric.py +1 -1
  61. execsql/utils/regex.py +9 -9
  62. {execsql2-2.21.2.dist-info → execsql2-2.22.1.dist-info}/METADATA +114 -132
  63. execsql2-2.22.1.dist-info/RECORD +123 -0
  64. execsql2-2.21.2.dist-info/RECORD +0 -122
  65. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/README.md +0 -0
  66. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  67. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  68. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
  69. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/md_compare.sql +0 -0
  70. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/md_glossary.sql +0 -0
  71. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/md_upsert.sql +0 -0
  72. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/pg_compare.sql +0 -0
  73. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  74. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  75. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/script_template.sql +0 -0
  76. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/ss_compare.sql +0 -0
  77. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  78. {execsql2-2.21.2.data → execsql2-2.22.1.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  79. {execsql2-2.21.2.dist-info → execsql2-2.22.1.dist-info}/WHEEL +0 -0
  80. {execsql2-2.21.2.dist-info → execsql2-2.22.1.dist-info}/entry_points.txt +0 -0
  81. {execsql2-2.21.2.dist-info → execsql2-2.22.1.dist-info}/licenses/LICENSE.txt +0 -0
  82. {execsql2-2.21.2.dist-info → execsql2-2.22.1.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
 
@@ -76,7 +78,9 @@ def check_dir(filename: str) -> None:
76
78
  import execsql.state as _state
77
79
 
78
80
  conf = _state.conf
79
- if conf.make_export_dirs:
81
+ # conf is None when called via the library API (e.g. a db factory
82
+ # function) before run() has initialized global state.
83
+ if conf is not None and conf.make_export_dirs:
80
84
  make_export_dirs(filename)
81
85
  else:
82
86
  dn = str(Path(filename).parent)
@@ -207,11 +211,11 @@ class FileWriter(multiprocessing.Process):
207
211
  self.open_timeout = open_timeout
208
212
  self.encoding = encoding
209
213
  self.openmode = "a"
210
- self.handle = None
214
+ self.handle: io.TextIOWrapper | None = None
211
215
  self.status = self.STATUS_UNOPENED
212
- self.open_start_time = None
216
+ self.open_start_time: float | None = None
213
217
  self.fail_message_written = False
214
- self.output_queue: collections.deque = collections.deque()
218
+ self.output_queue: collections.deque[str] = collections.deque()
215
219
  self.close_after_write = False
216
220
 
217
221
  def __del__(self) -> None:
@@ -221,6 +225,7 @@ class FileWriter(multiprocessing.Process):
221
225
  pass # Best-effort cleanup at interpreter shutdown.
222
226
 
223
227
  def write_queue(self) -> None:
228
+ assert self.handle is not None
224
229
  while len(self.output_queue) > 0:
225
230
  m = self.output_queue.pop()
226
231
  self.handle.write(m)
@@ -238,11 +243,14 @@ class FileWriter(multiprocessing.Process):
238
243
  self.open_start_time = time.time()
239
244
  if time.time() - self.open_start_time < self.open_timeout:
240
245
  try:
241
- self.handle = open( # noqa: SIM115
242
- file=self.filename,
243
- mode=self.openmode,
244
- encoding=self.encoding,
245
- errors="backslashreplace",
246
+ self.handle = cast(
247
+ io.TextIOWrapper,
248
+ open( # noqa: SIM115
249
+ file=self.filename,
250
+ mode=self.openmode,
251
+ encoding=self.encoding,
252
+ errors="backslashreplace",
253
+ ),
246
254
  )
247
255
  except Exception:
248
256
  self.status = self.STATUS_WAITING
@@ -298,10 +306,10 @@ class FileWriter(multiprocessing.Process):
298
306
  self.return_msg_queue = return_msg_queue
299
307
  self.file_encoding = file_encoding
300
308
  self.open_timeout = open_timeout
301
- self.files: dict = {}
309
+ self.files: dict[str, FileWriter.FileControl] = {}
302
310
  self.active = True
303
311
  # Functions in execvec must be in the same order as the CMD enums.
304
- self.execvec = (
312
+ self.execvec: tuple[Callable[..., None], ...] = (
305
313
  self.write,
306
314
  self.close_if_open,
307
315
  self.close_all,
@@ -424,7 +432,7 @@ def filewriter_filestatus(filename: str) -> int:
424
432
  if not _writer_alive():
425
433
  return FileWriter.FileControl.STATUS_CLOSED
426
434
  fw_input.put((FileWriter.CMD_GET_STATUS, (filename,)))
427
- return fw_output.get()
435
+ return cast(int, fw_output.get())
428
436
 
429
437
 
430
438
  def filewriter_write(filename: str, message: str) -> None:
@@ -527,19 +535,23 @@ class EncodedFile:
527
535
 
528
536
  if Path(filename).exists():
529
537
  self.encoding, self.bom_length = detect_by_bom(filename, file_encoding)
530
- self.fo = None
538
+ self.fo: io.TextIOWrapper | None = None
531
539
 
532
540
  def open(self, mode: str = "r") -> io.TextIOWrapper:
533
541
  import execsql.state as _state
534
542
 
535
543
  conf = _state.conf
536
- self.fo = open( # noqa: SIM115
537
- file=self.filename,
538
- mode=mode,
539
- encoding=self.encoding,
540
- errors=conf.enc_err_disposition,
541
- newline=None,
544
+ self.fo = cast(
545
+ io.TextIOWrapper,
546
+ open( # noqa: SIM115
547
+ file=self.filename,
548
+ mode=mode,
549
+ encoding=self.encoding,
550
+ errors=conf.enc_err_disposition,
551
+ newline=None,
552
+ ),
542
553
  )
554
+ assert self.fo is not None
543
555
  return self.fo
544
556
 
545
557
  def close(self) -> None:
@@ -557,6 +569,7 @@ class EncodedFile:
557
569
  # before entering the ``with`` block.
558
570
  if self.fo is None:
559
571
  self.open("r")
572
+ assert self.fo is not None
560
573
  return self.fo
561
574
 
562
575
  def __exit__(self, exc_type, exc_val, exc_tb) -> None:
@@ -565,7 +578,15 @@ class EncodedFile:
565
578
 
566
579
  class Logger:
567
580
  # A custom logger for execsql that writes several different types of messages to a log file.
568
- log_file = None
581
+ log_file: io.TextIOWrapper | None = None
582
+ _COMMON_SECRET_PATTERNS = (
583
+ re.compile(r"(?i)(://[^/\s:@]+:)([^@\s]+)(@)"),
584
+ re.compile(r"(?i)\b(password|passwd|token|secret|api[_-]?key|private[_-]?key|webhook)(\s*[=:]\s*)([^\s,;]+)"),
585
+ re.compile(r"\bsk-(?:live|test)-[A-Za-z0-9_-]+\b"),
586
+ re.compile(r"\bAKIA[A-Z0-9]{12,}\b"),
587
+ re.compile(r"\bgh[pousr]_[A-Za-z0-9_]{20,}\b"),
588
+ re.compile(r"\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b"),
589
+ )
569
590
 
570
591
  def __repr__(self) -> str:
571
592
  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 +608,7 @@ class Logger:
587
608
  self.db_name = db_name
588
609
  self.server_name = server_name
589
610
  self.cmdline_options = cmdline_options
611
+ self._redaction_values: list[str] = []
590
612
  if log_file_name:
591
613
  self.log_file_name = log_file_name
592
614
  else:
@@ -630,6 +652,9 @@ class Logger:
630
652
  self.run_start = _now
631
653
  self.run_id = _now.strftime("%Y%m%d_%H%M_%S_") + f"{_now.microsecond // 1000:03d}"
632
654
  self.user = getpass.getuser()
655
+ sz: int
656
+ dt: str
657
+ abs_script: str
633
658
  if script_file_name and Path(script_file_name).is_file():
634
659
  sz, dt = file_size_date(script_file_name)
635
660
  abs_script = str(Path(script_file_name).resolve())
@@ -653,9 +678,9 @@ class Logger:
653
678
  self.seq_no = 0
654
679
  atexit.register(self.close)
655
680
  self.exit_type = "unknown"
656
- self.exit_scriptfile = None
657
- self.exit_lno = None
658
- self.exit_description = None
681
+ self.exit_scriptfile: str | None = None
682
+ self.exit_lno: int | None = None
683
+ self.exit_description: str | None = None
659
684
  atexit.register(self.log_exit)
660
685
 
661
686
  def _ts(self) -> str:
@@ -663,6 +688,29 @@ class Logger:
663
688
 
664
689
  return _datetime.datetime.now().isoformat(timespec="seconds")
665
690
 
691
+ def add_redaction_value(self, value: str | None) -> None:
692
+ if value is None:
693
+ return
694
+ value = str(value)
695
+ if not value or value in self._redaction_values:
696
+ return
697
+ self._redaction_values.append(value)
698
+
699
+ def _redact(self, msg: str | None) -> str | None:
700
+ if msg is None:
701
+ return None
702
+ redacted = str(msg)
703
+ for value in sorted(self._redaction_values, key=len, reverse=True):
704
+ redacted = redacted.replace(value, "***")
705
+ for pattern in self._COMMON_SECRET_PATTERNS:
706
+ if pattern.pattern.startswith("(?i)(://"):
707
+ redacted = pattern.sub(r"\1***\3", redacted)
708
+ elif "password|passwd|token" in pattern.pattern:
709
+ redacted = pattern.sub(r"\1\2***", redacted)
710
+ else:
711
+ redacted = pattern.sub("***", redacted)
712
+ return redacted
713
+
666
714
  def _rotate_if_needed(self) -> None:
667
715
  try:
668
716
  import execsql.state as _state
@@ -701,32 +749,32 @@ class Logger:
701
749
  self.writelog(msg)
702
750
 
703
751
  def log_status_exception(self, msg: str | None) -> None:
704
- msg = None if not msg else msg.replace("\n", "")
752
+ msg = self._redact(None if not msg else msg.replace("\n", ""))
705
753
  self.seq_no += 1
706
754
  wmsg = f"status\t{self.run_id}\t{self.seq_no}\t{self._ts()}\texception\t{msg or ''}\n"
707
755
  self.writelog(wmsg)
708
756
 
709
757
  def log_status_error(self, msg: str | None) -> None:
710
- msg = None if not msg else msg.replace("\n", "")
758
+ msg = self._redact(None if not msg else msg.replace("\n", ""))
711
759
  self.seq_no += 1
712
760
  wmsg = f"status\t{self.run_id}\t{self.seq_no}\t{self._ts()}\terror\t{msg or ''}\n"
713
761
  self.writelog(wmsg)
714
762
 
715
763
  def log_status_info(self, msg: str | None) -> None:
716
- msg = None if not msg else msg.replace("\n", "")
764
+ msg = self._redact(None if not msg else msg.replace("\n", ""))
717
765
  self.seq_no += 1
718
766
  wmsg = f"status\t{self.run_id}\t{self.seq_no}\t{self._ts()}\tinfo\t{msg or ''}\n"
719
767
  self.writelog(wmsg)
720
768
 
721
769
  def log_status_warning(self, msg: str | None) -> None:
722
- msg = None if not msg else msg.replace("\n", "")
770
+ msg = self._redact(None if not msg else msg.replace("\n", ""))
723
771
  self.seq_no += 1
724
772
  wmsg = f"status\t{self.run_id}\t{self.seq_no}\t{self._ts()}\twarning\t{msg or ''}\n"
725
773
  self.writelog(wmsg)
726
774
 
727
775
  def log_sql_query(self, sql: str, db_name: str, line_no: int | None = None) -> None:
728
776
  """Log an executed SQL statement for audit purposes."""
729
- cleaned = sql.replace("\n", " ").replace("\t", " ")
777
+ cleaned = self._redact(sql.replace("\n", " ").replace("\t", " ")) or ""
730
778
  if len(cleaned) > 2000:
731
779
  cleaned = cleaned[:2000] + "..."
732
780
  self.seq_no += 1
@@ -735,7 +783,7 @@ class Logger:
735
783
  self.writelog(wmsg)
736
784
 
737
785
  def log_user_msg(self, msg: str | None) -> None:
738
- msg = None if not msg else msg.replace("\n", "")
786
+ msg = self._redact(None if not msg else msg.replace("\n", ""))
739
787
  if msg != "":
740
788
  self.seq_no += 1
741
789
  wmsg = f"user_msg\t{self.run_id}\t{self.seq_no}\t{self._ts()}\tinfo\t{msg}\n"
@@ -760,14 +808,14 @@ class Logger:
760
808
  self.exit_type = "exception"
761
809
  self.exit_scriptfile = None
762
810
  self.exit_lno = None
763
- self.exit_description = msg.replace("\n", "")
811
+ self.exit_description = self._redact(msg.replace("\n", ""))
764
812
 
765
813
  def log_exit_error(self, msg: str | None) -> None:
766
814
  # Save values to be used by exit() function triggered on program exit
767
815
  self.exit_type = "error"
768
816
  self.exit_scriptfile = None
769
817
  self.exit_lno = None
770
- self.exit_description = None if not msg else msg.replace("\n", "")
818
+ self.exit_description = self._redact(None if not msg else msg.replace("\n", ""))
771
819
 
772
820
  def log_exit(self) -> None:
773
821
  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 msvcrt.kbhit():
694
- ch = msvcrt.getwch()
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 msvcrt.kbhit():
707
- ch = msvcrt.getwch()
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 type(strval) in (int, float):
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\.\-\\\/\'~`!@#$^&(+={{}}\[\]:;,])",