execsql2 2.22.2__py3-none-any.whl → 2.22.4__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 (25) hide show
  1. execsql/format.py +12 -1
  2. execsql/gui/base.py +56 -1
  3. execsql/gui/console.py +10 -33
  4. execsql/gui/tui.py +45 -15
  5. execsql/metacommands/control.py +26 -0
  6. {execsql2-2.22.2.dist-info → execsql2-2.22.4.dist-info}/METADATA +3 -3
  7. {execsql2-2.22.2.dist-info → execsql2-2.22.4.dist-info}/RECORD +25 -25
  8. {execsql2-2.22.2.dist-info → execsql2-2.22.4.dist-info}/WHEEL +1 -1
  9. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/README.md +0 -0
  10. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  11. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  12. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/make_config_db.sql +0 -0
  13. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/md_compare.sql +0 -0
  14. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/md_glossary.sql +0 -0
  15. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/md_upsert.sql +0 -0
  16. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/pg_compare.sql +0 -0
  17. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  18. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  19. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/script_template.sql +0 -0
  20. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/ss_compare.sql +0 -0
  21. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  22. {execsql2-2.22.2.data → execsql2-2.22.4.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  23. {execsql2-2.22.2.dist-info → execsql2-2.22.4.dist-info}/entry_points.txt +0 -0
  24. {execsql2-2.22.2.dist-info → execsql2-2.22.4.dist-info}/licenses/LICENSE.txt +0 -0
  25. {execsql2-2.22.2.dist-info → execsql2-2.22.4.dist-info}/licenses/NOTICE +0 -0
execsql/format.py CHANGED
@@ -642,6 +642,12 @@ def format_file(source: str, indent: int = 4, use_sql: bool = True, leading_comm
642
642
  # "body" for `$body$`, etc.). Nested markers with a different tag
643
643
  # are ignored — only a matching close marker re-opens us.
644
644
  current_dq_tag: str | None = None
645
+ # Sticky flag: True once the current accumulator has seen ANY dollar-quote
646
+ # marker. `in_dollar_quote` alone is False by the time a complete
647
+ # `CREATE FUNCTION ... $$ ... $$;` is flushed (the closing `$$` reset it),
648
+ # which would wrongly hand the PL/pgSQL body to sqlglot. This flag keeps the
649
+ # whole containing statement opaque to sqlglot. Reset in flush_sql().
650
+ sql_acc_contains_dollar_quote = False
645
651
  in_block_comment = False
646
652
  # Track whether we are inside an open SQL statement (last SQL line
647
653
  # did not end with ';'). Blank lines mid-statement should NOT flush
@@ -655,15 +661,19 @@ def format_file(source: str, indent: int = 4, use_sql: bool = True, leading_comm
655
661
 
656
662
  def flush_sql() -> None:
657
663
  nonlocal in_dollar_quote, current_dq_tag, in_sql_statement
664
+ nonlocal sql_acc_contains_dollar_quote
658
665
  if sql_acc:
659
666
  # If any line in the accumulated block is inside a $$-delimited
660
667
  # region, skip sqlglot formatting entirely. PL/pgSQL function
661
668
  # bodies contain IF/END IF, LOOP, RETURN, etc. that sqlglot does
662
669
  # not understand and will corrupt (e.g., rewriting to COMMIT).
663
- safe_for_sqlglot = use_sql and not in_dollar_quote
670
+ # `sql_acc_contains_dollar_quote` catches the case where the region
671
+ # already closed (`... $$;`) so `in_dollar_quote` is False at flush.
672
+ safe_for_sqlglot = use_sql and not in_dollar_quote and not sql_acc_contains_dollar_quote
664
673
  output.extend(format_sql_block(sql_acc, depth, indent, safe_for_sqlglot, leading_comma=leading_comma))
665
674
  sql_acc.clear()
666
675
  in_sql_statement = False
676
+ sql_acc_contains_dollar_quote = False
667
677
 
668
678
  for raw_line in source.expandtabs(4).splitlines():
669
679
  stripped_line = raw_line.strip()
@@ -727,6 +737,7 @@ def format_file(source: str, indent: int = 4, use_sql: bool = True, leading_comm
727
737
  # PL/pgSQL. Walk every dollar-quote marker on the line; toggle
728
738
  # state only when we hit the matching open or close.
729
739
  for m in _DOLLAR_QUOTE_RE.finditer(raw_line):
740
+ sql_acc_contains_dollar_quote = True
730
741
  tag = m.group(1) or ""
731
742
  if not in_dollar_quote:
732
743
  in_dollar_quote = True
execsql/gui/base.py CHANGED
@@ -9,11 +9,66 @@ from typing import TYPE_CHECKING, Any
9
9
  if TYPE_CHECKING:
10
10
  pass
11
11
 
12
- __all__ = ["DiffResult", "GuiBackend", "compare_stats", "compute_row_diffs"]
12
+ __all__ = ["DiffResult", "GuiBackend", "compare_stats", "compute_row_diffs", "format_table", "row_count_text"]
13
13
 
14
14
  DIFF_MARKER = "● "
15
15
 
16
16
 
17
+ # ---------------------------------------------------------------------------
18
+ # Plain-text table rendering (shared by the console backend and by
19
+ # non-interactive metacommand fallbacks such as HALT ... DISPLAY)
20
+ # ---------------------------------------------------------------------------
21
+
22
+
23
+ def row_count_text(n: int) -> str:
24
+ """Return a human-readable row count string, e.g. ``'3 rows'`` or ``'1 row'``."""
25
+ return f"{n:,} row{'s' if n != 1 else ''}"
26
+
27
+
28
+ def format_table(
29
+ headers: list,
30
+ rows: list,
31
+ row_states: list[str] | None = None,
32
+ changed_cols: list[set[str]] | None = None,
33
+ ) -> str:
34
+ """Render *headers* and *rows* as a plain-text table.
35
+
36
+ Columns are left-aligned and padded to the widest value. When *row_states*
37
+ and *changed_cols* are provided, cells that differ in ``"changed"`` rows are
38
+ prefixed with the diff marker (``"● "``) and the marker width is included in
39
+ the column sizing.
40
+
41
+ Returns an empty string when *headers* is empty. The result has no trailing
42
+ newline.
43
+ """
44
+ if not headers:
45
+ return ""
46
+ headers_str = [str(h) for h in headers]
47
+
48
+ # Pre-compute display values so column widths account for markers.
49
+ display_rows: list[list[str]] = []
50
+ for ridx, row in enumerate(rows):
51
+ cells: list[str] = []
52
+ state = row_states[ridx] if row_states and ridx < len(row_states) else ""
53
+ diff_set = changed_cols[ridx] if changed_cols and ridx < len(changed_cols) else set()
54
+ for ci, cell in enumerate(row):
55
+ val = str(cell) if cell is not None else ""
56
+ if state == "changed" and ci < len(headers_str) and headers_str[ci] in diff_set:
57
+ val = f"{DIFF_MARKER}{val}"
58
+ cells.append(val)
59
+ display_rows.append(cells)
60
+
61
+ col_widths = [len(h) for h in headers_str]
62
+ for cells in display_rows:
63
+ for i, cell in enumerate(cells):
64
+ col_widths[i] = max(col_widths[i], len(cell))
65
+ fmt = " " + " ".join(f"{{:<{w}}}" for w in col_widths)
66
+ sep = " " + " ".join("-" * w for w in col_widths)
67
+ lines = [fmt.format(*headers_str), sep]
68
+ lines.extend(fmt.format(*cells) for cells in display_rows)
69
+ return "\n".join(lines)
70
+
71
+
17
72
  # ---------------------------------------------------------------------------
18
73
  # Value comparison helpers
19
74
  # ---------------------------------------------------------------------------
execsql/gui/console.py CHANGED
@@ -11,7 +11,13 @@ import sys
11
11
  import time
12
12
  from typing import Any, cast
13
13
 
14
- from execsql.gui.base import DIFF_MARKER, GuiBackend, compare_stats as _compare_stats, compute_row_diffs
14
+ from execsql.gui.base import (
15
+ GuiBackend,
16
+ compare_stats as _compare_stats,
17
+ compute_row_diffs,
18
+ format_table,
19
+ row_count_text as _row_count_text,
20
+ )
15
21
 
16
22
  __all__ = ["ConsoleBackend"]
17
23
 
@@ -23,11 +29,6 @@ def _print_help_url(args: dict) -> None:
23
29
  print(f" Help: {url}", file=sys.stderr)
24
30
 
25
31
 
26
- def _row_count_text(n: int) -> str:
27
- """Return a human-readable row count string, e.g. '3 rows' or '1 row'."""
28
- return f"{n:,} row{'s' if n != 1 else ''}"
29
-
30
-
31
32
  def _print_table(
32
33
  headers: list,
33
34
  rows: list,
@@ -42,33 +43,9 @@ def _print_table(
42
43
  """
43
44
  if file is None:
44
45
  file = sys.stderr
45
- if not headers:
46
- return
47
- headers_str = [str(h) for h in headers]
48
-
49
- # Pre-compute display values so column widths account for markers.
50
- display_rows: list[list[str]] = []
51
- for ridx, row in enumerate(rows):
52
- cells: list[str] = []
53
- state = row_states[ridx] if row_states and ridx < len(row_states) else ""
54
- diff_set = changed_cols[ridx] if changed_cols and ridx < len(changed_cols) else set()
55
- for ci, cell in enumerate(row):
56
- val = str(cell) if cell is not None else ""
57
- if state == "changed" and ci < len(headers_str) and headers_str[ci] in diff_set:
58
- val = f"{DIFF_MARKER}{val}"
59
- cells.append(val)
60
- display_rows.append(cells)
61
-
62
- col_widths = [len(h) for h in headers_str]
63
- for cells in display_rows:
64
- for i, cell in enumerate(cells):
65
- col_widths[i] = max(col_widths[i], len(cell))
66
- fmt = " " + " ".join(f"{{:<{w}}}" for w in col_widths)
67
- sep = " " + " ".join("-" * w for w in col_widths)
68
- print(fmt.format(*headers_str), file=file)
69
- print(sep, file=file)
70
- for cells in display_rows:
71
- print(fmt.format(*cells), file=file)
46
+ rendered = format_table(headers, rows, row_states=row_states, changed_cols=changed_cols)
47
+ if rendered:
48
+ print(rendered, file=file)
72
49
 
73
50
 
74
51
  def _prompt_buttons(button_list: list) -> int | None:
execsql/gui/tui.py CHANGED
@@ -89,14 +89,18 @@ def _help_button(url: str | None) -> Button | None:
89
89
  return None
90
90
 
91
91
 
92
- def _button_row(button_list: list) -> list[Button]:
92
+ def _button_row(button_list: list, no_cancel: bool = False) -> list[Button]:
93
93
  """Create Button widgets from a button_list of (label, value, key?) tuples.
94
94
 
95
- A Cancel button (id ``btn_cancel_exit``) is always prepended so it appears
96
- on the left while the primary action button sits on the far right.
97
- Entries in *button_list* with a falsy value (0 or None) are treated as the
98
- cancel label source and are otherwise excluded from the numbered buttons so
99
- that ``btn_N`` indices stay aligned with ``button_list`` positions.
95
+ A Cancel button (id ``btn_cancel_exit``) is prepended so it appears on the
96
+ left while the primary action button sits on the far right. Entries in
97
+ *button_list* with a falsy value (0 or None) are treated as the cancel
98
+ label source and are otherwise excluded from the numbered buttons so that
99
+ ``btn_N`` indices stay aligned with ``button_list`` positions.
100
+
101
+ When *no_cancel* is set, the Cancel button is omitted — dialogs such as
102
+ HALT have no meaningful cancel action, and cancelling one would exit with
103
+ the generic status 2 rather than the status the caller asked for.
100
104
  """
101
105
  cancel_label = "Cancel"
102
106
  primary_buttons = []
@@ -107,6 +111,8 @@ def _button_row(button_list: list) -> list[Button]:
107
111
  else:
108
112
  variant: Literal["primary", "default"] = "primary" if i == 0 else "default"
109
113
  primary_buttons.append(Button(label, id=f"btn_{i}", variant=variant))
114
+ if no_cancel:
115
+ return primary_buttons
110
116
  return [Button(cancel_label, id="btn_cancel_exit", variant="warning")] + primary_buttons
111
117
 
112
118
 
@@ -182,7 +188,14 @@ class _BaseDialog(ModalScreen):
182
188
  return self._result
183
189
 
184
190
  def action_cancel(self) -> None:
185
- """Dismiss the dialog as cancelled (triggered by Escape key)."""
191
+ """Dismiss the dialog as cancelled (triggered by Escape key).
192
+
193
+ Ignored for dialogs that pass ``no_cancel`` (e.g. HALT), which have no
194
+ meaningful cancel action. This matches the Tkinter backend, which does
195
+ not bind ``<Escape>`` to cancellation in that case.
196
+ """
197
+ if self.args.get("no_cancel", False):
198
+ return
186
199
  self._result = {"button": None, "cancelled": True}
187
200
  self.dismiss(self._result)
188
201
 
@@ -210,31 +223,48 @@ class _BaseDialog(ModalScreen):
210
223
 
211
224
 
212
225
  class MsgScreen(_BaseDialog):
213
- """Simple message dialog with Continue and Cancel buttons."""
226
+ """Message dialog with an optional data table.
227
+
228
+ Honours the caller's ``button_list`` and ``no_cancel`` arguments, and
229
+ renders ``column_headers``/``rowset`` when supplied (as the HALT
230
+ metacommand's DISPLAY clause does), matching the Tkinter MsgDialog.
231
+ """
214
232
 
215
233
  BINDINGS = [
216
234
  *_BaseDialog.BINDINGS,
217
235
  Binding("enter", "submit", "Continue", show=True),
218
236
  ]
219
237
 
238
+ def _button_list(self) -> list:
239
+ return cast(list, self.args.get("button_list") or [("Continue", 1, "<Return>")])
240
+
220
241
  def compose(self) -> ComposeResult:
221
242
  title = self.args.get("title", "Message")
222
243
  message = self.args.get("message", "")
244
+ headers = self.args.get("column_headers")
245
+ rows = self.args.get("rowset")
223
246
  with Container(id="dialog"):
224
247
  yield Label(title, id="title")
225
248
  yield Static(message, id="message")
249
+ if headers and rows:
250
+ with ScrollableContainer(id="table_container"):
251
+ yield _make_table_widget("msg_table", headers, rows)
252
+ yield Static(_row_count_text(len(rows)), classes="row-count")
226
253
  with Horizontal(id="buttons"):
227
- yield Button("Cancel", id="btn_cancel_exit", variant="warning")
228
- yield Button("Continue", id="btn_close", variant="primary")
254
+ yield from _button_row(self._button_list(), no_cancel=self.args.get("no_cancel", False))
229
255
 
230
256
  def action_submit(self) -> None:
231
- """Continue the dialog (triggered by Enter key)."""
232
- self._result = {"button": 1}
233
- self.dismiss(self._result)
257
+ """Dismiss with the first non-cancel button value (triggered by Enter)."""
258
+ for btn in self._button_list():
259
+ if btn[1]:
260
+ self._result = {"button": btn[1]}
261
+ self.dismiss(self._result)
262
+ return
234
263
 
235
264
  def on_button_pressed(self, event: Button.Pressed) -> None:
236
- if event.button.id == "btn_close":
237
- self._result = {"button": 1}
265
+ btn_id = event.button.id
266
+ if btn_id and btn_id.startswith("btn_") and btn_id[4:].isdigit():
267
+ self._result = {"button": self._button_list()[int(btn_id[4:])][1]}
238
268
  self.dismiss(self._result)
239
269
 
240
270
 
@@ -33,6 +33,7 @@ from execsql.exceptions import ErrInfo
33
33
  from typing import Any
34
34
 
35
35
  import execsql.state as _state
36
+ from execsql.gui.base import format_table, row_count_text
36
37
  from execsql.script import current_script_line
37
38
  from execsql.utils.errors import exit_now
38
39
  from execsql.utils.fileio import EncodedFile, check_dir
@@ -214,6 +215,14 @@ def x_wait_until(**kwargs: Any) -> None:
214
215
 
215
216
  # x_halt_msg is also needed by x_halt - define it here
216
217
  def x_halt_msg(**kwargs: Any) -> None:
218
+ """Halt the script, reporting *errmsg* in a dialog or on the console.
219
+
220
+ The dialog is used only when the run is interactive — either a GUI console
221
+ is already running, or ``gui_level`` is greater than 1. Otherwise the
222
+ message (and any ``DISPLAY`` rowset) is written to the error output and the
223
+ script exits, so that unattended runs fail instead of blocking forever on a
224
+ modal nobody can dismiss.
225
+ """
217
226
  import queue as _queue
218
227
 
219
228
  errmsg = kwargs["errmsg"]
@@ -242,6 +251,23 @@ def x_halt_msg(**kwargs: Any) -> None:
242
251
  headers, rows = db.select_data(sql)
243
252
  else:
244
253
  headers, rows = None, None
254
+
255
+ # Non-interactive runs (no GUI console, gui_level 0 or 1) must not block on
256
+ # a modal dialog: report on the console and exit instead. This mirrors the
257
+ # guard in x_halt, which the message-bearing HALT forms never reach because
258
+ # their metacommand patterns are registered later and so take priority.
259
+ if not (gui_console_isrunning() or conf.gui_level > 1):
260
+ if errmsg:
261
+ _state.output.write_err(errmsg)
262
+ if headers:
263
+ # Unlike the dialog, render an empty rowset too — a HALT diagnostic
264
+ # showing zero rows is more useful than showing nothing at all.
265
+ _state.output.write_err(format_table(headers, rows or []))
266
+ _state.output.write_err(row_count_text(len(rows or [])))
267
+ _state.exec_log.log_exit_halt(*current_script_line(), msg=errmsg)
268
+ exit_now(errlevel, None)
269
+ return
270
+
245
271
  enable_gui()
246
272
  return_queue: _queue.Queue[Any] = _queue.Queue()
247
273
  gui_args = {
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: execsql2
3
- Version: 2.22.2
3
+ Version: 2.22.4
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
@@ -384,7 +384,7 @@ execsql-format --no-sql --in-place scripts/
384
384
  ```yaml
385
385
  repos:
386
386
  - repo: https://github.com/geocoug/execsql
387
- rev: v2.22.2
387
+ rev: v2.22.4
388
388
  hooks:
389
389
  - id: execsql-format
390
390
  ```
@@ -3,7 +3,7 @@ execsql/__main__.py,sha256=HdbK-SAhyUmfB6xINY5AzxdMSxGzWSGEG_2dv42Jn64,315
3
3
  execsql/api.py,sha256=EOtBwMnm-8fmB_KmvxyjglR6C7CjdYhCPBisJ9CFfJg,22227
4
4
  execsql/config.py,sha256=txKziUJLV7UPonj2sGFgkmlinDdSV50aHmR_cuCbEIo,34369
5
5
  execsql/exceptions.py,sha256=GlphAiR8iZu4UR4U50WgSqEUMd9_8AeQJ_hhPcJOeQU,9381
6
- execsql/format.py,sha256=ON8Jy5BSen4UI4SGY5cHKRwsvR0ZoLhH0BdblMwydYE,33670
6
+ execsql/format.py,sha256=3w-m0EsHapBdSKx6oTFWYlXspFLdUR_RF79gMvHhrqQ,34440
7
7
  execsql/models.py,sha256=-DWTVrPNwM-n1h3e3rXMoBv9-wpWVdZahyy4RB7fRRE,15502
8
8
  execsql/parser.py,sha256=aOxZ7Mzgvk7G4-nvy9Bgz7xlaZm6oo81cffDeBk_LCk,15597
9
9
  execsql/plugins.py,sha256=l82VWa5NpvOQWD6vcO-WKP-vtn0wp9__wWDFAbwxZg0,12838
@@ -55,10 +55,10 @@ execsql/exporters/xml.py,sha256=t1PSr3i8xvTCWzd9jaxxAy5NcRpl6sqjnZALafKCqpM,2575
55
55
  execsql/exporters/yaml.py,sha256=R61Rs_0rd5G3bg344L4hC84GM79IfYXaDO2tkF-BluY,3121
56
56
  execsql/exporters/zip.py,sha256=341z4Z4MPtEkXJEtfiMSiuJigERh5_f8RDbNMBbt8KM,5078
57
57
  execsql/gui/__init__.py,sha256=oCb-cyhLZzVpWJ4WU5HbqEDBrV-lm0ytEwxemrOZyqs,2048
58
- execsql/gui/base.py,sha256=15wCaSLvSb21hToCczru46ghmd56ryMpEOOZ0R_dE3U,12740
59
- execsql/gui/console.py,sha256=hgFnxPP7lLolKgmKCBTSMXIt4uU2L4C32XcA82ytr2U,19438
58
+ execsql/gui/base.py,sha256=DMTqHLlpZlGCsNKyrLlJPbaxvukC0n1YmR4aHEN3jPY,14983
59
+ execsql/gui/console.py,sha256=dzdSl4NJNvRd9VvlwRCXHvfnXjQPimyTx7NcibcUZC8,18305
60
60
  execsql/gui/desktop.py,sha256=922OdzjV4OGNDIesOxKOmlsrNp1aJ8St8cVwED0H2sE,59521
61
- execsql/gui/tui.py,sha256=yXKBqbbJDNC6a25sUKpvy3BR10U2LhLSuGAxkw6ADcQ,65358
61
+ execsql/gui/tui.py,sha256=jlCoP1lsEJgFsFikKVqF4B3s3KyKRs6JbWfwtlFHyDc,66800
62
62
  execsql/importers/__init__.py,sha256=zZwdQxMaValCNqUrVdvaA7XPU3J8NmqVJ4uolNWY2iU,299
63
63
  execsql/importers/base.py,sha256=3Iq_Sr4cCXKHpjjJYkhZX6uhsIIRAdmvAvTdC4mFlWo,4326
64
64
  execsql/importers/csv.py,sha256=Kec1VCycRdfstNoxIXsEsSFqQprZp_edpNiMZm43eTI,4914
@@ -69,7 +69,7 @@ execsql/importers/xls.py,sha256=D7Ul42gIRYZZC5QxD35ft_wmqQv2f1jNMSbnqlzV0ZA,3851
69
69
  execsql/metacommands/__init__.py,sha256=H_D1Z5kN6zfYtxatejQhQhQV1tlVZ7rcdFZ_9uhXu2Q,13010
70
70
  execsql/metacommands/conditions.py,sha256=2bfdVV_LCl91quG4SRLur9Jk60NsIOStHlBCeJJ-Djc,30068
71
71
  execsql/metacommands/connect.py,sha256=SPk4h9yiUgTD3w1gNOT_oYx8VShggsO357by5Hoy6OI,15344
72
- execsql/metacommands/control.py,sha256=EAURAsBjEZm3YwOJG0Gr3oI2H5_8p3Cu8-AguhBD_ds,8007
72
+ execsql/metacommands/control.py,sha256=QT3CJyneANrHxgnbK7vlwmLbckRCbSSwSxy4D3oxN4A,9375
73
73
  execsql/metacommands/data.py,sha256=Rp18lHUKSTkwTFRjOQ7bDgZd42LNLVoTS3Zc_bcNEWY,12199
74
74
  execsql/metacommands/debug.py,sha256=MeVXAob8ItEg2QzuSUkKDaQCEABnH6u0XcAwJzw36CE,13015
75
75
  execsql/metacommands/dispatch.py,sha256=t7x0xWHJA0PeCrYf7jYeSMJgf0yxcZ_xh-_YAtBPHLw,87192
@@ -101,23 +101,23 @@ execsql/utils/numeric.py,sha256=_7bi5R6pHjMCkOPne_Sh5nUj-B8e9g6WFmjptFHaunA,1573
101
101
  execsql/utils/regex.py,sha256=B3mSBhRRx_DpUnX_t9IoYd05KvWAebiLMCZqQAmEwcw,3885
102
102
  execsql/utils/strings.py,sha256=UQNjpRCEFa1UO6feU-M-9e24wWAvizs_iu_4fFusLxo,8516
103
103
  execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
104
- execsql2-2.22.2.data/data/execsql2_extras/README.md,sha256=vX4NTL095dUoA_hesyRMGYBorEZ_Y_tJ9qrd-MVV09I,5032
105
- execsql2-2.22.2.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
106
- execsql2-2.22.2.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
107
- execsql2-2.22.2.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
108
- execsql2-2.22.2.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
109
- execsql2-2.22.2.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
110
- execsql2-2.22.2.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
111
- execsql2-2.22.2.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
112
- execsql2-2.22.2.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
113
- execsql2-2.22.2.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
114
- execsql2-2.22.2.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
115
- execsql2-2.22.2.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
116
- execsql2-2.22.2.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
117
- execsql2-2.22.2.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
118
- execsql2-2.22.2.dist-info/METADATA,sha256=qL8AZS7bCoWljac0GuIdc5uHHJCUtBKegwm_sayfAlQ,20382
119
- execsql2-2.22.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
120
- execsql2-2.22.2.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
121
- execsql2-2.22.2.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
122
- execsql2-2.22.2.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
123
- execsql2-2.22.2.dist-info/RECORD,,
104
+ execsql2-2.22.4.data/data/execsql2_extras/README.md,sha256=vX4NTL095dUoA_hesyRMGYBorEZ_Y_tJ9qrd-MVV09I,5032
105
+ execsql2-2.22.4.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
106
+ execsql2-2.22.4.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
107
+ execsql2-2.22.4.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
108
+ execsql2-2.22.4.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
109
+ execsql2-2.22.4.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
110
+ execsql2-2.22.4.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
111
+ execsql2-2.22.4.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
112
+ execsql2-2.22.4.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
113
+ execsql2-2.22.4.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
114
+ execsql2-2.22.4.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
115
+ execsql2-2.22.4.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
116
+ execsql2-2.22.4.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
117
+ execsql2-2.22.4.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
118
+ execsql2-2.22.4.dist-info/METADATA,sha256=gD2Dm8BheKQrFz6xAs7KyfpqGzs93Rpv7vxWyPWXeQE,20382
119
+ execsql2-2.22.4.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
120
+ execsql2-2.22.4.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
121
+ execsql2-2.22.4.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
122
+ execsql2-2.22.4.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
123
+ execsql2-2.22.4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.31.0
2
+ Generator: hatchling 1.32.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any