execsql2 2.0.1__py3-none-any.whl → 2.1.2__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 (90) hide show
  1. execsql/cli.py +322 -108
  2. execsql/config.py +134 -114
  3. execsql/db/access.py +89 -65
  4. execsql/db/base.py +97 -68
  5. execsql/db/dsn.py +45 -29
  6. execsql/db/duckdb.py +4 -5
  7. execsql/db/factory.py +27 -27
  8. execsql/db/firebird.py +30 -18
  9. execsql/db/mysql.py +38 -14
  10. execsql/db/oracle.py +58 -33
  11. execsql/db/postgres.py +68 -28
  12. execsql/db/sqlite.py +36 -27
  13. execsql/db/sqlserver.py +45 -30
  14. execsql/exceptions.py +68 -64
  15. execsql/exporters/__init__.py +1 -1
  16. execsql/exporters/base.py +42 -17
  17. execsql/exporters/delimited.py +60 -59
  18. execsql/exporters/duckdb.py +8 -12
  19. execsql/exporters/feather.py +32 -24
  20. execsql/exporters/html.py +33 -30
  21. execsql/exporters/json.py +18 -17
  22. execsql/exporters/latex.py +11 -13
  23. execsql/exporters/ods.py +50 -46
  24. execsql/exporters/parquet.py +32 -0
  25. execsql/exporters/pretty.py +16 -15
  26. execsql/exporters/raw.py +9 -11
  27. execsql/exporters/sqlite.py +38 -38
  28. execsql/exporters/templates.py +15 -72
  29. execsql/exporters/values.py +13 -12
  30. execsql/exporters/xls.py +26 -26
  31. execsql/exporters/xml.py +12 -12
  32. execsql/exporters/zip.py +0 -3
  33. execsql/gui/__init__.py +2 -2
  34. execsql/gui/console.py +0 -1
  35. execsql/gui/desktop.py +6 -7
  36. execsql/gui/tui.py +8 -14
  37. execsql/importers/base.py +6 -9
  38. execsql/importers/csv.py +10 -17
  39. execsql/importers/feather.py +16 -22
  40. execsql/importers/ods.py +3 -4
  41. execsql/importers/xls.py +5 -6
  42. execsql/metacommands/__init__.py +8 -8
  43. execsql/metacommands/conditions.py +41 -33
  44. execsql/metacommands/connect.py +113 -99
  45. execsql/metacommands/control.py +38 -26
  46. execsql/metacommands/data.py +35 -33
  47. execsql/metacommands/debug.py +13 -9
  48. execsql/metacommands/io.py +288 -229
  49. execsql/metacommands/prompt.py +179 -157
  50. execsql/metacommands/script_ext.py +11 -9
  51. execsql/metacommands/system.py +44 -25
  52. execsql/models.py +9 -16
  53. execsql/parser.py +10 -10
  54. execsql/script.py +183 -157
  55. execsql/state.py +170 -208
  56. execsql/types.py +46 -81
  57. execsql/utils/auth.py +114 -14
  58. execsql/utils/crypto.py +31 -4
  59. execsql/utils/datetime.py +7 -7
  60. execsql/utils/errors.py +34 -29
  61. execsql/utils/fileio.py +90 -55
  62. execsql/utils/gui.py +22 -23
  63. execsql/utils/mail.py +15 -17
  64. execsql/utils/numeric.py +2 -3
  65. execsql/utils/regex.py +9 -12
  66. execsql/utils/strings.py +10 -12
  67. execsql/utils/timer.py +0 -2
  68. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/execsql.conf +1 -1
  69. execsql2-2.1.2.dist-info/METADATA +300 -0
  70. execsql2-2.1.2.dist-info/RECORD +96 -0
  71. execsql2-2.0.1.dist-info/METADATA +0 -406
  72. execsql2-2.0.1.dist-info/RECORD +0 -95
  73. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/READ_ME.rst +0 -0
  74. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  75. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  76. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/make_config_db.sql +0 -0
  77. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_compare.sql +0 -0
  78. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_glossary.sql +0 -0
  79. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_upsert.sql +0 -0
  80. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_compare.sql +0 -0
  81. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  82. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  83. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/script_template.sql +0 -0
  84. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_compare.sql +0 -0
  85. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  86. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  87. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/WHEEL +0 -0
  88. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/entry_points.txt +0 -0
  89. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/licenses/LICENSE.txt +0 -0
  90. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/licenses/NOTICE +0 -0
