execsql2 2.4.5__py3-none-any.whl → 2.6.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.
- execsql/cli/__init__.py +14 -0
- execsql/cli/dsn.py +2 -0
- execsql/cli/help.py +2 -0
- execsql/cli/run.py +4 -2
- execsql/constants.py +11 -0
- execsql/db/access.py +20 -0
- execsql/db/base.py +4 -0
- execsql/db/dsn.py +11 -8
- execsql/db/duckdb.py +12 -8
- execsql/db/firebird.py +17 -8
- execsql/db/mysql.py +13 -8
- execsql/db/oracle.py +22 -8
- execsql/db/postgres.py +21 -9
- execsql/db/sqlite.py +18 -8
- execsql/db/sqlserver.py +14 -8
- execsql/exporters/__init__.py +6 -1
- execsql/exporters/base.py +2 -0
- execsql/exporters/delimited.py +10 -0
- execsql/exporters/protocol.py +128 -0
- execsql/exporters/xls.py +8 -0
- execsql/format.py +3 -1
- execsql/gui/__init__.py +2 -0
- execsql/gui/base.py +2 -0
- execsql/gui/console.py +2 -0
- execsql/gui/desktop.py +1 -0
- execsql/gui/tui.py +134 -0
- execsql/importers/base.py +1 -0
- execsql/importers/csv.py +2 -0
- execsql/importers/feather.py +2 -0
- execsql/importers/ods.py +1 -0
- execsql/importers/xls.py +1 -0
- execsql/metacommands/__init__.py +386 -180
- execsql/metacommands/dispatch.py +2 -0
- execsql/metacommands/io.py +41 -0
- execsql/models.py +17 -0
- execsql/parser.py +41 -0
- execsql/script/control.py +2 -0
- execsql/script/engine.py +19 -0
- execsql/script/variables.py +9 -5
- execsql/state.py +312 -199
- execsql/types.py +46 -0
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/METADATA +2 -2
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/RECORD +62 -61
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/WHEEL +0 -0
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/licenses/NOTICE +0 -0
execsql/db/sqlite.py
CHANGED
|
@@ -50,6 +50,7 @@ class SQLiteDatabase(Database):
|
|
|
50
50
|
return f"SQLiteDatabase({self.db_name!r})"
|
|
51
51
|
|
|
52
52
|
def open_db(self) -> None:
|
|
53
|
+
"""Open a connection to the SQLite database file."""
|
|
53
54
|
import sqlite3
|
|
54
55
|
|
|
55
56
|
if self.conn is None:
|
|
@@ -67,18 +68,20 @@ class SQLiteDatabase(Database):
|
|
|
67
68
|
self.encoding = pragma_data[0][0]
|
|
68
69
|
|
|
69
70
|
def exec_cmd(self, querycommand: str) -> None:
|
|
71
|
+
"""Execute a query command as a view selection, since SQLite lacks stored procedures."""
|
|
70
72
|
# SQLite does not support stored functions or views, so the querycommand
|
|
71
73
|
# is treated as (and therefore must be) a view.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
with self._cursor() as curs:
|
|
75
|
+
cmd = f"select * from {querycommand};"
|
|
76
|
+
try:
|
|
77
|
+
curs.execute(cmd.encode(self.encoding))
|
|
78
|
+
_state.subvars.add_substitution("$LAST_ROWCOUNT", curs.rowcount)
|
|
79
|
+
except Exception:
|
|
80
|
+
self.rollback()
|
|
81
|
+
raise
|
|
80
82
|
|
|
81
83
|
def table_exists(self, table_name: str, schema_name: str | None = None) -> bool:
|
|
84
|
+
"""Return True if the named table exists in the SQLite database."""
|
|
82
85
|
curs = self.cursor()
|
|
83
86
|
sql = "select name from sqlite_master where type='table' and name=?;"
|
|
84
87
|
try:
|
|
@@ -102,10 +105,12 @@ class SQLiteDatabase(Database):
|
|
|
102
105
|
column_name: str,
|
|
103
106
|
schema_name: str | None = None,
|
|
104
107
|
) -> bool:
|
|
108
|
+
"""Return True if the named column exists in the given SQLite table."""
|
|
105
109
|
cols = self.table_columns(table_name, schema_name)
|
|
106
110
|
return column_name in cols
|
|
107
111
|
|
|
108
112
|
def table_columns(self, table_name: str, schema_name: str | None = None) -> list[str]:
|
|
113
|
+
"""Return a list of column names for the given SQLite table."""
|
|
109
114
|
curs = self.cursor()
|
|
110
115
|
quoted_tbl = self.quote_identifier(table_name)
|
|
111
116
|
sql = f"select * from {quoted_tbl} where 1=0;"
|
|
@@ -124,6 +129,7 @@ class SQLiteDatabase(Database):
|
|
|
124
129
|
return [d[0] for d in curs.description]
|
|
125
130
|
|
|
126
131
|
def view_exists(self, view_name: str) -> bool:
|
|
132
|
+
"""Return True if the named view exists in the SQLite database."""
|
|
127
133
|
curs = self.cursor()
|
|
128
134
|
sql = "select name from sqlite_master where type='view' and name=?;"
|
|
129
135
|
try:
|
|
@@ -142,9 +148,11 @@ class SQLiteDatabase(Database):
|
|
|
142
148
|
return len(rows) > 0
|
|
143
149
|
|
|
144
150
|
def schema_exists(self, schema_name: str) -> bool:
|
|
151
|
+
"""Return False; SQLite does not support schemas."""
|
|
145
152
|
return False
|
|
146
153
|
|
|
147
154
|
def drop_table(self, tablename: str) -> None:
|
|
155
|
+
"""Drop the named table from the SQLite database if it exists."""
|
|
148
156
|
tablename = self.type.quoted(tablename)
|
|
149
157
|
self.execute(f"drop table if exists {tablename};")
|
|
150
158
|
|
|
@@ -156,6 +164,7 @@ class SQLiteDatabase(Database):
|
|
|
156
164
|
column_list: list[str],
|
|
157
165
|
tablespec_src: Any,
|
|
158
166
|
) -> None:
|
|
167
|
+
"""Populate a SQLite table from a row source generator."""
|
|
159
168
|
# The rowsource argument must be a generator yielding a list of values for the columns of the table.
|
|
160
169
|
# The column_list argument must an iterable containing column names in the same order as produced by the rowsource.
|
|
161
170
|
sq_name = self.schema_qualified_table_name(None, table_name)
|
|
@@ -233,6 +242,7 @@ class SQLiteDatabase(Database):
|
|
|
233
242
|
column_name: str,
|
|
234
243
|
file_name: str,
|
|
235
244
|
) -> None:
|
|
245
|
+
"""Import an entire binary file into a single column of a table."""
|
|
236
246
|
import sqlite3
|
|
237
247
|
|
|
238
248
|
with open(file_name, "rb") as f:
|
execsql/db/sqlserver.py
CHANGED
|
@@ -58,6 +58,7 @@ class SqlServerDatabase(Database):
|
|
|
58
58
|
)
|
|
59
59
|
|
|
60
60
|
def open_db(self) -> None:
|
|
61
|
+
"""Open a connection to the SQL Server database via pyodbc."""
|
|
61
62
|
import pyodbc
|
|
62
63
|
|
|
63
64
|
if self.conn is None:
|
|
@@ -138,17 +139,19 @@ class SqlServerDatabase(Database):
|
|
|
138
139
|
self.conn.commit()
|
|
139
140
|
|
|
140
141
|
def exec_cmd(self, querycommand: str) -> None:
|
|
142
|
+
"""Execute a stored procedure by name."""
|
|
141
143
|
# The querycommand must be a stored procedure
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
with self._cursor() as curs:
|
|
145
|
+
cmd = f"execute {querycommand};"
|
|
146
|
+
try:
|
|
147
|
+
curs.execute(cmd.encode(self.encoding))
|
|
148
|
+
_state.subvars.add_substitution("$LAST_ROWCOUNT", curs.rowcount)
|
|
149
|
+
except Exception:
|
|
150
|
+
self.rollback()
|
|
151
|
+
raise
|
|
150
152
|
|
|
151
153
|
def schema_exists(self, schema_name: str) -> bool:
|
|
154
|
+
"""Return True if the named schema exists in the SQL Server database."""
|
|
152
155
|
curs = self.cursor()
|
|
153
156
|
curs.execute("select * from sys.schemas where name = ?;", (schema_name,))
|
|
154
157
|
rows = curs.fetchall()
|
|
@@ -156,6 +159,7 @@ class SqlServerDatabase(Database):
|
|
|
156
159
|
return len(rows) > 0
|
|
157
160
|
|
|
158
161
|
def role_exists(self, rolename: str) -> bool:
|
|
162
|
+
"""Return True if the named role or principal exists in the SQL Server database."""
|
|
159
163
|
curs = self.cursor()
|
|
160
164
|
curs.execute(
|
|
161
165
|
"select name from sys.database_principals where type in ('R', 'S') and name = ?;",
|
|
@@ -166,6 +170,7 @@ class SqlServerDatabase(Database):
|
|
|
166
170
|
return len(rows) > 0
|
|
167
171
|
|
|
168
172
|
def drop_table(self, tablename: str) -> None:
|
|
173
|
+
"""Drop the named table from the SQL Server database."""
|
|
169
174
|
# SQL Server and Firebird will throw an error if there are foreign keys to the table.
|
|
170
175
|
tablename = self.type.quoted(tablename)
|
|
171
176
|
self.execute(f"drop table {tablename};")
|
|
@@ -177,6 +182,7 @@ class SqlServerDatabase(Database):
|
|
|
177
182
|
column_name: str,
|
|
178
183
|
file_name: str,
|
|
179
184
|
) -> None:
|
|
185
|
+
"""Import an entire binary file into a single column of a table."""
|
|
180
186
|
import pyodbc
|
|
181
187
|
|
|
182
188
|
with open(file_name, "rb") as f:
|
execsql/exporters/__init__.py
CHANGED
|
@@ -10,5 +10,10 @@ importing directly from here.
|
|
|
10
10
|
|
|
11
11
|
Sub-modules: ``base``, ``delimited``, ``json``, ``xml``, ``html``,
|
|
12
12
|
``latex``, ``ods``, ``xls``, ``zip``, ``raw``, ``pretty``, ``values``,
|
|
13
|
-
``templates``, ``feather``, ``parquet``, ``duckdb``, ``sqlite
|
|
13
|
+
``templates``, ``feather``, ``parquet``, ``duckdb``, ``sqlite``,
|
|
14
|
+
``protocol``.
|
|
14
15
|
"""
|
|
16
|
+
|
|
17
|
+
from execsql.exporters.protocol import QueryExporter, RowsetExporter
|
|
18
|
+
|
|
19
|
+
__all__ = ["QueryExporter", "RowsetExporter"]
|
execsql/exporters/base.py
CHANGED
|
@@ -39,6 +39,7 @@ class ExportRecord:
|
|
|
39
39
|
zipfile: str | None = None,
|
|
40
40
|
description: str | None = None,
|
|
41
41
|
) -> None:
|
|
42
|
+
"""Record export details for the given query name and output file."""
|
|
42
43
|
self.exported = False
|
|
43
44
|
# Record is a list of: table_or_query_name, filename, zipfilename, file_path, user_description, script_name,
|
|
44
45
|
# script_path, script_line_no, script_datetime, database_name, database_server, user_name.
|
|
@@ -87,6 +88,7 @@ class ExportMetadata:
|
|
|
87
88
|
]
|
|
88
89
|
|
|
89
90
|
def __init__(self) -> None:
|
|
91
|
+
"""Initialise an empty record collection."""
|
|
90
92
|
self.recordlist: list[ExportRecord] = []
|
|
91
93
|
|
|
92
94
|
def add(self, exp_record: ExportRecord) -> None:
|
execsql/exporters/delimited.py
CHANGED
|
@@ -118,6 +118,7 @@ class CsvWriter:
|
|
|
118
118
|
escchar: str | None,
|
|
119
119
|
append: bool = False,
|
|
120
120
|
) -> None:
|
|
121
|
+
"""Open a file and prepare a delimited writer with the given format settings."""
|
|
121
122
|
mode = "wt" if not append else "at"
|
|
122
123
|
if filename.lower() == "stdout":
|
|
123
124
|
self.output = sys.stdout
|
|
@@ -184,9 +185,12 @@ class CsvFile(EncodedFile):
|
|
|
184
185
|
self.lineformat_set = True
|
|
185
186
|
|
|
186
187
|
class CsvLine:
|
|
188
|
+
"""Represent a single CSV line for delimiter diagnosis and parsing."""
|
|
189
|
+
|
|
187
190
|
escchar = "\\"
|
|
188
191
|
|
|
189
192
|
def __init__(self, line_text: str) -> None:
|
|
193
|
+
"""Store the raw line text and prepare delimiter-count storage."""
|
|
190
194
|
self.text = line_text
|
|
191
195
|
self.delim_counts = {}
|
|
192
196
|
self.item_errors = [] # A list of error messages.
|
|
@@ -202,6 +206,7 @@ class CsvFile(EncodedFile):
|
|
|
202
206
|
)
|
|
203
207
|
|
|
204
208
|
def count_delim(self, delim: str) -> None:
|
|
209
|
+
"""Count occurrences of the given delimiter in the line text."""
|
|
205
210
|
# If the delimiter is a space, consider multiple spaces to be equivalent
|
|
206
211
|
# to a single delimiter, split on the space(s), and consider the delimiter
|
|
207
212
|
# count to be one fewer than the items returned.
|
|
@@ -211,6 +216,7 @@ class CsvFile(EncodedFile):
|
|
|
211
216
|
self.delim_counts[delim] = self.text.count(delim)
|
|
212
217
|
|
|
213
218
|
def delim_count(self, delim: str) -> int:
|
|
219
|
+
"""Return the previously counted occurrence total for the given delimiter."""
|
|
214
220
|
return self.delim_counts[delim]
|
|
215
221
|
|
|
216
222
|
def _well_quoted(self, element: str, qchar: str):
|
|
@@ -239,9 +245,11 @@ class CsvFile(EncodedFile):
|
|
|
239
245
|
return (False, True, False)
|
|
240
246
|
|
|
241
247
|
def record_format_error(self, pos_no: int, errmsg: str) -> None:
|
|
248
|
+
"""Append a parse error message annotated with its character position."""
|
|
242
249
|
self.item_errors.append(f"{errmsg} in position {pos_no}.")
|
|
243
250
|
|
|
244
251
|
def items(self, delim: str | None, qchar: str | None) -> Any:
|
|
252
|
+
"""Parse the line into a list of items, splitting on unquoted delimiters."""
|
|
245
253
|
# Parses the line into a list of items, breaking it at delimiters that are not
|
|
246
254
|
# within quoted stretches.
|
|
247
255
|
self.item_errors = []
|
|
@@ -348,6 +356,7 @@ class CsvFile(EncodedFile):
|
|
|
348
356
|
return elements
|
|
349
357
|
|
|
350
358
|
def well_quoted_line(self, delim: str | None, qchar: str | None):
|
|
359
|
+
"""Return a tuple of (all-well-quoted, quote-usage-count, uses-escape-char)."""
|
|
351
360
|
# Returns a tuple of boolean, int, and boolean
|
|
352
361
|
wq = [self._well_quoted(el, qchar) for el in self.items(delim, qchar)]
|
|
353
362
|
return (all(b[0] for b in wq), sum([b[1] for b in wq]), any(b[2] for b in wq))
|
|
@@ -358,6 +367,7 @@ class CsvFile(EncodedFile):
|
|
|
358
367
|
possible_delimiters: list[str] | None = None,
|
|
359
368
|
possible_quotechars: list[str] | None = None,
|
|
360
369
|
):
|
|
370
|
+
"""Analyse a line stream and return the detected (delimiter, quote char, escape char) tuple."""
|
|
361
371
|
# Returns a tuple consisting of the delimiter, quote character, and escape
|
|
362
372
|
# character for quote characters within elements of a line. All may be None.
|
|
363
373
|
conf = _state.conf
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exporter Protocol definitions for execsql.
|
|
3
|
+
|
|
4
|
+
Defines two ``@runtime_checkable`` Protocols that describe the two main
|
|
5
|
+
exporter calling conventions used throughout the ``exporters`` package:
|
|
6
|
+
|
|
7
|
+
- :class:`QueryExporter` — functions that accept a SQL SELECT statement
|
|
8
|
+
and a database connection, execute the query, and write the results.
|
|
9
|
+
- :class:`RowsetExporter` — functions that accept pre-fetched column
|
|
10
|
+
headers and rows and write them to an output destination.
|
|
11
|
+
|
|
12
|
+
These Protocols capture the *most common* parameter signature. Several
|
|
13
|
+
concrete exporters have additional keyword arguments (``tablename``,
|
|
14
|
+
``sheetname``, ``template_file``, ``write_types``, ``and_val``, etc.)
|
|
15
|
+
that extend the base contract. Such functions remain structurally
|
|
16
|
+
compatible: they satisfy the Protocol when called with the base
|
|
17
|
+
arguments, and the extra parameters have defaults or are supplied by
|
|
18
|
+
the dispatch layer.
|
|
19
|
+
|
|
20
|
+
.. note::
|
|
21
|
+
|
|
22
|
+
The ``io_export.py`` dispatch chain is **not** refactored here.
|
|
23
|
+
These Protocols exist as a documentation and static-type-checking
|
|
24
|
+
layer that formalises the implicit interface already present in the
|
|
25
|
+
codebase.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from typing import Any, Protocol, runtime_checkable
|
|
31
|
+
|
|
32
|
+
__all__ = ["QueryExporter", "RowsetExporter"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@runtime_checkable
|
|
36
|
+
class QueryExporter(Protocol):
|
|
37
|
+
"""Protocol for exporters that execute a query and write output.
|
|
38
|
+
|
|
39
|
+
The canonical signature is::
|
|
40
|
+
|
|
41
|
+
def __call__(
|
|
42
|
+
self,
|
|
43
|
+
select_stmt: str,
|
|
44
|
+
db: Any,
|
|
45
|
+
outfile: str,
|
|
46
|
+
append: bool = False,
|
|
47
|
+
desc: str | None = None,
|
|
48
|
+
zipfile: str | None = None,
|
|
49
|
+
) -> None: ...
|
|
50
|
+
|
|
51
|
+
Conforming functions
|
|
52
|
+
--------------------
|
|
53
|
+
- ``write_query_to_json``
|
|
54
|
+
- ``write_query_to_html``
|
|
55
|
+
- ``write_query_to_cgi_html``
|
|
56
|
+
- ``write_query_to_latex``
|
|
57
|
+
- ``write_query_to_values``
|
|
58
|
+
- ``prettyprint_query`` (adds ``and_val``)
|
|
59
|
+
|
|
60
|
+
Functions with extended signatures
|
|
61
|
+
----------------------------------
|
|
62
|
+
- ``write_query_to_xml`` (adds ``tablename``)
|
|
63
|
+
- ``write_query_to_json_ts`` (adds ``write_types``)
|
|
64
|
+
- ``write_query_to_ods`` (adds ``sheetname``, no ``zipfile``)
|
|
65
|
+
- ``write_query_to_hdf5`` (adds ``table_name``, no ``zipfile``)
|
|
66
|
+
- ``write_query_to_duckdb`` (adds ``tablename``, no ``desc``/``zipfile``)
|
|
67
|
+
- ``write_query_to_sqlite`` (adds ``tablename``, no ``desc``/``zipfile``)
|
|
68
|
+
- ``report_query`` (adds ``template_file``)
|
|
69
|
+
- ``write_queries_to_ods`` (``table_list`` instead of ``select_stmt``)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __call__(
|
|
73
|
+
self,
|
|
74
|
+
select_stmt: str,
|
|
75
|
+
db: Any,
|
|
76
|
+
outfile: str,
|
|
77
|
+
append: bool = False,
|
|
78
|
+
desc: str | None = None,
|
|
79
|
+
zipfile: str | None = None,
|
|
80
|
+
) -> None: ...
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@runtime_checkable
|
|
84
|
+
class RowsetExporter(Protocol):
|
|
85
|
+
"""Protocol for exporters that accept pre-fetched headers and rows.
|
|
86
|
+
|
|
87
|
+
The canonical signature is::
|
|
88
|
+
|
|
89
|
+
def __call__(
|
|
90
|
+
self,
|
|
91
|
+
outfile: str,
|
|
92
|
+
hdrs: list[str],
|
|
93
|
+
rows: Any,
|
|
94
|
+
append: bool = False,
|
|
95
|
+
desc: str | None = None,
|
|
96
|
+
zipfile: str | None = None,
|
|
97
|
+
) -> None: ...
|
|
98
|
+
|
|
99
|
+
Conforming functions
|
|
100
|
+
--------------------
|
|
101
|
+
- ``export_values``
|
|
102
|
+
|
|
103
|
+
Functions with extended signatures
|
|
104
|
+
----------------------------------
|
|
105
|
+
- ``export_html`` (adds ``querytext``)
|
|
106
|
+
- ``export_cgi_html`` (adds ``querytext``)
|
|
107
|
+
- ``export_latex`` (adds ``querytext``)
|
|
108
|
+
- ``export_ods`` (adds ``querytext``, ``sheetname``, no ``zipfile``)
|
|
109
|
+
- ``prettyprint_rowset`` (uses ``colhdrs``/``output_dest``, adds ``and_val``)
|
|
110
|
+
- ``export_duckdb`` (adds ``tablename``, no ``desc``/``zipfile``)
|
|
111
|
+
- ``export_sqlite`` (adds ``tablename``, no ``desc``/``zipfile``)
|
|
112
|
+
- ``write_query_to_feather`` (minimal: ``outfile``, ``headers``, ``rows`` only)
|
|
113
|
+
- ``write_query_to_parquet`` (minimal: ``outfile``, ``headers``, ``rows`` only)
|
|
114
|
+
- ``write_query_raw`` (uses ``rowsource`` + ``db_encoding``)
|
|
115
|
+
- ``write_query_b64`` (uses ``rowsource`` only)
|
|
116
|
+
- ``write_delimited_file`` (uses ``filefmt``, ``column_headers``, ``rowsource``,
|
|
117
|
+
``file_encoding``)
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __call__(
|
|
121
|
+
self,
|
|
122
|
+
outfile: str,
|
|
123
|
+
hdrs: list[str],
|
|
124
|
+
rows: Any,
|
|
125
|
+
append: bool = False,
|
|
126
|
+
desc: str | None = None,
|
|
127
|
+
zipfile: str | None = None,
|
|
128
|
+
) -> None: ...
|
execsql/exporters/xls.py
CHANGED
|
@@ -26,10 +26,14 @@ class XlsFile:
|
|
|
26
26
|
return "XlsFile()"
|
|
27
27
|
|
|
28
28
|
class XlsLog:
|
|
29
|
+
"""Capture xlrd warning messages as a list of strings."""
|
|
30
|
+
|
|
29
31
|
def __init__(self) -> None:
|
|
32
|
+
"""Initialise an empty message list."""
|
|
30
33
|
self.log_msgs = []
|
|
31
34
|
|
|
32
35
|
def write(self, msg: str) -> None:
|
|
36
|
+
"""Append a log message to the internal list."""
|
|
33
37
|
self.log_msgs.append(msg)
|
|
34
38
|
|
|
35
39
|
def __init__(self) -> None:
|
|
@@ -161,10 +165,14 @@ class XlsxFile:
|
|
|
161
165
|
return "XlsxFile()"
|
|
162
166
|
|
|
163
167
|
class XlsxLog:
|
|
168
|
+
"""Capture openpyxl warning messages as a list of strings."""
|
|
169
|
+
|
|
164
170
|
def __init__(self) -> None:
|
|
171
|
+
"""Initialise an empty message list."""
|
|
165
172
|
self.log_msgs = []
|
|
166
173
|
|
|
167
174
|
def write(self, msg: str) -> None:
|
|
175
|
+
"""Append a log message to the internal list."""
|
|
168
176
|
self.log_msgs.append(msg)
|
|
169
177
|
|
|
170
178
|
def __init__(self) -> None:
|
execsql/format.py
CHANGED
|
@@ -19,6 +19,8 @@ from pathlib import Path
|
|
|
19
19
|
import sqlglot
|
|
20
20
|
import sqlglot.errors
|
|
21
21
|
|
|
22
|
+
__all__ = ["collect_paths", "format_file", "main", "parse_keyword"]
|
|
23
|
+
|
|
22
24
|
# ---------------------------------------------------------------------------
|
|
23
25
|
# Constants
|
|
24
26
|
# ---------------------------------------------------------------------------
|
|
@@ -345,7 +347,7 @@ def main() -> None:
|
|
|
345
347
|
source = path.read_text(encoding="utf-8")
|
|
346
348
|
except OSError as exc:
|
|
347
349
|
_err_console.print(f"[bold red]Error:[/bold red] reading {path}: {exc}")
|
|
348
|
-
raise typer.Exit(code=1)
|
|
350
|
+
raise typer.Exit(code=1) from None
|
|
349
351
|
|
|
350
352
|
formatted = format_file(source, indent=indent, use_sql=use_sql)
|
|
351
353
|
|
execsql/gui/__init__.py
CHANGED
|
@@ -21,6 +21,8 @@ from typing import TYPE_CHECKING, Any
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from execsql.gui.base import GuiBackend
|
|
23
23
|
|
|
24
|
+
__all__ = ["get_backend", "gui_manager_loop"]
|
|
25
|
+
|
|
24
26
|
|
|
25
27
|
def get_backend(framework: str = "tkinter") -> GuiBackend:
|
|
26
28
|
"""Return the best available backend for *framework*.
|
execsql/gui/base.py
CHANGED
execsql/gui/console.py
CHANGED