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.
Files changed (31) hide show
  1. execsql/gui/base.py +52 -1
  2. execsql/gui/console.py +86 -9
  3. execsql/gui/desktop.py +261 -39
  4. execsql/gui/tui.py +325 -51
  5. execsql/metacommands/connect.py +5 -1
  6. execsql/metacommands/dispatch.py +49 -6
  7. execsql/metacommands/io_export.py +2 -2
  8. execsql/metacommands/prompt.py +6 -11
  9. execsql/metacommands/upsert.py +125 -17
  10. execsql/utils/gui.py +2 -2
  11. {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/METADATA +3 -3
  12. {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/RECORD +31 -31
  13. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/README.md +0 -0
  14. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  15. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  16. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/execsql.conf +0 -0
  17. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
  18. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/md_compare.sql +0 -0
  19. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
  20. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
  21. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
  22. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  23. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  24. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/script_template.sql +0 -0
  25. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
  26. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  27. {execsql2-2.13.2.data → execsql2-2.15.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  28. {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/WHEEL +0 -0
  29. {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/entry_points.txt +0 -0
  30. {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/licenses/LICENSE.txt +0 -0
  31. {execsql2-2.13.2.dist-info → execsql2-2.15.0.dist-info}/licenses/NOTICE +0 -0
@@ -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-2])\s*$",
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>[^+]+)")?\s*$',
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]+))?(?:\s+(?P<free>FREE))?\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>[^"]+)")?(?:\s+(?P<free>FREE))?\s*$',
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>(.|\n)*)"(?:\s+HELP\s+(?P<help>[^\s]+))?(?:\s+(?P<free>FREE))?\s*$',
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>(.|\n)*)"(?:\s+HELP\s+"(?P<help>[^"]+)")?(?:\s+(?P<free>FREE))?\s*$',
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 is not None
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 is not None
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
 
@@ -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
- if not free:
83
- user_response = return_queue.get(block=True)
84
- btn = user_response["button"]
85
- if not btn and _state.status.cancel_halt:
86
- msg = f"Halted from display of {sq_name}"
87
- _state.exec_log.log_exit_halt(script, line_no, msg)
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)
@@ -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
- ups = PgUpsert(
245
- conn=db.conn,
246
- staging_schema=staging_schema,
247
- base_schema=base_schema,
248
- tables=opts["tables"],
249
- do_commit=opts["commit"],
250
- interactive=opts["interactive"],
251
- compact=opts["compact"],
252
- upsert_method=opts["method"],
253
- exclude_cols=opts["exclude_cols"],
254
- exclude_null_check_cols=opts["exclude_null_check_cols"],
255
- ui_mode=ui_mode,
256
- callback=_make_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 = get_password(f"Password for {uname}: ")
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.13.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.20.0; extra == 'all'
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.20.0; extra == 'upsert'
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=sfNRkDrf7FhIgMRUOdyZpRLS1Xk9RqNhrV0A1RP6PXU,6068
55
- execsql/gui/console.py,sha256=TuGzm7XFxa8iWrofGLwx5DuVIlr3wqqyP_pSnAJ1S3s,14271
56
- execsql/gui/desktop.py,sha256=LxRzX0R5iOfXO-OFGm2nzgUZqzGtMJYm0VgY3TV5feA,42152
57
- execsql/gui/tui.py,sha256=lBwUrePXMF0eWbFqkJ8kBLEHEr3tc-wmxwbyFjs-gfk,50579
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=Nsm0D91i3RX-R2rzQQ-Br-gULaI6Uvdn9fqb7DOAVfE,14804
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=LYmTuSfC0nFKgAPWRK8n8Qf8sbrRtRmbh6ntosOjQEo,85587
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=7lkCSnPhXy9FVau9_hT1u68NOVdG2DsWmvUh9hM1QWI,18359
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=xd3mAkdbn4AE4hUPuSfN5DgZGUZmk-Db23iL-JJPwXs,36918
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=P2935aQHDZPiVwnXi0fGQ7Guxrm-Sy_YunyuSqVSegI,15880
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=UFtwrXPNqNvZCJFCbumZ1aG2d9B-vyaJXIG83fDHteo,18409
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.13.2.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
98
- execsql2-2.13.2.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
99
- execsql2-2.13.2.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
100
- execsql2-2.13.2.data/data/execsql2_extras/execsql.conf,sha256=_45iJ-KWZnB8uMW_gEg067MM5pmGJ-dVl7VbAZMunAE,9530
101
- execsql2-2.13.2.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
102
- execsql2-2.13.2.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
103
- execsql2-2.13.2.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
104
- execsql2-2.13.2.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
105
- execsql2-2.13.2.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
106
- execsql2-2.13.2.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
107
- execsql2-2.13.2.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
108
- execsql2-2.13.2.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
109
- execsql2-2.13.2.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
110
- execsql2-2.13.2.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
111
- execsql2-2.13.2.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
112
- execsql2-2.13.2.dist-info/METADATA,sha256=R_jJi0OgB5NoPJ6g8mLse9YttgHU2EIZxlCZADPe9d0,17566
113
- execsql2-2.13.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
114
- execsql2-2.13.2.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
115
- execsql2-2.13.2.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
116
- execsql2-2.13.2.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
117
- execsql2-2.13.2.dist-info/RECORD,,
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,,