execsql/exporters/json.py CHANGED
@@ -8,13 +8,14 @@ Provides :func:`write_query_to_json` (standard JSON array of objects) and
8
8
  both of which serialize a query result set to a file or stream.
9
9
  """
10
10
 
11
- import datetime
12
- import io
13
- import os
14
- from typing import Any, Optional, List
11
+ from typing import Any
15
12
 
16
13
  import execsql.state as _state
17
14
  from execsql.exporters.zip import ZipWriter
15
+ from execsql.exceptions import ErrInfo
16
+ from execsql.models import DataTable
17
+ from execsql.utils.errors import exception_desc
18
+ from execsql.utils.fileio import filewriter_close
18
19
 
19
20
 
20
21
  def write_query_to_json(
@@ -22,8 +23,8 @@ def write_query_to_json(
22
23
  db: Any,
23
24
  outfile: str,
24
25
  append: bool = False,
25
- desc: Optional[str] = None,
26
- zipfile: Optional[str] = None,
26
+ desc: str | None = None,
27
+ zipfile: str | None = None,
27
28
  ) -> None:
28
29
  global json
29
30
  import json
@@ -31,12 +32,12 @@ def write_query_to_json(
31
32
  conf = _state.conf
32
33
  try:
33
34
  hdrs, rows = db.select_rowsource(select_stmt)
34
- except _state.ErrInfo:
35
+ except ErrInfo:
35
36
  raise
36
- except:
37
- raise _state.ErrInfo("db", select_stmt, exception_msg=_state.exception_desc())
37
+ except Exception:
38
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
38
39
  if zipfile is None:
39
- _state.filewriter_close(outfile)
40
+ filewriter_close(outfile)
40
41
  from execsql.utils.fileio import EncodedFile
41
42
 
42
43
  ef = EncodedFile(outfile, conf.output_encoding)
@@ -69,19 +70,19 @@ def write_query_to_json_ts(
69
70
  outfile: str,
70
71
  append: bool = False,
71
72
  write_types: bool = True,
72
- desc: Optional[str] = None,
73
- zipfile: Optional[str] = None,
73
+ desc: str | None = None,
74
+ zipfile: str | None = None,
74
75
  ) -> None:
75
76
  conf = _state.conf
76
77
  try:
77
78
  hdrs, rows = db.select_rowsource(select_stmt)
78
- except _state.ErrInfo:
79
+ except ErrInfo:
79
80
  raise
80
- except:
81
- raise _state.ErrInfo("db", select_stmt, exception_msg=_state.exception_desc())
81
+ except Exception:
82
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
82
83
  max_col_idx = len(hdrs) - 1
83
84
  if zipfile is None:
84
- _state.filewriter_close(outfile)
85
+ filewriter_close(outfile)
85
86
  from execsql.utils.fileio import EncodedFile
86
87
 
87
88
  ef = EncodedFile(outfile, conf.output_encoding)
@@ -98,7 +99,7 @@ def write_query_to_json_ts(
98
99
  f.write(' "fields": [\n')
99
100
  if write_types:
100
101
  # Scan the data to determine data types.
101
- tbl_desc = _state.DataTable(hdrs, rows)
102
+ tbl_desc = DataTable(hdrs, rows)
102
103
  # Write the column descriptions to the header.
103
104
  # Iterate over hdrs instead of tbl_desc.cols to preserve column order.
104
105
  for i, h in enumerate(hdrs):
@@ -8,11 +8,10 @@ set to a LaTeX ``tabular`` environment suitable for inclusion in a
8
8
  ``.tex`` document.
9
9
  """
10
10
 
11
- import io
12
11
  import os
13
- import re
14
12
  import tempfile
15
- from typing import Any, List, Optional
13
+ from pathlib import Path
14
+ from typing import Any
16
15
 
17
16
  from execsql.exceptions import ErrInfo
18
17
  from execsql.exporters.zip import WriteableZipfile
@@ -21,15 +20,14 @@ import execsql.state as _state
21
20
 
