execsql2 2.2.1__py3-none-any.whl → 2.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. execsql/config.py +52 -0
  2. execsql/db/access.py +11 -3
  3. execsql/db/base.py +180 -135
  4. execsql/db/dsn.py +4 -0
  5. execsql/db/duckdb.py +4 -0
  6. execsql/db/factory.py +21 -0
  7. execsql/db/firebird.py +4 -0
  8. execsql/db/mysql.py +4 -0
  9. execsql/db/oracle.py +4 -0
  10. execsql/db/postgres.py +3 -0
  11. execsql/db/sqlite.py +3 -0
  12. execsql/db/sqlserver.py +11 -2
  13. execsql/exceptions.py +18 -0
  14. execsql/exporters/base.py +6 -0
  15. execsql/exporters/delimited.py +36 -0
  16. execsql/exporters/duckdb.py +4 -0
  17. execsql/exporters/feather.py +4 -0
  18. execsql/exporters/html.py +6 -0
  19. execsql/exporters/json.py +5 -6
  20. execsql/exporters/latex.py +4 -0
  21. execsql/exporters/ods.py +28 -7
  22. execsql/exporters/parquet.py +3 -0
  23. execsql/exporters/pretty.py +5 -0
  24. execsql/exporters/raw.py +5 -3
  25. execsql/exporters/sqlite.py +4 -0
  26. execsql/exporters/templates.py +16 -6
  27. execsql/exporters/values.py +4 -0
  28. execsql/exporters/xls.py +26 -7
  29. execsql/exporters/xml.py +3 -0
  30. execsql/exporters/zip.py +15 -0
  31. execsql/importers/base.py +2 -0
  32. execsql/importers/csv.py +2 -0
  33. execsql/importers/feather.py +2 -0
  34. execsql/importers/ods.py +2 -0
  35. execsql/importers/xls.py +2 -0
  36. execsql/metacommands/__init__.py +177 -1968
  37. execsql/metacommands/dispatch.py +2011 -0
  38. execsql/models.py +7 -0
  39. execsql/parser.py +10 -0
  40. execsql/script/__init__.py +95 -0
  41. execsql/script/control.py +162 -0
  42. execsql/{script.py → script/engine.py} +144 -406
  43. execsql/script/variables.py +281 -0
  44. execsql/types.py +29 -0
  45. execsql/utils/auth.py +2 -0
  46. execsql/utils/crypto.py +4 -6
  47. execsql/utils/datetime.py +1 -0
  48. execsql/utils/errors.py +11 -0
  49. execsql/utils/fileio.py +18 -0
  50. execsql/utils/gui.py +46 -0
  51. execsql/utils/mail.py +7 -17
  52. execsql/utils/numeric.py +2 -0
  53. execsql/utils/regex.py +9 -0
  54. execsql/utils/strings.py +16 -0
  55. execsql/utils/timer.py +2 -0
  56. execsql2-2.4.0.data/data/execsql2_extras/README.md +65 -0
  57. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/execsql.conf +1 -1
  58. {execsql2-2.2.1.dist-info → execsql2-2.4.0.dist-info}/METADATA +8 -1
  59. execsql2-2.4.0.dist-info/RECORD +108 -0
  60. execsql2-2.2.1.data/data/execsql2_extras/READ_ME.rst +0 -127
  61. execsql2-2.2.1.dist-info/RECORD +0 -104
  62. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  63. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  64. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
  65. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/md_compare.sql +0 -0
  66. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
  67. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
  68. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
  69. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  70. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  71. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/script_template.sql +0 -0
  72. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
  73. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  74. {execsql2-2.2.1.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  75. {execsql2-2.2.1.dist-info → execsql2-2.4.0.dist-info}/WHEEL +0 -0
  76. {execsql2-2.2.1.dist-info → execsql2-2.4.0.dist-info}/entry_points.txt +0 -0
  77. {execsql2-2.2.1.dist-info → execsql2-2.4.0.dist-info}/licenses/LICENSE.txt +0 -0
  78. {execsql2-2.2.1.dist-info → execsql2-2.4.0.dist-info}/licenses/NOTICE +0 -0
execsql/config.py CHANGED
@@ -23,12 +23,25 @@ from pathlib import Path
23
23
  from execsql.exceptions import ConfigError
24
24
  from execsql.utils.crypto import Encrypt
25
25
 
26
+ __all__ = [
27
+ "StatObj",
28
+ "ConfigData",
29
+ "WriteHooks",
30
+ ]
31
+
26
32
 
27
33
  class StatObj:
34
+ """Lightweight container for runtime status flags used by the metacommand engine.
35
+
36
+ Tracks error conditions, halt-on-error policy, dialog cancellation state,
37
+ and the current batch nesting level.
38
+ """
39
+
28
40
  # A generic object to maintain status indicators. These status
29
41
  # indicators are primarily those used in the metacommand
30
42
  # environment rather than for the program as a whole.
31
43
  def __init__(self) -> None:
44
+ """Initialise all status flags to their default (non-error) values."""
32
45
  self.halt_on_err = True
33
46
  self.sql_error = False
34
47
  self.halt_on_metacommand_err = True
@@ -42,6 +55,13 @@ class StatObj:
42
55
 
43
56
 
44
57
  class ConfigData:
58
+ """Reads and merges ``execsql.conf`` INI files, exposing all options as attributes.
59
+
60
+ Searches system, user, script-directory, and working-directory locations
61
+ (in that order) and applies each file's settings cumulatively so that
62
+ later files override earlier ones.
63
+ """
64
+
45
65
  config_file_name = "execsql.conf"
46
66
  _CONNECT_SECTION = "connect"
47
67
  _ENCODING_SECTION = "encoding"
@@ -55,6 +75,15 @@ class ConfigData:
55
75
  _INCLUDE_OPT_SECTION = "include_optional"
56
76
 
57
77
  def __init__(self, script_path: str, variable_pool: object) -> None:
78
+ """Load and merge all discoverable execsql.conf files for the given script path.
79
+
80
+ Args:
81
+ script_path: Directory of the running script; used to locate a
82
+ script-adjacent ``execsql.conf``.
83
+ variable_pool: Substitution-variable registry used to expand
84
+ ``config_file`` path values and to populate ``[variables]``
85
+ sections.
86
+ """
58
87
  self.db_type = "a"
59
88
  self.server = None
60
89
  self.port = None
@@ -547,6 +576,12 @@ class ConfigData:
547
576
 
548
577
 
549
578
  class WriteHooks:
579
+ """Thin wrapper around stdout/stderr that supports GUI or test-harness redirection.
580
+
581
+ Each output hook is a callable that accepts a single string. When a hook
582
+ is ``None`` the default ``sys.stdout`` or ``sys.stderr`` is used.
583
+ """
584
+
550
585
  def __repr__(self) -> str:
551
586
  return f"WriteHooks({self.write_func!r}, {self.err_func!r}, {self.status_func!r})"
552
587
 
@@ -556,6 +591,16 @@ class WriteHooks:
556
591
  error_output_func: object = None,
557
592
  status_output_func: object = None,
558
593
  ) -> None:
