execsql2 2.22.0__py3-none-any.whl → 2.22.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 (25) hide show
  1. execsql/api.py +8 -0
  2. execsql/cli/__init__.py +1 -1
  3. execsql/config.py +3 -0
  4. execsql/script/executor.py +2 -0
  5. execsql/utils/fileio.py +3 -1
  6. {execsql2-2.22.0.dist-info → execsql2-2.22.1.dist-info}/METADATA +114 -132
  7. {execsql2-2.22.0.dist-info → execsql2-2.22.1.dist-info}/RECORD +25 -25
  8. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/README.md +0 -0
  9. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  10. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  11. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
  12. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/md_compare.sql +0 -0
  13. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/md_glossary.sql +0 -0
  14. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/md_upsert.sql +0 -0
  15. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/pg_compare.sql +0 -0
  16. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  17. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  18. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/script_template.sql +0 -0
  19. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/ss_compare.sql +0 -0
  20. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  21. {execsql2-2.22.0.data → execsql2-2.22.1.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  22. {execsql2-2.22.0.dist-info → execsql2-2.22.1.dist-info}/WHEEL +0 -0
  23. {execsql2-2.22.0.dist-info → execsql2-2.22.1.dist-info}/entry_points.txt +0 -0
  24. {execsql2-2.22.0.dist-info → execsql2-2.22.1.dist-info}/licenses/LICENSE.txt +0 -0
  25. {execsql2-2.22.0.dist-info → execsql2-2.22.1.dist-info}/licenses/NOTICE +0 -0
execsql/api.py CHANGED
@@ -528,6 +528,14 @@ def run(
528
528
  except Exception as exc:
529
529
  errors.append(ScriptError(message=str(exc), source="<runtime>"))
530
530
 
531
+ # Non-halting errors (halt_on_error=False, METACOMMAND_ERROR_HALT OFF)
532
+ # are recorded in status.error_history as execution continues; a
533
+ # halting error never reaches the history, so there is no overlap
534
+ # with the exception paths above.
535
+ if ctx.status is not None:
536
+ for err_source, err_line, err_cmd, err_msg in ctx.status.error_history:
537
+ errors.append(ScriptError(message=err_msg, source=err_source, line=err_line, sql=err_cmd))
538
+
531
539
  elapsed = time.perf_counter() - t0
532
540
 
533
541
  # ------------------------------------------------------------------
execsql/cli/__init__.py CHANGED
@@ -292,7 +292,7 @@ def main(
292
292
  "-a",
293
293
  "--assign-arg",
294
294
  metavar="VALUE",
295
- help="Define the replacement string for a substitution variable [cyan]\\$ARG_x[/cyan].",
295
+ help="Define the replacement string for a substitution variable [cyan]$ARG_x[/cyan].",
296
296
  ),
297
297
  user_logfile: bool = typer.Option(
298
298
  False,
execsql/config.py CHANGED
@@ -56,6 +56,9 @@ class StatObj:
56
56
  self.metacommand_error = False
57
57
  self.cancel_halt = True
58
58
  self.dialog_canceled = False
59
+ # Cumulative record of non-halting errors: (source, line_no, command, message).
60
+ # Unlike sql_error/metacommand_error, this is never reset between statements.
61
+ self.error_history: list[tuple[str, int | None, str, str]] = []
59
62
  # BatchLevels is defined in the main module; defer import to avoid circular deps
60
63
  from execsql.script import BatchLevels
61
64
 
@@ -278,6 +278,7 @@ def _exec_sql(
278
278
  ctx.exec_log.log_status_info(f"SQL error: {e.errmsg()}")
279
279
  if status.halt_on_err:
280
280
  exit_now(1, e)
281
+ status.error_history.append((source, line_no, cmd, e.errmsg()))
281
282
  return
282
283
  subvars.add_substitution("$LAST_SQL", cmd)
283
284
 
@@ -334,6 +335,7 @@ def _exec_metacommand(
334
335
  ctx.exec_log.log_status_info(f"Metacommand error: {e.errmsg()}")
335
336
  if status.halt_on_metacommand_err:
336
337
  raise e
338
+ status.error_history.append((source, line_no, cmd, e.errmsg()))
337
339
  return None
338
340
  # No handler matched — truly unknown metacommand
339
341
  status.metacommand_error = True
execsql/utils/fileio.py CHANGED
@@ -78,7 +78,9 @@ def check_dir(filename: str) -> None:
78
78
  import execsql.state as _state
79
79
 
80
80
  conf = _state.conf
81
- if conf.make_export_dirs:
81
+ # conf is None when called via the library API (e.g. a db factory
82
+ # function) before run() has initialized global state.
83
+ if conf is not None and conf.make_export_dirs:
82
84
  make_export_dirs(filename)
83
85
  else:
84
86
  dn = str(Path(filename).parent)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.22.0
3
+ Version: 2.22.1
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
@@ -130,10 +130,7 @@ Requires-Dist: pg-upsert>=1.23.0; extra == 'upsert'
130
130
  Description-Content-Type: text/markdown
131
131
 
132
132
  > [!NOTE]
133
- > **This is a maintained fork of [execsql](https://execsql.readthedocs.io/).**
134
- > The original monolith has been fully refactored into a modular package.
135
- > The CLI and configuration are backwards-compatible with upstream v1.130.1.
136
- > Report issues at [github.com/geocoug/execsql/issues](https://github.com/geocoug/execsql/issues).
133
+ > ***execsql2* is a maintained fork of [execsql](https://execsql.readthedocs.io/)**, originally authored by R.Dreas Nielsen and no longer actively maintained upstream. The monolith has been fully refactored into a modular package; the CLI and configuration remain backwards-compatible with upstream v1.130.1. Maintained by [Caleb Grant](https://github.com/geocoug) and distributed on PyPI as [`execsql2`](https://pypi.org/project/execsql2/). Report issues at [github.com/geocoug/execsql/issues](https://github.com/geocoug/execsql/issues).
137
134
 
138
135
  <div align="center">
139
136
 
@@ -155,48 +152,59 @@ Description-Content-Type: text/markdown
155
152
 
156
153
  </div>
157
154
 
158
- # Fork
155
+ ## Overview
159
156
 
160
- *execsql2* is a maintained fork of [execsql](https://execsql.readthedocs.io/) originally authored by R.Dreas Nielsen. The upstream project is no longer actively maintained. This fork is maintained by [Caleb Grant](https://github.com/geocoug) and is distributed on PyPI as `execsql2`. Complete documentation is at [execsql2.readthedocs.io](https://execsql2.readthedocs.io/).
157
+ *execsql* runs SQL scripts against PostgreSQL, MySQL/MariaDB, SQLite, DuckDB, MS-SQL-Server, MS-Access, Firebird, Oracle, or an ODBC DSN. In addition to standard SQL, it supports a set of metacommands (embedded in SQL comments) for importing and exporting data, copying data between databases, conditional execution, looping, substitution variables, and interactive prompts. Because metacommands live in SQL comments, scripts remain valid SQL and are ignored by other tools such as `psql` or `sqlcmd`.
161
158
 
162
- # Overview
159
+ ## Example
163
160
 
164
- *execsql* runs SQL scripts against PostgreSQL, MySQL/MariaDB, SQLite, DuckDB, MS-SQL-Server, MS-Access, Firebird, Oracle, or an ODBC DSN. In addition to standard SQL, it supports a set of metacommands (embedded in SQL comments) for importing and exporting data, copying data between databases, conditional execution, looping, substitution variables, and interactive prompts. Because metacommands live in SQL comments, scripts remain valid SQL and are ignored by other tools such as `psql` or `sqlcmd`.
161
+ Lines prefixed with `-- !x!` are metacommands; identifiers wrapped in `!!` are substitution variables:
165
162
 
166
- # Installation
163
+ ```sql
164
+ -- ==== Configuration ====
165
+ -- Put the (date-tagged) logfile name in the 'inputfile' substitution variable.
166
+ -- !x! SUB inputfile logs/errors_!!$date_tag!!
167
+ -- Ensure that the export directory will be created if necessary.
168
+ -- !x! CONFIG MAKE_EXPORT_DIRS Yes
167
169
 
168
- ```bash
169
- pip install execsql2
170
+ -- ==== Display Fatal Errors ====
171
+ -- !x! IF(file_exists(!!inputfile!!))
172
+ -- Import the data to a staging table.
173
+ -- !x! IMPORT TO REPLACEMENT staging.errorlog FROM !!inputfile!!
174
+ -- Create a view to display only fatal errors.
175
+ create temporary view fatals as
176
+ select user, run_time, process
177
+ from staging.errorlog
178
+ where severity = 'FATAL';
179
+ -- !x! IF(HASROWS(fatals))
180
+ -- Export the fatal errors to a dated report.
181
+ -- !x! EXPORT fatals TO reports/error_report_!!$date_tag!! AS CSV
182
+ -- Also display it to the user in a GUI.
183
+ -- !x! PROMPT MESSAGE "Fatal errors in !!inputfile!!:" DISPLAY fatals
184
+ -- !x! ELSE
185
+ -- !x! WRITE "There are no fatal errors."
186
+ -- !x! ENDIF
187
+ -- !x! ELSE
188
+ -- !x! WRITE "There is no error log."
189
+ -- !x! ENDIF
190
+ drop table if exists staging.errorlog cascade;
170
191
  ```
171
192
 
172
- Optional extras install database drivers and feature bundles:
193
+ The `PROMPT` metacommand produces a GUI display of the data:
194
+
195
+ ![PROMPT display of 'fatals' view](https://execsql2.readthedocs.io/en/latest/images/fatals.png)
196
+
197
+ ## Installation
173
198
 
174
199
  ```bash
175
- # Database drivers
176
- pip install execsql2[postgres] # PostgreSQL (psycopg3 / psycopg[binary])
177
- pip install execsql2[mysql] # MySQL / MariaDB (pymysql)
178
- pip install execsql2[mssql] # SQL Server (pyodbc)
179
- pip install execsql2[duckdb] # DuckDB
180
- pip install execsql2[firebird] # Firebird (firebird-driver)
181
- pip install execsql2[oracle] # Oracle (oracledb)
182
- pip install execsql2[odbc] # ODBC DSN (pyodbc)
183
-
184
- # Feature bundles
185
- pip install execsql2[formats] # ODS, Excel, Jinja2, Feather, Parquet, HDF5
186
- pip install execsql2[upsert] # pg-upsert for PostgreSQL upsert operations
187
- pip install execsql2[auth] # OS keyring integration
188
- pip install execsql2[auth-plaintext] # Keyring + plaintext file backend (headless Linux)
189
- pip install execsql2[auth-encrypted] # Keyring + encrypted file backend (headless Linux)
190
- pip install execsql2[map] # tkintermapview for PROMPT MAP
191
-
192
- # Convenience
193
- pip install execsql2[all-db] # All database drivers
194
- pip install execsql2[all] # Everything
200
+ pip install execsql2 # core — SQLite works with no extras
201
+ pip install "execsql2[postgres]" # add a driver: postgres, mysql, mssql, duckdb, firebird, oracle, odbc
202
+ pip install "execsql2[all]" # everything: all drivers plus all feature extras
195
203
  ```
196
204
 
197
- SQLite connections use Python's standard library and require no additional packages.
205
+ Feature extras cover spreadsheet and Parquet/Feather formats, keyring authentication, PostgreSQL upsert, and more see the [installation guide](https://execsql2.readthedocs.io/en/latest/getting-started/installation/) for the full list.
198
206
 
199
- # Usage
207
+ ## Usage
200
208
 
201
209
  ```text
202
210
  execsql [OPTIONS] SQL_SCRIPT [SERVER DATABASE | DATABASE_FILE]
@@ -214,7 +222,7 @@ execsql -to script.sql myserver myservice # Oracle
214
222
  execsql script.sql # read connection from config file
215
223
  ```
216
224
 
217
- ## Supported Databases
225
+ ### Supported Databases
218
226
 
219
227
  | Flag | Database |
220
228
  | ---- | --------------- |
@@ -228,67 +236,53 @@ execsql script.sql # read connection from config file
228
236
  | `o` | Oracle |
229
237
  | `d` | ODBC DSN |
230
238
 
231
- ## Options
232
-
233
- | Flag | Description |
234
- | ------------------------------------- | ------------------------------------------------------------------ |
235
- | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
236
- | `-u USER` | Database username |
237
- | `-p PORT` | Server port |
238
- | `-a VALUE` | Set substitution variable `$ARG_x` |
239
- | `-b` / `--boolean-int` | Treat integers 0 and 1 as boolean values |
240
- | `-c SCRIPT` | Execute inline SQL or metacommand string |
241
- | `-d` | Auto-create export directories |
242
- | `-e ENCODING` / `--database-encoding` | Character encoding used in the database |
243
- | `-f ENCODING` | Script file encoding (default: UTF-8) |
244
- | `-g ENCODING` / `--output-encoding` | Encoding for WRITE and EXPORT output |
245
- | `-i ENCODING` / `--import-encoding` | Encoding for data files used with IMPORT |
246
- | `-l` | Write run log to `~/execsql.log` |
247
- | `-m` | List metacommands and exit |
248
- | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
249
- | `-o` / `--online-help` | Open the online documentation in the default browser |
250
- | `-s N` / `--scan-lines` | Lines to scan for IMPORT format detection (0 = scan entire file) |
251
- | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
252
- | `-w` | Skip password prompt when a username is supplied |
253
- | `-y` / `--encodings` | List available encoding names and exit |
254
- | `-z KB` / `--import-buffer` | Import buffer size in KB (default: 32) |
255
- | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
256
- | `--output-dir DIR` | Default base directory for EXPORT output files |
257
- | `--dry-run` | Parse the script and report commands without executing |
258
- | `--lint` | Static analysis: check structure and warn on issues (no DB) |
259
- | `--parse-tree` | Print the script's AST structure and exit (no DB) |
260
- | `--list-plugins` | List discovered plugins and exit |
261
- | `--ping` | Test database connectivity and exit |
262
- | `--profile` | Show per-statement timing summary after execution |
263
- | `--profile-limit N` | Top N statements to display in `--profile` summary (default: 20) |
264
- | `--progress` | Show a progress bar for long-running IMPORT operations |
265
- | `--config FILE` | Load an explicit config file (highest priority after CLI args) |
266
- | `--no-system-cmd` | Disable the `SYSTEM_CMD` metacommand (safer for CI / shared envs) |
267
- | `--no-rm-file` | Disable the `RM_FILE` metacommand (no script-driven file deletion) |
268
- | `--no-serve` | Disable the `SERVE` metacommand (no script-driven file streaming) |
269
- | `--init-config` | Print a default `execsql.conf` template to stdout and exit |
270
- | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
271
- | `--dump-keywords` | Print metacommand keywords as JSON and exit |
272
- | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
273
-
274
- Run `execsql --help` for the full option list, or `execsql -m` to list all metacommands.
275
-
276
- # Features
239
+ ### Common options
240
+
241
+ | Flag | Description |
242
+ | ------------------------------------------------- | --------------------------------------------------------------- |
243
+ | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
244
+ | `-u USER` | Database username |
245
+ | `-p PORT` | Server port |
246
+ | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
247
+ | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
248
+ | `-c SCRIPT` | Execute inline SQL or metacommand string |
249
+ | `-a VALUE` | Set substitution variable `$ARG_x` |
250
+ | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
251
+ | `--config FILE` | Load an explicit config file |
252
+ | `--dry-run` | Parse the script and report commands without executing |
253
+ | `--lint` | Static analysis: check structure and warn on issues (no DB) |
254
+ | `--ping` | Test database connectivity and exit |
255
+ | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
256
+ | `--no-system-cmd` / `--no-rm-file` / `--no-serve` | Disable the `SYSTEM_CMD` / `RM_FILE` / `SERVE` metacommands |
257
+
258
+ See the [full options reference](https://execsql2.readthedocs.io/en/latest/getting-started/syntax/#options) or run `execsql --help` for the complete list, and `execsql -m` for all metacommands.
259
+
260
+ ## Features
261
+
262
+ **Data movement**
277
263
 
278
264
  - Import data from CSV, TSV, JSON, Excel, OpenDocument, Feather, or Parquet files into a database table.
279
265
  - 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.
280
266
  - Copy data between databases, including across different DBMS types.
267
+
268
+ **Script logic**
269
+
281
270
  - Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
282
- - Validate data with `ASSERT` halt the script with a clear error message if a condition is false (ideal for CI pipelines).
283
- - Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`.
271
+ - Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`; include or chain scripts with `INCLUDE` and `SCRIPT`.
284
272
  - Use substitution variables (`SUB`, `$ARG_x`, built-in variables like `$date_tag`) to parameterize scripts.
285
- - Include or chain scripts with `INCLUDE` and `SCRIPT`.
273
+ - Validate data with `ASSERT` halt the script with a clear error message if a condition is false (ideal for CI pipelines).
274
+
275
+ **Interaction & observability**
276
+
286
277
  - Display query results in a GUI dialog; optionally prompt the user to select a row, enter a value, or submit a form.
287
278
  - Write status messages or tabular output to the console or a file during execution.
288
279
  - Automatically log each run, recording databases used, scripts executed, and user responses.
280
+
281
+ **Extensibility**
282
+
289
283
  - Extend with custom metacommands, exporters, and importers via the plugin system.
290
284
 
291
- # Library API
285
+ ## Library API
292
286
 
293
287
  execsql can be used as a Python library for programmatic script execution:
294
288
 
@@ -331,56 +325,43 @@ if not result.success:
331
325
  result.raise_on_error() # raises ExecSqlError
332
326
  ```
333
327
 
334
- Use a pre-existing database connection instead of a DSN:
328
+ Use a pre-existing database connection instead of a DSN — useful for reusing one connection across multiple `run()` calls, or when connection parameters come from your application's configuration rather than a URL:
335
329
 
336
330
  ```python
337
- from execsql.db.factory import db_SQLite
338
- conn = db_SQLite("my.db", new_db=True)
339
- result = run(sql="SELECT 1;", connection=conn)
340
- # run() does NOT close this connection you manage its lifecycle
341
- ```
342
-
343
- Each call to `run()` uses an isolated `RuntimeContext`, so multiple calls do not share state.
344
-
345
- # An Illustration
331
+ from execsql import run
332
+ from execsql.db.factory import db_Postgres, db_SQLite
333
+
334
+ # PostgreSQL connection object (psycopg3 under the hood)
335
+ conn = db_Postgres(
336
+ "db.example.com", # server
337
+ "warehouse", # database
338
+ user="etl",
339
+ port=5432, # optional, default 5432
340
+ password="s3cret", # omit to use keyring / interactive prompt
341
+ )
346
342
 
347
- The following script demonstrates metacommands and substitution variables. Lines prefixed with `-- !x!` are metacommands; identifiers wrapped in `!!` are substitution variables.
343
+ # Reuse the same connection across multiple calls
344
+ staged = run(script="stage.sql", connection=conn)
345
+ loaded = run(
346
+ script="load.sql",
347
+ connection=conn,
348
+ variables={"RUN_DATE": "2026-07-03"},
349
+ )
348
350
 
349
- ```sql
350
- -- ==== Configuration ====
351
- -- Put the (date-tagged) logfile name in the 'inputfile' substitution variable.
352
- -- !x! SUB inputfile logs/errors_!!$date_tag!!
353
- -- Ensure that the export directory will be created if necessary.
354
- -- !x! CONFIG MAKE_EXPORT_DIRS Yes
351
+ # run() does NOT close the connection — you manage its lifecycle
352
+ conn.close()
355
353
 
356
- -- ==== Display Fatal Errors ====
357
- -- !x! IF(file_exists(!!inputfile!!))
358
- -- Import the data to a staging table.
359
- -- !x! IMPORT TO REPLACEMENT staging.errorlog FROM !!inputfile!!
360
- -- Create a view to display only fatal errors.
361
- create temporary view fatals as
362
- select user, run_time, process
363
- from staging.errorlog
364
- where severity = 'FATAL';
365
- -- !x! IF(HASROWS(fatals))
366
- -- Export the fatal errors to a dated report.
367
- -- !x! EXPORT fatals TO reports/error_report_!!$date_tag!! AS CSV
368
- -- Also display it to the user in a GUI.
369
- -- !x! PROMPT MESSAGE "Fatal errors in !!inputfile!!:" DISPLAY fatals
370
- -- !x! ELSE
371
- -- !x! WRITE "There are no fatal errors."
372
- -- !x! ENDIF
373
- -- !x! ELSE
374
- -- !x! WRITE "There is no error log."
375
- -- !x! ENDIF
376
- drop table if exists staging.errorlog cascade;
354
+ # SQLite works the same way
355
+ conn = db_SQLite("my.db", new_db=True)
356
+ result = run(sql="SELECT 1;", connection=conn)
357
+ conn.close()
377
358
  ```
378
359
 
379
- The `PROMPT` metacommand produces a GUI display of the data:
360
+ Factory functions exist for every supported backend (`db_Postgres`, `db_MySQL`, `db_SQLite`, `db_DuckDB`, `db_SqlServer`, `db_Oracle`, `db_Firebird`, `db_Access`, `db_Dsn`) — see [`execsql.db.factory`](https://execsql2.readthedocs.io/en/latest/api/db/).
380
361
 
381
- ![PROMPT display of 'fatals' view](https://execsql2.readthedocs.io/en/latest/images/fatals.png)
362
+ Each call to `run()` uses an isolated `RuntimeContext`, so multiple calls do not share state.
382
363
 
383
- # Formatting Scripts
364
+ ## Formatting Scripts
384
365
 
385
366
  The `execsql-format` command normalizes execsql script files: it uppercases metacommand keywords, corrects block indentation, and optionally reformats SQL via `sqlglot`. The metacommand / indent / keyword reformatting is built into `execsql2`; SQL reformatting requires the `[formatter]` extra (or pass `--no-sql` to skip it):
386
367
 
@@ -403,14 +384,14 @@ execsql-format --no-sql --in-place scripts/
403
384
  ```yaml
404
385
  repos:
405
386
  - repo: https://github.com/geocoug/execsql
406
- rev: v2.22.0
387
+ rev: v2.22.1
407
388
  hooks:
408
389
  - id: execsql-format
409
390
  ```
410
391
 
411
392
  The hook rewrites `*.sql` files in place by default. See the [formatter documentation](https://execsql2.readthedocs.io/en/latest/guides/formatter/) for `--check`, `--indent`, and other options.
412
393
 
413
- # VS Code Syntax Highlighting
394
+ ## VS Code Syntax Highlighting
414
395
 
415
396
  A VS Code extension for execsql syntax highlighting is included in [`extras/vscode-execsql`](extras/vscode-execsql). It injects a TextMate grammar into `.sql` files, adding highlighting for `-- !x!` metacommand markers, keywords (control flow, block, action, directive), variable substitutions (`!!var!!`, `!{var}!`), built-in functions, export formats, and config options — all layered on top of standard SQL highlighting.
416
397
 
@@ -422,7 +403,7 @@ ln -s /path/to/execsql/extras/vscode-execsql ~/.vscode/extensions/execsql-syntax
422
403
 
423
404
  See the [extension README](extras/vscode-execsql/README.md) for Windows instructions, color customization, and troubleshooting.
424
405
 
425
- # Templates
406
+ ## Templates
426
407
 
427
408
  The `templates/` directory in this repository includes ready-to-use execsql scripts:
428
409
 
@@ -432,13 +413,14 @@ The `templates/` directory in this repository includes ready-to-use execsql scri
432
413
  - **`script_template.sql`**: A framework for new scripts with sections for configuration, logging, and error reporting.
433
414
  - **`execsql.conf`**: An annotated configuration file covering all available settings.
434
415
 
435
- # Documentation
416
+ ## Documentation
436
417
 
437
418
  Full documentation, including a complete metacommand reference and 30+ examples, is at [execsql2.readthedocs.io](https://execsql2.readthedocs.io/).
438
419
 
439
- # Copyright and License
420
+ ## Copyright and License
440
421
 
441
422
  Copyright (c) 2007-2025 R.Dreas Nielsen
423
+
442
424
  Copyright (c) 2026-present Caleb Grant
443
425
 
444
426
  This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [GNU General Public License](http://www.gnu.org/licenses/) for more details.
@@ -1,7 +1,7 @@
1
1
  execsql/__init__.py,sha256=ZeVnpEWu8M2bn3qLmVM72NhWRueUGNbsX8UAPTermo0,901
2
2
  execsql/__main__.py,sha256=HdbK-SAhyUmfB6xINY5AzxdMSxGzWSGEG_2dv42Jn64,315
3
- execsql/api.py,sha256=v_M46MkVcuGPkIG6xp5VD4XNfwMg7HG45OKxdAwrAEs,21732
4
- execsql/config.py,sha256=gxv7SDjiVrpR05rU_rSjCBd_PP3RmwsyhOyPWOqfAGQ,34123
3
+ execsql/api.py,sha256=EOtBwMnm-8fmB_KmvxyjglR6C7CjdYhCPBisJ9CFfJg,22227
4
+ execsql/config.py,sha256=txKziUJLV7UPonj2sGFgkmlinDdSV50aHmR_cuCbEIo,34369
5
5
  execsql/exceptions.py,sha256=GlphAiR8iZu4UR4U50WgSqEUMd9_8AeQJ_hhPcJOeQU,9381
6
6
  execsql/format.py,sha256=ON8Jy5BSen4UI4SGY5cHKRwsvR0ZoLhH0BdblMwydYE,33670
7
7
  execsql/models.py,sha256=-DWTVrPNwM-n1h3e3rXMoBv9-wpWVdZahyy4RB7fRRE,15502
@@ -11,7 +11,7 @@ execsql/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  execsql/state.py,sha256=T6UoXXxAkUP-4KKQpfFAlI3WMzm2xUi3LSplJDuRLY0,21965
12
12
  execsql/state.pyi,sha256=ES5xdoId-ALCGnLIQvQIGBZljZRB6aOOEMRLKXjO9hA,3770
13
13
  execsql/types.py,sha256=eKMF2kb5LHrEwKsiFpy6z_fEH05yC0Cpsg98Ea4SpVA,32231
14
- execsql/cli/__init__.py,sha256=aJknKKIGxYCXpny0cyHXfqJsJ95dBtlEXXhPASFG8GQ,23114
14
+ execsql/cli/__init__.py,sha256=WPwEIELz-9XFG3Dpf-037eeYBdjtZStxWkT1bc3tMek,23112
15
15
  execsql/cli/dsn.py,sha256=svaZtrUXFRL2W5G6FRRiKtR6kehOp7urrVhIx_642Z8,2820
16
16
  execsql/cli/help.py,sha256=ThwdZuMIfLPxLAPpGWwXFY_UfyWvYOCjdlBNK20Vzd8,5718
17
17
  execsql/cli/lint.py,sha256=YqKzFNUhyb_Th69hYgKk1ZZVjCsZfJMIiUGqp06JwNs,17236
@@ -86,7 +86,7 @@ execsql/script/__init__.py,sha256=eGJPBDWj42aaId2lX_quSrqoKrvGwGElIrGDNCyoV1Y,35
86
86
  execsql/script/ast.py,sha256=TQ4_7Lfw1F8_k6ycdvMZdzwNafrZiljSrthVRWUsuIk,20585
87
87
  execsql/script/control.py,sha256=WqLy-HLPqHG3vEzYpKMiIJsD7LpORjyQuUWzFzcGz4w,2327
88
88
  execsql/script/engine.py,sha256=KbSB5aXkuDlazh3rRP4vRym_bVTDKmChLQ_OFf3-N8Q,21016
89
- execsql/script/executor.py,sha256=64dYw8QuNcHgvnJX1PwTK01UvDgOOYcI1IKceBL4PO4,36948
89
+ execsql/script/executor.py,sha256=NEIro9TIIcOvugoqdtk2xs08DQnrzkeQIo1CUwaTbIY,37092
90
90
  execsql/script/parser.py,sha256=VhFTJK8kODNRWgIDc3LjYjA3_SMPFATkpjVl82dYnrM,35179
91
91
  execsql/script/variables.py,sha256=_hklqCeqPbdOT646Yg3ZcpmPke38_8bukMG3eZMTKZw,16384
92
92
  execsql/utils/__init__.py,sha256=0uR6JwVJQRX3vceByNBduCAf5dd5assKjeqJUWvpZoA,278
@@ -94,30 +94,30 @@ execsql/utils/auth.py,sha256=8Wixkx5nayEBG7RT4WTUQpCiElIKS3yOTV_6_-hHTRs,8889
94
94
  execsql/utils/crypto.py,sha256=KlT6lPk-v0-qQQfAMfWvT9Q6kRdCD3_5JVaLajNocKM,3051
95
95
  execsql/utils/datetime.py,sha256=8BrD24KWeMoouJZiFaXK4HF7plc-O_xrzIw12qOEyp8,3629
96
96
  execsql/utils/errors.py,sha256=bDWooUmGdN8xqUt6csq466wPqKPLHHSi7U4hsmrFZxA,8577
97
- execsql/utils/fileio.py,sha256=q1d1121GRilGSIpEF7Srgft7oF_latVUEghNdkAZbsw,33171
97
+ execsql/utils/fileio.py,sha256=_15UIdcRZxZn2R6YP4xQn-6C9CVK2yxj1oEfxULo2_c,33329
98
98
  execsql/utils/gui.py,sha256=T22ThhkedzQz9kBidxZwIArt1umIvrgrSUwsPWAwvug,23548
99
99
  execsql/utils/mail.py,sha256=SXpTBDx0WIPl2NSjkjmnoChHYVEMyZhIKU4QiJFFZNY,6011
100
100
  execsql/utils/numeric.py,sha256=_7bi5R6pHjMCkOPne_Sh5nUj-B8e9g6WFmjptFHaunA,1573
101
101
  execsql/utils/regex.py,sha256=B3mSBhRRx_DpUnX_t9IoYd05KvWAebiLMCZqQAmEwcw,3885
102
102
  execsql/utils/strings.py,sha256=UQNjpRCEFa1UO6feU-M-9e24wWAvizs_iu_4fFusLxo,8516
103
103
  execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
104
- execsql2-2.22.0.data/data/execsql2_extras/README.md,sha256=vX4NTL095dUoA_hesyRMGYBorEZ_Y_tJ9qrd-MVV09I,5032
105
- execsql2-2.22.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
106
- execsql2-2.22.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
107
- execsql2-2.22.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
108
- execsql2-2.22.0.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
109
- execsql2-2.22.0.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
110
- execsql2-2.22.0.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
111
- execsql2-2.22.0.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
112
- execsql2-2.22.0.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
113
- execsql2-2.22.0.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
114
- execsql2-2.22.0.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
115
- execsql2-2.22.0.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
116
- execsql2-2.22.0.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
117
- execsql2-2.22.0.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
118
- execsql2-2.22.0.dist-info/METADATA,sha256=aYtTGfrjV6YMqEipLcyiyWymmhwBnx1rF6DGcY87AB8,22623
119
- execsql2-2.22.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
120
- execsql2-2.22.0.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
121
- execsql2-2.22.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
122
- execsql2-2.22.0.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
123
- execsql2-2.22.0.dist-info/RECORD,,
104
+ execsql2-2.22.1.data/data/execsql2_extras/README.md,sha256=vX4NTL095dUoA_hesyRMGYBorEZ_Y_tJ9qrd-MVV09I,5032
105
+ execsql2-2.22.1.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
106
+ execsql2-2.22.1.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
107
+ execsql2-2.22.1.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
108
+ execsql2-2.22.1.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
109
+ execsql2-2.22.1.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
110
+ execsql2-2.22.1.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
111
+ execsql2-2.22.1.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
112
+ execsql2-2.22.1.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
113
+ execsql2-2.22.1.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
114
+ execsql2-2.22.1.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
115
+ execsql2-2.22.1.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
116
+ execsql2-2.22.1.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
117
+ execsql2-2.22.1.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
118
+ execsql2-2.22.1.dist-info/METADATA,sha256=z76Sqoh_SVHX3ygG4-PohP8X4SJL97UlO8LpmIu2tQ8,20382
119
+ execsql2-2.22.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
120
+ execsql2-2.22.1.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
121
+ execsql2-2.22.1.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
122
+ execsql2-2.22.1.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
123
+ execsql2-2.22.1.dist-info/RECORD,,