execsql2 2.18.0__py3-none-any.whl → 2.19.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. execsql/cli/__init__.py +3 -5
  2. execsql/cli/lint.py +433 -18
  3. execsql/cli/run.py +46 -17
  4. execsql/data/execsql.conf.template +34 -2
  5. execsql/db/access.py +0 -6
  6. execsql/db/base.py +0 -13
  7. execsql/db/mysql.py +0 -6
  8. execsql/db/oracle.py +0 -6
  9. execsql/db/sqlserver.py +0 -6
  10. execsql/debug/repl.py +117 -35
  11. execsql/exporters/feather.py +10 -9
  12. execsql/format.py +23 -4
  13. execsql/importers/base.py +3 -4
  14. execsql/importers/xls.py +6 -1
  15. execsql/metacommands/__init__.py +2 -2
  16. execsql/metacommands/data.py +1 -0
  17. execsql/metacommands/dispatch.py +5 -10
  18. execsql/metacommands/script_ext.py +8 -7
  19. execsql/script/engine.py +1 -12
  20. execsql/script/executor.py +2 -6
  21. execsql/script/parser.py +49 -12
  22. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/example_config_prompt.sql +1 -1
  23. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/execsql.conf +34 -2
  24. {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/METADATA +54 -43
  25. {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/RECORD +42 -43
  26. {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/WHEEL +1 -1
  27. execsql/cli/lint_ast.py +0 -439
  28. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/README.md +0 -0
  29. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  30. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
  31. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/md_compare.sql +0 -0
  32. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
  33. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
  34. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
  35. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  36. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  37. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/script_template.sql +0 -0
  38. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
  39. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  40. {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  41. {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/entry_points.txt +0 -0
  42. {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/licenses/LICENSE.txt +0 -0
  43. {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/licenses/NOTICE +0 -0
execsql/script/parser.py CHANGED
@@ -102,6 +102,19 @@ def _strip_quotes(s: str) -> str:
102
102
  return s
103
103
 
104
104
 
105
+ def _unclosed_block_msg(top: _BlockFrame) -> str:
106
+ """Message blaming an unclosed opening block for a wrong-kind END-keyword error."""
107
+ if top.kind == "if":
108
+ return f"IF on line {top.start_line} of {top.source} has no matching ENDIF."
109
+ if top.kind == "loop":
110
+ return f"LOOP on line {top.start_line} of {top.source} has no matching ENDLOOP."
111
+ if top.kind == "batch":
112
+ return f"BEGIN BATCH on line {top.start_line} of {top.source} has no matching END BATCH."
113
+ if top.kind == "script":
114
+ return f"BEGIN SCRIPT on line {top.start_line} of {top.source} has no matching END SCRIPT."
115
+ return f"{top.kind.upper()} on line {top.start_line} of {top.source} is unclosed."
116
+
117
+
105
118
  _EXEC_SCRIPT_RX = re.compile(
106
119
  r"^\s*(?:EXEC(?:UTE)?|RUN)\s+SCRIPT"
107
120
  r"(?P<exists>\s+IF\s+EXISTS)?"
@@ -529,11 +542,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
529
542
  end_name = m.group("name")
530
543
  if end_name is not None:
531
544
  end_name = end_name.lower()
532
- if not block_stack or block_stack[-1].kind != "script":
545
+ if not block_stack:
546
+ raise ErrInfo(
547
+ type="cmd",
548
+ command_text=line,
549
+ other_msg=f"END SCRIPT on line {file_lineno} of {source_name} has no matching BEGIN SCRIPT.",
550
+ )
551
+ if block_stack[-1].kind != "script":
533
552
  raise ErrInfo(
534
553
  type="cmd",
535
554
  command_text=line,
536
- other_msg=f"Unmatched END SCRIPT metacommand on line {file_lineno} of file {source_name}.",
555
+ other_msg=_unclosed_block_msg(block_stack[-1]),
537
556
  )
538
557
  frame = block_stack[-1]
539
558
  script_node = frame.node
@@ -595,7 +614,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
595
614
  raise ErrInfo(
596
615
  type="cmd",
597
616
  command_text=line,
598
- other_msg=f"ELSEIF without matching IF on line {file_lineno} of {source_name}.",
617
+ other_msg=f"ELSEIF on line {file_lineno} of {source_name} has no matching IF.",
599
618
  )
600
619
  frame = block_stack[-1]
601
620
  frame._in_else = False
@@ -616,7 +635,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
616
635
  raise ErrInfo(
617
636
  type="cmd",
618
637
  command_text=line,
619
- other_msg=f"ANDIF without matching IF on line {file_lineno} of {source_name}.",
638
+ other_msg=f"ANDIF on line {file_lineno} of {source_name} has no matching IF.",
620
639
  )
621
640
  modifier = ConditionModifier(
622
641
  kind="AND",
@@ -638,7 +657,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
638
657
  raise ErrInfo(
639
658
  type="cmd",
640
659
  command_text=line,
641
- other_msg=f"ORIF without matching IF on line {file_lineno} of {source_name}.",
660
+ other_msg=f"ORIF on line {file_lineno} of {source_name} has no matching IF.",
642
661
  )
643
662
  modifier = ConditionModifier(
644
663
  kind="OR",
@@ -660,7 +679,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
660
679
  raise ErrInfo(
661
680
  type="cmd",
662
681
  command_text=line,
663
- other_msg=f"ELSE without matching IF on line {file_lineno} of {source_name}.",
682
+ other_msg=f"ELSE on line {file_lineno} of {source_name} has no matching IF.",
664
683
  )
665
684
  frame = block_stack[-1]
666
685
  frame._in_else = True
@@ -671,11 +690,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
671
690
  # -- ENDIF --
672
691
  m = _ENDIF_RX.match(cmd_text)
673
692
  if m:
674
- if not block_stack or block_stack[-1].kind != "if":
693
+ if not block_stack:
675
694
  raise ErrInfo(
676
695
  type="cmd",
677
696
  command_text=line,
678
- other_msg=f"ENDIF without matching IF on line {file_lineno} of {source_name}.",
697
+ other_msg=f"ENDIF on line {file_lineno} of {source_name} has no matching IF.",
698
+ )
699
+ if block_stack[-1].kind != "if":
700
+ raise ErrInfo(
701
+ type="cmd",
702
+ command_text=line,
703
+ other_msg=_unclosed_block_msg(block_stack[-1]),
679
704
  )
680
705
  frame = block_stack.pop()
681
706
  frame.node.span = SourceSpan(source_name, frame.start_line, file_lineno)
@@ -702,11 +727,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
702
727
  # -- ENDLOOP --
703
728
  m = _ENDLOOP_RX.match(cmd_text)
704
729
  if m:
705
- if not block_stack or block_stack[-1].kind != "loop":
730
+ if not block_stack:
731
+ raise ErrInfo(
732
+ type="cmd",
733
+ command_text=line,
734
+ other_msg=f"ENDLOOP on line {file_lineno} of {source_name} has no matching LOOP.",
735
+ )
736
+ if block_stack[-1].kind != "loop":
706
737
  raise ErrInfo(
707
738
  type="cmd",
708
739
  command_text=line,
709
- other_msg=f"ENDLOOP without matching LOOP on line {file_lineno} of {source_name}.",
740
+ other_msg=_unclosed_block_msg(block_stack[-1]),
710
741
  )
711
742
  frame = block_stack.pop()
712
743
  frame.node.span = SourceSpan(source_name, frame.start_line, file_lineno)
@@ -729,11 +760,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
729
760
  # -- END BATCH --
730
761
  m = _END_BATCH_RX.match(cmd_text)
731
762
  if m:
732
- if not block_stack or block_stack[-1].kind != "batch":
763
+ if not block_stack:
764
+ raise ErrInfo(
765
+ type="cmd",
766
+ command_text=line,
767
+ other_msg=f"END BATCH on line {file_lineno} of {source_name} has no matching BEGIN BATCH.",
768
+ )
769
+ if block_stack[-1].kind != "batch":
733
770
  raise ErrInfo(
734
771
  type="cmd",
735
772
  command_text=line,
736
- other_msg=f"END BATCH without matching BEGIN BATCH on line {file_lineno} of {source_name}.",
773
+ other_msg=_unclosed_block_msg(block_stack[-1]),
737
774
  )
738
775
  frame = block_stack.pop()
739
776
  frame.node.span = SourceSpan(source_name, frame.start_line, file_lineno)
@@ -122,7 +122,7 @@
122
122
  from configspecs cs inner join configusage cu
123
123
  on cu.sub_var = cs.sub_var
124
124
  where
125
- usage = '!!#usage!!';
125
+ usage = !'!#usage!'!;
126
126
  -- !x! prompt entry_form !!~spectbl!! message "You may change any of the configuration settings below."
127
127
  -- !x! if(sub_defined(~boolean_int)) {config boolean_int !!~boolean_int!!}
128
128
  -- !x! if(sub_defined(~boolean_words)) {config boolean_words !!~boolean_words!!}
@@ -1,6 +1,6 @@
1
1
  # execsql.conf — Configuration file for execsql
2
2
  #
3
- # Documentation: https://execsql2.readthedocs.io/reference/configuration/
3
+ # Documentation: https://execsql2.readthedocs.io/en/latest/reference/configuration/
4
4
  #
5
5
  # This file uses INI format. Section names are case-sensitive (lowercase).
6
6
  # Property names are not case-sensitive. Lines starting with # are comments.
@@ -263,7 +263,7 @@
263
263
  #message_css=
264
264
 
265
265
  # Obfuscated password (XOR, not cryptographically secure — use keyring instead).
266
- # See: https://execsql2.readthedocs.io/reference/security/#credentials
266
+ # See: https://execsql2.readthedocs.io/en/latest/reference/security/#credentials
267
267
  #enc_password=
268
268
 
269
269
 
@@ -279,6 +279,38 @@
279
279
  # Values: Yes or No. Default: Yes.
280
280
  #allow_system_cmd=Yes
281
281
 
282
+ # Whether to allow the RM_FILE metacommand (which deletes a file).
283
+ # Set to No to prevent scripts from deleting files.
284
+ # Also controllable via --no-rm-file CLI flag.
285
+ # Values: Yes or No. Default: Yes.
286
+ #allow_rm_file=Yes
287
+
288
+ # Whether to allow the SERVE metacommand (which opens an HTTP server on
289
+ # a local port to serve a single file). Set to No to disable.
290
+ # Also controllable via --no-serve CLI flag.
291
+ # Values: Yes or No. Default: Yes.
292
+ #allow_serve=Yes
293
+
294
+ # Root directory under which INCLUDE / EXECUTE SCRIPT targets must
295
+ # resolve. When set, attempts to include files outside this root via
296
+ # ../, absolute paths, drive letters, or UNC paths are rejected with
297
+ # an error. Default: no containment (any readable path is permitted).
298
+ #include_root=
299
+
300
+ # Root directory under which SERVE targets must resolve. Same
301
+ # containment semantics as include_root. Default: no containment.
302
+ #serve_root=
303
+
304
+ # Root directory under which Jinja2 / string.Template loader paths
305
+ # must resolve. Same containment semantics as include_root.
306
+ # Default: no containment.
307
+ #template_root=
308
+
309
+ # Maximum byte size of any single substitution-variable expansion,
310
+ # enforced by the substitute_vars() engine to defeat exponential-
311
+ # expansion bombs. Default: 10 MB (10485760).
312
+ #max_substitution_bytes=10485760
313
+
282
314
  # Whether to log all data variable assignments.
283
315
  # Values: Yes or No. Default: Yes.
284
316
  #log_datavars=Yes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.18.0
3
+ Version: 2.19.0
4
4
  Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
5
5
  Project-URL: Homepage, https://execsql2.readthedocs.io
6
6
  Project-URL: Repository, https://github.com/geocoug/execsql
@@ -41,8 +41,7 @@ Classifier: Topic :: Database :: Front-Ends
41
41
  Requires-Python: >=3.10
42
42
  Requires-Dist: python-dateutil>=2.8
43
43
  Requires-Dist: rich>=13.0
44
- Requires-Dist: sqlglot>=25.0
45
- Requires-Dist: textual>=0.47.0
44
+ Requires-Dist: textual>=1.0
46
45
  Requires-Dist: typer>=0.12
47
46
  Provides-Extra: all
48
47
  Requires-Dist: defusedxml; extra == 'all'
@@ -59,6 +58,7 @@ Requires-Dist: psycopg2-binary; extra == 'all'
59
58
  Requires-Dist: pymysql; extra == 'all'
60
59
  Requires-Dist: pyodbc; extra == 'all'
61
60
  Requires-Dist: pyyaml; extra == 'all'
61
+ Requires-Dist: sqlglot>=25.0; extra == 'all'
62
62
  Requires-Dist: tables; extra == 'all'
63
63
  Requires-Dist: tkintermapview>=1.29; extra == 'all'
64
64
  Requires-Dist: xlrd; extra == 'all'
@@ -93,6 +93,7 @@ Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
93
93
  Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
94
94
  Requires-Dist: pyyaml; extra == 'dev'
95
95
  Requires-Dist: ruff>=0.4; extra == 'dev'
96
+ Requires-Dist: sqlglot>=25.0; extra == 'dev'
96
97
  Requires-Dist: tables; extra == 'dev'
97
98
  Requires-Dist: tox-uv>=1.13.1; extra == 'dev'
98
99
  Requires-Dist: twine>=6.1.0; extra == 'dev'
@@ -111,6 +112,8 @@ Requires-Dist: polars; extra == 'formats'
111
112
  Requires-Dist: pyyaml; extra == 'formats'
112
113
  Requires-Dist: tables; extra == 'formats'
113
114
  Requires-Dist: xlrd; extra == 'formats'
115
+ Provides-Extra: formatter
116
+ Requires-Dist: sqlglot>=25.0; extra == 'formatter'
114
117
  Provides-Extra: map
115
118
  Requires-Dist: tkintermapview>=1.29; extra == 'map'
116
119
  Provides-Extra: mssql
@@ -228,44 +231,46 @@ execsql script.sql # read connection from config file
228
231
 
229
232
  ## Options
230
233
 
231
- | Flag | Description |
232
- | ------------------------------------- | ----------------------------------------------------------------- |
233
- | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
234
- | `-u USER` | Database username |
235
- | `-p PORT` | Server port |
236
- | `-a VALUE` | Set substitution variable `$ARG_x` |
237
- | `-b` / `--boolean-int` | Treat integers 0 and 1 as boolean values |
238
- | `-c SCRIPT` | Execute inline SQL or metacommand string |
239
- | `-d` | Auto-create export directories |
240
- | `-e ENCODING` / `--database-encoding` | Character encoding used in the database |
241
- | `-f ENCODING` | Script file encoding (default: UTF-8) |
242
- | `-g ENCODING` / `--output-encoding` | Encoding for WRITE and EXPORT output |
243
- | `-i ENCODING` / `--import-encoding` | Encoding for data files used with IMPORT |
244
- | `-l` | Write run log to `~/execsql.log` |
245
- | `-m` | List metacommands and exit |
246
- | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
247
- | `-o` / `--online-help` | Open the online documentation in the default browser |
248
- | `-s N` / `--scan-lines` | Lines to scan for IMPORT format detection (0 = scan entire file) |
249
- | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
250
- | `-w` | Skip password prompt when a username is supplied |
251
- | `-y` / `--encodings` | List available encoding names and exit |
252
- | `-z KB` / `--import-buffer` | Import buffer size in KB (default: 32) |
253
- | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
254
- | `--output-dir DIR` | Default base directory for EXPORT output files |
255
- | `--dry-run` | Parse the script and report commands without executing |
256
- | `--lint` | Static analysis: check structure and warn on issues (no DB) |
257
- | `--parse-tree` | Print the script's AST structure and exit (no DB) |
258
- | `--list-plugins` | List discovered plugins and exit |
259
- | `--ping` | Test database connectivity and exit |
260
- | `--profile` | Show per-statement timing summary after execution |
261
- | `--profile-limit N` | Top N statements to display in `--profile` summary (default: 20) |
262
- | `--progress` | Show a progress bar for long-running IMPORT operations |
263
- | `--config FILE` | Load an explicit config file (highest priority after CLI args) |
264
- | `--no-system-cmd` | Disable the `SYSTEM_CMD` metacommand (safer for CI / shared envs) |
265
- | `--init-config` | Print a default `execsql.conf` template to stdout and exit |
266
- | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
267
- | `--dump-keywords` | Print metacommand keywords as JSON and exit |
268
- | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
234
+ | Flag | Description |
235
+ | ------------------------------------- | ------------------------------------------------------------------ |
236
+ | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
237
+ | `-u USER` | Database username |
238
+ | `-p PORT` | Server port |
239
+ | `-a VALUE` | Set substitution variable `$ARG_x` |
240
+ | `-b` / `--boolean-int` | Treat integers 0 and 1 as boolean values |
241
+ | `-c SCRIPT` | Execute inline SQL or metacommand string |
242
+ | `-d` | Auto-create export directories |
243
+ | `-e ENCODING` / `--database-encoding` | Character encoding used in the database |
244
+ | `-f ENCODING` | Script file encoding (default: UTF-8) |
245
+ | `-g ENCODING` / `--output-encoding` | Encoding for WRITE and EXPORT output |
246
+ | `-i ENCODING` / `--import-encoding` | Encoding for data files used with IMPORT |
247
+ | `-l` | Write run log to `~/execsql.log` |
248
+ | `-m` | List metacommands and exit |
249
+ | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
250
+ | `-o` / `--online-help` | Open the online documentation in the default browser |
251
+ | `-s N` / `--scan-lines` | Lines to scan for IMPORT format detection (0 = scan entire file) |
252
+ | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
253
+ | `-w` | Skip password prompt when a username is supplied |
254
+ | `-y` / `--encodings` | List available encoding names and exit |
255
+ | `-z KB` / `--import-buffer` | Import buffer size in KB (default: 32) |
256
+ | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
257
+ | `--output-dir DIR` | Default base directory for EXPORT output files |
258
+ | `--dry-run` | Parse the script and report commands without executing |
259
+ | `--lint` | Static analysis: check structure and warn on issues (no DB) |
260
+ | `--parse-tree` | Print the script's AST structure and exit (no DB) |
261
+ | `--list-plugins` | List discovered plugins and exit |
262
+ | `--ping` | Test database connectivity and exit |
263
+ | `--profile` | Show per-statement timing summary after execution |
264
+ | `--profile-limit N` | Top N statements to display in `--profile` summary (default: 20) |
265
+ | `--progress` | Show a progress bar for long-running IMPORT operations |
266
+ | `--config FILE` | Load an explicit config file (highest priority after CLI args) |
267
+ | `--no-system-cmd` | Disable the `SYSTEM_CMD` metacommand (safer for CI / shared envs) |
268
+ | `--no-rm-file` | Disable the `RM_FILE` metacommand (no script-driven file deletion) |
269
+ | `--no-serve` | Disable the `SERVE` metacommand (no script-driven file streaming) |
270
+ | `--init-config` | Print a default `execsql.conf` template to stdout and exit |
271
+ | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
272
+ | `--dump-keywords` | Print metacommand keywords as JSON and exit |
273
+ | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
269
274
 
270
275
  Run `execsql --help` for the full option list, or `execsql -m` to list all metacommands.
271
276
 
@@ -378,14 +383,20 @@ The `PROMPT` metacommand produces a GUI display of the data:
378
383
 
379
384
  # Formatting Scripts
380
385
 
381
- The `execsql-format` command normalizes execsql script files: it uppercases metacommand keywords, corrects block indentation, and optionally reformats SQL via sqlglot. It is installed automatically with the `execsql2` package.
386
+ 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):
382
387
 
383
388
  ```bash
389
+ # Install with the SQL-reformatting extra
390
+ pip install execsql2[formatter]
391
+
384
392
  # Format files in place
385
393
  execsql-format --in-place scripts/
386
394
 
387
395
  # Check formatting without writing (useful in CI)
388
396
  execsql-format --check scripts/
397
+
398
+ # Run the formatter without sqlglot — keyword/indent normalization only
399
+ execsql-format --no-sql --in-place scripts/
389
400
  ```
390
401
 
391
402
  `execsql-format` is also available as a [pre-commit](https://pre-commit.com/) hook:
@@ -393,7 +404,7 @@ execsql-format --check scripts/
393
404
  ```yaml
394
405
  repos:
395
406
  - repo: https://github.com/geocoug/execsql
396
- rev: v2.11.0
407
+ rev: v2.18.0
397
408
  hooks:
398
409
  - id: execsql-format
399
410
  args: [--in-place]
@@ -3,40 +3,39 @@ execsql/__main__.py,sha256=HdbK-SAhyUmfB6xINY5AzxdMSxGzWSGEG_2dv42Jn64,315
3
3
  execsql/api.py,sha256=ZFTo_XZPhG21w2vxaeS1lS6o5XmF1FUJRIaypgTOjA8,20919
4
4
  execsql/config.py,sha256=OOrCcn9m3CNuXkxVOLp7uMhQikzUS2wh_QVhvIzRqIM,33296
5
5
  execsql/exceptions.py,sha256=EkM5cw2s0D9QCOgS4BU29FEyOnEtCxJ0esPT6l1hT9s,9205
6
- execsql/format.py,sha256=el_gQyMbj4VllToGEcU_61PtxUFotD1hXjrVzEhackM,25507
6
+ execsql/format.py,sha256=HJlWD4kSOvq7lBv99rRF1Cq1FNaMjG-qA3iwSTZtf8Q,26050
7
7
  execsql/models.py,sha256=kCTUQg9-vReM6WNFfB_ZrEppuOW5u1uMBQThSkfPC0o,13264
8
8
  execsql/parser.py,sha256=P3ea8k7T_XLMrbhpFNZXwytdShrY302MKnhosqza1lo,15493
9
9
  execsql/plugins.py,sha256=2voLwT6eFap6BCBoZYndNNC_bMEJO1f_aP6xQTVXwYI,12815
10
10
  execsql/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  execsql/state.py,sha256=T6UoXXxAkUP-4KKQpfFAlI3WMzm2xUi3LSplJDuRLY0,21965
12
12
  execsql/types.py,sha256=5K3aTuWQZHftz5slFODwqxlcvvt6lROISUnvUtRUazs,31799
13
- execsql/cli/__init__.py,sha256=km5f65aRHKZ1vLHRMnbZVXo-31gImxxOr1BvOIQg7B4,23226
13
+ execsql/cli/__init__.py,sha256=aJknKKIGxYCXpny0cyHXfqJsJ95dBtlEXXhPASFG8GQ,23114
14
14
  execsql/cli/dsn.py,sha256=svaZtrUXFRL2W5G6FRRiKtR6kehOp7urrVhIx_642Z8,2820
15
15
  execsql/cli/help.py,sha256=ThwdZuMIfLPxLAPpGWwXFY_UfyWvYOCjdlBNK20Vzd8,5718
16
- execsql/cli/lint.py,sha256=xoOvFcoYFJpGjpgFD9CGgPAJmoJ_FPuyecBNBCazfJo,3459
17
- execsql/cli/lint_ast.py,sha256=c9UEFsZ7PZlFdrK0zJCe-WXfCqvS3WlOUWEycZozqB8,14688
18
- execsql/cli/run.py,sha256=QoSHVBfg20n2knPrqf7RFJmcfFpC5aq7NkwX5o6qRnA,36326
16
+ execsql/cli/lint.py,sha256=YqKzFNUhyb_Th69hYgKk1ZZVjCsZfJMIiUGqp06JwNs,17236
17
+ execsql/cli/run.py,sha256=4plvi8ZaGea5fUeOhtS2f4MrVnGTaetvJyz7qX07s0I,37706
19
18
  execsql/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- execsql/data/execsql.conf.template,sha256=1a2g2Vga7s128wcu3ftIFRkHlKKtuvkuOHSD1XuNT7o,9404
19
+ execsql/data/execsql.conf.template,sha256=Sq1Huwb_Uf_lI7zW7m3h11fqU6zjGpGnCU4OVJ_tCe0,10696
21
20
  execsql/db/__init__.py,sha256=jTbuafuKOqYtXFR1wvCOoKK5Lr3l1uErfaIbIr6UywI,1063
22
- execsql/db/access.py,sha256=pqxFzxF5yXBv_UzJEtp5YHNJy3yHqxLYCRzVLV4qolo,19404
23
- execsql/db/base.py,sha256=m0xOGU-GAUU6SR1kF6LUnybaeX3fUgXqsofbzemyM28,35295
21
+ execsql/db/access.py,sha256=GcY5Pq_vXdp8Thzm6aSLg9vfp5koovGiYKXipQtRt50,19167
22
+ execsql/db/base.py,sha256=UUiYSztUen8xCWVCxDkRQeD098Yv1iA3WPl_qo4XjDI,34686
24
23
  execsql/db/dsn.py,sha256=59OzMAuCIfHcdOZNarK9TlDzaBJjhZ1SFFMvyXlH6u8,6086
25
24
  execsql/db/duckdb.py,sha256=79lRzKRhw1Pjfqcrba27S4Oq8a8AbDO_d0XkaNKKPQo,3197
26
25
  execsql/db/factory.py,sha256=YHdgyqQYy16548O3fGyElLC5C7DdIgva4Z29OsDxXjs,5367
27
26
  execsql/db/firebird.py,sha256=p_7RFWhFI7y5ukKCMXeDPE0wjeQ6dpO4IK6uz2dYjrc,9224
28
- execsql/db/mysql.py,sha256=HOFziabEN-ZD97LIfTrfhQB_yXwcCi9ZsqPqt6LFDHk,17964
29
- execsql/db/oracle.py,sha256=vYs1Oqym0LdAV5WQFdJFDesHlsx7RjsOaDyX0UTegPQ,12286
27
+ execsql/db/mysql.py,sha256=gOm1IRzFmxnT4ekGEXiF2yM3jB6r4p38Cmtt1Utj27Y,17712
28
+ execsql/db/oracle.py,sha256=1_odb5xmlm8vjdJdQXz7SHm9dzIbZ5sxP_IxTklH3kA,12018
30
29
  execsql/db/postgres.py,sha256=UNzrXzMniEyT3Z7qjCA_HLEUY0PVr1cJShuhAxtl5l0,21241
31
30
  execsql/db/sqlite.py,sha256=xooU6bvD9Y3frRpnbyesE63r6E1fwEHkkcN1YD_UIUE,11519
32
- execsql/db/sqlserver.py,sha256=6HtjLa__SlVyqbM7S80Wsipe-IFIDnzmYNg8BLvuQJM,9006
31
+ execsql/db/sqlserver.py,sha256=sxtOrcN1pGJQ0x7CctrKIL9XFIaHCDVIVvEvxzEOdzY,8744
33
32
  execsql/debug/__init__.py,sha256=j6EGUR0dHzUhWN1mHHtf1-Lhjq3Sb1V-vmnq2Ztgj1M,178
34
- execsql/debug/repl.py,sha256=d-Pq4wBQU0rcvjXWqDkq_FOxL29vJ7VCP3xoG6TtXpE,22309
33
+ execsql/debug/repl.py,sha256=JObeoEXh15qyk4Q3WQCCqMcBtojlIcu_Xg-ZRDZJi5Q,25491
35
34
  execsql/exporters/__init__.py,sha256=-Cnji-OgodJV8ftcDcOyTof0kQMy9J5kKVC8GVFpc3o,670
36
35
  execsql/exporters/base.py,sha256=XTPenHl5TbmbZ3cfPYLVFirGNiVps3Kt3AQKFVKy6ss,6408
37
36
  execsql/exporters/delimited.py,sha256=GIEeennL_elvcZgq5oSvgxAKAgwr8ea3o5_M_pLmc4g,32341
38
37
  execsql/exporters/duckdb.py,sha256=R4WbvzBEIK1ptnIc8w6c7jcinG-cuuFYC85_NYCumH0,3146
39
- execsql/exporters/feather.py,sha256=Ie7DCheyAj5y3ktpaMN76Mu3rnTQ0biKcxmaJdcIWvg,4184
38
+ execsql/exporters/feather.py,sha256=ardyidIcuhrqRBJh9ftUZkG3ZCY7NS7eGC-VAnpFIXI,4131
40
39
  execsql/exporters/html.py,sha256=BPTGYODiC5_5zaQsVkZ9QVAl67yfCWFTsjK0D-QOJkM,9717
41
40
  execsql/exporters/json.py,sha256=G9lyJcjgmMvymu_MoVrkSqx2H6JRN7qwA5UEomnPkVQ,4343
42
41
  execsql/exporters/latex.py,sha256=w_B83_5vKPe8uYxCWGdqvxwJeq0mw5zzKYDiAb7dbN0,4503
@@ -60,34 +59,34 @@ execsql/gui/console.py,sha256=pCBUcFGjlKXMkMjztbmt9glP3me9jAKAgQxnmUE38-0,19396
60
59
  execsql/gui/desktop.py,sha256=hdy-1-QxeKXe3GDG1lhxkQHhTjZxH7KNl_mVg5ki1qw,59080
61
60
  execsql/gui/tui.py,sha256=INBCuW6iEEV15P_JrHeviI1JlNCXfoiyPBU-IC5MaLY,65200
62
61
  execsql/importers/__init__.py,sha256=zZwdQxMaValCNqUrVdvaA7XPU3J8NmqVJ4uolNWY2iU,299
63
- execsql/importers/base.py,sha256=FGVz3ntN6xHL99rQixlQj3tAf570K_oU83UtbYE1lJg,4124
62
+ execsql/importers/base.py,sha256=k4ni92H33_-A32dNfxhBd6R5WPwM3o_8dSGABisUlR0,4136
64
63
  execsql/importers/csv.py,sha256=GUVRP294vHlOlF8XNecPEzatUBOOFIqnrEV9cBQkiv0,4849
65
64
  execsql/importers/feather.py,sha256=g2B69d2uv9vmnXcmjFyTVsMP40LYEzFYkhk3gD26mGw,1900
66
65
  execsql/importers/json.py,sha256=fL47h09Zzx4Qw3TTQRQJbdPdD3qVMiU-kK9C7j1IQzg,5534
67
66
  execsql/importers/ods.py,sha256=VsxIkr7opBamB0PbSFmMWt7G11w8lLUH1k_kRwz28zw,2847
68
- execsql/importers/xls.py,sha256=IB-ShXw2zHJUWPUtMCDbkjxYue-hACyR66tKNOZqzSw,3685
69
- execsql/metacommands/__init__.py,sha256=6PE1rXfJzIpHCxIgbeokh6fY2cAe82KR04J6pzawVlE,12993
67
+ execsql/importers/xls.py,sha256=7mGYqXko5_wyvEUz186xSzIx8Eeobn5A6DCVAg4YfTM,3838
68
+ execsql/metacommands/__init__.py,sha256=H_D1Z5kN6zfYtxatejQhQhQV1tlVZ7rcdFZ_9uhXu2Q,13010
70
69
  execsql/metacommands/conditions.py,sha256=5njexsBqSN_MNQdnw9Ra51BcS7ekACHNvBI2MXZF6_g,29995
71
70
  execsql/metacommands/connect.py,sha256=W24gYGmYDXNQyzBTsqWtl9-qbX2FS0v_c4s_OHj97mY,15327
72
71
  execsql/metacommands/control.py,sha256=btF9hP_jzTuTIODPK72CYF0v_oKYpwXpKLATt-Ti2kc,7988
73
- execsql/metacommands/data.py,sha256=u9D6F3ambIqXhEHVmFOI6RDrbmdXQ-FUiqw7aMGo5bQ,12135
72
+ execsql/metacommands/data.py,sha256=PPO6AnF_swFxy9GNrtMsv3YttT5ZrjOMA4QtEORDcso,12136
74
73
  execsql/metacommands/debug.py,sha256=MeVXAob8ItEg2QzuSUkKDaQCEABnH6u0XcAwJzw36CE,13015
75
- execsql/metacommands/dispatch.py,sha256=DvpgpkX1yMWnJrVW8ir_o1xKgfZgXzlTt6IwP0Lwoek,87441
74
+ execsql/metacommands/dispatch.py,sha256=t7x0xWHJA0PeCrYf7jYeSMJgf0yxcZ_xh-_YAtBPHLw,87192
76
75
  execsql/metacommands/io.py,sha256=vlGBje5sgnqeilooMdhJDgSRIhysHy5_7LrKtik9Xjs,3011
77
76
  execsql/metacommands/io_export.py,sha256=-7VDtUUQegwGRantw-NpdkI_9hwIKU-36ZvReIYC2QA,14285
78
77
  execsql/metacommands/io_fileops.py,sha256=6yED22UlVNXcRHNxZTgna8HmwFcR8s4nt6epMGLMtHY,10139
79
78
  execsql/metacommands/io_import.py,sha256=kUHIiI16WMuxXpqDMIRBc06KvI-_sYuYfJVNMTKOoAo,15706
80
79
  execsql/metacommands/io_write.py,sha256=RqOklmeGifu3lQgi_0glOoBWTY9FFIDlWIpOpb_hc3o,12789
81
80
  execsql/metacommands/prompt.py,sha256=LUl7doqKx8fLR0qt2lXbhdgNpdF1s6KZ6Ruitt10YnM,37483
82
- execsql/metacommands/script_ext.py,sha256=NShl29U9eI75_aL9WTSx8m4GjhCvMgJehzlES5M36cY,3693
81
+ execsql/metacommands/script_ext.py,sha256=CsUbyq8QFPnnvfX_k7fPlYNvvRw1xLmAWER4Exo5pwc,3658
83
82
  execsql/metacommands/system.py,sha256=5ETiWbkVDGsdwTldDaMM4rwY_Smd02wCWUROtf6XH_M,7385
84
83
  execsql/metacommands/upsert.py,sha256=wzoMpM8g49pEvU9GkHZ62fPvqV3w1UIUfxVA7HAsS_o,20317
85
84
  execsql/script/__init__.py,sha256=eGJPBDWj42aaId2lX_quSrqoKrvGwGElIrGDNCyoV1Y,3547
86
85
  execsql/script/ast.py,sha256=TQ4_7Lfw1F8_k6ycdvMZdzwNafrZiljSrthVRWUsuIk,20585
87
86
  execsql/script/control.py,sha256=WqLy-HLPqHG3vEzYpKMiIJsD7LpORjyQuUWzFzcGz4w,2327
88
- execsql/script/engine.py,sha256=woSTkoLOKaOpVqKzpAjPa0j7hs6ZpCFm172o6zJ_J3g,21363
89
- execsql/script/executor.py,sha256=UTQ4k8EjxH5CdKYZ0E_l-0WyQ-i769mUJw6cJmMvSxI,36018
90
- execsql/script/parser.py,sha256=K-mgwuQ729KdmimOpEmb0OBzMyOvX3gxhBKLgr5P4VA,33697
87
+ execsql/script/engine.py,sha256=52RmtQJGk4KWqXpZY7jfKeiPojAoULHWaigOcm1azm4,20979
88
+ execsql/script/executor.py,sha256=Y0con6Mz4n240krEVeVSqmeCLVvIXyF4aLlNBMyZQyc,35790
89
+ execsql/script/parser.py,sha256=z19gERLqyvEoDx4hDUws3Z8EKqTM7huNOitTOSmg8_0,35300
91
90
  execsql/script/variables.py,sha256=t0BwrRuA8m1LYHGLkDPNbqW6QmudXroOFYsO0fwK2N0,16302
92
91
  execsql/utils/__init__.py,sha256=0uR6JwVJQRX3vceByNBduCAf5dd5assKjeqJUWvpZoA,278
93
92
  execsql/utils/auth.py,sha256=dnLie8jFxN_l7ZrrRufVuxGw92iG62DIVatIjlEb4pM,8717
@@ -101,24 +100,24 @@ execsql/utils/numeric.py,sha256=xh02ANSRk3nUpQ-rtm66ILoMqoi7HtzCoRMIOT9U8QI,1570
101
100
  execsql/utils/regex.py,sha256=diEzTZqU_HHwVMadPAvN1Vgzhl7I03eVaEFGCXyGGL8,3770
102
101
  execsql/utils/strings.py,sha256=UQNjpRCEFa1UO6feU-M-9e24wWAvizs_iu_4fFusLxo,8516
103
102
  execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
104
- execsql2-2.18.0.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
105
- execsql2-2.18.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
106
- execsql2-2.18.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
107
- execsql2-2.18.0.data/data/execsql2_extras/execsql.conf,sha256=1a2g2Vga7s128wcu3ftIFRkHlKKtuvkuOHSD1XuNT7o,9404
108
- execsql2-2.18.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
109
- execsql2-2.18.0.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
110
- execsql2-2.18.0.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
111
- execsql2-2.18.0.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
112
- execsql2-2.18.0.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
113
- execsql2-2.18.0.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
114
- execsql2-2.18.0.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
115
- execsql2-2.18.0.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
116
- execsql2-2.18.0.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
117
- execsql2-2.18.0.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
118
- execsql2-2.18.0.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
119
- execsql2-2.18.0.dist-info/METADATA,sha256=4ww8XW7Ujq-834kd1N2PMyF8YRmlNsz6Bb5TC_c2SEw,21651
120
- execsql2-2.18.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
121
- execsql2-2.18.0.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
122
- execsql2-2.18.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
123
- execsql2-2.18.0.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
124
- execsql2-2.18.0.dist-info/RECORD,,
103
+ execsql2-2.19.0.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
104
+ execsql2-2.19.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
105
+ execsql2-2.19.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
106
+ execsql2-2.19.0.data/data/execsql2_extras/execsql.conf,sha256=Sq1Huwb_Uf_lI7zW7m3h11fqU6zjGpGnCU4OVJ_tCe0,10696
107
+ execsql2-2.19.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
108
+ execsql2-2.19.0.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
109
+ execsql2-2.19.0.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
110
+ execsql2-2.19.0.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
111
+ execsql2-2.19.0.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
112
+ execsql2-2.19.0.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
113
+ execsql2-2.19.0.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
114
+ execsql2-2.19.0.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
115
+ execsql2-2.19.0.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
116
+ execsql2-2.19.0.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
117
+ execsql2-2.19.0.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
118
+ execsql2-2.19.0.dist-info/METADATA,sha256=hafmsG77iDxgwzrG7s4oMg5o97R-fMx_BH513avRy2w,22340
119
+ execsql2-2.19.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
120
+ execsql2-2.19.0.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
121
+ execsql2-2.19.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
122
+ execsql2-2.19.0.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
123
+ execsql2-2.19.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.29.0
2
+ Generator: hatchling 1.30.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any