594
+ """Store optional hook callables; ``None`` means use the default stream.
595
+
596
+ Args:
597
+ standard_output_func: Callable to receive standard-output text, or
598
+ ``None`` to use ``sys.stdout``.
599
+ error_output_func: Callable to receive error-output text, or
600
+ ``None`` to use ``sys.stderr``.
601
+ status_output_func: Callable to receive status-line text, or
602
+ ``None`` to suppress.
603
+ """
559
604
  # Arguments should be functions that take a single string and
560
605
  # write it to the desired destination. Both stdout and stderr can be hooked.
561
606
  # If a hook function is not specified, the default of stdout or stderr will
@@ -567,22 +612,27 @@ class WriteHooks:
567
612
  self.tee_stderr = True
568
613
 
569
614
  def reset(self) -> None:
615
+ """Reset both output hooks to ``None``, restoring stdout/stderr behaviour."""
570
616
  # Resets output to stdout and stderr.
571
617
  self.write_func = None
572
618
  self.err_func = None
573
619
 
574
620
  def redir_stdout(self, standard_output_func: object) -> None:
621
+ """Replace the standard-output hook with the given callable."""
575
622
  self.write_func = standard_output_func
576
623
 
577
624
  def redir_stderr(self, error_output_func: object, tee: bool = True) -> None:
