execsql2 2.2.1__py3-none-any.whl → 2.4.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.
- execsql/cli/run.py +11 -5
- execsql/config.py +52 -0
- execsql/db/access.py +11 -3
- execsql/db/base.py +180 -135
- execsql/db/dsn.py +4 -0
- execsql/db/duckdb.py +4 -0
- execsql/db/factory.py +31 -5
- execsql/db/firebird.py +4 -0
- execsql/db/mysql.py +18 -1
- execsql/db/oracle.py +4 -0
- execsql/db/postgres.py +3 -0
- execsql/db/sqlite.py +3 -0
- execsql/db/sqlserver.py +11 -2
- execsql/exceptions.py +18 -0
- execsql/exporters/base.py +6 -0
- execsql/exporters/delimited.py +36 -0
- execsql/exporters/duckdb.py +4 -0
- execsql/exporters/feather.py +4 -0
- execsql/exporters/html.py +6 -0
- execsql/exporters/json.py +5 -6
- execsql/exporters/latex.py +4 -0
- execsql/exporters/ods.py +28 -7
- execsql/exporters/parquet.py +3 -0
- execsql/exporters/pretty.py +5 -0
- execsql/exporters/raw.py +5 -3
- execsql/exporters/sqlite.py +4 -0
- execsql/exporters/templates.py +16 -6
- execsql/exporters/values.py +4 -0
- execsql/exporters/xls.py +26 -7
- execsql/exporters/xml.py +3 -0
- execsql/exporters/zip.py +15 -0
- execsql/importers/base.py +5 -3
- execsql/importers/csv.py +7 -5
- execsql/importers/feather.py +6 -4
- execsql/importers/ods.py +2 -0
- execsql/importers/xls.py +2 -0
- execsql/metacommands/__init__.py +177 -1968
- execsql/metacommands/dispatch.py +2011 -0
- execsql/models.py +7 -0
- execsql/parser.py +10 -0
- execsql/script/__init__.py +95 -0
- execsql/script/control.py +162 -0
- execsql/{script.py → script/engine.py} +144 -406
- execsql/script/variables.py +281 -0
- execsql/types.py +29 -0
- execsql/utils/auth.py +2 -0
- execsql/utils/crypto.py +4 -6
- execsql/utils/datetime.py +1 -0
- execsql/utils/errors.py +11 -0
- execsql/utils/fileio.py +18 -0
- execsql/utils/gui.py +46 -0
- execsql/utils/mail.py +7 -17
- execsql/utils/numeric.py +2 -0
- execsql/utils/regex.py +9 -0
- execsql/utils/strings.py +16 -0
- execsql/utils/timer.py +2 -0
- execsql2-2.4.1.data/data/execsql2_extras/README.md +65 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/execsql.conf +1 -1
- {execsql2-2.2.1.dist-info → execsql2-2.4.1.dist-info}/METADATA +8 -1
- execsql2-2.4.1.dist-info/RECORD +108 -0
- execsql2-2.2.1.data/data/execsql2_extras/READ_ME.rst +0 -127
- execsql2-2.2.1.dist-info/RECORD +0 -104
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.2.1.dist-info → execsql2-2.4.1.dist-info}/WHEEL +0 -0
- {execsql2-2.2.1.dist-info → execsql2-2.4.1.dist-info}/entry_points.txt +0 -0
- {execsql2-2.2.1.dist-info → execsql2-2.4.1.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.2.1.dist-info → execsql2-2.4.1.dist-info}/licenses/NOTICE +0 -0
execsql/cli/run.py
CHANGED
|
@@ -141,18 +141,19 @@ def _run(
|
|
|
141
141
|
raise SystemExit(1)
|
|
142
142
|
db_type = db_type or parsed_dsn["db_type"]
|
|
143
143
|
conf.db_type = db_type
|
|
144
|
-
|
|
144
|
+
# DSN values override conf-file values — the CLI flag is explicit.
|
|
145
|
+
if parsed_dsn["server"]:
|
|
145
146
|
conf.server = parsed_dsn["server"]
|
|
146
|
-
if parsed_dsn["db"]
|
|
147
|
+
if parsed_dsn["db"]:
|
|
147
148
|
conf.db = parsed_dsn["db"]
|
|
148
|
-
if parsed_dsn["db_file"]
|
|
149
|
+
if parsed_dsn["db_file"]:
|
|
149
150
|
conf.db_file = parsed_dsn["db_file"]
|
|
150
|
-
if parsed_dsn["user"]
|
|
151
|
+
if parsed_dsn["user"]:
|
|
151
152
|
user = parsed_dsn["user"]
|
|
152
153
|
if parsed_dsn["password"]:
|
|
153
154
|
conf.db_password = parsed_dsn["password"]
|
|
154
155
|
conf.passwd_prompt = False
|
|
155
|
-
if parsed_dsn["port"]
|
|
156
|
+
if parsed_dsn["port"]:
|
|
156
157
|
port = parsed_dsn["port"]
|
|
157
158
|
|
|
158
159
|
# Apply CLI options over config-file values
|
|
@@ -538,6 +539,7 @@ def _connect_initial_db(conf: ConfigData):
|
|
|
538
539
|
pw_needed=conf.passwd_prompt,
|
|
539
540
|
port=conf.port,
|
|
540
541
|
encoding=conf.db_encoding,
|
|
542
|
+
password=getattr(conf, "db_password", None),
|
|
541
543
|
)
|
|
542
544
|
elif conf.db_type == "l":
|
|
543
545
|
if conf.db_file is None:
|
|
@@ -553,6 +555,7 @@ def _connect_initial_db(conf: ConfigData):
|
|
|
553
555
|
pw_needed=conf.passwd_prompt,
|
|
554
556
|
port=conf.port,
|
|
555
557
|
encoding=conf.db_encoding,
|
|
558
|
+
password=getattr(conf, "db_password", None),
|
|
556
559
|
)
|
|
557
560
|
elif conf.db_type == "k":
|
|
558
561
|
if conf.db_file is None:
|
|
@@ -568,6 +571,7 @@ def _connect_initial_db(conf: ConfigData):
|
|
|
568
571
|
pw_needed=conf.passwd_prompt,
|
|
569
572
|
port=conf.port,
|
|
570
573
|
encoding=conf.db_encoding,
|
|
574
|
+
password=getattr(conf, "db_password", None),
|
|
571
575
|
)
|
|
572
576
|
elif conf.db_type == "f":
|
|
573
577
|
return db_Firebird(
|
|
@@ -577,6 +581,7 @@ def _connect_initial_db(conf: ConfigData):
|
|
|
577
581
|
pw_needed=conf.passwd_prompt,
|
|
578
582
|
port=conf.port,
|
|
579
583
|
encoding=conf.db_encoding,
|
|
584
|
+
password=getattr(conf, "db_password", None),
|
|
580
585
|
)
|
|
581
586
|
elif conf.db_type == "d":
|
|
582
587
|
return db_Dsn(
|
|
@@ -584,6 +589,7 @@ def _connect_initial_db(conf: ConfigData):
|
|
|
584
589
|
user=conf.username,
|
|
585
590
|
pw_needed=conf.passwd_prompt,
|
|
586
591
|
encoding=conf.db_encoding,
|
|
592
|
+
password=getattr(conf, "db_password", None),
|
|
587
593
|
)
|
|
588
594
|
else:
|
|
589
595
|
from execsql.utils.errors import fatal_error
|
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(
|
|
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(
|
|
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,
|
|
370
|
+
if isinstance(val, datetime.date | datetime.datetime | datetime.time):
|
|
363
371
|
return val
|
|
364
372
|
else:
|
|
365
373
|
try:
|