execsql2 2.12.6__py3-none-any.whl → 2.13.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 (32) hide show
  1. execsql/exporters/html.py +10 -2
  2. execsql/exporters/raw.py +31 -19
  3. execsql/exporters/zip.py +21 -3
  4. execsql/importers/json.py +142 -0
  5. execsql/metacommands/__init__.py +2 -0
  6. execsql/metacommands/dispatch.py +12 -0
  7. execsql/metacommands/io.py +2 -0
  8. execsql/metacommands/io_import.py +36 -0
  9. execsql/metacommands/system.py +4 -3
  10. execsql/utils/fileio.py +8 -2
  11. execsql/utils/mail.py +19 -3
  12. {execsql2-2.12.6.dist-info → execsql2-2.13.0.dist-info}/METADATA +4 -4
  13. {execsql2-2.12.6.dist-info → execsql2-2.13.0.dist-info}/RECORD +32 -31
  14. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/README.md +0 -0
  15. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  16. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  17. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/execsql.conf +0 -0
  18. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
  19. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/md_compare.sql +0 -0
  20. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
  21. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
  22. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
  23. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  24. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  25. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/script_template.sql +0 -0
  26. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
  27. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  28. {execsql2-2.12.6.data → execsql2-2.13.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  29. {execsql2-2.12.6.dist-info → execsql2-2.13.0.dist-info}/WHEEL +0 -0
  30. {execsql2-2.12.6.dist-info → execsql2-2.13.0.dist-info}/entry_points.txt +0 -0
  31. {execsql2-2.12.6.dist-info → execsql2-2.13.0.dist-info}/licenses/LICENSE.txt +0 -0
  32. {execsql2-2.12.6.dist-info → execsql2-2.13.0.dist-info}/licenses/NOTICE +0 -0
execsql/exporters/html.py CHANGED
@@ -154,8 +154,16 @@ def export_html(
154
154
  finally:
155
155
  t.close()
156
156
  f.close()
157
- os.unlink(outfile)
158
- os.rename(tempfname, outfile)
157
+ try:
158
+ os.unlink(outfile)
159
+ os.rename(tempfname, outfile)
160
+ except OSError:
161
+ # Clean up temp file if rename fails.
162
+ try:
163
+ os.unlink(tempfname)
164
+ except OSError:
165
+ pass
166
+ raise
159
167
 
160
168
 
161
169
  def export_cgi_html(
execsql/exporters/raw.py CHANGED
@@ -29,21 +29,30 @@ def write_query_raw(
29
29
  if zipfile is None:
30
30
  filewriter_close(outfile)
31
31
  mode = "wb" if not append else "ab"
32
- of = open(outfile, mode) # noqa: SIM115
32
+ with open(outfile, mode) as of:
33
+ for row in rowsource:
34
+ for col in row:
35
+ if isinstance(col, bytearray):
36
+ of.write(col)
37
+ else:
38
+ if isinstance(col, str):
39
+ of.write(bytes(col, db_encoding))
40
+ else:
41
+ of.write(bytes(str(col), db_encoding))
33
42
  else:
34
43
  of = ZipWriter(zipfile, outfile, append)
35
- try:
36
- for row in rowsource:
37
- for col in row:
38
- if isinstance(col, bytearray):
39
- of.write(col)
40
- else:
41
- if isinstance(col, str):
42
- of.write(bytes(col, db_encoding))
44
+ try:
45
+ for row in rowsource:
46
+ for col in row:
47
+ if isinstance(col, bytearray):
48
+ of.write(col)
43
49
  else:
44
- of.write(bytes(str(col), db_encoding))
45
- finally:
46
- of.close()
50
+ if isinstance(col, str):
51
+ of.write(bytes(col, db_encoding))
52
+ else:
53
+ of.write(bytes(str(col), db_encoding))
54
+ finally:
55
+ of.close()
47
56
 
48
57
 
49
58
  def write_query_b64(outfile: str, rowsource: Any, append: bool = False, zipfile: str | None = None) -> None:
@@ -51,12 +60,15 @@ def write_query_b64(outfile: str, rowsource: Any, append: bool = False, zipfile:
51
60
  if zipfile is None:
52
61
  filewriter_close(outfile)
53
62
  mode = "wb" if not append else "ab"
54
- of = open(outfile, mode) # noqa: SIM115
63
+ with open(outfile, mode) as of:
64
+ for row in rowsource:
65
+ for col in row:
66
+ of.write(base64.standard_b64decode(col))
55
67
  else:
56
68
  of = ZipWriter(zipfile, outfile, append)
57
- try:
58
- for row in rowsource:
59
- for col in row:
60
- of.write(base64.standard_b64decode(col))
61
- finally:
62
- of.close()
69
+ try:
70
+ for row in rowsource:
71
+ for col in row:
72
+ of.write(base64.standard_b64decode(col))
73
+ finally:
74
+ of.close()
execsql/exporters/zip.py CHANGED
@@ -32,8 +32,18 @@ class WriteableZipfile:
32
32
  self.zf = zipfile.ZipFile(zipfile_name, mode=zmode, compression=comp, compresslevel=9)
33
33
  self.current_handle = None
34
34
 
35
- def __del__(self) -> None:
35
+ def __enter__(self) -> WriteableZipfile:
36
+ return self
37
+
38
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
36
39
  self.close()
40
+ return None
41
+
42
+ def __del__(self) -> None:
43
+ try:
44
+ self.close()
45
+ except Exception:
46
+ pass # Best-effort cleanup at interpreter shutdown.
37
47
 
38
48
  def member_file(self, member_filename: str) -> None:
39
49
  """Create a new member entry in the archive and open it for writing."""
@@ -97,11 +107,19 @@ class ZipWriter:
97
107
  self.zwriter = WriteableZipfile(self.zip_fname, append)
98
108
  self.member = self.zwriter.member_file(member_fname)
99
109
 
110
+ def __enter__(self) -> ZipWriter:
111
+ return self
112
+
113
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
114
+ self.close()
115
+ return None
116
+
100
117
  def write(self, str_data: str) -> None:
101
118
  """Write a string to the current zip member."""
102
119
  self.zwriter.write(str_data)
103
120
 
104
121
  def close(self) -> None:
105
122
  """Close the zip member and finalise the archive."""
106
- self.zwriter.close()
107
- self.zwriter = None
123
+ if self.zwriter is not None:
124
+ self.zwriter.close()
125
+ self.zwriter = None
@@ -0,0 +1,142 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ JSON import for execsql.
5
+
6
+ Provides :func:`import_json`, used by ``IMPORT … FORMAT json``.
7
+ Supports JSON arrays of objects (``[{…}, …]``) and newline-delimited
8
+ JSON (NDJSON, one object per line). Nested objects are flattened with
9
+ dot-separated keys; nested arrays and non-object values are serialized
10
+ as JSON strings so every column maps to a scalar database value.
11
+ """
12
+
13
+ import json
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ from execsql.db.base import Database
18
+ from execsql.exceptions import ErrInfo
19
+ from execsql.importers.base import import_data_table
20
+
21
+ __all__ = ["import_json"]
22
+
23
+
24
+ def _flatten(obj: Any, prefix: str = "", sep: str = ".") -> dict[str, Any]:
25
+ """Recursively flatten a nested dict.
26
+
27
+ Nested dicts produce dot-separated keys. All other compound values
28
+ (lists, nested lists-of-dicts) are serialized as JSON strings so the
29
+ result is always ``{str: scalar}``.
30
+ """
31
+ items: dict[str, Any] = {}
32
+ if isinstance(obj, dict):
33
+ for key, value in obj.items():
34
+ new_key = f"{prefix}{sep}{key}" if prefix else key
35
+ if isinstance(value, dict):
36
+ items.update(_flatten(value, new_key, sep))
37
+ elif isinstance(value, list):
38
+ # Serialize arrays as JSON strings — tables are flat.
39
+ items[new_key] = json.dumps(value, default=str)
40
+ else:
41
+ items[new_key] = value
42
+ return items
43
+
44
+
45
+ def _parse_json_file(filename: str, encoding: str) -> list[dict[str, Any]]:
46
+ """Read a JSON file and return a list of flat dicts.
47
+
48
+ Accepts either a JSON array of objects or newline-delimited JSON
49
+ (NDJSON).
50
+ """
51
+ text = Path(filename).read_text(encoding=encoding)
52
+ stripped = text.strip()
53
+
54
+ if stripped.startswith("["):
55
+ # Standard JSON array.
56
+ raw = json.loads(stripped)
57
+ if not isinstance(raw, list):
58
+ raise ErrInfo(type="error", other_msg="JSON file root is not an array of objects.")
59
+ records = raw
60
+ elif stripped.startswith("{"):
61
+ # Try NDJSON (one object per line) or a single object.
62
+ records = []
63
+ for lineno, line in enumerate(stripped.splitlines(), 1):
64
+ line = line.strip()
65
+ if not line:
66
+ continue
67
+ try:
68
+ obj = json.loads(line)
69
+ except json.JSONDecodeError as exc:
70
+ raise ErrInfo(
71
+ type="error",
72
+ other_msg=f"Invalid JSON on line {lineno}: {exc}",
73
+ ) from exc
74
+ if not isinstance(obj, dict):
75
+ raise ErrInfo(
76
+ type="error",
77
+ other_msg=f"Line {lineno} is not a JSON object.",
78
+ )
79
+ records.append(obj)
80
+ else:
81
+ raise ErrInfo(
82
+ type="error",
83
+ other_msg="JSON import expects a file starting with '[' (array) or '{' (object/NDJSON).",
84
+ )
85
+
86
+ if not records:
87
+ raise ErrInfo(type="error", other_msg="JSON file contains no records.")
88
+
89
+ # Validate that all records are dicts.
90
+ for i, rec in enumerate(records):
91
+ if not isinstance(rec, dict):
92
+ raise ErrInfo(
93
+ type="error",
94
+ other_msg=f"Record {i} in JSON file is not an object (got {type(rec).__name__}).",
95
+ )
96
+
97
+ return [_flatten(rec) for rec in records]
98
+
99
+
100
+ def import_json(
101
+ db: Database,
102
+ schemaname: str | None,
103
+ tablename: str,
104
+ filename: str,
105
+ is_new: Any,
106
+ encoding: str | None = None,
107
+ ) -> None:
108
+ """Import a JSON file into a database table.
109
+
110
+ Objects are flattened so that nested keys become dot-separated column
111
+ names (e.g. ``address.city``). Arrays within objects are stored as
112
+ JSON strings.
113
+ """
114
+ from execsql.utils.errors import exception_desc
115
+
116
+ import execsql.state as _state
117
+
118
+ enc = encoding if encoding else _state.conf.import_encoding
119
+
120
+ try:
121
+ flat_records = _parse_json_file(filename, enc)
122
+ except ErrInfo:
123
+ raise
124
+ except Exception as e:
125
+ raise ErrInfo(
126
+ "exception",
127
+ exception_msg=exception_desc(),
128
+ other_msg=f"Can't parse JSON file {filename}",
129
+ ) from e
130
+
131
+ # Build a union of all keys across records (preserving first-seen order).
132
+ seen: dict[str, None] = {}
133
+ for rec in flat_records:
134
+ for key in rec:
135
+ if key not in seen:
136
+ seen[key] = None
137
+ hdrs = list(seen)
138
+
139
+ # Build row data aligned to hdrs — missing keys become None.
140
+ data = [[rec.get(h) for h in hdrs] for rec in flat_records]
141
+
142
+ import_data_table(db, schemaname, tablename, is_new, hdrs, data)
@@ -118,6 +118,7 @@ from execsql.metacommands.io import (
118
118
  x_import_xls_pattern,
119
119
  x_import_parquet,
120
120
  x_import_feather,
121
+ x_import_json,
121
122
  x_import_row_buffer,
122
123
  x_show_progress,
123
124
  x_export_row_buffer,
@@ -325,6 +326,7 @@ __all__ = [
325
326
  "x_import_xls_pattern",
326
327
  "x_import_parquet",
327
328
  "x_import_feather",
329
+ "x_import_json",
328
330
  "x_import_row_buffer",
329
331
  "x_show_progress",
330
332
  "x_export_row_buffer",
@@ -118,6 +118,7 @@ from execsql.metacommands.io import (
118
118
  x_import,
119
119
  x_import_feather,
120
120
  x_import_file,
121
+ x_import_json,
121
122
  x_import_ods,
122
123
  x_import_ods_pattern,
123
124
  x_import_parquet,
@@ -540,6 +541,17 @@ def build_dispatch_table() -> MetaCommandList:
540
541
  x_import_feather,
541
542
  )
542
543
 
544
+ # ------------------------------------------------------------------
545
+ # IMPORT JSON
546
+ # ------------------------------------------------------------------
547
+ mcl.add(
548
+ ins_table_rxs(
549
+ r"^\s*IMPORT\s+TO\s+(?:(?P<new>NEW|REPLACEMENT)\s+)?",
550
+ ins_fn_rxs(r"\s+FROM\s+JSON\s+", r"\s*$"),
551
+ ),
552
+ x_import_json,
553
+ )
554
+
543
555
  # ------------------------------------------------------------------
544
556
  # PROMPT ACTION
545
557
  # ------------------------------------------------------------------
@@ -34,6 +34,7 @@ from execsql.metacommands.io_import import ( # noqa: F401
34
34
  x_import,
35
35
  x_import_feather,
36
36
  x_import_file,
37
+ x_import_json,
37
38
  x_import_ods,
38
39
  x_import_ods_pattern,
39
40
  x_import_parquet,
@@ -88,6 +89,7 @@ __all__ = [
88
89
  "x_import",
89
90
  "x_import_feather",
90
91
  "x_import_file",
92
+ "x_import_json",
91
93
  "x_import_ods",
92
94
  "x_import_ods_pattern",
93
95
  "x_import_parquet",
@@ -14,6 +14,7 @@ import execsql.state as _state
14
14
  from execsql.exceptions import ErrInfo
15
15
  from execsql.importers.csv import importfile, importtable
16
16
  from execsql.importers.feather import import_feather, import_parquet
17
+ from execsql.importers.json import import_json
17
18
  from execsql.importers.ods import OdsFile, importods
18
19
  from execsql.exporters.xls import XlsFile, XlsxFile
19
20
  from execsql.importers.xls import importxls
@@ -388,6 +389,41 @@ def x_import_feather(**kwargs: Any) -> None:
388
389
  return None
389
390
 
390
391
 
392
+ def x_import_json(**kwargs: Any) -> None:
393
+ newstr = kwargs["new"]
394
+ if newstr:
395
+ is_new = 1 + ["new", "replacement"].index(newstr.lower())
396
+ else:
397
+ is_new = 0
398
+ schemaname = kwargs["schema"]
399
+ tablename = kwargs["table"]
400
+ filename = kwargs["filename"]
401
+ if len(filename) > 1 and filename[0] == "~" and filename[1] == os.sep:
402
+ filename = str(Path.home() / filename[2:])
403
+ if not Path(filename).exists():
404
+ raise ErrInfo(
405
+ type="cmd",
406
+ command_text=kwargs["metacommandline"],
407
+ other_msg=f"Input file {filename} does not exist",
408
+ )
409
+ enc = kwargs.get("encoding")
410
+ from execsql.metacommands.conditions import file_size_date
411
+
412
+ sz, dt = file_size_date(filename)
413
+ _state.exec_log.log_status_info(f"IMPORTing from JSON file {filename} ({sz}, {dt})")
414
+ try:
415
+ import_json(_state.dbs.current(), schemaname, tablename, filename, is_new, encoding=enc)
416
+ except ErrInfo:
417
+ raise
418
+ except Exception as e:
419
+ raise ErrInfo(
420
+ "exception",
421
+ exception_msg=exception_desc(),
422
+ other_msg=f"Can't import data from JSON file {filename}",
423
+ ) from e
424
+ return None
425
+
426
+
391
427
  def x_import_row_buffer(**kwargs: Any) -> None:
392
428
  rows = kwargs["rows"]
393
429
  _state.conf.import_row_buffer = int(rows)
@@ -51,7 +51,8 @@ def x_system_cmd(**kwargs: Any) -> None:
51
51
  returncode = subprocess.call(cmdargs)
52
52
  _state.subvars.add_substitution("$SYSTEM_CMD_EXIT_STATUS", str(returncode))
53
53
  else:
54
- subprocess.Popen(cmdargs)
54
+ proc = subprocess.Popen(cmdargs)
55
+ _state.subvars.add_substitution("$SYSTEM_CMD_PID", str(proc.pid))
55
56
  return None
56
57
 
57
58
 
@@ -62,8 +63,8 @@ def x_email(**kwargs: Any) -> None:
62
63
  msg = kwargs["msg"]
63
64
  msg_file = kwargs["msg_file"]
64
65
  att_file = kwargs["att_file"]
65
- m = Mailer()
66
- m.sendmail(from_addr, to_addr, subject, msg, msg_file, att_file)
66
+ with Mailer() as m:
67
+ m.sendmail(from_addr, to_addr, subject, msg, msg_file, att_file)
67
68
 
68
69
 
69
70
  def x_timer(**kwargs: Any) -> None:
execsql/utils/fileio.py CHANGED
@@ -119,7 +119,10 @@ class FileWriter(multiprocessing.Process):
119
119
  self.close_after_write = False
120
120
 
121
121
  def __del__(self) -> None:
122
- self.close()
122
+ try:
123
+ self.close()
124
+ except Exception:
125
+ pass # Best-effort cleanup at interpreter shutdown.
123
126
 
124
127
  def write_queue(self) -> None:
125
128
  while len(self.output_queue) > 0:
@@ -215,7 +218,10 @@ class FileWriter(multiprocessing.Process):
215
218
  )
216
219
 
217
220
  def __del__(self) -> None:
218
- self.close_all()
221
+ try:
222
+ self.close_all()
223
+ except Exception:
224
+ pass # Best-effort cleanup at interpreter shutdown.
219
225
 
220
226
  def close_all(self) -> None:
221
227
  for fc in getattr(self, "files", {}).values():
execsql/utils/mail.py CHANGED
@@ -28,9 +28,24 @@ class Mailer:
28
28
  def __repr__(self) -> str:
29
29
  return "Mailer()"
30
30
 
31
- def __del__(self) -> None:
31
+ def __enter__(self) -> Mailer:
32
+ return self
33
+
34
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
35
+ self.close()
36
+ return None
37
+
38
+ def close(self) -> None:
32
39
  if hasattr(self, "smtpconn"):
33
- self.smtpconn.quit()
40
+ try:
41
+ self.smtpconn.quit()
42
+ except Exception:
43
+ pass # Best-effort; connection may already be closed.
44
+ finally:
45
+ del self.smtpconn
46
+
47
+ def __del__(self) -> None:
48
+ self.close()
34
49
 
35
50
  def __init__(self) -> None:
36
51
  conf = _state.conf
@@ -134,5 +149,6 @@ class MailSpec:
134
149
  content_filename = _state.subvars.substitute_all(content_filename)
135
150
  attach_filename = _state.commandliststack[-1].localvars.substitute_all(self.attach_filename)
136
151
  attach_filename = _state.subvars.substitute_all(attach_filename)
137
- Mailer().sendmail(send_from, send_to, subject, msg_content, content_filename, attach_filename)
152
+ with Mailer() as m:
153
+ m.sendmail(send_from, send_to, subject, msg_content, content_filename, attach_filename)
138
154
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.12.6
3
+ Version: 2.13.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.18.0; extra == 'all'
54
+ Requires-Dist: pg-upsert>=1.18.2; 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.18.0; extra == 'upsert'
112
+ Requires-Dist: pg-upsert>=1.18.2; extra == 'upsert'
113
113
  Description-Content-Type: text/markdown
114
114
 
115
115
  > [!NOTE]
@@ -238,7 +238,7 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
238
238
 
239
239
  # Features
240
240
 
241
- - Import data from CSV, TSV, Excel, OpenDocument, Feather, or Parquet files into a database table.
241
+ - Import data from CSV, TSV, JSON, Excel, OpenDocument, Feather, or Parquet files into a database table.
242
242
  - Export query results in 20+ formats including CSV, TSV, JSON, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
243
243
  - Copy data between databases, including across different DBMS types.
244
244
  - Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
@@ -33,7 +33,7 @@ execsql/exporters/base.py,sha256=W9USFyk_2eztjJ51X6CJh7-chE1i3cSx-STOtbHXCNI,637
33
33
  execsql/exporters/delimited.py,sha256=URvEQo1IRF_tfdVHL3uBwEonihC-XfDm0f1argQPf4M,32088
34
34
  execsql/exporters/duckdb.py,sha256=Wc9I5uiV4MzmVQzCX-vgVHQUL7U3ZWdOkFVFWBv5SXM,2911
35
35
  execsql/exporters/feather.py,sha256=w2qZAnewzeiRMnmPXECvkgD-6KtyxaiQwjokRT7Awrc,4167
36
- execsql/exporters/html.py,sha256=ISQBOr7AJ5koKlebXSvWqzEvl1nXriCRGeKmk-bzkrc,9335
36
+ execsql/exporters/html.py,sha256=2FhC1pe60w7PYkFfesY3YrbULcBED_7QdXceRDuwzH8,9583
37
37
  execsql/exporters/json.py,sha256=yljlRBbmvDVSTQUe0EdfdqTTRpD5sHfn7-jQ457ydvc,4139
38
38
  execsql/exporters/latex.py,sha256=w_B83_5vKPe8uYxCWGdqvxwJeq0mw5zzKYDiAb7dbN0,4503
39
39
  execsql/exporters/markdown.py,sha256=_ZX3dikbtAb6qZxYeWxDZAPF0-cNKTPR7or5kTbD2ZU,4436
@@ -41,7 +41,7 @@ execsql/exporters/ods.py,sha256=jl2qVHUeCLLv8xrkZfG3jgXbaglQ3rggCHziv7tNQOI,1887
41
41
  execsql/exporters/parquet.py,sha256=186vUTH1oVAQ0s_qayLzEQVsKKu3ijAkhYEI6tysXkg,1095
42
42
  execsql/exporters/pretty.py,sha256=9isA8f6xUz-3-JhMJimibnvtybVrT1cnoAjGnzsPEGI,3423
43
43
  execsql/exporters/protocol.py,sha256=BxATgz0xKHbB2FpZBeNg7wZfIiCohhD1awlr3JCec0c,4372
44
- execsql/exporters/raw.py,sha256=jNzfVqUgpKSIO7BsOVwBi4CcfCV5ooZLLq_kEXa4W_I,1918
44
+ execsql/exporters/raw.py,sha256=hqO5XEv_Ab9KiphPvZ9sZEdxWTg-kyn8PH9V3qFksyo,2488
45
45
  execsql/exporters/sqlite.py,sha256=XA0ALLvy-r6Pz1lpOFkWWbvpSP9Hm1tHHiuo_BvPVDk,2686
46
46
  execsql/exporters/templates.py,sha256=T9nk7vJrlxiPGfOWGc79xqqDxK3TCYu0wXq48U02npw,5564
47
47
  execsql/exporters/values.py,sha256=HIyud31aux_dbCphfKHEGeZB9fkIPE5PoGXQz817XIE,2520
@@ -49,7 +49,7 @@ execsql/exporters/xls.py,sha256=nPROgxL8XK2oiBVoqN2L-o0j_jynRIMokwB8NpvOBt0,1062
49
49
  execsql/exporters/xlsx.py,sha256=Gm8ns_KeqSMu2DONSSJ1DcwPBEjYwpbU7frmX0g5L7c,11487
50
50
  execsql/exporters/xml.py,sha256=lqcOM8uKDoCayU42BPSLNH1_2DIHU5D3LtQItREU90c,2564
51
51
  execsql/exporters/yaml.py,sha256=1Vuc6uMDuLTkCuXCfXWKz4gLkkAVdEXkLs4gEB_67Xo,3110
52
- execsql/exporters/zip.py,sha256=9-hExltQorONNThiMfxPDYHqHsbTeq9zM9zmtG4oFb8,4410
52
+ execsql/exporters/zip.py,sha256=kr0X6VLE_ULGVQtMzfqZZSUmc1Qupxg9HbMOAvnTQvI,4890
53
53
  execsql/gui/__init__.py,sha256=oCb-cyhLZzVpWJ4WU5HbqEDBrV-lm0ytEwxemrOZyqs,2048
54
54
  execsql/gui/base.py,sha256=sfNRkDrf7FhIgMRUOdyZpRLS1Xk9RqNhrV0A1RP6PXU,6068
55
55
  execsql/gui/console.py,sha256=TuGzm7XFxa8iWrofGLwx5DuVIlr3wqqyP_pSnAJ1S3s,14271
@@ -59,23 +59,24 @@ execsql/importers/__init__.py,sha256=dDsxSVeQYXBvm9yGqN3QswyGbLWTwt08pvUuRJgZhl4
59
59
  execsql/importers/base.py,sha256=FGVz3ntN6xHL99rQixlQj3tAf570K_oU83UtbYE1lJg,4124
60
60
  execsql/importers/csv.py,sha256=Mu848WNzuhVO1ade-WurPyxqGOuVNRO8UwRF3-bav_I,4845
61
61
  execsql/importers/feather.py,sha256=g2B69d2uv9vmnXcmjFyTVsMP40LYEzFYkhk3gD26mGw,1900
62
+ execsql/importers/json.py,sha256=Z7QJZQ9fyqaxFxjfqcfZoaoW2GSZt-DMqW5LZiEfyNs,4684
62
63
  execsql/importers/ods.py,sha256=MJsdsjropzCvxAA3DDZfAL_AnmZ4yij7DnrjGyDJqHQ,2843
63
64
  execsql/importers/xls.py,sha256=e0Zfe47ZiCpA1Ae3XDJ1ko3sCiH3-8U6XLKi6NvD0jQ,3683
64
- execsql/metacommands/__init__.py,sha256=jrp5PvAZyQYvZ0a_qYNxjv1ZqxDlGYE_P3dyqRTgVSM,11394
65
+ execsql/metacommands/__init__.py,sha256=3Kz-VasFS9B-C-UdHOjr3RMXjheMeYHe6qYBwp5e7wE,11434
65
66
  execsql/metacommands/conditions.py,sha256=Fzrk83-pWbFOoKahYdQW7CZjQeh3zByDUbfgpTM_bjQ,29259
66
67
  execsql/metacommands/connect.py,sha256=Nsm0D91i3RX-R2rzQQ-Br-gULaI6Uvdn9fqb7DOAVfE,14804
67
68
  execsql/metacommands/control.py,sha256=PAZFK1ck5SDSm5QdFV1ctif3KpEiyYWIXdDceRWgQ6k,8513
68
69
  execsql/metacommands/data.py,sha256=tRQBGTAuW-eJ2tBNWaoZI9OjTyNNyHJISo7gOdL-sm8,11370
69
70
  execsql/metacommands/debug.py,sha256=pnT24dfvfOx8xFu86mO5czfVCGKbcvgBLyXnqaMWO4w,8184
70
- execsql/metacommands/dispatch.py,sha256=2Ec7dJmOFstbY3tEuFoTODV8yaHpR-Sfa-ZydOmvIxo,85205
71
- execsql/metacommands/io.py,sha256=Duh60caM4go9JczbGYNMKKYpcMimwPzF6EQ_tshKxdE,2971
71
+ execsql/metacommands/dispatch.py,sha256=LYmTuSfC0nFKgAPWRK8n8Qf8sbrRtRmbh6ntosOjQEo,85587
72
+ execsql/metacommands/io.py,sha256=vlGBje5sgnqeilooMdhJDgSRIhysHy5_7LrKtik9Xjs,3011
72
73
  execsql/metacommands/io_export.py,sha256=7lkCSnPhXy9FVau9_hT1u68NOVdG2DsWmvUh9hM1QWI,18359
73
74
  execsql/metacommands/io_fileops.py,sha256=RrcJTh_cgj7bJ-bezjo0yNl-fN3CoWV-aZ71z1KHYZs,9803
74
- execsql/metacommands/io_import.py,sha256=wyxJJdlW07P5ZIhweejhXyyGANAvEhY5uMjKZ200Jyc,12983
75
+ execsql/metacommands/io_import.py,sha256=fzShl_m74DWt2_SmxlxWQe00H53hDIrOYZtvvqKBhSM,14231
75
76
  execsql/metacommands/io_write.py,sha256=NpL2aYGfBpbqmPpYsqniYltYfd_SCA1EQz3_4qSdNbo,8279
76
77
  execsql/metacommands/prompt.py,sha256=xd3mAkdbn4AE4hUPuSfN5DgZGUZmk-Db23iL-JJPwXs,36918
77
78
  execsql/metacommands/script_ext.py,sha256=TUgAldB2LSJAwZrCvDDi804hQ1d9BDQD2GDqHNPVOcM,2280
78
- execsql/metacommands/system.py,sha256=sUR5kLL7idTVg8WXIMdd-Kv7nkERIiaeL0beWsz8NyY,7293
79
+ execsql/metacommands/system.py,sha256=AeyxbMLZVOi2nThbHiZ2F_UAIrZ1r4kjI3G80TtnSD4,7385
79
80
  execsql/metacommands/upsert.py,sha256=P2935aQHDZPiVwnXi0fGQ7Guxrm-Sy_YunyuSqVSegI,15880
80
81
  execsql/script/__init__.py,sha256=HbVQmQEVn4gBtzwy5_nlbDGuRnbWd4dI4nG-q1KyBxs,3498
81
82
  execsql/script/control.py,sha256=s-1eZdGARM6H1FwZ6VDdO_f50j7bvvRtTHesfUm9tbc,6144
@@ -86,31 +87,31 @@ execsql/utils/auth.py,sha256=onXzNkNZQZxGC5w7eey06sjvAIAX_Lf9g7nUJtcsel0,7009
86
87
  execsql/utils/crypto.py,sha256=2OnBWwn9bCBGc1ZkyRv16TvhottoCNYtXqgbE3mG3Sg,2960
87
88
  execsql/utils/datetime.py,sha256=V_itd5vVvUPjT86P_z_hh4mlerMDGhDzI5MwPMDBaI4,7715
88
89
  execsql/utils/errors.py,sha256=YKhYD27-3timuZavc2vIrRIfHa71vzih-KVPsAKgvkU,8163
89
- execsql/utils/fileio.py,sha256=F6M4osE0Mb2ycTcvwwcYnhBXH1L36v6d7Oxdab6J16s,24110
90
+ execsql/utils/fileio.py,sha256=RGaMUe-e0xIw32OeprNJCezh0Jr9gQimQNqOXBEaJ2M,24338
90
91
  execsql/utils/gui.py,sha256=UFtwrXPNqNvZCJFCbumZ1aG2d9B-vyaJXIG83fDHteo,18409
91
- execsql/utils/mail.py,sha256=75A1cMnEfyP0_QMMWuSLv8hrcIjc9cGBCrttLpr2TXQ,5372
92
+ execsql/utils/mail.py,sha256=Sd7vWj-dz3w0XDSFU9PM8gmy41pojk-Vsgbfven2DMk,5786
92
93
  execsql/utils/numeric.py,sha256=xh02ANSRk3nUpQ-rtm66ILoMqoi7HtzCoRMIOT9U8QI,1570
93
94
  execsql/utils/regex.py,sha256=diEzTZqU_HHwVMadPAvN1Vgzhl7I03eVaEFGCXyGGL8,3770
94
95
  execsql/utils/strings.py,sha256=5Dvzrk-9SIw2lpxXZQkiJbNyo1sy7iXXAtSULlZ0KG8,8488
95
96
  execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
96
- execsql2-2.12.6.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
97
- execsql2-2.12.6.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
98
- execsql2-2.12.6.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
99
- execsql2-2.12.6.data/data/execsql2_extras/execsql.conf,sha256=_45iJ-KWZnB8uMW_gEg067MM5pmGJ-dVl7VbAZMunAE,9530
100
- execsql2-2.12.6.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
101
- execsql2-2.12.6.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
102
- execsql2-2.12.6.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
103
- execsql2-2.12.6.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
104
- execsql2-2.12.6.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
105
- execsql2-2.12.6.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
106
- execsql2-2.12.6.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
107
- execsql2-2.12.6.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
108
- execsql2-2.12.6.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
109
- execsql2-2.12.6.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
110
- execsql2-2.12.6.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
111
- execsql2-2.12.6.dist-info/METADATA,sha256=QM1G8hAxmsNAj05cWEcly_22msjrWAEkNFinUGaH9Fc,17560
112
- execsql2-2.12.6.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
113
- execsql2-2.12.6.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
114
- execsql2-2.12.6.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
115
- execsql2-2.12.6.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
116
- execsql2-2.12.6.dist-info/RECORD,,
97
+ execsql2-2.13.0.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
98
+ execsql2-2.13.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
99
+ execsql2-2.13.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
100
+ execsql2-2.13.0.data/data/execsql2_extras/execsql.conf,sha256=_45iJ-KWZnB8uMW_gEg067MM5pmGJ-dVl7VbAZMunAE,9530
101
+ execsql2-2.13.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
102
+ execsql2-2.13.0.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
103
+ execsql2-2.13.0.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
104
+ execsql2-2.13.0.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
105
+ execsql2-2.13.0.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
106
+ execsql2-2.13.0.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
107
+ execsql2-2.13.0.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
108
+ execsql2-2.13.0.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
109
+ execsql2-2.13.0.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
110
+ execsql2-2.13.0.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
111
+ execsql2-2.13.0.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
112
+ execsql2-2.13.0.dist-info/METADATA,sha256=vzxeVjRXdmJJWziI9LKFzkkLK8G_JIQxmSbyGD9wWks,17566
113
+ execsql2-2.13.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
114
+ execsql2-2.13.0.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
115
+ execsql2-2.13.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
116
+ execsql2-2.13.0.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
117
+ execsql2-2.13.0.dist-info/RECORD,,