625
+ """Replace the error-output hook and optionally keep tee-to-stderr behaviour."""
578
626
  self.err_func = error_output_func
579
627
  self.tee_stderr = tee
580
628
 
581
629
  def redir(self, standard_output_func: object, error_output_func: object) -> None:
630
+ """Redirect both stdout and stderr hooks in one call."""
582
631
  self.redir_stdout(standard_output_func)
583
632
  self.redir_stderr(error_output_func)
584
633
 
585
634
  def write(self, strval: str) -> None:
635
+ """Write a string to the standard-output hook, or to sys.stdout if unset."""
586
636
  if self.write_func:
587
637
  self.write_func(strval)
588
638
  else:
@@ -590,6 +640,7 @@ class WriteHooks:
590
640
  sys.stdout.flush()
591
641
 
592
642
  def write_err(self, strval: str) -> None:
643
+ """Write an error string to the error-output hook, or to sys.stderr if unset."""
593
644
  if strval[-1] != "\n":
594
645
  strval += "\n"
595
646
  if self.err_func:
@@ -602,5 +653,6 @@ class WriteHooks:
602
653
  sys.stderr.flush()
603
654
 
604
655
  def write_status(self, strval: str) -> None:
656
+ """Forward a status string to the status hook if one is registered."""
605
657
  if self.status_func:
606
658
  self.status_func(strval)
execsql/db/access.py CHANGED
@@ -20,8 +20,12 @@ from execsql.utils.errors import exception_desc, fatal_error
20
20
  from execsql.utils.auth import clear_stored_password, get_password, password_from_keyring
21
21
  import execsql.state as _state
22
22
 
23
+ __all__ = ["AccessDatabase"]
24
+
23
25
 
24
26
  class AccessDatabase(Database):
27
+ """MS Access adapter connecting to .mdb/.accdb files via DAO (win32com) with pyodbc fallback."""
28
+
25
29
  # Regex for the 'create temporary view' SQL extension
26
30
  temp_rx = re.compile(
27
31
  r"^\s*create(?:\s+or\s+replace)?(\s+temp(?:orary)?)?\s+(?:(view|query))\s+(\w+) as\s+",
@@ -103,9 +107,13 @@ class AccessDatabase(Database):
103
107
  try:
104
108
  self.conn = pyodbc.connect(connstr)
105
109
  except Exception:
106
- _state.exec_log.log_status_info(f"Could not connect via ODBC using: {connstr}")
110
+ _state.exec_log.log_status_info(
111
+ f"Could not connect via ODBC using: {re.sub(r'Pwd=[^;]*', 'Pwd=***', connstr)}",
112
+ )
107
113
  else:
108
- _state.exec_log.log_status_info(f"Connected via ODBC using: {connstr}")
114
+ _state.exec_log.log_status_info(
115
+ f"Connected via ODBC using: {re.sub(r'Pwd=[^;]*', 'Pwd=***', connstr)}",
116
+ )
109
117
  self.jet4 = jet4flag
110
118
  return True
111
119
  return False
@@ -359,7 +367,7 @@ class AccessDatabase(Database):
359
367
 
360
368
  if val is None or (isinstance(val, _state.stringtypes) and len(val) == 0):
361
369
  return None
362
- if isinstance(val, (datetime.date, datetime.datetime, datetime.time)):
370
+ if isinstance(val, datetime.date | datetime.datetime | datetime.time):
363
371
  return val
364
372
  else:
365
373
  try: