execsql2 2.22.7__py3-none-any.whl → 2.22.9__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 (23) hide show
  1. execsql/data/execsql.conf.template +1 -0
  2. execsql/format.py +164 -33
  3. execsql/utils/mail.py +9 -5
  4. {execsql2-2.22.7.dist-info → execsql2-2.22.9.dist-info}/METADATA +2 -2
  5. {execsql2-2.22.7.dist-info → execsql2-2.22.9.dist-info}/RECORD +23 -23
  6. {execsql2-2.22.7.dist-info → execsql2-2.22.9.dist-info}/WHEEL +1 -1
  7. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/README.md +0 -0
  8. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  9. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  10. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/make_config_db.sql +0 -0
  11. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/md_compare.sql +0 -0
  12. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/md_glossary.sql +0 -0
  13. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/md_upsert.sql +0 -0
  14. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/pg_compare.sql +0 -0
  15. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  16. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  17. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/script_template.sql +0 -0
  18. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/ss_compare.sql +0 -0
  19. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  20. {execsql2-2.22.7.data → execsql2-2.22.9.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  21. {execsql2-2.22.7.dist-info → execsql2-2.22.9.dist-info}/entry_points.txt +0 -0
  22. {execsql2-2.22.7.dist-info → execsql2-2.22.9.dist-info}/licenses/LICENSE.txt +0 -0
  23. {execsql2-2.22.7.dist-info → execsql2-2.22.9.dist-info}/licenses/NOTICE +0 -0
@@ -242,6 +242,7 @@
242
242
  # Settings for the EMAIL metacommand.
243
243
 
244
244
  # SMTP server connection.
245
+ # If username is set, password (or enc_password, below) is required too.
245
246
  #host=
246
247
  #port=
247
248
  #username=
execsql/format.py CHANGED
@@ -176,13 +176,18 @@ _EXECSQL_VAR_RE = re.compile(r"""!(['"]?)!([^!\s][^!]*)!\1!|!\{[^}]+\}!""")
176
176
  _IDENT_CHAR_RE = re.compile(r"[A-Za-z0-9_$]")
177
177
 
178
178
 
179
- def _iter_sql_literals(sql: str) -> Iterator[tuple[int, int, str]]:
180
- """Yield ``(start, end, kind)`` spans for every string literal in *sql*.
179
+ _LITERAL_KINDS = frozenset({"estring", "plain", "dollar"})
180
+
181
+
182
+ def _iter_sql_spans(sql: str) -> Iterator[tuple[int, int, str]]:
183
+ """Yield ``(start, end, kind)`` spans for every non-code region of *sql*.
181
184
 
182
185
  This is a lexical walk rather than a regex sweep, because an apostrophe
183
- only starts a literal in some contexts. Line comments, block comments
184
- (which nest in PostgreSQL), and quoted identifiers are skipped entirely,
185
- so quotes inside them never open a literal.
186
+ only starts a literal in some contexts: inside a line comment, a block
187
+ comment (which nests in PostgreSQL), or a quoted identifier it is just a
188
+ character. Those three regions are reported as spans of their own so
189
+ callers that care about code positions — :func:`_paren_depths` — can mask
190
+ them out, and :func:`_iter_sql_literals` filters them back off.
186
191
 
187
192
  *kind* is one of:
188
193
 
@@ -195,6 +200,8 @@ def _iter_sql_literals(sql: str) -> Iterator[tuple[int, int, str]]:
195
200
  ``"dollar"``
196
201
  A dollar-quoted body, ``$$...$$`` or ``$tag$...$tag$``, in which no
197
202
  character is special.
203
+ ``"line_comment"``, ``"block_comment"``, ``"quoted_ident"``
204
+ Regions that are not code and hold no literal.
198
205
  """
199
206
  i, n = 0, len(sql)
200
207
  while i < n:
@@ -202,10 +209,13 @@ def _iter_sql_literals(sql: str) -> Iterator[tuple[int, int, str]]:
202
209
 
203
210
  if c == "-" and sql.startswith("--", i):
204
211
  nl = sql.find("\n", i)
205
- i = n if nl < 0 else nl + 1
212
+ stop = n if nl < 0 else nl
213
+ yield i, stop, "line_comment"
214
+ i = stop
206
215
  continue
207
216
 
208
217
  if c == "/" and sql.startswith("/*", i):
218
+ start = i
209
219
  depth, i = 1, i + 2
210
220
  while i < n and depth:
211
221
  if sql.startswith("/*", i):
@@ -214,9 +224,11 @@ def _iter_sql_literals(sql: str) -> Iterator[tuple[int, int, str]]:
214
224
  depth, i = depth - 1, i + 2
215
225
  else:
216
226
  i += 1
227
+ yield start, i, "block_comment"
217
228
  continue
218
229
 
219
230
  if c == '"':
231
+ start = i
220
232
  i += 1
221
233
  while i < n:
222
234
  if sql[i] == '"':
@@ -226,6 +238,7 @@ def _iter_sql_literals(sql: str) -> Iterator[tuple[int, int, str]]:
226
238
  i += 1
227
239
  break
228
240
  i += 1
241
+ yield start, i, "quoted_ident"
229
242
  continue
230
243
 
231
244
  if c == "$":
@@ -273,6 +286,46 @@ def _iter_sql_literals(sql: str) -> Iterator[tuple[int, int, str]]:
273
286
  i += 1
274
287
 
275
288
 
289
+ def _iter_sql_literals(sql: str) -> Iterator[tuple[int, int, str]]:
290
+ """Yield ``(start, end, kind)`` spans for every string literal in *sql*.
291
+
292
+ The literal subset of :func:`_iter_sql_spans`; comments and quoted
293
+ identifiers are dropped, so quotes inside them never open a literal.
294
+ """
295
+ for start, end, kind in _iter_sql_spans(sql):
296
+ if kind in _LITERAL_KINDS:
297
+ yield start, end, kind
298
+
299
+
300
+ def _paren_depths(lines: list[str]) -> list[int]:
301
+ """Return the parenthesis nesting depth at the start of each of *lines*.
302
+
303
+ Parentheses inside string literals, comments, and quoted identifiers do
304
+ not count. Depth resets at every ``;`` so one statement with unbalanced
305
+ parens — which only happens when the input is a fragment — cannot skew
306
+ the statements that follow.
307
+ """
308
+ text = "\n".join(lines)
309
+ chars = list(text)
310
+ for start, end, _kind in _iter_sql_spans(text):
311
+ for i in range(start, min(end, len(chars))):
312
+ if chars[i] != "\n":
313
+ chars[i] = " "
314
+
315
+ depths: list[int] = []
316
+ depth = 0
317
+ for line in "".join(chars).split("\n"):
318
+ depths.append(depth)
319
+ for ch in line:
320
+ if ch == "(":
321
+ depth += 1
322
+ elif ch == ")":
323
+ depth = max(0, depth - 1)
324
+ elif ch == ";":
325
+ depth = 0
326
+ return depths
327
+
328
+
276
329
  def _sql_literal_texts(sql: str) -> list[str]:
277
330
  """Return the source text of every string literal in *sql*, in order."""
278
331
  return [sql[start:end] for start, end, _ in _iter_sql_literals(sql)]
@@ -588,6 +641,34 @@ _CMT_MARKER_RE = re.compile(rf"/\*\s*({re.escape(_CMT_MARKER)}\d+)\s*\*/")
588
641
  # converge. Matching the run as a unit removes it cleanly. See issue #35.
589
642
  _CMT_MARKER_RUN_RE = re.compile(rf"\s*(?:/\*\s*{re.escape(_CMT_MARKER)}\d+\s*\*/\s*)+")
590
643
 
644
+ # Characters that must not be preceded by a space once a marker run between
645
+ # them and the previous token is removed.
646
+ _NO_SPACE_BEFORE = frozenset(",;).")
647
+
648
+
649
+ def _strip_marker_runs(line: str) -> str:
650
+ """Remove every comment-marker run from *line*, leaving no stray spacing.
651
+
652
+ A run stands where the author wrote nothing, so it is replaced by the one
653
+ space that keeps its neighbours apart — ``AND /* m */ RTRIM(`` must not
654
+ become ``ANDRTRIM(``. At the ends of the line, or against punctuation
655
+ that binds to the token before it, that space is not a separator but
656
+ residue: sqlglot emitting ``1 = 1 /* m */;`` left ``1 = 1 ;``, which the
657
+ next run then normalised — costing a pass and making ``--check`` fail on a
658
+ file that was already formatted. See issue #35.
659
+ """
660
+
661
+ def repl(match: re.Match[str]) -> str:
662
+ before = line[: match.start()]
663
+ after = line[match.end() :]
664
+ if not before.strip() or not after.strip():
665
+ return ""
666
+ if after[:1] in _NO_SPACE_BEFORE or before.endswith("("):
667
+ return ""
668
+ return " "
669
+
670
+ return _CMT_MARKER_RUN_RE.sub(repl, line)
671
+
591
672
 
592
673
  def _format_preserving_comments(
593
674
  lines: list[str],
@@ -613,6 +694,7 @@ def _format_preserving_comments(
613
694
  comment_store: dict[str, str] = {} # marker → original comment line
614
695
  # Track the SQL line that originally followed each comment, for fallback
615
696
  anchor_sql: dict[str, str] = {} # marker → next SQL line (stripped)
697
+ anchor_line: dict[str, int] = {} # marker → index of that line in `processed`
616
698
  pending_markers: list[str] = []
617
699
  processed: list[str] = []
618
700
  in_block = False
@@ -639,50 +721,87 @@ def _format_preserving_comments(
639
721
  processed.append(f"{prefix} {line}" if prefix else line)
640
722
  for m in pending_markers:
641
723
  anchor_sql[m] = stripped
724
+ anchor_line[m] = len(processed) - 1
642
725
  pending_markers.clear()
643
726
 
644
727
  # Trailing comments with no following SQL — preserve as-is
645
728
  trailing: list[str] = [comment_store[m] for m in pending_markers]
646
729
 
730
+ # Parenthesis depth each comment sat at in the input. A comment on its own
731
+ # line belongs to the SQL line below it, so it shares that line's depth.
732
+ in_depths = _paren_depths(processed)
733
+ anchor_depth: dict[str, int] = {m: in_depths[i] for m, i in anchor_line.items()}
734
+
647
735
  # ---- Step 2: format through sqlglot ---------------------------------
648
736
  formatted = _sqlglot_format(processed, sql_indent=sql_indent, leading_comma=leading_comma)
649
737
 
650
738
  # ---- Step 3: restore surviving markers to comment lines -------------
651
739
  found_markers: set[str] = set()
652
- result: list[str] = []
740
+ sql_out: list[str] = []
741
+ marker_sites: list[tuple[int, str]] = [] # (index in sql_out, marker)
653
742
  for fline in formatted:
654
743
  markers_here = _CMT_MARKER_RE.findall(fline)
655
- if markers_here:
656
- # Strip markers to get the underlying SQL line and its indent
657
- cleaned = _CMT_MARKER_RUN_RE.sub(" ", fline).strip()
658
- # Indent comes from the line's own leading whitespace, never from
659
- # what is left after removing the marker. Markers are injected as
660
- # ``/* marker */ line`` — with a separating space — so measuring
661
- # after removal counts that space as indentation. Whenever
662
- # sqlglot leaves the line untouched (an unparsable statement falls
663
- # back verbatim) the extra space is re-measured and re-added on the
664
- # next run, growing the indent by one on every pass and never
665
- # converging. See issue #35.
666
- line_indent = fline[: len(fline) - len(fline.lstrip())]
667
- for m in markers_here:
668
- if m in comment_store:
669
- orig = comment_store[m]
670
- # Re-indent the comment to match the SQL line it precedes
671
- orig_stripped = orig.strip()
672
- if orig_stripped:
673
- result.append(line_indent + orig_stripped)
674
- else:
675
- result.append("")
676
- found_markers.add(m)
677
- if cleaned:
678
- result.append(line_indent + cleaned)
744
+ if not markers_here:
745
+ sql_out.append(fline)
746
+ continue
747
+ # Strip markers to get the underlying SQL line and its indent
748
+ cleaned = _strip_marker_runs(fline).strip()
749
+ # Indent comes from the line's own leading whitespace, never from
750
+ # what is left after removing the marker. Markers are injected as
751
+ # ``/* marker */ line`` — with a separating space — so measuring
752
+ # after removal counts that space as indentation. Whenever
753
+ # sqlglot leaves the line untouched (an unparsable statement falls
754
+ # back verbatim) the extra space is re-measured and re-added on the
755
+ # next run, growing the indent by one on every pass and never
756
+ # converging. See issue #35.
757
+ line_indent = fline[: len(fline) - len(fline.lstrip())]
758
+ for m in markers_here:
759
+ if m in comment_store:
760
+ marker_sites.append((len(sql_out), m))
761
+ found_markers.add(m)
762
+ if cleaned:
763
+ sql_out.append(line_indent + cleaned)
764
+
765
+ # Place each comment above the line where its expression *starts*, not
766
+ # above whatever line the marker landed on. sqlglot attaches an inline
767
+ # comment to a node, and when it then reflows that node across lines the
768
+ # marker can surface deep inside the expression — ``COALESCE((a.b).c,
769
+ # FALSE)`` puts it on the ``).c, FALSE)`` continuation line. Restoring
770
+ # there splits the expression around a comment that now documents a
771
+ # fragment of it. The comment's own line in the input sat at the
772
+ # parenthesis depth of the SQL line below it, so walk back from the marker
773
+ # to the nearest line at that depth: the line the reflowed expression
774
+ # opens on. A marker that did not move is already at its anchor depth and
775
+ # stays where it is.
776
+ out_depths = _paren_depths(sql_out)
777
+ comments_before: dict[int, list[str]] = {}
778
+ for idx, marker in marker_sites:
779
+ target = idx
780
+ want = anchor_depth.get(marker, 0)
781
+ while target > 0 and target < len(out_depths) and out_depths[target] > want:
782
+ target -= 1
783
+ orig_stripped = comment_store[marker].strip()
784
+ if not orig_stripped:
785
+ comments_before.setdefault(target, []).append("")
786
+ continue
787
+ # Re-indent the comment to match the SQL line it precedes
788
+ if target < len(sql_out):
789
+ anchor = sql_out[target]
790
+ indent_str = anchor[: len(anchor) - len(anchor.lstrip())]
679
791
  else:
680
- result.append(fline)
792
+ indent_str = ""
793
+ comments_before.setdefault(target, []).append(indent_str + orig_stripped)
794
+
795
+ result: list[str] = []
796
+ for i, line in enumerate(sql_out):
797
+ result.extend(comments_before.get(i, ()))
798
+ result.append(line)
799
+ result.extend(comments_before.get(len(sql_out), ()))
681
800
 
682
801
  # ---- Step 4: reinsert lost markers ----------------------------------
683
802
  lost = [m for m in comment_store if m not in found_markers and m not in set(pending_markers)]
684
803
  if lost:
685
- _reinsert_lost_comments(result, lost, comment_store, anchor_sql)
804
+ _reinsert_lost_comments(result, lost, comment_store, anchor_sql, anchor_depth)
686
805
 
687
806
  result.extend(trailing)
688
807
  return result
@@ -693,14 +812,22 @@ def _reinsert_lost_comments(
693
812
  lost_markers: list[str],
694
813
  comment_store: dict[str, str],
695
814
  anchor_sql: dict[str, str],
815
+ anchor_depth: dict[str, int],
696
816
  ) -> None:
697
817
  """Best-effort reinsertion of comments that sqlglot dropped.
698
818
 
699
819
  For each lost comment, extract key tokens from its anchor SQL line and
700
820
  find the output line that best matches, then insert the comment before
701
821
  that line (indented to match). Operates on *result* in place.
822
+
823
+ Token matching scores whole lines, so on a reflowed expression the best
824
+ match is often a continuation line — ``a.meas_value`` out of ``CASE WHEN
825
+ (a.meas_value).undetected``. Inserting there splits the expression, so
826
+ the match is pulled back to the line where the expression opens, the same
827
+ way surviving markers are placed in :func:`_format_preserving_comments`.
702
828
  """
703
829
  _word_re = re.compile(r"[a-zA-Z_]\w*")
830
+ out_depths = _paren_depths(result)
704
831
 
705
832
  # Process in reverse order so earlier inserts don't shift later indices.
706
833
  insertions: list[tuple[int, str]] = []
@@ -727,6 +854,10 @@ def _reinsert_lost_comments(
727
854
  best_score = score
728
855
  best_idx = i
729
856
 
857
+ want = anchor_depth.get(marker, 0)
858
+ while best_idx > 0 and best_idx < len(out_depths) and out_depths[best_idx] > want:
859
+ best_idx -= 1
860
+
730
861
  # Re-indent comment to match the target line
731
862
  if best_idx < len(result):
732
863
  target = result[best_idx]
execsql/utils/mail.py CHANGED
@@ -66,15 +66,19 @@ class Mailer:
66
66
  self.smtpconn = smtplib.SMTP_SSL(conf.smtp_host, conf.smtp_port, timeout=smtp_timeout)
67
67
  else:
68
68
  self.smtpconn = smtplib.SMTP(conf.smtp_host, conf.smtp_port, timeout=smtp_timeout)
69
- self.smtpconn.ehlo_or_hello_if_needed()
69
+ self.smtpconn.ehlo_or_helo_if_needed()
70
70
  if conf.smtp_tls:
71
71
  self.smtpconn.starttls()
72
72
  self.smtpconn.ehlo(conf.smtp_host)
73
73
  if conf.smtp_username:
74
- if conf.smtp_password:
75
- self.smtpconn.login(conf.smtp_username, conf.smtp_password)
76
- else:
77
- self.smtpconn.login(conf.smtp_username)
74
+ if not conf.smtp_password:
75
+ # smtplib.login() requires a password; calling it with only a
76
+ # username raises TypeError deep in the stdlib.
77
+ raise ErrInfo(
78
+ type="error",
79
+ other_msg="Can't send email; an SMTP username is configured but no password.",
80
+ )
81
+ self.smtpconn.login(conf.smtp_username, conf.smtp_password)
78
82
 
79
83
  def sendmail(
80
84
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: execsql2
3
- Version: 2.22.7
3
+ Version: 2.22.9
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.7
387
+ rev: v2.22.9
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=oJvAxDaWi-TQAnj_enwuEH-wtjZylKf67QWwHrSNEUU,47254
6
+ execsql/format.py,sha256=OOxsOX6g7otVgL3gI6uGxa5WIh8GTvjJ8WO-_P91zXg,52886
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
@@ -17,7 +17,7 @@ execsql/cli/help.py,sha256=ThwdZuMIfLPxLAPpGWwXFY_UfyWvYOCjdlBNK20Vzd8,5718
17
17
  execsql/cli/lint.py,sha256=YqKzFNUhyb_Th69hYgKk1ZZVjCsZfJMIiUGqp06JwNs,17236
18
18
  execsql/cli/run.py,sha256=Yb-Ri3cds0st1hUn4_w-PHMphTsArHhC4fx919SW9gU,39390
19
19
  execsql/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- execsql/data/execsql.conf.template,sha256=w_rJ1mx3FOWi_7qWLGcxsf3HTR5nSMsbKAzlfcJh_tM,10959
20
+ execsql/data/execsql.conf.template,sha256=zbjEluk6j9vnW74nlWpvopM6HDYe0rwJhsPVzU-e0Ss,11032
21
21
  execsql/db/__init__.py,sha256=jTbuafuKOqYtXFR1wvCOoKK5Lr3l1uErfaIbIr6UywI,1063
22
22
  execsql/db/access.py,sha256=h2zAMr3mQrm6vFlOYaN7iaG19K7gitXDuonllelqbcE,19539
23
23
  execsql/db/base.py,sha256=0K6mSCqFcheg_T91VOwEnagqqP_eLL2dP5VN4yDz36Q,35414
@@ -96,28 +96,28 @@ execsql/utils/datetime.py,sha256=8BrD24KWeMoouJZiFaXK4HF7plc-O_xrzIw12qOEyp8,362
96
96
  execsql/utils/errors.py,sha256=bDWooUmGdN8xqUt6csq466wPqKPLHHSi7U4hsmrFZxA,8577
97
97
  execsql/utils/fileio.py,sha256=_15UIdcRZxZn2R6YP4xQn-6C9CVK2yxj1oEfxULo2_c,33329
98
98
  execsql/utils/gui.py,sha256=X5aw6LVoeaAdHaQkymm4ba176jga0cS2GrqxSFWTWTg,23853
99
- execsql/utils/mail.py,sha256=SXpTBDx0WIPl2NSjkjmnoChHYVEMyZhIKU4QiJFFZNY,6011
99
+ execsql/utils/mail.py,sha256=SXZac24p62qxDbIviO8VdDZTbKljIoJWgWZfxIGu5zI,6260
100
100
  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.7.data/data/execsql2_extras/README.md,sha256=vX4NTL095dUoA_hesyRMGYBorEZ_Y_tJ9qrd-MVV09I,5032
105
- execsql2-2.22.7.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
106
- execsql2-2.22.7.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
107
- execsql2-2.22.7.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
108
- execsql2-2.22.7.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
109
- execsql2-2.22.7.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
110
- execsql2-2.22.7.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
111
- execsql2-2.22.7.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
112
- execsql2-2.22.7.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
113
- execsql2-2.22.7.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
114
- execsql2-2.22.7.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
115
- execsql2-2.22.7.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
116
- execsql2-2.22.7.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
117
- execsql2-2.22.7.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
118
- execsql2-2.22.7.dist-info/METADATA,sha256=DbIBWCsTnJIRQx1h0UKA9kB5kXFMybxn7U9j-xWG8P4,20382
119
- execsql2-2.22.7.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
120
- execsql2-2.22.7.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
121
- execsql2-2.22.7.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
122
- execsql2-2.22.7.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
123
- execsql2-2.22.7.dist-info/RECORD,,
104
+ execsql2-2.22.9.data/data/execsql2_extras/README.md,sha256=vX4NTL095dUoA_hesyRMGYBorEZ_Y_tJ9qrd-MVV09I,5032
105
+ execsql2-2.22.9.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
106
+ execsql2-2.22.9.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
107
+ execsql2-2.22.9.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
108
+ execsql2-2.22.9.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
109
+ execsql2-2.22.9.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
110
+ execsql2-2.22.9.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
111
+ execsql2-2.22.9.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
112
+ execsql2-2.22.9.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
113
+ execsql2-2.22.9.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
114
+ execsql2-2.22.9.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
115
+ execsql2-2.22.9.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
116
+ execsql2-2.22.9.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
117
+ execsql2-2.22.9.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
118
+ execsql2-2.22.9.dist-info/METADATA,sha256=Ku6XhxrqV05mj1lvzrZEO6p4uKCa_PA2tbs43yBT9ew,20382
119
+ execsql2-2.22.9.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
120
+ execsql2-2.22.9.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
121
+ execsql2-2.22.9.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
122
+ execsql2-2.22.9.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
123
+ execsql2-2.22.9.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any