execsql2 2.13.2__py3-none-any.whl → 2.15.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/gui/base.py +52 -1
- execsql/gui/console.py +86 -9
- execsql/gui/desktop.py +261 -39
- execsql/gui/tui.py +325 -51
- execsql/metacommands/connect.py +5 -1
- execsql/metacommands/dispatch.py +49 -6
- execsql/metacommands/io_export.py +2 -2
- execsql/metacommands/prompt.py +6 -11
- execsql/metacommands/upsert.py +125 -17
- execsql/utils/gui.py +2 -2
- {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/METADATA +3 -3
- {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/RECORD +31 -31
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/WHEEL +0 -0
- {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/licenses/NOTICE +0 -0
execsql/metacommands/dispatch.py
CHANGED
|
@@ -857,7 +857,7 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
857
857
|
x_write_warnings,
|
|
858
858
|
)
|
|
859
859
|
mcl.add(
|
|
860
|
-
r"^\s*CONFIG\s+GUI_LEVEL\s+(?P<level>[0-
|
|
860
|
+
r"^\s*CONFIG\s+GUI_LEVEL\s+(?P<level>[0-3])\s*$",
|
|
861
861
|
x_gui_level,
|
|
862
862
|
description="GUI_LEVEL",
|
|
863
863
|
category="config_option",
|
|
@@ -1443,7 +1443,7 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
1443
1443
|
ins_table_rxs(
|
|
1444
1444
|
r'^\s*PROMPT\s+ENTER_SUB\s+(?P<match_str>~?\w+)\s+(?:(?P<password>PASSWORD)\s+)?MESSAGE\s+"(?P<message>([^"]|\n)*)"(?:\s+DISPLAY\s+',
|
|
1445
1445
|
r")?(?:\s+TYPE\s+(?P<type>INT|FLOAT|BOOL|IDENT))?(?:\s+(?P<case>LCASE|UCASE))?"
|
|
1446
|
-
r'(?:\s+INITIALLY\s+"(?P<initial>[^"]+)")?(?:\s+HELP\s+"(?P<help>[
|
|
1446
|
+
r'(?:\s+INITIALLY\s+"(?P<initial>[^"]+)")?(?:\s+HELP\s+"(?P<help>[^"]+)")?\s*$',
|
|
1447
1447
|
),
|
|
1448
1448
|
x_prompt_enter,
|
|
1449
1449
|
)
|
|
@@ -1527,6 +1527,7 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
1527
1527
|
# ------------------------------------------------------------------
|
|
1528
1528
|
# PROMPT COMPARE / PROMPT ASK COMPARE
|
|
1529
1529
|
# ------------------------------------------------------------------
|
|
1530
|
+
# HELP before MESSAGE (documented order)
|
|
1530
1531
|
mcl.add(
|
|
1531
1532
|
ins_table_rxs(
|
|
1532
1533
|
r"^\s*PROMPT\s+COMPARE\s+",
|
|
@@ -1555,6 +1556,33 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
1555
1556
|
),
|
|
1556
1557
|
x_prompt_compare,
|
|
1557
1558
|
)
|
|
1559
|
+
# HELP after MESSAGE (alternate order)
|
|
1560
|
+
mcl.add(
|
|
1561
|
+
ins_table_rxs(
|
|
1562
|
+
r"^\s*PROMPT\s+COMPARE\s+",
|
|
1563
|
+
ins_table_rxs(
|
|
1564
|
+
r"(?:\s+IN\s+(?P<alias1>\w+))?\s+(?P<orient>AND|BESIDE)\s+",
|
|
1565
|
+
r'(?:\s+IN\s+(?P<alias2>\w+))?\s+(?:PK|KEY)\s*\((?P<pks>(("[A-Z_0-9]+")|[A-Z_0-9]+)'
|
|
1566
|
+
r'(\s*,\s*(("[A-Z_0-9]+")|[A-Z_0-9]+))*)\)\s+MESSAGE\s+"(?P<msg>[^"]*)"\s+HELP\s+(?P<help>[^\s]+)\s*$',
|
|
1567
|
+
suffix="2",
|
|
1568
|
+
),
|
|
1569
|
+
suffix="1",
|
|
1570
|
+
),
|
|
1571
|
+
x_prompt_compare,
|
|
1572
|
+
)
|
|
1573
|
+
mcl.add(
|
|
1574
|
+
ins_table_rxs(
|
|
1575
|
+
r"^\s*PROMPT\s+COMPARE\s+",
|
|
1576
|
+
ins_table_rxs(
|
|
1577
|
+
r"(?:\s+IN\s+(?P<alias1>\w+))?\s+(?P<orient>AND|BESIDE)\s+",
|
|
1578
|
+
r'(?:\s+IN\s+(?P<alias2>\w+))?\s+(?:PK|KEY)\s*\((?P<pks>(("[A-Z_0-9]+")|[A-Z_0-9]+)'
|
|
1579
|
+
r'(\s*,\s*(("[A-Z_0-9]+")|[A-Z_0-9]+))*)\)\s+MESSAGE\s+"(?P<msg>[^"]*)"\s+HELP\s+"(?P<help>[^"]+)"\s*$',
|
|
1580
|
+
suffix="2",
|
|
1581
|
+
),
|
|
1582
|
+
suffix="1",
|
|
1583
|
+
),
|
|
1584
|
+
x_prompt_compare,
|
|
1585
|
+
)
|
|
1558
1586
|
mcl.add(
|
|
1559
1587
|
ins_table_rxs(
|
|
1560
1588
|
r'^\s*PROMPT\s+ASK\s+"(?P<msg>(.|\n)*)"\s+SUB\s+(?P<match>~?\w+)\s+COMPARE\s+',
|
|
@@ -2073,7 +2101,7 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
2073
2101
|
mcl.add(
|
|
2074
2102
|
ins_table_rxs(
|
|
2075
2103
|
r'^\s*PROMPT\s+MESSAGE\s+"(?P<message>(.|\n)*)"\s+DISPLAY\s+',
|
|
2076
|
-
r"(?:\s+HELP\s+(?P<help>[^\s]+))
|
|
2104
|
+
r"(?:\s+HELP\s+(?P<help>[^\s]+))?\s*$",
|
|
2077
2105
|
),
|
|
2078
2106
|
x_prompt,
|
|
2079
2107
|
description="PROMPT DISPLAY",
|
|
@@ -2082,21 +2110,36 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
2082
2110
|
mcl.add(
|
|
2083
2111
|
ins_table_rxs(
|
|
2084
2112
|
r'^\s*PROMPT\s+MESSAGE\s+"(?P<message>(.|\n)*)"\s+DISPLAY\s+',
|
|
2085
|
-
r'(?:\s+HELP\s+"(?P<help>[^"]+)")
|
|
2113
|
+
r'(?:\s+HELP\s+"(?P<help>[^"]+)")?\s*$',
|
|
2114
|
+
),
|
|
2115
|
+
x_prompt,
|
|
2116
|
+
)
|
|
2117
|
+
mcl.add(
|
|
2118
|
+
ins_table_rxs(
|
|
2119
|
+
r"^\s*PROMPT\s+DISPLAY\s+",
|
|
2120
|
+
r'\s+MESSAGE\s+"(?P<message>(.|\n)*)"(?:\s+HELP\s+(?P<help>[^\s]+))?\s*$',
|
|
2121
|
+
),
|
|
2122
|
+
x_prompt,
|
|
2123
|
+
)
|
|
2124
|
+
mcl.add(
|
|
2125
|
+
ins_table_rxs(
|
|
2126
|
+
r"^\s*PROMPT\s+DISPLAY\s+",
|
|
2127
|
+
r'\s+MESSAGE\s+"(?P<message>(.|\n)*)"(?:\s+HELP\s+"(?P<help>[^"]+)")?\s*$',
|
|
2086
2128
|
),
|
|
2087
2129
|
x_prompt,
|
|
2088
2130
|
)
|
|
2131
|
+
# PROMPT DISPLAY with HELP after MESSAGE (non-greedy message to avoid capturing HELP)
|
|
2089
2132
|
mcl.add(
|
|
2090
2133
|
ins_table_rxs(
|
|
2091
2134
|
r"^\s*PROMPT\s+DISPLAY\s+",
|
|
2092
|
-
r'\s+MESSAGE\s+"(?P<message>
|
|
2135
|
+
r'\s+MESSAGE\s+"(?P<message>[^"]*)"\s+HELP\s+(?P<help>[^\s]+)\s*$',
|
|
2093
2136
|
),
|
|
2094
2137
|
x_prompt,
|
|
2095
2138
|
)
|
|
2096
2139
|
mcl.add(
|
|
2097
2140
|
ins_table_rxs(
|
|
2098
2141
|
r"^\s*PROMPT\s+DISPLAY\s+",
|
|
2099
|
-
r'\s+MESSAGE\s+"(?P<message>
|
|
2142
|
+
r'\s+MESSAGE\s+"(?P<message>[^"]*)"\s+HELP\s+"(?P<help>[^"]+)"\s*$',
|
|
2100
2143
|
),
|
|
2101
2144
|
x_prompt,
|
|
2102
2145
|
)
|
|
@@ -460,7 +460,7 @@ def x_export_ods_multiple(**kwargs: Any) -> None:
|
|
|
460
460
|
tee = kwargs["tee"]
|
|
461
461
|
tee = bool(tee)
|
|
462
462
|
append = kwargs["append"]
|
|
463
|
-
append = append
|
|
463
|
+
append = bool(append)
|
|
464
464
|
check_dir(outfile)
|
|
465
465
|
write_queries_to_ods(table_list, _state.dbs.current(), outfile, append, tee, desc=description)
|
|
466
466
|
|
|
@@ -473,7 +473,7 @@ def x_export_xlsx_multiple(**kwargs: Any) -> None:
|
|
|
473
473
|
tee = kwargs["tee"]
|
|
474
474
|
tee = bool(tee)
|
|
475
475
|
append = kwargs["append"]
|
|
476
|
-
append = append
|
|
476
|
+
append = bool(append)
|
|
477
477
|
check_dir(outfile)
|
|
478
478
|
write_queries_to_xlsx(table_list, _state.dbs.current(), outfile, append, tee, desc=description)
|
|
479
479
|
|
execsql/metacommands/prompt.py
CHANGED
|
@@ -62,7 +62,6 @@ def x_prompt(**kwargs: Any) -> None:
|
|
|
62
62
|
table = kwargs["table"]
|
|
63
63
|
message = kwargs["message"]
|
|
64
64
|
help_url = unquoted(kwargs["help"])
|
|
65
|
-
free = kwargs["free"] is not None
|
|
66
65
|
sq_name = db.schema_qualified_table_name(schema, table)
|
|
67
66
|
script, line_no = current_script_line()
|
|
68
67
|
cmd = f"select * from {sq_name};"
|
|
@@ -76,16 +75,14 @@ def x_prompt(**kwargs: Any) -> None:
|
|
|
76
75
|
"column_headers": colnames,
|
|
77
76
|
"rowset": rows,
|
|
78
77
|
"help_url": help_url,
|
|
79
|
-
"free": free,
|
|
80
78
|
}
|
|
81
79
|
_state.gui_manager_queue.put(GuiSpec(GUI_DISPLAY, gui_args, return_queue))
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
exit_now(2, None)
|
|
80
|
+
user_response = return_queue.get(block=True)
|
|
81
|
+
btn = user_response["button"]
|
|
82
|
+
if not btn and _state.status.cancel_halt:
|
|
83
|
+
msg = f"Halted from display of {sq_name}"
|
|
84
|
+
_state.exec_log.log_exit_halt(script, line_no, msg)
|
|
85
|
+
exit_now(2, None)
|
|
89
86
|
return None
|
|
90
87
|
|
|
91
88
|
|
|
@@ -119,7 +116,6 @@ def x_prompt_enter(**kwargs: Any) -> None:
|
|
|
119
116
|
"textentrycase": textcase,
|
|
120
117
|
"initialtext": initial,
|
|
121
118
|
"help_url": help_url,
|
|
122
|
-
"free": False,
|
|
123
119
|
}
|
|
124
120
|
_state.gui_manager_queue.put(GuiSpec(GUI_DISPLAY, gui_args, return_queue))
|
|
125
121
|
user_response = return_queue.get(block=True)
|
|
@@ -862,7 +858,6 @@ def x_ask(**kwargs: Any) -> None:
|
|
|
862
858
|
"title": script,
|
|
863
859
|
"message": kwargs["question"],
|
|
864
860
|
"button_list": [("Yes", 1, "y"), ("No", 0, "n")],
|
|
865
|
-
"free": False,
|
|
866
861
|
}
|
|
867
862
|
_state.gui_manager_queue.put(GuiSpec(GUI_DISPLAY, gui_args, return_queue))
|
|
868
863
|
user_response = return_queue.get(block=True)
|
execsql/metacommands/upsert.py
CHANGED
|
@@ -26,11 +26,11 @@ from execsql.utils.errors import exception_desc
|
|
|
26
26
|
|
|
27
27
|
_KW_METHOD = re.compile(r"\bMETHOD\s+(upsert|update|insert)\b", re.IGNORECASE)
|
|
28
28
|
_KW_EXCLUDE = re.compile(
|
|
29
|
-
r"\bEXCLUDE\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|LOGFILE|CLEANUP)\b|\s*$)",
|
|
29
|
+
r"\bEXCLUDE\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|LOGFILE|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS)\b|\s*$)",
|
|
30
30
|
re.IGNORECASE,
|
|
31
31
|
)
|
|
32
32
|
_KW_EXCLUDE_NULL = re.compile(
|
|
33
|
-
r"\bEXCLUDE_NULL\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE|LOGFILE|CLEANUP)\b|\s*$)",
|
|
33
|
+
r"\bEXCLUDE_NULL\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE|LOGFILE|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS)\b|\s*$)",
|
|
34
34
|
re.IGNORECASE,
|
|
35
35
|
)
|
|
36
36
|
_KW_COMMIT = re.compile(r"\bCOMMIT\b", re.IGNORECASE)
|
|
@@ -38,13 +38,21 @@ _KW_INTERACTIVE = re.compile(r"\bINTERACTIVE\b", re.IGNORECASE)
|
|
|
38
38
|
_KW_COMPACT = re.compile(r"\bCOMPACT\b", re.IGNORECASE)
|
|
39
39
|
_KW_CLEANUP = re.compile(r"\bCLEANUP\b", re.IGNORECASE)
|
|
40
40
|
_KW_LOGFILE = re.compile(r"""\bLOGFILE\s+(?:"([^"]+)"|'([^']+)'|(\S+))""", re.IGNORECASE)
|
|
41
|
+
_KW_EXPORT_FAILURES = re.compile(
|
|
42
|
+
r"""\bEXPORT_FAILURES\s+(?:"([^"]+)"|'([^']+)'|(\S+))""",
|
|
43
|
+
re.IGNORECASE,
|
|
44
|
+
)
|
|
45
|
+
_KW_EXPORT_FORMAT = re.compile(r"\bEXPORT_FORMAT\s+(\S+)", re.IGNORECASE)
|
|
46
|
+
_KW_EXPORT_MAX_ROWS = re.compile(r"\bEXPORT_MAX_ROWS\s+(\S+)", re.IGNORECASE)
|
|
41
47
|
|
|
42
48
|
# All recognized keywords — used to split table names from options.
|
|
43
49
|
_ALL_KEYWORDS = re.compile(
|
|
44
|
-
r"\b(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|EXCLUDE|LOGFILE|CLEANUP)\b",
|
|
50
|
+
r"\b(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|EXCLUDE|LOGFILE|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS)\b",
|
|
45
51
|
re.IGNORECASE,
|
|
46
52
|
)
|
|
47
53
|
|
|
54
|
+
_VALID_EXPORT_FORMATS = ("csv", "json", "xlsx")
|
|
55
|
+
|
|
48
56
|
|
|
49
57
|
def _parse_tables_and_options(tail: str) -> dict[str, Any]:
|
|
50
58
|
"""Parse the trailing text after ``TABLES`` into table names and options.
|
|
@@ -90,6 +98,40 @@ def _parse_tables_and_options(tail: str) -> dict[str, Any]:
|
|
|
90
98
|
if m:
|
|
91
99
|
logfile = m.group(1) or m.group(2) or m.group(3)
|
|
92
100
|
|
|
101
|
+
export_failures: str | None = None
|
|
102
|
+
m = _KW_EXPORT_FAILURES.search(opts_part)
|
|
103
|
+
if m:
|
|
104
|
+
export_failures = m.group(1) or m.group(2) or m.group(3)
|
|
105
|
+
|
|
106
|
+
export_format = "csv"
|
|
107
|
+
m = _KW_EXPORT_FORMAT.search(opts_part)
|
|
108
|
+
if m:
|
|
109
|
+
fmt = m.group(1).lower()
|
|
110
|
+
if fmt not in _VALID_EXPORT_FORMATS:
|
|
111
|
+
raise ErrInfo(
|
|
112
|
+
"cmd",
|
|
113
|
+
other_msg=(
|
|
114
|
+
f"PG_UPSERT: unsupported EXPORT_FORMAT {m.group(1)!r}. "
|
|
115
|
+
f"Supported: {', '.join(_VALID_EXPORT_FORMATS)}"
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
export_format = fmt
|
|
119
|
+
|
|
120
|
+
export_max_rows = 1000
|
|
121
|
+
m = _KW_EXPORT_MAX_ROWS.search(opts_part)
|
|
122
|
+
if m:
|
|
123
|
+
raw = m.group(1)
|
|
124
|
+
try:
|
|
125
|
+
val = int(raw)
|
|
126
|
+
if val <= 0:
|
|
127
|
+
raise ValueError
|
|
128
|
+
except ValueError as exc:
|
|
129
|
+
raise ErrInfo(
|
|
130
|
+
"cmd",
|
|
131
|
+
other_msg=(f"PG_UPSERT: EXPORT_MAX_ROWS must be a positive integer, got {raw!r}"),
|
|
132
|
+
) from exc
|
|
133
|
+
export_max_rows = val
|
|
134
|
+
|
|
93
135
|
return {
|
|
94
136
|
"tables": tables,
|
|
95
137
|
"method": method,
|
|
@@ -100,6 +142,9 @@ def _parse_tables_and_options(tail: str) -> dict[str, Any]:
|
|
|
100
142
|
"exclude_null_check_cols": exclude_null,
|
|
101
143
|
"logfile": logfile,
|
|
102
144
|
"cleanup": bool(_KW_CLEANUP.search(opts_part)),
|
|
145
|
+
"export_failures": export_failures,
|
|
146
|
+
"export_format": export_format,
|
|
147
|
+
"export_max_rows": export_max_rows,
|
|
103
148
|
}
|
|
104
149
|
|
|
105
150
|
|
|
@@ -157,6 +202,9 @@ def _set_subvars(result: Any) -> None:
|
|
|
157
202
|
sv("$PG_UPSERT_STARTED_AT", result.started_at)
|
|
158
203
|
sv("$PG_UPSERT_FINISHED_AT", result.finished_at)
|
|
159
204
|
sv("$PG_UPSERT_RESULT_JSON", json.dumps(result.to_dict(), separators=(",", ":")))
|
|
205
|
+
# Default export path subvar to empty; _export_failures_if_requested
|
|
206
|
+
# will overwrite it with the actual path if an export was produced.
|
|
207
|
+
sv("$PG_UPSERT_EXPORT_PATH", "")
|
|
160
208
|
|
|
161
209
|
|
|
162
210
|
def _qa_failure_msg(result: Any) -> str:
|
|
@@ -241,20 +289,26 @@ def _create_pgupsert(
|
|
|
241
289
|
if _state.conf:
|
|
242
290
|
ui_mode = _state.conf.gui_framework
|
|
243
291
|
|
|
244
|
-
|
|
245
|
-
conn
|
|
246
|
-
staging_schema
|
|
247
|
-
base_schema
|
|
248
|
-
tables
|
|
249
|
-
do_commit
|
|
250
|
-
interactive
|
|
251
|
-
compact
|
|
252
|
-
upsert_method
|
|
253
|
-
exclude_cols
|
|
254
|
-
exclude_null_check_cols
|
|
255
|
-
ui_mode
|
|
256
|
-
callback
|
|
257
|
-
|
|
292
|
+
kwargs: dict[str, Any] = {
|
|
293
|
+
"conn": db.conn,
|
|
294
|
+
"staging_schema": staging_schema,
|
|
295
|
+
"base_schema": base_schema,
|
|
296
|
+
"tables": opts["tables"],
|
|
297
|
+
"do_commit": opts["commit"],
|
|
298
|
+
"interactive": opts["interactive"],
|
|
299
|
+
"compact": opts["compact"],
|
|
300
|
+
"upsert_method": opts["method"],
|
|
301
|
+
"exclude_cols": opts["exclude_cols"],
|
|
302
|
+
"exclude_null_check_cols": opts["exclude_null_check_cols"],
|
|
303
|
+
"ui_mode": ui_mode,
|
|
304
|
+
"callback": _make_callback(),
|
|
305
|
+
}
|
|
306
|
+
# Only pass fix-sheet capture args when an export was requested, so the
|
|
307
|
+
# metacommand stays compatible with any pg-upsert build that lacks them.
|
|
308
|
+
if opts.get("export_failures"):
|
|
309
|
+
kwargs["capture_detail_rows"] = True
|
|
310
|
+
kwargs["max_export_rows"] = opts.get("export_max_rows", 1000)
|
|
311
|
+
ups = PgUpsert(**kwargs)
|
|
258
312
|
return ups
|
|
259
313
|
|
|
260
314
|
|
|
@@ -328,6 +382,57 @@ def _run_with_autocommit_guard(db: Any, fn: Any) -> Any:
|
|
|
328
382
|
db.autocommit_on()
|
|
329
383
|
|
|
330
384
|
|
|
385
|
+
def _export_failures_if_requested(
|
|
386
|
+
result: Any,
|
|
387
|
+
opts: dict[str, Any],
|
|
388
|
+
metacommandline: str | None,
|
|
389
|
+
) -> None:
|
|
390
|
+
"""Export a QA fix sheet if EXPORT_FAILURES was given in the metacommand.
|
|
391
|
+
|
|
392
|
+
Always called after ``_set_subvars(result)`` so ``$PG_UPSERT_EXPORT_PATH``
|
|
393
|
+
is initialized to empty first, then overwritten here on a successful
|
|
394
|
+
export. Called even when QA failed — that's the whole point of the
|
|
395
|
+
fix sheet.
|
|
396
|
+
"""
|
|
397
|
+
path = opts.get("export_failures")
|
|
398
|
+
if not path:
|
|
399
|
+
return
|
|
400
|
+
fmt = opts["export_format"]
|
|
401
|
+
try:
|
|
402
|
+
exported = result.export_failures(path, fmt=fmt)
|
|
403
|
+
except Exception as exc:
|
|
404
|
+
raise ErrInfo(
|
|
405
|
+
"exception",
|
|
406
|
+
exception_msg=exception_desc(),
|
|
407
|
+
other_msg=(f"PG_UPSERT failed to export failure sheet to {path}"),
|
|
408
|
+
) from exc
|
|
409
|
+
_state.subvars.add_substitution(
|
|
410
|
+
"$PG_UPSERT_EXPORT_PATH",
|
|
411
|
+
str(exported) if exported else "",
|
|
412
|
+
)
|
|
413
|
+
if exported:
|
|
414
|
+
msg = f"PG_UPSERT: exported QA failures to {exported} ({fmt})"
|
|
415
|
+
else:
|
|
416
|
+
msg = f"PG_UPSERT: no QA failures to export (EXPORT_FAILURES {path} skipped)"
|
|
417
|
+
_state.exec_log.log_user_msg(msg)
|
|
418
|
+
try:
|
|
419
|
+
_state.output.write(msg + "\n")
|
|
420
|
+
except Exception:
|
|
421
|
+
# Output sink may be unavailable in some contexts (e.g. tests);
|
|
422
|
+
# the log message above is sufficient in that case.
|
|
423
|
+
pass
|
|
424
|
+
# Tee to the LOGFILE keyword target if one was given, matching how the
|
|
425
|
+
# pg-upsert display output is routed there via _FileWriterHandler.
|
|
426
|
+
logfile = opts.get("logfile")
|
|
427
|
+
if logfile:
|
|
428
|
+
try:
|
|
429
|
+
from execsql.utils.fileio import filewriter_write
|
|
430
|
+
|
|
431
|
+
filewriter_write(logfile, msg + "\n")
|
|
432
|
+
except Exception:
|
|
433
|
+
pass
|
|
434
|
+
|
|
435
|
+
|
|
331
436
|
def _handle_pg_upsert_errors(fn: Any, metacommandline: str | None) -> Any:
|
|
332
437
|
"""Run *fn*, translating pg-upsert exceptions to ErrInfo."""
|
|
333
438
|
from pg_upsert import UserCancelledError
|
|
@@ -381,6 +486,7 @@ def x_pg_upsert(**kwargs: Any) -> None:
|
|
|
381
486
|
_detach_log_handlers(loggers, handlers, prev_levels)
|
|
382
487
|
|
|
383
488
|
_set_subvars(result)
|
|
489
|
+
_export_failures_if_requested(result, opts, metacommandline)
|
|
384
490
|
if opts.get("cleanup"):
|
|
385
491
|
ups.cleanup()
|
|
386
492
|
|
|
@@ -420,6 +526,7 @@ def x_pg_upsert_qa(**kwargs: Any) -> None:
|
|
|
420
526
|
|
|
421
527
|
result = _build_result_from_qa_errors(ups)
|
|
422
528
|
_set_subvars(result)
|
|
529
|
+
_export_failures_if_requested(result, opts, metacommandline)
|
|
423
530
|
if opts.get("cleanup"):
|
|
424
531
|
ups.cleanup()
|
|
425
532
|
|
|
@@ -462,6 +569,7 @@ def x_pg_upsert_check(**kwargs: Any) -> None:
|
|
|
462
569
|
|
|
463
570
|
result = _build_result_from_qa_errors(ups)
|
|
464
571
|
_set_subvars(result)
|
|
572
|
+
_export_failures_if_requested(result, opts, metacommandline)
|
|
465
573
|
if opts.get("cleanup"):
|
|
466
574
|
ups.cleanup()
|
|
467
575
|
|
execsql/utils/gui.py
CHANGED
|
@@ -498,10 +498,10 @@ def gui_credentials(
|
|
|
498
498
|
cmd:
|
|
499
499
|
The originating metacommand line (for logging only).
|
|
500
500
|
"""
|
|
501
|
+
import getpass as _getpass
|
|
501
502
|
import queue as _queue
|
|
502
503
|
|
|
503
504
|
import execsql.state as _state
|
|
504
|
-
from execsql.utils.auth import get_password
|
|
505
505
|
|
|
506
506
|
gui_level = _state.conf.gui_level if _state.conf else 0
|
|
507
507
|
if gui_level > 0 and _state.gui_manager_thread is not None and _state.gui_manager_thread.is_alive():
|
|
@@ -515,7 +515,7 @@ def gui_credentials(
|
|
|
515
515
|
if message:
|
|
516
516
|
print(message, file=sys.stderr)
|
|
517
517
|
uname = input("Username: ")
|
|
518
|
-
passwd =
|
|
518
|
+
passwd = _getpass.getpass(f"Password for {uname}: ")
|
|
519
519
|
|
|
520
520
|
if username:
|
|
521
521
|
_state.subvars.add_substitution(username, uname)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.15.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
|
|
@@ -51,7 +51,7 @@ Requires-Dist: keyring; extra == 'all'
|
|
|
51
51
|
Requires-Dist: odfpy; extra == 'all'
|
|
52
52
|
Requires-Dist: openpyxl; extra == 'all'
|
|
53
53
|
Requires-Dist: oracledb; extra == 'all'
|
|
54
|
-
Requires-Dist: pg-upsert>=1.
|
|
54
|
+
Requires-Dist: pg-upsert>=1.21.0; extra == 'all'
|
|
55
55
|
Requires-Dist: polars; extra == 'all'
|
|
56
56
|
Requires-Dist: psycopg2-binary; extra == 'all'
|
|
57
57
|
Requires-Dist: pymysql; extra == 'all'
|
|
@@ -109,7 +109,7 @@ Requires-Dist: oracledb; extra == 'oracle'
|
|
|
109
109
|
Provides-Extra: postgres
|
|
110
110
|
Requires-Dist: psycopg2-binary; extra == 'postgres'
|
|
111
111
|
Provides-Extra: upsert
|
|
112
|
-
Requires-Dist: pg-upsert>=1.
|
|
112
|
+
Requires-Dist: pg-upsert>=1.21.0; extra == 'upsert'
|
|
113
113
|
Description-Content-Type: text/markdown
|
|
114
114
|
|
|
115
115
|
> [!NOTE]
|
|
@@ -51,10 +51,10 @@ execsql/exporters/xml.py,sha256=lqcOM8uKDoCayU42BPSLNH1_2DIHU5D3LtQItREU90c,2564
|
|
|
51
51
|
execsql/exporters/yaml.py,sha256=1Vuc6uMDuLTkCuXCfXWKz4gLkkAVdEXkLs4gEB_67Xo,3110
|
|
52
52
|
execsql/exporters/zip.py,sha256=kr0X6VLE_ULGVQtMzfqZZSUmc1Qupxg9HbMOAvnTQvI,4890
|
|
53
53
|
execsql/gui/__init__.py,sha256=oCb-cyhLZzVpWJ4WU5HbqEDBrV-lm0ytEwxemrOZyqs,2048
|
|
54
|
-
execsql/gui/base.py,sha256=
|
|
55
|
-
execsql/gui/console.py,sha256=
|
|
56
|
-
execsql/gui/desktop.py,sha256=
|
|
57
|
-
execsql/gui/tui.py,sha256=
|
|
54
|
+
execsql/gui/base.py,sha256=Kwguh8CGgs5kRFgnpaIEt1yOMP7ejj7TwXrtbiRpnm0,7776
|
|
55
|
+
execsql/gui/console.py,sha256=5vDTHx9jJHQZ47WdKMWOTqNHgeG_CbD7e1o9QCYS0X8,18040
|
|
56
|
+
execsql/gui/desktop.py,sha256=TWehraUdvBBj9SiBmUuW8DOHdWq-Kux0RJ6fKBt_xyA,53182
|
|
57
|
+
execsql/gui/tui.py,sha256=AQ0vtITTIbxwGfa21FfsDZ6nj2wMsVaiRkPyfZ5-udw,62718
|
|
58
58
|
execsql/importers/__init__.py,sha256=dDsxSVeQYXBvm9yGqN3QswyGbLWTwt08pvUuRJgZhl4,289
|
|
59
59
|
execsql/importers/base.py,sha256=FGVz3ntN6xHL99rQixlQj3tAf570K_oU83UtbYE1lJg,4124
|
|
60
60
|
execsql/importers/csv.py,sha256=Mu848WNzuhVO1ade-WurPyxqGOuVNRO8UwRF3-bav_I,4845
|
|
@@ -64,20 +64,20 @@ execsql/importers/ods.py,sha256=MJsdsjropzCvxAA3DDZfAL_AnmZ4yij7DnrjGyDJqHQ,2843
|
|
|
64
64
|
execsql/importers/xls.py,sha256=e0Zfe47ZiCpA1Ae3XDJ1ko3sCiH3-8U6XLKi6NvD0jQ,3683
|
|
65
65
|
execsql/metacommands/__init__.py,sha256=3Kz-VasFS9B-C-UdHOjr3RMXjheMeYHe6qYBwp5e7wE,11434
|
|
66
66
|
execsql/metacommands/conditions.py,sha256=Fzrk83-pWbFOoKahYdQW7CZjQeh3zByDUbfgpTM_bjQ,29259
|
|
67
|
-
execsql/metacommands/connect.py,sha256=
|
|
67
|
+
execsql/metacommands/connect.py,sha256=U17ByjVmwrLN5DB7Ea9lTLyFJr8On2c9LfPO1Wc4l4U,14890
|
|
68
68
|
execsql/metacommands/control.py,sha256=PAZFK1ck5SDSm5QdFV1ctif3KpEiyYWIXdDceRWgQ6k,8513
|
|
69
69
|
execsql/metacommands/data.py,sha256=tRQBGTAuW-eJ2tBNWaoZI9OjTyNNyHJISo7gOdL-sm8,11370
|
|
70
70
|
execsql/metacommands/debug.py,sha256=pnT24dfvfOx8xFu86mO5czfVCGKbcvgBLyXnqaMWO4w,8184
|
|
71
|
-
execsql/metacommands/dispatch.py,sha256=
|
|
71
|
+
execsql/metacommands/dispatch.py,sha256=rY259pp5Bcq1q0N86NvxFgmFyOhYrNtgByzrNjocipI,87083
|
|
72
72
|
execsql/metacommands/io.py,sha256=vlGBje5sgnqeilooMdhJDgSRIhysHy5_7LrKtik9Xjs,3011
|
|
73
|
-
execsql/metacommands/io_export.py,sha256=
|
|
73
|
+
execsql/metacommands/io_export.py,sha256=c04uiFX3KcPqHUNiId0gF4sWBVZizZL95pL0aSr7mrM,18347
|
|
74
74
|
execsql/metacommands/io_fileops.py,sha256=RrcJTh_cgj7bJ-bezjo0yNl-fN3CoWV-aZ71z1KHYZs,9803
|
|
75
75
|
execsql/metacommands/io_import.py,sha256=fzShl_m74DWt2_SmxlxWQe00H53hDIrOYZtvvqKBhSM,14231
|
|
76
76
|
execsql/metacommands/io_write.py,sha256=NpL2aYGfBpbqmPpYsqniYltYfd_SCA1EQz3_4qSdNbo,8279
|
|
77
|
-
execsql/metacommands/prompt.py,sha256=
|
|
77
|
+
execsql/metacommands/prompt.py,sha256=E2e7q4pxbl_wEBrhco0B2gm5hO_HG3rNIF75PLdTgGg,36767
|
|
78
78
|
execsql/metacommands/script_ext.py,sha256=TUgAldB2LSJAwZrCvDDi804hQ1d9BDQD2GDqHNPVOcM,2280
|
|
79
79
|
execsql/metacommands/system.py,sha256=AeyxbMLZVOi2nThbHiZ2F_UAIrZ1r4kjI3G80TtnSD4,7385
|
|
80
|
-
execsql/metacommands/upsert.py,sha256=
|
|
80
|
+
execsql/metacommands/upsert.py,sha256=W_5EEcp4QbDpq1LdS7Dz9eTa6do1D1a9aro2OCYZaMY,20032
|
|
81
81
|
execsql/script/__init__.py,sha256=HbVQmQEVn4gBtzwy5_nlbDGuRnbWd4dI4nG-q1KyBxs,3498
|
|
82
82
|
execsql/script/control.py,sha256=s-1eZdGARM6H1FwZ6VDdO_f50j7bvvRtTHesfUm9tbc,6144
|
|
83
83
|
execsql/script/engine.py,sha256=6LYabzy1LI-_ISjYzTJos0BrLO62QF6FEKdqcN0YzK4,42995
|
|
@@ -88,30 +88,30 @@ execsql/utils/crypto.py,sha256=2OnBWwn9bCBGc1ZkyRv16TvhottoCNYtXqgbE3mG3Sg,2960
|
|
|
88
88
|
execsql/utils/datetime.py,sha256=V_itd5vVvUPjT86P_z_hh4mlerMDGhDzI5MwPMDBaI4,7715
|
|
89
89
|
execsql/utils/errors.py,sha256=YKhYD27-3timuZavc2vIrRIfHa71vzih-KVPsAKgvkU,8163
|
|
90
90
|
execsql/utils/fileio.py,sha256=RGaMUe-e0xIw32OeprNJCezh0Jr9gQimQNqOXBEaJ2M,24338
|
|
91
|
-
execsql/utils/gui.py,sha256=
|
|
91
|
+
execsql/utils/gui.py,sha256=eZeFJm8EaWnzeHIw_O-tn9hO8sxGjZRX_aUFDtGQp4w,18396
|
|
92
92
|
execsql/utils/mail.py,sha256=Sd7vWj-dz3w0XDSFU9PM8gmy41pojk-Vsgbfven2DMk,5786
|
|
93
93
|
execsql/utils/numeric.py,sha256=xh02ANSRk3nUpQ-rtm66ILoMqoi7HtzCoRMIOT9U8QI,1570
|
|
94
94
|
execsql/utils/regex.py,sha256=diEzTZqU_HHwVMadPAvN1Vgzhl7I03eVaEFGCXyGGL8,3770
|
|
95
95
|
execsql/utils/strings.py,sha256=5Dvzrk-9SIw2lpxXZQkiJbNyo1sy7iXXAtSULlZ0KG8,8488
|
|
96
96
|
execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
|
|
97
|
-
execsql2-2.
|
|
98
|
-
execsql2-2.
|
|
99
|
-
execsql2-2.
|
|
100
|
-
execsql2-2.
|
|
101
|
-
execsql2-2.
|
|
102
|
-
execsql2-2.
|
|
103
|
-
execsql2-2.
|
|
104
|
-
execsql2-2.
|
|
105
|
-
execsql2-2.
|
|
106
|
-
execsql2-2.
|
|
107
|
-
execsql2-2.
|
|
108
|
-
execsql2-2.
|
|
109
|
-
execsql2-2.
|
|
110
|
-
execsql2-2.
|
|
111
|
-
execsql2-2.
|
|
112
|
-
execsql2-2.
|
|
113
|
-
execsql2-2.
|
|
114
|
-
execsql2-2.
|
|
115
|
-
execsql2-2.
|
|
116
|
-
execsql2-2.
|
|
117
|
-
execsql2-2.
|
|
97
|
+
execsql2-2.15.0.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
|
|
98
|
+
execsql2-2.15.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
|
|
99
|
+
execsql2-2.15.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
|
|
100
|
+
execsql2-2.15.0.data/data/execsql2_extras/execsql.conf,sha256=_45iJ-KWZnB8uMW_gEg067MM5pmGJ-dVl7VbAZMunAE,9530
|
|
101
|
+
execsql2-2.15.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
|
|
102
|
+
execsql2-2.15.0.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
|
|
103
|
+
execsql2-2.15.0.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
|
|
104
|
+
execsql2-2.15.0.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
|
|
105
|
+
execsql2-2.15.0.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
|
|
106
|
+
execsql2-2.15.0.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
|
|
107
|
+
execsql2-2.15.0.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
|
|
108
|
+
execsql2-2.15.0.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
|
|
109
|
+
execsql2-2.15.0.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
|
|
110
|
+
execsql2-2.15.0.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
|
|
111
|
+
execsql2-2.15.0.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
|
|
112
|
+
execsql2-2.15.0.dist-info/METADATA,sha256=4EszZ5kfmBXU3czXlw0mfPpf7vNyPYLd69_Y20nlzMw,17566
|
|
113
|
+
execsql2-2.15.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
114
|
+
execsql2-2.15.0.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
|
|
115
|
+
execsql2-2.15.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
|
|
116
|
+
execsql2-2.15.0.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
|
|
117
|
+
execsql2-2.15.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/example_config_prompt.sql
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|