execsql2 2.2.1__py3-none-any.whl → 2.4.1__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 (79) hide show
  1. execsql/cli/run.py +11 -5
  2. execsql/config.py +52 -0
  3. execsql/db/access.py +11 -3
  4. execsql/db/base.py +180 -135
  5. execsql/db/dsn.py +4 -0
  6. execsql/db/duckdb.py +4 -0
  7. execsql/db/factory.py +31 -5
  8. execsql/db/firebird.py +4 -0
  9. execsql/db/mysql.py +18 -1
  10. execsql/db/oracle.py +4 -0
  11. execsql/db/postgres.py +3 -0
  12. execsql/db/sqlite.py +3 -0
  13. execsql/db/sqlserver.py +11 -2
  14. execsql/exceptions.py +18 -0
  15. execsql/exporters/base.py +6 -0
  16. execsql/exporters/delimited.py +36 -0
  17. execsql/exporters/duckdb.py +4 -0
  18. execsql/exporters/feather.py +4 -0
  19. execsql/exporters/html.py +6 -0
  20. execsql/exporters/json.py +5 -6
  21. execsql/exporters/latex.py +4 -0
  22. execsql/exporters/ods.py +28 -7
  23. execsql/exporters/parquet.py +3 -0
  24. execsql/exporters/pretty.py +5 -0
  25. execsql/exporters/raw.py +5 -3
  26. execsql/exporters/sqlite.py +4 -0
  27. execsql/exporters/templates.py +16 -6
  28. execsql/exporters/values.py +4 -0
  29. execsql/exporters/xls.py +26 -7
  30. execsql/exporters/xml.py +3 -0
  31. execsql/exporters/zip.py +15 -0
  32. execsql/importers/base.py +5 -3
  33. execsql/importers/csv.py +7 -5
  34. execsql/importers/feather.py +6 -4
  35. execsql/importers/ods.py +2 -0
  36. execsql/importers/xls.py +2 -0
  37. execsql/metacommands/__init__.py +177 -1968
  38. execsql/metacommands/dispatch.py +2011 -0
  39. execsql/models.py +7 -0
  40. execsql/parser.py +10 -0
  41. execsql/script/__init__.py +95 -0
  42. execsql/script/control.py +162 -0
  43. execsql/{script.py → script/engine.py} +144 -406
  44. execsql/script/variables.py +281 -0
  45. execsql/types.py +29 -0
  46. execsql/utils/auth.py +2 -0
  47. execsql/utils/crypto.py +4 -6
  48. execsql/utils/datetime.py +1 -0
  49. execsql/utils/errors.py +11 -0
  50. execsql/utils/fileio.py +18 -0
  51. execsql/utils/gui.py +46 -0
  52. execsql/utils/mail.py +7 -17
  53. execsql/utils/numeric.py +2 -0
  54. execsql/utils/regex.py +9 -0
  55. execsql/utils/strings.py +16 -0
  56. execsql/utils/timer.py +2 -0
  57. execsql2-2.4.1.data/data/execsql2_extras/README.md +65 -0
  58. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/execsql.conf +1 -1
  59. {execsql2-2.2.1.dist-info → execsql2-2.4.1.dist-info}/METADATA +8 -1
  60. execsql2-2.4.1.dist-info/RECORD +108 -0
  61. execsql2-2.2.1.data/data/execsql2_extras/READ_ME.rst +0 -127
  62. execsql2-2.2.1.dist-info/RECORD +0 -104
  63. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  64. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  65. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
  66. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/md_compare.sql +0 -0
  67. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/md_glossary.sql +0 -0
  68. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/md_upsert.sql +0 -0
  69. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/pg_compare.sql +0 -0
  70. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  71. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  72. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/script_template.sql +0 -0
  73. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/ss_compare.sql +0 -0
  74. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  75. {execsql2-2.2.1.data → execsql2-2.4.1.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  76. {execsql2-2.2.1.dist-info → execsql2-2.4.1.dist-info}/WHEEL +0 -0
  77. {execsql2-2.2.1.dist-info → execsql2-2.4.1.dist-info}/entry_points.txt +0 -0
  78. {execsql2-2.2.1.dist-info → execsql2-2.4.1.dist-info}/licenses/LICENSE.txt +0 -0
  79. {execsql2-2.2.1.dist-info → execsql2-2.4.1.dist-info}/licenses/NOTICE +0 -0
@@ -16,6 +16,8 @@ from execsql.exceptions import ErrInfo
16
16
  from execsql.utils.errors import exception_desc
17
17
  from execsql.utils.fileio import filewriter_close
18
18
 
19
+ __all__ = ["prettyprint_query", "prettyprint_rowset"]
20
+
19
21
 
20
22
  def prettyprint_rowset(
21
23
  colhdrs: list[str],
@@ -26,6 +28,8 @@ def prettyprint_rowset(
26
28
  desc: str | None = None,
27
29
  zipfile: str | None = None,
28
30
  ) -> None:
31
+ """Format a pre-fetched result set as a fixed-width human-readable text table and write it."""
32
+
29
33
  # Adapted from the pp() function by Aaron Watters,
30
34
  # posted to gadfly-rdbms@egroups.com 1999-01-18.
31
35
  def as_ucode(s):
@@ -97,6 +101,7 @@ def prettyprint_query(
97
101
  desc: str | None = None,
98
102
  zipfile: str | None = None,
99
103
  ) -> None:
104
+ """Execute a SELECT and write the result set as a column-aligned text table."""
100
105
  _state.status.sql_error = False
101
106
  names, rows = db.select_data(select_stmt)
102
107
  prettyprint_rowset(names, rows, outfile, append, and_val, desc, zipfile=zipfile)
execsql/exporters/raw.py CHANGED
@@ -9,11 +9,14 @@ used by the ``EXPORT … FORMAT raw`` and ``FORMAT b64`` metacommand
9
9
  variants.
10
10
  """
11
11
 
12
+ import base64
12
13
  from typing import Any
13
14
 
14
15
  from execsql.exporters.zip import ZipWriter
15
16
  from execsql.utils.fileio import filewriter_close
16
17
 
18
+ __all__ = ["write_query_raw", "write_query_b64"]
19
+
17
20
 
18
21
  def write_query_raw(
19
22
  outfile: str,
@@ -22,6 +25,7 @@ def write_query_raw(
22
25
  append: bool = False,
23
26
  zipfile: str | None = None,
24
27
  ) -> None:
28
+ """Write raw binary column data from a row source directly to a file or ZIP archive."""
25
29
  if zipfile is None:
26
30
  filewriter_close(outfile)
27
31
  mode = "wb" if not append else "ab"
@@ -43,9 +47,7 @@ def write_query_raw(
43
47
 
44
48
 
45
49
  def write_query_b64(outfile: str, rowsource: Any, append: bool = False, zipfile: str | None = None) -> None:
46
- global base64
47
- import base64
48
-
50
+ """Decode base64 column data from a row source and write the raw bytes to a file or ZIP archive."""
49
51
  if zipfile is None:
50
52
  filewriter_close(outfile)
51
53
  mode = "wb" if not append else "ab"
@@ -14,6 +14,8 @@ from typing import Any
14
14
  from execsql.exceptions import ErrInfo
15
15
  from execsql.types import dbt_sqlite
16
16
 
17
+ __all__ = ["export_sqlite", "write_query_to_sqlite"]
18
+
17
19
 
18
20
  def export_sqlite(
19
21
  outfile: str,
@@ -22,6 +24,7 @@ def export_sqlite(
22
24
  append: bool,
23
25
  tablename: str,
24
26
  ) -> None:
27
+ """Write pre-fetched rows to a table in an SQLite database file, creating it if necessary."""
25
28
  import sqlite3
26
29
 
27
30
  from execsql.models import DataTable
@@ -71,6 +74,7 @@ def write_query_to_sqlite(
71
74
  append: bool,
72
75
  tablename: str,
73
76
  ) -> None:
77
+ """Execute a SELECT and write the result set to a named table in an SQLite database."""
74
78
  from execsql.utils.errors import exception_desc
75
79
 
76
80
  try:
@@ -9,6 +9,7 @@ substitution) and :func:`report_query`, which drives the
9
9
  variants. The Jinja2 template processor is loaded lazily when selected.
10
10
  """
11
11
 
12
+ import string
12
13
  from typing import Any
13
14
 
14
15
  import execsql.state as _state
@@ -16,14 +17,16 @@ from execsql.exceptions import ErrInfo
16
17
  from execsql.utils.errors import fatal_error
17
18
  from execsql.utils.fileio import filewriter_close
18
19
 
20
+ __all__ = ["StrTemplateReport", "JinjaTemplateReport", "report_query"]
21
+
19
22
 
20
23
  class StrTemplateReport:
24
+ """Generates a report by applying Python's :class:`string.Template` to each row of a data table."""
25
+
21
26
  # Exporting/reporting using Python's default string.Template, iterated over all
22
27
  # rows of a data table.
23
28
  def __init__(self, template_file: str) -> None:
24
- global string
25
- import string
26
-
29
+ """Load and compile the template from the given file path."""
27
30
  conf = _state.conf
28
31
  self.infname = template_file
29
32
  from execsql.utils.fileio import EncodedFile
@@ -46,6 +49,7 @@ class StrTemplateReport:
46
49
  append: bool = False,
47
50
  zipfile: str | None = None,
48
51
  ) -> None:
52
+ """Render the template for each row in ``data_dict_rows`` and write the output."""
49
53
  conf = _state.conf
50
54
  from execsql.utils.fileio import EncodedFile
51
55
  from execsql.exporters.zip import ZipWriter
@@ -70,12 +74,16 @@ class StrTemplateReport:
70
74
 
71
75
 
72
76
  class JinjaTemplateReport:
77
+ """Generates a report by rendering a Jinja2 template against the full data table."""
78
+
73
79
  # Exporting/reporting using the Jinja2 templating library.
74
80
  def __init__(self, template_file: str) -> None:
75
- global jinja2
81
+ """Load and compile the Jinja2 template from the given file path."""
76
82
  try:
77
83
  import jinja2
78
84
  from jinja2.sandbox import SandboxedEnvironment
85
+
86
+ self._jinja2 = jinja2
79
87
  except ImportError:
80
88
  fatal_error(
81
89
  "The jinja2 library is required to produce reports with the Jinja2 templating system. See http://jinja.pocoo.org/",
@@ -102,6 +110,7 @@ class JinjaTemplateReport:
102
110
  append: bool = False,
103
111
  zipfile: str | None = None,
104
112
  ) -> None:
113
+ """Render the Jinja2 template with ``headers`` and ``datatable`` context and write the output."""
105
114
  conf = _state.conf
106
115
  from execsql.utils.fileio import EncodedFile
107
116
  from execsql.exporters.zip import ZipWriter
@@ -119,9 +128,9 @@ class JinjaTemplateReport:
119
128
  ofile = ZipWriter(zipfile, output_dest, append)
120
129
  try:
121
130
  ofile.write(self.template.render(headers=headers, datatable=data_dict_rows))
122
- except jinja2.TemplateSyntaxError as e:
131
+ except self._jinja2.TemplateSyntaxError as e:
123
132
  raise ErrInfo("error", other_msg=e.message + f" on template line {e.lineno}") from e
124
- except jinja2.TemplateError as e:
133
+ except self._jinja2.TemplateError as e:
125
134
  raise ErrInfo("error", other_msg=f"Jinja2 template error ({e.message})") from e
126
135
  finally:
127
136
  if output_dest != "stdout":
@@ -136,6 +145,7 @@ def report_query(
136
145
  append: bool = False,
137
146
  zipfile: str | None = None,
138
147
  ) -> None:
148
+ """Execute a SELECT and render the result set through a str-template or Jinja2 template file."""
139
149
  # Write (export) a template-based report.
140
150
  conf = _state.conf
141
151
  _state.status.sql_error = False
@@ -16,6 +16,8 @@ from execsql.exceptions import ErrInfo
16
16
  from execsql.utils.errors import exception_desc
17
17
  from execsql.utils.fileio import filewriter_close
18
18
 
19
+ __all__ = ["export_values", "write_query_to_values"]
20
+
19
21
 
20
22
  def export_values(
21
23
  outfile: str,
@@ -25,6 +27,7 @@ def export_values(
25
27
  desc: str | None = None,
26
28
  zipfile: str | None = None,
27
29
  ) -> None:
30
+ """Write pre-fetched rows as SQL INSERT … VALUES statements to a file or ZIP archive."""
28
31
  conf = _state.conf
29
32
  if outfile.lower() == "stdout":
30
33
  f = _state.output
@@ -70,6 +73,7 @@ def write_query_to_values(
70
73
  desc: str | None = None,
71
74
  zipfile: str | None = None,
72
75
  ) -> None:
76
+ """Execute a SELECT and write the result set as SQL INSERT … VALUES statements."""
73
77
  try:
74
78
  hdrs, rows = db.select_rowsource(select_stmt)
75
79
  except ErrInfo:
execsql/exporters/xls.py CHANGED
@@ -16,8 +16,12 @@ from typing import Any
16
16
  from execsql.exceptions import XlsFileError, XlsxFileError
17
17
  from execsql.utils.errors import fatal_error
18
18
 
19
+ __all__ = ["XlsFile", "XlsxFile"]
20
+
19
21
 
20
22
  class XlsFile:
23
+ """Read-only wrapper around ``xlrd`` for importing legacy ``.xls`` spreadsheets."""
24
+
21
25
  def __repr__(self) -> str:
22
26
  return "XlsFile()"
23
27
 
@@ -29,9 +33,11 @@ class XlsFile:
29
33
  self.log_msgs.append(msg)
30
34
 
31
35
  def __init__(self) -> None:
36
+ """Import xlrd and initialise file state; raises a fatal error if xlrd is absent."""
32
37
  try:
33
- global xlrd
34
38
  import xlrd
39
+
40
+ self._xlrd = xlrd
35
41
  except ImportError:
36
42
  fatal_error("The xlrd library is needed to read Excel (.xls) spreadsheets.")
37
43
  self.filename = None
@@ -41,20 +47,23 @@ class XlsFile:
41
47
  self.errlog = self.XlsLog()
42
48
 
43
49
  def open(self, filename: str, encoding: str | None = None, read_only: bool = False) -> None:
50
+ """Open an existing ``.xls`` file for reading; raises XlsFileError if absent."""
44
51
  self.filename = filename
45
52
  self.encoding = encoding
46
53
  self.read_only = read_only
47
54
  if Path(filename).is_file():
48
55
  # The 'read_only' argument is not used, but is present for compatibility with XlsxFile.open().
49
- self.wbk = xlrd.open_workbook(filename, logfile=self.errlog, encoding_override=self.encoding)
56
+ self.wbk = self._xlrd.open_workbook(filename, logfile=self.errlog, encoding_override=self.encoding)
50
57
  self.datemode = self.wbk.datemode
51
58
  else:
52
59
  raise XlsFileError(f"There is no Excel file {self.filename}.")
53
60
 
54
61
  def sheetnames(self) -> Any:
62
+ """Return the list of sheet objects in the open workbook."""
55
63
  return self.wbk.sheets()
56
64
 
57
65
  def sheet_named(self, sheetname: Any) -> Any:
66
+ """Return the sheet matching a name or 1-based integer, raising XlsFileError if absent."""
58
67
  # Return the sheet with the matching name. If the name is actually an integer,
59
68
  # return that sheet number.
60
69
  if isinstance(sheetname, int):
@@ -74,6 +83,7 @@ class XlsFile:
74
83
  return sheet
75
84
 
76
85
  def sheet_data(self, sheetname: Any, junk_header_rows: int = 0) -> list:
86
+ """Return all row data from the named sheet, optionally skipping leading junk rows."""
77
87
  try:
78
88
  sheet = self.sheet_named(sheetname)
79
89
  except Exception as e:
@@ -109,7 +119,7 @@ class XlsFile:
109
119
  datarow.append(c.value)
110
120
  elif c.ctype == 3:
111
121
  # date
112
- dt = xlrd.xldate_as_tuple(c.value, self.datemode)
122
+ dt = self._xlrd.xldate_as_tuple(c.value, self.datemode)
113
123
  # Convert to time or datetime
114
124
  if not any(dt[:3]):
115
125
  # No date values
@@ -121,7 +131,7 @@ class XlsFile:
121
131
  datarow.append(bool(c.value))
122
132
  elif c.ctype == 5:
123
133
  # Error code
124
- datarow.append(xlrd.error_text_from_code(c.value))
134
+ datarow.append(self._xlrd.error_text_from_code(c.value))
125
135
  elif c.ctype == 6:
126
136
  # blank
127
137
  datarow.append(None)
@@ -145,6 +155,8 @@ class XlsFile:
145
155
 
146
156
 
147
157
  class XlsxFile:
158
+ """Read/write wrapper around ``openpyxl`` for ``.xlsx`` spreadsheets."""
159
+
148
160
  def __repr__(self) -> str:
149
161
  return "XlsxFile()"
150
162
 
@@ -156,9 +168,11 @@ class XlsxFile:
156
168
  self.log_msgs.append(msg)
157
169
 
158
170
  def __init__(self) -> None:
171
+ """Import openpyxl and initialise file state; raises a fatal error if openpyxl is absent."""
159
172
  try:
160
- global openpyxl
161
173
  import openpyxl
174
+
175
+ self._openpyxl = openpyxl
162
176
  except ImportError:
163
177
  fatal_error("The openpyxl library is needed to read Excel (.xlsx) spreadsheets.")
164
178
  self.filename = None
@@ -168,18 +182,20 @@ class XlsxFile:
168
182
  self.errlog = self.XlsxLog()
169
183
 
170
184
  def open(self, filename: str, encoding: str | None = None, read_only: bool = False) -> None:
185
+ """Open an existing ``.xlsx`` file for reading; raises XlsxFileError if absent."""
171
186
  self.filename = filename
172
187
  self.encoding = encoding
173
188
  self.read_only = read_only
174
189
  if Path(filename).is_file():
175
190
  if read_only:
176
- self.wbk = openpyxl.load_workbook(filename, read_only=True)
191
+ self.wbk = self._openpyxl.load_workbook(filename, read_only=True)
177
192
  else:
178
- self.wbk = openpyxl.load_workbook(filename)
193
+ self.wbk = self._openpyxl.load_workbook(filename)
179
194
  else:
180
195
  raise XlsxFileError(f"There is no Excel file {self.filename}.")
181
196
 
182
197
  def close(self) -> None:
198
+ """Close the open workbook and reset all state attributes."""
183
199
  if self.wbk is not None:
184
200
  self.wbk.close()
185
201
  self.wbk = None
@@ -187,9 +203,11 @@ class XlsxFile:
187
203
  self.encoding = None
188
204
 
189
205
  def sheetnames(self) -> list[str]:
206
+ """Return the list of worksheet names in the open workbook."""
190
207
  return self.wbk.sheetnames
191
208
 
192
209
  def sheet_named(self, sheetname: Any) -> Any:
210
+ """Return the sheet matching a name or 1-based integer index."""
193
211
  # Return the sheet with the matching name. If the name is actually an integer,
194
212
  # return that sheet number.
195
213
  if isinstance(sheetname, int):
@@ -209,6 +227,7 @@ class XlsxFile:
209
227
  return sheet
210
228
 
211
229
  def sheet_data(self, sheetname: Any, junk_header_rows: int = 0) -> list:
230
+ """Return all row data from the named sheet, optionally skipping leading junk rows."""
212
231
  try:
213
232
  sheet = self.sheet_named(sheetname)
214
233
  except Exception as e:
execsql/exporters/xml.py CHANGED
@@ -18,6 +18,8 @@ from execsql.exceptions import ErrInfo
18
18
  from execsql.utils.errors import exception_desc
19
19
  from execsql.utils.fileio import filewriter_close
20
20
 
21
+ __all__ = ["write_query_to_xml"]
22
+
21
23
 
22
24
  def write_query_to_xml(
23
25
  select_stmt: str,
@@ -28,6 +30,7 @@ def write_query_to_xml(
28
30
  desc: str | None = None,
29
31
  zipfile: str | None = None,
30
32
  ) -> None:
33
+ """Execute a SELECT and write the result set as a well-formed XML document."""
31
34
  conf = _state.conf
32
35
  try:
33
36
  hdrs, rows = db.select_rowsource(select_stmt)
execsql/exporters/zip.py CHANGED
@@ -15,9 +15,14 @@ import zipfile
15
15
 
16
16
  import execsql.state as _state
17
17
 
18
+ __all__ = ["WriteableZipfile", "ZipWriter"]
19
+
18
20
 
19
21
  class WriteableZipfile:
22
+ """Thin ZipFile wrapper that accepts chunked string writes via an internal buffer."""
23
+
20
24
  def __init__(self, zipfile_name: str, append: bool = False) -> None:
25
+ """Open (or create) a ZIP archive and allocate a write buffer."""
21
26
  conf = _state.conf
22
27
  self.bufsize = conf.zip_buffer_mb * 1024 * 1000
23
28
  self.buf = memoryview(bytearray(self.bufsize))
@@ -31,6 +36,7 @@ class WriteableZipfile:
31
36
  self.close()
32
37
 
33
38
  def member_file(self, member_filename: str) -> None:
39
+ """Create a new member entry in the archive and open it for writing."""
34
40
  # Creates a ZipInfo object (file) within the zipfile and opens it for writing.
35
41
  self.current_zinfo = zipfile.ZipInfo(
36
42
  filename=member_filename,
@@ -49,6 +55,7 @@ class WriteableZipfile:
49
55
  self.current_handle = self.zf.open(self.current_zinfo, mode="w")
50
56
 
51
57
  def zip_buffer(self) -> None:
58
+ """Flush any buffered bytes to the currently open zip member file."""
52
59
  # Writes the buffer contents, if any, to the zip member file.
53
60
  if self.buflen > 0 and self.current_handle is not None:
54
61
  with self.zf._lock:
@@ -57,6 +64,7 @@ class WriteableZipfile:
57
64
  self.buflen = 0
58
65
 
59
66
  def write(self, str_data: str) -> None:
67
+ """Buffer a UTF-8-encoded string for writing to the currently open member."""
60
68
  # Writes the given text to the currently open member.
61
69
  # Convert from string to bytes.
62
70
  data = str_data.encode("utf-8")
@@ -67,26 +75,33 @@ class WriteableZipfile:
67
75
  self.buflen = self.buflen + datalen
68
76
 
69
77
  def close_member(self) -> None:
78
+ """Flush the buffer and close the currently open member file handle."""
70
79
  if self.current_handle is not None:
71
80
  self.zip_buffer()
72
81
  self.current_handle.close()
73
82
  self.current_handle = None
74
83
 
75
84
  def close(self) -> None:
85
+ """Close the open member (flushing the buffer) and finalise the ZIP archive."""
76
86
  self.close_member()
77
87
  self.zf.close()
78
88
 
79
89
 
80
90
  class ZipWriter:
91
+ """High-level write-only interface used by EXPORT metacommands to stream output into a ZIP archive."""
92
+
81
93
  def __init__(self, zip_fname: str, member_fname: str, append: bool = False) -> None:
94
+ """Open the archive at ``zip_fname`` and begin a new member file named ``member_fname``."""
82
95
  self.zip_fname = zip_fname
83
96
  self.member_fname = member_fname
84
97
  self.zwriter = WriteableZipfile(self.zip_fname, append)
85
98
  self.member = self.zwriter.member_file(member_fname)
86
99
 
87
100
  def write(self, str_data: str) -> None:
101
+ """Write a string to the current zip member."""
88
102
  self.zwriter.write(str_data)
89
103
 
90
104
  def close(self) -> None:
105
+ """Close the zip member and finalise the archive."""
91
106
  self.zwriter.close()
92
107
  self.zwriter = None
execsql/importers/base.py CHANGED
@@ -17,6 +17,8 @@ from execsql.db.base import Database
17
17
  import execsql.state as _state
18
18
  from execsql.types import dbt_firebird
19
19
 
20
+ __all__ = ["import_data_table"]
21
+
20
22
 
21
23
  def import_data_table(
22
24
  db: Database,
@@ -26,7 +28,7 @@ def import_data_table(
26
28
  hdrs: list[str],
27
29
  data: list[Any],
28
30
  ) -> None:
29
- from execsql.utils.errors import exception_info
31
+ from execsql.utils.errors import exception_desc
30
32
 
31
33
  conf = _state.conf
32
34
  if any(x is None or len(x.strip()) == 0 for x in hdrs):
@@ -89,7 +91,7 @@ def import_data_table(
89
91
  raise ErrInfo(
90
92
  type="db",
91
93
  command_text=sql,
92
- exception_msg=exception_info(),
94
+ exception_msg=exception_desc(),
93
95
  other_msg=f"Could not create new table ({tablename}) for IMPORT metacommand",
94
96
  ) from e
95
97
  table_cols = db.table_columns(tablename, schemaname)
@@ -109,4 +111,4 @@ def import_data_table(
109
111
  except ErrInfo:
110
112
  raise
111
113
  except Exception as e:
112
- raise ErrInfo("db", "Call to populate_table when importing data", exception_msg=exception_info()) from e
114
+ raise ErrInfo("db", "Call to populate_table when importing data", exception_msg=exception_desc()) from e
execsql/importers/csv.py CHANGED
@@ -17,6 +17,8 @@ from execsql.db.base import Database
17
17
  import execsql.state as _state
18
18
  from execsql.types import dbt_firebird
19
19
 
20
+ __all__ = ["importfile", "importtable"]
21
+
20
22
 
21
23
  def importtable(
22
24
  db: Database,
@@ -30,7 +32,7 @@ def importtable(
30
32
  encoding: str | None = None,
31
33
  junk_header_lines: int = 0,
32
34
  ) -> None:
33
- from execsql.utils.errors import exception_info
35
+ from execsql.utils.errors import exception_desc
34
36
 
35
37
  conf = _state.conf
36
38
  if not Path(filename).is_file():
@@ -64,7 +66,7 @@ def importtable(
64
66
  raise ErrInfo(
65
67
  type="db",
66
68
  command_text=sql,
67
- exception_msg=exception_info(),
69
+ exception_msg=exception_desc(),
68
70
  other_msg=f"Could not create new table ({tablename}) for IMPORT metacommand",
69
71
  ) from e
70
72
  else:
@@ -89,7 +91,7 @@ def importtable(
89
91
  fq_tablename = db.schema_qualified_table_name(schemaname, tablename)
90
92
  raise ErrInfo(
91
93
  "exception",
92
- exception_msg=exception_info(),
94
+ exception_msg=exception_desc(),
93
95
  other_msg=f"Can't import tabular file ({filename}) to table ({fq_tablename})",
94
96
  ) from e
95
97
  inf.close()
@@ -102,7 +104,7 @@ def importfile(
102
104
  columname: str,
103
105
  filename: str,
104
106
  ) -> None:
105
- from execsql.utils.errors import exception_info
107
+ from execsql.utils.errors import exception_desc
106
108
 
107
109
  if schemaname is not None:
108
110
  if not db.table_exists(tablename, schemaname):
@@ -125,6 +127,6 @@ def importfile(
125
127
  fq_tablename = db.schema_qualified_table_name(schemaname, tablename)
126
128
  raise ErrInfo(
127
129
  "exception",
128
- exception_msg=exception_info(),
130
+ exception_msg=exception_desc(),
129
131
  other_msg=f"Can't import file ({filename}) to table ({fq_tablename})",
130
132
  ) from e
@@ -14,6 +14,8 @@ from execsql.exceptions import ErrInfo
14
14
  from execsql.db.base import Database
15
15
  from execsql.importers.base import import_data_table
16
16
 
17
+ __all__ = ["import_feather", "import_parquet"]
18
+
17
19
 
18
20
  def import_feather(
19
21
  db: Database,
@@ -22,14 +24,14 @@ def import_feather(
22
24
  filename: str,
23
25
  is_new: Any,
24
26
  ) -> None:
25
- from execsql.utils.errors import exception_info
27
+ from execsql.utils.errors import exception_desc
26
28
 
27
29
  try:
28
30
  import polars as pl
29
31
  except Exception as e:
30
32
  raise ErrInfo(
31
33
  "exception",
32
- exception_msg=exception_info(),
34
+ exception_msg=exception_desc(),
33
35
  other_msg="The polars Python library must be installed to import data from the Feather format.",
34
36
  ) from e
35
37
  df = pl.read_ipc(filename)
@@ -45,14 +47,14 @@ def import_parquet(
45
47
  filename: str,
46
48
  is_new: Any,
47
49
  ) -> None:
48
- from execsql.utils.errors import exception_info
50
+ from execsql.utils.errors import exception_desc
49
51
 
50
52
  try:
51
53
  import polars as pl
52
54
  except Exception as e:
53
55
  raise ErrInfo(
54
56
  "exception",
55
- exception_msg=exception_info(),
57
+ exception_msg=exception_desc(),
56
58
  other_msg="The polars Python library must be installed to import data from the Parquet format.",
57
59
  ) from e
58
60
  df = pl.read_parquet(filename)
execsql/importers/ods.py CHANGED
@@ -17,6 +17,8 @@ from execsql.exporters.ods import OdsFile
17
17
  from execsql.importers.base import import_data_table
18
18
  import execsql.state as _state
19
19
 
20
+ __all__ = ["importods", "ods_data"]
21
+
20
22
 
21
23
  def ods_data(
22
24
  filename: str,
execsql/importers/xls.py CHANGED
@@ -16,6 +16,8 @@ from execsql.db.base import Database
16
16
  from execsql.importers.base import import_data_table
17
17
  import execsql.state as _state
18
18
 
19
+ __all__ = ["importxls", "xls_data"]
20
+
19
21
 
20
22
  def xls_data(
21
23
  filename: str,