22
21
  def export_latex(
23
22
  outfile: str,
24
- hdrs: List[str],
23
+ hdrs: list[str],
25
24
  rows: Any,
26
25
  append: bool = False,
27
- querytext: Optional[str] = None,
28
- desc: Optional[str] = None,
29
- zipfile: Optional[Any] = None,
26
+ querytext: str | None = None,
27
+ desc: str | None = None,
28
+ zipfile: Any | None = None,
30
29
  ) -> None:
31
30
  from execsql.utils.fileio import EncodedFile
32
- from execsql.utils.errors import exception_info
33
31
 
34
32
  def write_table(f: Any) -> None:
35
33
  f.write("\\begin{center}\n")
@@ -69,7 +67,7 @@ def export_latex(
69
67
  if outfile.lower() != "stdout":
70
68
  f.close()
71
69
  else:
72
- if outfile.lower() == "stdout" or not os.path.isfile(outfile):
70
+ if outfile.lower() == "stdout" or not Path(outfile).is_file():
73
71
  if outfile.lower() == "stdout":
74
72
  import sys
75
73
 
@@ -113,15 +111,15 @@ def write_query_to_latex(
113
111
  db: Any,
114
112
  outfile: str,
115
113
  append: bool = False,
116
- desc: Optional[str] = None,
117
- zipfile: Optional[Any] = None,
114
+ desc: str | None = None,
115
+ zipfile: Any | None = None,
118
116
  ) -> None:
119
- from execsql.utils.errors import exception_info
117
+ from execsql.utils.errors import exception_desc
120
118
 
121
119
  try:
122
120
  hdrs, rows = db.select_rowsource(select_stmt)
123
121
  except ErrInfo:
124
122
  raise
125
123
  except Exception:
126
- raise ErrInfo("db", select_stmt, exception_msg=exception_info())
124
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
127
125
  export_latex(outfile, hdrs, rows, append, select_stmt, desc, zipfile=zipfile)
execsql/exporters/ods.py CHANGED
@@ -11,13 +11,17 @@ Provides :func:`write_query_to_ods` (single-sheet export),
11
11
 
12
12
  import datetime
13
13
  import getpass
14
- import io
15
14
  import os
16
- import re
17
- from typing import Any, Optional, List
15
+ from pathlib import Path
16
+ from typing import Any
18
17
 
19
18
  import execsql.state as _state
20
19
  from execsql.exceptions import OdsFileError
20
+ from execsql.exceptions import ErrInfo
21
+ from execsql.script import current_script_line
22
+ from execsql.utils.errors import exception_desc, fatal_error
23
+ from execsql.utils.fileio import filewriter_close
24
+ from execsql.utils.strings import unquoted
21
25
 
22
26
 
23
27
  class OdsFile:
@@ -27,20 +31,21 @@ class OdsFile:
27
31
  def __init__(self) -> None:
28
32
  global of
29
33
  try:
34
+ import of as of
30
35
  import of.opendocument
31
36
  import of.table
32
37
  import of.text
33
38
  import of.number
34
39
  import of.style
35
- except:
36
- _state.fatal_error("The odfpy library is needed to create OpenDocument spreadsheets.")
40
+ except ImportError:
41
+ fatal_error("The odfpy library is needed to create OpenDocument spreadsheets.")
37
42
  self.filename = None
38
43
  self.wbk = None
39
44
  self.cell_style_names = []
40
45
 
41
46
  def open(self, filename: str) -> None:
42
47
  self.filename = filename
43
- if os.path.isfile(filename):
48
+ if Path(filename).is_file():
44
49
  self.wbk = of.opendocument.load(filename)
45
50
  # Get a list of all cell style names used, so as not to re-define them.
46
51
  for sty in self.wbk.automaticstyles.childNodes:
@@ -50,8 +55,8 @@ class OdsFile:
50
55
  name = sty.getAttribute("name")
51
56
  if name not in self.cell_style_names:
52
57
  self.cell_style_names.append(name)
53
- except:
54
- pass
58
+ except Exception:
59
+ pass # Skip nodes without expected attributes.
55
60
  else:
56
61
  self.wbk = of.opendocument.OpenDocumentSpreadsheet()
57
62
 
@@ -119,7 +124,7 @@ class OdsFile:
119
124
  self.wbk.automaticstyles.addElement(dts)
120
125
  self.cell_style_names.append(st_name)
121
126
 
122
- def sheetnames(self) -> List[str]:
127
+ def sheetnames(self) -> list[str]:
123
128
  # Returns a list of the worksheet names in the specified ODS spreadsheet.
124
129
  return [sheet.getAttribute("name") for sheet in self.wbk.spreadsheet.getElementsByType(of.table.Table)]
125
130
 
@@ -133,7 +138,7 @@ class OdsFile:
133
138
  sheet_no = int(sheetname)
134
139
  if sheet_no < 1:
135
140
  sheet_no = None
136
- except:
141
+ except (ValueError, TypeError):
137
142
  sheet_no = None
138
143
  if sheet_no is not None:
139
144
  for i, sheet in enumerate(self.wbk.spreadsheet.getElementsByType(of.table.Table)):
@@ -147,7 +152,7 @@ class OdsFile:
147
152
  return sheet
148
153
  return None
149
154
 
150
- def sheet_data(self, sheetname: Any, junk_header_rows: int = 0) -> List:
155
+ def sheet_data(self, sheetname: Any, junk_header_rows: int = 0) -> list:
151
156
  sheet = self.sheet_named(sheetname)
152
157
  if not sheet:
153
158
  raise OdsFileError(f"There is no sheet named {sheetname}")
@@ -166,19 +171,19 @@ class OdsFile:
166
171
  repeat = spanned
167
172
  ps = cell.getElementsByType(of.text.P)
168
173
  if len(ps) == 0:
169
- for rr in range(int(repeat)):
174
+ for _rr in range(int(repeat)):
170
175
  p_content.append(None)
171
176
  else:
172
177
  for p in ps:
173
178
  pval = str(p)
174
179
  if len(pval) == 0:
175
- for rr in range(int(repeat)):
180
+ for _rr in range(int(repeat)):
176
181
  p_content.append(None)
177
182
  else:
178
- for rr in range(int(repeat)):
183
+ for _rr in range(int(repeat)):
179
184
  p_content.append(pval)
180
185
  if len(p_content) == 0:
181
- for rr in range(int(repeat)):
186
+ for _rr in range(int(repeat)):
182
187
  rowdata.append(None)
183
188
  elif p_content[0] != "#":
184
189
  rowdata.extend(p_content)
@@ -206,7 +211,7 @@ class OdsFile:
206
211
  if isinstance(item, bool):
207
212
  # Booleans must be evaluated before numbers.
208
213
  tc = of.table.TableCell(valuetype="boolean", value=1 if item else 0, stylename=style_name)
209
- elif isinstance(item, float) or isinstance(item, int):
214
+ elif isinstance(item, (float, int)):
210
215
  tc = of.table.TableCell(valuetype="float", value=item, stylename=style_name)
211
216
  elif isinstance(item, datetime.datetime):
212
217
  self.define_iso_datetime_style()
@@ -245,9 +250,8 @@ class OdsFile:
245
250
  self.wbk.spreadsheet.addElement(of_table)
246
251
 
247
252
  def save_close(self) -> None:
248
- ofile = io.open(self.filename, "wb")
249
- self.wbk.write(ofile)
250
- ofile.close()
253
+ with open(self.filename, "wb") as ofile:
254
+ self.wbk.write(ofile)
251
255
  self.filename = None
252
256
  self.wbk = None
253
257
 
@@ -258,16 +262,16 @@ class OdsFile:
258
262
 
259
263
  def export_ods(
260
264
  outfile: str,
261
- hdrs: List[str],
265
+ hdrs: list[str],
262
266
  rows: Any,
263
267
  append: bool = False,
264
- querytext: Optional[str] = None,
265
- sheetname: Optional[str] = None,
266
- desc: Optional[str] = None,
268
+ querytext: str | None = None,
269
+ sheetname: str | None = None,
270
+ desc: str | None = None,
267
271
  ) -> None:
268
272
  # If not given, determine the worksheet name to use. The pattern is "Sheetx", where x is
269
273
  # the first integer for which there is not already a sheet name.
270
- if append and os.path.isfile(outfile):
274
+ if append and Path(outfile).is_file():
271
275
  wbk = OdsFile()
272
276
  wbk.open(outfile)
273
277
  sheet_names = wbk.sheetnames()
@@ -282,8 +286,8 @@ def export_ods(
282
286
  wbk.close()
283
287
  else:
284
288
  sheet_name = sheetname or "Sheet1"
285
- if os.path.isfile(outfile):
286
- _state.filewriter_close(outfile)
289
+ if Path(outfile).is_file():
290
+ filewriter_close(outfile)
287
291
  os.unlink(outfile)
288
292
  wbk = OdsFile()
289
293
  wbk.open(outfile)
@@ -307,11 +311,11 @@ def export_ods(
307
311
  # Add information to the "Datasheets" sheet.
308
312
  datasheetlist = wbk.sheet_named(datasheet_name)
309
313
  if datasheetlist:
310
- script, lno = _state.current_script_line()
314
+ script, lno = current_script_line()
311
315
  if querytext:
312
- src = f"{querytext} with database {_state.dbs.current().name()}, with script {os.path.abspath(script)}, line {lno}"
316
+ src = f"{querytext} with database {_state.dbs.current().name()}, with script {str(Path(script).resolve())}, line {lno}"
313
317
  else:
314
- src = f"From database {_state.dbs.current().name()}, with script {os.path.abspath(script)}, line {lno}"
318
+ src = f"From database {_state.dbs.current().name()}, with script {str(Path(script).resolve())}, line {lno}"
315
319
  wbk.add_row_to_sheet(
316
320
  (
317
321
  sheet_name,
@@ -331,15 +335,15 @@ def write_query_to_ods(
331
335
  db: Any,
332
336
  outfile: str,
333
337
  append: bool = False,
334
- sheetname: Optional[str] = None,
335
- desc: Optional[str] = None,
338
+ sheetname: str | None = None,
339
+ desc: str | None = None,
336
340
  ) -> None:
337
341
  try:
338
342
  hdrs, rows = db.select_rowsource(select_stmt)
339
- except _state.ErrInfo:
343
+ except ErrInfo:
340
344
  raise
341
- except:
342
- raise _state.ErrInfo("db", select_stmt, exception_msg=_state.exception_desc())
345
+ except Exception:
346
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
343
347
  export_ods(outfile, hdrs, rows, append, select_stmt, sheetname, desc)
344
348
 
345
349
 
@@ -349,7 +353,7 @@ def write_queries_to_ods(
349
353
  outfile: str,
350
354
  append: bool = False,
351
355
  tee: bool = False,
352
- desc: Optional[str] = None,
356
+ desc: str | None = None,
353
357
  ) -> None:
354
358
  from execsql.exporters.pretty import prettyprint_query
355
359
  from execsql.exporters.base import ExportRecord
@@ -358,8 +362,8 @@ def write_queries_to_ods(
358
362
  if desc is not None:
359
363
  descriptions = [d.strip() for d in desc.split(",")]
360
364
  one_desc = len(descriptions) != len(tables)
361
- if os.path.isfile(outfile) and not append:
362
- _state.filewriter_close(outfile)
365
+ if Path(outfile).is_file() and not append:
366
+ filewriter_close(outfile)
363
367
  os.unlink(outfile)
364
368
  wbk = OdsFile()
365
369
  wbk.open(outfile)
@@ -377,13 +381,13 @@ def write_queries_to_ods(
377
381
  if "." in t:
378
382
  st = t.split(".")
379
383
  if len(st) != 2:
380
- raise _state.ErrInfo("cmd", other_msg=f"Unrecognized table specification in <{t}>")
384
+ raise ErrInfo("cmd", other_msg=f"Unrecognized table specification in <{t}>")
381
385
  if len(st) == 1:
382
- tblname = _state.unquoted(st[0])
386
+ tblname = unquoted(st[0])
383
387
  else:
384
- tblname = _state.unquoted(st[1])
388
+ tblname = unquoted(st[1])
385
389
  else:
386
- tblname = _state.unquoted(t)
390
+ tblname = unquoted(t)
387
391
  # Get next sheet number for sheet name
388
392
  sheet_names = wbk.sheetnames()
389
393
  sheet_name = tblname
@@ -397,10 +401,10 @@ def write_queries_to_ods(
397
401
  select_stmt = f"select * from {t};"
398
402
  try:
399
403
  hdrs, rows = db.select_rowsource(select_stmt)
400
- except _state.ErrInfo:
404
+ except ErrInfo:
401
405
  raise
402
- except:
403
- raise _state.ErrInfo("db", select_stmt, exception_msg=_state.exception_desc())
406
+ except Exception:
407
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
404
408
  # Add the data to a new sheet.
405
409
  tbl = wbk.new_sheet(sheet_name)
406
410
  wbk.add_row_to_sheet(hdrs, tbl, header=True)
@@ -418,8 +422,8 @@ def write_queries_to_ods(
418
422
  d = descriptions[i]
419
423
  datasheetlist = wbk.sheet_named(inventory_name)
420
424
  if datasheetlist:
421
- script, lno = _state.current_script_line()
422
- src = f"From database {_state.dbs.current().name()}, with script {os.path.abspath(script)}, line {lno}"
425
+ script, lno = current_script_line()
426
+ src = f"From database {_state.dbs.current().name()}, with script {str(Path(script).resolve())}, line {lno}"
423
427
  wbk.add_row_to_sheet(
424
428
  (
425
429
  sheet_name,
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Apache Parquet export for execsql.
5
+
6
+ Provides :func:`write_query_to_parquet` (Parquet format via ``polars``).
7
+ Used by ``EXPORT … FORMAT parquet``. Polars is an optional dependency.
8
+ """
9
+
10
+ from typing import Any
11
+
12
+ from execsql.exceptions import ErrInfo
13
+ from execsql.utils.errors import exception_desc
14
+ from execsql.utils.fileio import filewriter_close
15
+
16
+
17
+ def write_query_to_parquet(outfile: str, headers: list[str], rows: Any) -> None:
18
+ try:
19
+ import polars as pl
20
+ except ImportError:
21
+ raise ErrInfo(
22
+ "exception",
23
+ exception_msg=exception_desc(),
24
+ other_msg="The polars Python package must be installed to export data to the parquet format.",
25
+ )
26
+ rows_list = list(rows)
27
+ if rows_list:
28
+ df = pl.DataFrame(rows_list, schema=headers, orient="row")
29
+ else:
30
+ df = pl.DataFrame({h: [] for h in headers})
31
+ filewriter_close(outfile)
32
+ df.write_parquet(outfile)
@@ -8,44 +8,45 @@ format a query result set as a fixed-width human-readable text table
8
8
  (column-aligned, with a header row and separator).
9
9
  """
10
10
 
11
- import io
12
- import os
13
- from typing import Any, Optional, List
11
+ from typing import Any
14
12
 
15
13
  import execsql.state as _state
16
14
  from execsql.exporters.zip import ZipWriter
15
+ from execsql.exceptions import ErrInfo
16
+ from execsql.utils.errors import exception_desc
17
+ from execsql.utils.fileio import filewriter_close
17
18
 
18
19
 
19
20
  def prettyprint_rowset(
20
- colhdrs: List[str],
21
+ colhdrs: list[str],
21
22
  rows: Any,
22
23
  output_dest: str,
23
24
  append: bool = False,
24
25
  and_val: str = "",
25
- desc: Optional[str] = None,
26
- zipfile: Optional[str] = None,
26
+ desc: str | None = None,
27
+ zipfile: str | None = None,
27
28
  ) -> None:
28
29
  # Adapted from the pp() function by Aaron Watters,
29
30
  # posted to gadfly-rdbms@egroups.com 1999-01-18.
30
31
  def as_ucode(s):
31
32
  if s is None:
32
33
  return and_val
33
- if isinstance(s, type("")):
34
+ if isinstance(s, str):
34
35
  return s
35
36
  if type(s) in (type(memoryview(b"")), bytes, bytearray):
36
37
  return f"Binary data ({len(s)} bytes)"
37
38
  else:
38
- if type(s) == type(b""):
39
+ if isinstance(s, bytes):
39
40
  return s.decode(_state.dbs.current().encoding)
40
41
  return str(s)
41
42
 
42
- if type(rows) != "list":
43
+ if not isinstance(rows, list):
43
44
  try:
44
45
  rows = list(rows)
45
- except:
46
- raise _state.ErrInfo(
46
+ except Exception:
47
+ raise ErrInfo(
47
48
  "exception",
48
- exception_msg=_state.exception_desc(),
49
+ exception_msg=exception_desc(),
49
50
  other_msg="Can't create a list in memory of the data to be displayed as formatted text.",
50
51
  )
51
52
  rcols = range(len(colhdrs))
@@ -68,7 +69,7 @@ def prettyprint_rowset(
68
69
  if zipfile is None:
69
70
  from execsql.utils.fileio import EncodedFile
70
71
 
71
- _state.filewriter_close(output_dest)
72
+ filewriter_close(output_dest)
72
73
  if append:
73
74
  ofile = EncodedFile(output_dest, _state.conf.output_encoding).open("a")
74
75
  else:
@@ -91,8 +92,8 @@ def prettyprint_query(
91
92
  outfile: str,
92
93
  append: bool = False,
93
94
  and_val: str = "",
94
- desc: Optional[str] = None,
95
- zipfile: Optional[str] = None,
95
+ desc: str | None = None,
96
+ zipfile: str | None = None,
96
97
  ) -> None:
97
98
  _state.status.sql_error = False
98
99
  names, rows = db.select_data(select_stmt)
execsql/exporters/raw.py CHANGED
@@ -9,12 +9,10 @@ used by the ``EXPORT … FORMAT raw`` and ``FORMAT b64`` metacommand
9
9
  variants.
10
10
  """
11
11
 
12
- import io
13
- import os
14
- from typing import Any, Optional
12
+ from typing import Any
15
13
 
16
- import execsql.state as _state
17
14
  from execsql.exporters.zip import ZipWriter
15
+ from execsql.utils.fileio import filewriter_close
18
16
 
19
17
 
20
18
  def write_query_raw(
@@ -22,17 +20,17 @@ def write_query_raw(
22
20
  rowsource: Any,
23
21
  db_encoding: str,
24
22
  append: bool = False,
25
- zipfile: Optional[str] = None,
23
+ zipfile: str | None = None,
26
24
  ) -> None:
27
25
  if zipfile is None:
28
- _state.filewriter_close(outfile)
26
+ filewriter_close(outfile)
29
27
  mode = "wb" if not append else "ab"
30
- of = io.open(outfile, mode)
28
+ of = open(outfile, mode) # noqa: SIM115
31
29
  else:
32
30
  of = ZipWriter(zipfile, outfile, append)
33
31
  for row in rowsource:
34
32
  for col in row:
35
- if type(col) == type(bytearray()):
33
+ if isinstance(col, bytearray):
36
34
  of.write(col)
37
35
  else:
38
36
  if isinstance(col, str):
@@ -42,14 +40,14 @@ def write_query_raw(
42
40
  of.close()
43
41
 
44
42
 
45
- def write_query_b64(outfile: str, rowsource: Any, append: bool = False, zipfile: Optional[str] = None) -> None:
43
+ def write_query_b64(outfile: str, rowsource: Any, append: bool = False, zipfile: str | None = None) -> None:
46
44
  global base64
47
45
  import base64
48
46
 
49
47
  if zipfile is None:
50
- _state.filewriter_close(outfile)
48
+ filewriter_close(outfile)
51
49
  mode = "wb" if not append else "ab"
52
- of = io.open(outfile, mode)
50
+ of = open(outfile, mode) # noqa: SIM115
53
51
  else:
54
52
  of = ZipWriter(zipfile, outfile, append)
55
53
  for row in rowsource:
@@ -8,16 +8,16 @@ to a table in an SQLite database file. Used by ``EXPORT … FORMAT sqlite``.
8
8
  """
9
9
 
10
10
  import math
11
- import os
12
- from typing import Any, List, Optional
11
+ from pathlib import Path
12
+ from typing import Any
13
13
 
14
14
  from execsql.exceptions import ErrInfo
15
- import execsql.state as _state
15
+ from execsql.types import dbt_sqlite
16
16
 
17
17
 
18
18
  def export_sqlite(
19
19
  outfile: str,
20
- hdrs: List[str],
20
+ hdrs: list[str],
21
21
  rows: Any,
22
22
  append: bool,
23
23
  tablename: str,
@@ -25,43 +25,43 @@ def export_sqlite(
25
25
  import sqlite3
26
26
 
27
27
  from execsql.models import DataTable
28
- from execsql.utils.errors import exception_info
29
28
 
30
29
  chunksize = 10000
31
- pre_exist = os.path.isfile(outfile)
30
+ pre_exist = Path(outfile).is_file()
32
31
  sdb = sqlite3.connect(outfile)
33
- if pre_exist:
32
+ try:
33
+ if pre_exist:
34
+ curs = sdb.cursor()
35
+ res = curs.execute(
36
+ f"select name from sqlite_master where type='table' and name='{tablename}';",
37
+ )
38
+ rv = res.fetchone()
39
+ if not (rv is None or rv[0] == 0):
40
+ if append:
41
+ raise ErrInfo(type="error", other_msg=f"The table {tablename} already exists in {outfile}.")
42
+ else:
43
+ curs.execute(f"drop table {tablename};")
44
+ curs.close()
45
+ # Construct and run the CREATE TABLE statement
46
+ rowdata = list(rows)
47
+ tablespec = DataTable(hdrs, rowdata)
48
+ sql = tablespec.create_table(dbt_sqlite, schemaname=None, tablename=tablename)
34
49
  curs = sdb.cursor()
35
- res = curs.execute(
36
- f"select name from sqlite_master where type='table' and name='{tablename}';",
37
- )
38
- rv = res.fetchone()
39
- if not (rv is None or rv[0] == 0):
40
- if append:
41
- raise ErrInfo(type="error", other_msg=f"The table {tablename} already exists in {outfile}.")
42
- else:
43
- curs.execute(f"drop table {tablename};")
50
+ curs.execute(sql)
51
+ # Export all rows of data
52
+ columns = [dbt_sqlite.quoted(col) for col in hdrs]
53
+ colspec = ",".join(columns)
54
+ paramspec = ",".join(("?",) * len(columns))
55
+ sql = f"insert into {tablename} ({colspec}) values ({paramspec});"
56
+ n_chunks = math.ceil(len(rowdata) / chunksize)
57
+ for i in range(n_chunks):
58
+ start = i * chunksize
59
+ end = start + chunksize
60
+ curs.executemany(sql, rowdata[start:end])
61
+ sdb.commit()
44
62
  curs.close()
45
- # Construct and run the CREATE TABLE statement
46
- rowdata = list(rows)
47
- tablespec = DataTable(hdrs, rowdata)
48
- dbt_sqlite = _state.dbt_sqlite
49
- sql = tablespec.create_table(dbt_sqlite, schemaname=None, tablename=tablename)
50
- curs = sdb.cursor()
51
- curs.execute(sql)
52
- # Export all rows of data
53
- columns = [dbt_sqlite.quoted(col) for col in hdrs]
54
- colspec = ",".join(columns)
55
- paramspec = ",".join(("?",) * len(columns))
56
- sql = f"insert into {tablename} ({colspec}) values ({paramspec});"
57
- n_chunks = math.ceil(len(rowdata) / chunksize)
58
- for i in range(n_chunks):
59
- start = i * chunksize
60
- end = start + chunksize
61
- curs.executemany(sql, rowdata[start:end])
62
- sdb.commit()
63
- curs.close()
64
- sdb.close()
63
+ finally:
64
+ sdb.close()
65
65
 
66
66
 
67
67
  def write_query_to_sqlite(
@@ -71,12 +71,12 @@ def write_query_to_sqlite(
71
71
  append: bool,
72
72
  tablename: str,
73
73
  ) -> None:
74
- from execsql.utils.errors import exception_info
74
+ from execsql.utils.errors import exception_desc
75
75
 
76
76
  try:
77
77
  hdrs, rows = db.select_rowsource(select_stmt)
78
78
  except ErrInfo:
79
79
  raise
80
80
  except Exception:
81
- raise ErrInfo("db", select_stmt, exception_msg=exception_info())
81
+ raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
82
82
  export_sqlite(outfile, hdrs, rows, append, tablename)