execsql2 2.23.0__py3-none-any.whl → 2.24.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 (63) hide show
  1. execsql/cli/__init__.py +17 -586
  2. execsql/cli/application.py +309 -0
  3. execsql/cli/commands/__init__.py +13 -0
  4. execsql/cli/commands/config.py +205 -0
  5. execsql/cli/commands/format.py +100 -0
  6. execsql/cli/commands/init.py +135 -0
  7. execsql/cli/commands/lint.py +233 -0
  8. execsql/cli/commands/listing.py +81 -0
  9. execsql/cli/commands/run.py +451 -0
  10. execsql/cli/dispatch.py +66 -0
  11. execsql/cli/help.py +175 -24
  12. execsql/cli/lint.py +513 -76
  13. execsql/cli/options.py +108 -0
  14. execsql/cli/run.py +41 -14
  15. execsql/config.py +251 -154
  16. execsql/config_validation.py +282 -0
  17. execsql/data/execsql.conf.template +24 -1
  18. execsql/data/script_header.sql +23 -0
  19. execsql/db/access.py +22 -6
  20. execsql/db/base.py +105 -1
  21. execsql/db/dsn.py +2 -0
  22. execsql/db/duckdb.py +95 -2
  23. execsql/db/factory.py +2 -2
  24. execsql/db/mysql.py +11 -0
  25. execsql/db/oracle.py +12 -4
  26. execsql/db/sqlite.py +16 -0
  27. execsql/debug/repl.py +51 -193
  28. execsql/exceptions.py +9 -0
  29. execsql/format.py +87 -75
  30. execsql/interactive.py +232 -0
  31. execsql/manifest.py +189 -0
  32. execsql/metacommands/conditions.py +1 -1
  33. execsql/metacommands/control.py +2 -3
  34. execsql/metacommands/effects.py +124 -0
  35. execsql/script/__init__.py +0 -9
  36. execsql/script/ast.py +1 -1
  37. execsql/script/engine.py +6 -93
  38. execsql/script/executor.py +153 -34
  39. execsql/state.py +11 -2
  40. execsql/state.pyi +3 -0
  41. execsql/utils/color.py +18 -0
  42. execsql/utils/errors.py +11 -1
  43. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/README.md +1 -1
  44. {execsql2-2.23.0.dist-info → execsql2-2.24.0.dist-info}/METADATA +111 -58
  45. {execsql2-2.23.0.dist-info → execsql2-2.24.0.dist-info}/RECORD +62 -46
  46. execsql2-2.24.0.dist-info/entry_points.txt +2 -0
  47. execsql2-2.23.0.dist-info/entry_points.txt +0 -3
  48. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  49. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  50. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
  51. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/md_compare.sql +0 -0
  52. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
  53. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
  54. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
  55. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  56. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  57. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/script_template.sql +0 -0
  58. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
  59. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  60. {execsql2-2.23.0.data → execsql2-2.24.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  61. {execsql2-2.23.0.dist-info → execsql2-2.24.0.dist-info}/WHEEL +0 -0
  62. {execsql2-2.23.0.dist-info → execsql2-2.24.0.dist-info}/licenses/LICENSE.txt +0 -0
  63. {execsql2-2.23.0.dist-info → execsql2-2.24.0.dist-info}/licenses/NOTICE +0 -0
execsql/cli/help.py CHANGED
@@ -1,4 +1,4 @@
1
- """Rich-formatted help output for the execsql CLI.
1
+ """Rich tables and console objects for the execsql CLI.
2
2
 
3
3
  Contains the metacommand reference table, encoding list, and the shared
4
4
  ``Console`` instances used by the other CLI submodules.
@@ -7,11 +7,26 @@ Contains the metacommand reference table, encoding list, and the shared
7
7
  from __future__ import annotations
8
8
 
9
9
  from encodings.aliases import aliases as codec_dict
10
+ from typing import Any
10
11
 
11
12
  from rich.console import Console
12
13
  from rich.table import Table
13
14
 
14
- __all__ = ["_console", "_err_console", "_init_config", "_print_encodings", "_print_metacommands"]
15
+ __all__ = [
16
+ "_console",
17
+ "_encoding_names",
18
+ "_err_console",
19
+ "_init_config",
20
+ "_init_config_text",
21
+ "_keywords_data",
22
+ "_metacommand_rows",
23
+ "_plugins_data",
24
+ "_print_encodings",
25
+ "_print_keywords_json",
26
+ "_print_keywords_text",
27
+ "_print_metacommands",
28
+ "_print_plugins",
29
+ ]
15
30
 
16
31
  _console = Console()
17
32
  _err_console = Console(stderr=True)
@@ -77,35 +92,29 @@ _SKIP_FROM_DISPATCH = {
77
92
  }
78
93
 
79
94
 
95
+ def _init_config_text() -> str:
96
+ """The default execsql.conf template, every option commented out and documented."""
97
+ import importlib.resources
98
+
99
+ return importlib.resources.files("execsql.data").joinpath("execsql.conf.template").read_text(encoding="utf-8")
100
+
101
+
80
102
  def _init_config() -> None:
81
103
  """Print the default execsql.conf template to stdout."""
82
- import importlib.resources
83
104
  import sys
84
105
 
85
- template = importlib.resources.files("execsql.data").joinpath("execsql.conf.template").read_text(encoding="utf-8")
86
- sys.stdout.write(template)
106
+ sys.stdout.write(_init_config_text())
87
107
 
88
108
 
89
- def _print_metacommands() -> None:
90
- """Print the metacommands table using Rich.
109
+ def _metacommand_rows() -> list[tuple[str, str]]:
110
+ """``(name, syntax)`` for every metacommand, in the order they are listed.
91
111
 
92
112
  Keyword list is derived from the dispatch table; syntax hints come from
93
- the ``_SYNTAX`` dict above. Keywords not in ``_SYNTAX`` are shown without
94
- a syntax column.
113
+ the ``_SYNTAX`` dict above. Keywords not in ``_SYNTAX`` get an empty
114
+ syntax hint.
95
115
  """
96
116
  from execsql.metacommands import DISPATCH_TABLE
97
117
 
98
- table = Table(
99
- title="execsql Metacommands",
100
- caption="Embed in SQL comment lines following the [bold]!x![/bold] token.",
101
- show_header=True,
102
- header_style="bold cyan",
103
- border_style="dim",
104
- expand=False,
105
- )
106
- table.add_column("Metacommand", style="bold green", no_wrap=True)
107
- table.add_column("Syntax", style="white")
108
-
109
118
  # Collect unique keyword names from the dispatch table.
110
119
  seen: set[str] = set()
111
120
  keywords: list[str] = []
@@ -119,21 +128,42 @@ def _print_metacommands() -> None:
119
128
  seen.add(extra)
120
129
  keywords.append(extra)
121
130
 
131
+ rows: list[tuple[str, str]] = []
122
132
  for kw in sorted(keywords):
123
133
  if kw in _SYNTAX:
124
- name, syntax = _SYNTAX[kw]
125
- table.add_row(name, syntax)
134
+ rows.append(_SYNTAX[kw])
126
135
  elif kw.startswith("CONFIG ") or kw.startswith("CONSOLE_") or "_" in kw:
127
136
  continue # skip config options / internal entries
128
137
  else:
129
- table.add_row(kw, "")
138
+ rows.append((kw, ""))
139
+ return rows
130
140
 
141
+
142
+ def _print_metacommands() -> None:
143
+ """Print the metacommands table using Rich."""
144
+ table = Table(
145
+ title="execsql Metacommands",
146
+ caption="Embed in SQL comment lines following the [bold]!x![/bold] token.",
147
+ show_header=True,
148
+ header_style="bold cyan",
149
+ border_style="dim",
150
+ expand=False,
151
+ )
152
+ table.add_column("Metacommand", style="bold green", no_wrap=True)
153
+ table.add_column("Syntax", style="white")
154
+ for name, syntax in _metacommand_rows():
155
+ table.add_row(name, syntax)
131
156
  _console.print(table)
132
157
 
133
158
 
159
+ def _encoding_names() -> list[str]:
160
+ """Every encoding name Python accepts, sorted."""
161
+ return sorted(codec_dict.keys())
162
+
163
+
134
164
  def _print_encodings() -> None:
135
165
  """Print available encodings using Rich."""
136
- enc = sorted(codec_dict.keys())
166
+ enc = _encoding_names()
137
167
  table = Table(
138
168
  title="Available Encodings",
139
169
  show_header=False,
@@ -149,3 +179,124 @@ def _print_encodings() -> None:
149
179
  row.append("")
150
180
  table.add_row(*row)
151
181
  _console.print(table)
182
+
183
+
184
+ def _keywords_data() -> dict[str, Any]:
185
+ """The full keyword vocabulary: the data behind ``execsql list keywords``.
186
+
187
+ The VS Code grammar build and ``tests/test_registry.py`` read its JSON
188
+ form, so the shape of this dict is a contract.
189
+ """
190
+ from execsql.metacommands import (
191
+ ALL_EXPORT_FORMATS,
192
+ DATABASE_TYPES,
193
+ DISPATCH_TABLE,
194
+ JSON_VARIANT_FORMATS,
195
+ METADATA_FORMATS,
196
+ QUERY_EXPORT_FORMATS,
197
+ SERVE_FORMATS,
198
+ TABLE_EXPORT_FORMATS,
199
+ )
200
+ from execsql.metacommands.conditions import CONDITIONAL_TABLE
201
+
202
+ mc_kw = DISPATCH_TABLE.keywords_by_category()
203
+ cond_kw = CONDITIONAL_TABLE.keywords_by_category()
204
+
205
+ data = {
206
+ "metacommands": {
207
+ "control": sorted(mc_kw.get("control", [])),
208
+ "block": sorted(
209
+ mc_kw.get("block", []) + ["BEGIN SCRIPT", "END SCRIPT", "BEGIN SQL", "END SQL"],
210
+ ),
211
+ "action": sorted(mc_kw.get("action", [])),
212
+ "config": sorted(mc_kw.get("config", [])),
213
+ "prompt": sorted(mc_kw.get("prompt", [])),
214
+ },
215
+ "conditions": sorted(cond_kw.get("condition", []) + ["IS_FALSE", "NOT", "OR"]),
216
+ "config_options": sorted(mc_kw.get("config_option", [])),
217
+ "export_formats": {
218
+ "query": sorted(QUERY_EXPORT_FORMATS),
219
+ "table": sorted(TABLE_EXPORT_FORMATS),
220
+ "serve": sorted(SERVE_FORMATS),
221
+ "metadata": sorted(METADATA_FORMATS),
222
+ "json_variants": sorted(JSON_VARIANT_FORMATS),
223
+ "all": sorted(ALL_EXPORT_FORMATS),
224
+ },
225
+ "database_types": sorted(DATABASE_TYPES),
226
+ "variable_patterns": {
227
+ "system": "!!$name!!",
228
+ "environment": "!!&name!!",
229
+ "parameter": "!!#name!!",
230
+ "column": "!!@name!!",
231
+ "local": "!!~name!!",
232
+ "local_alt": "!!+name!!",
233
+ "regular": "!!name!!",
234
+ "deferred": "!{name}!",
235
+ },
236
+ }
237
+ return data
238
+
239
+
240
+ def _print_keywords_json() -> None:
241
+ """Print :func:`_keywords_data` as JSON: ``list keywords --output-format json``, and ``--dump-keywords`` as it always has."""
242
+ import json
243
+
244
+ _console.print_json(json.dumps(_keywords_data(), indent=2))
245
+
246
+
247
+ def _print_keywords_text() -> None:
248
+ """Print :func:`_keywords_data` grouped under headings, for reading."""
249
+ import textwrap
250
+
251
+ data = _keywords_data()
252
+
253
+ def group(title: str, words: list[str]) -> None:
254
+ _console.print(f"[bold]{title}[/bold] ({len(words)})")
255
+ _console.print(textwrap.fill(", ".join(words), width=88, initial_indent=" ", subsequent_indent=" "))
256
+ _console.print()
257
+
258
+ for category, words in data["metacommands"].items():
259
+ group(f"Metacommands: {category}", words)
260
+ group("Conditions", data["conditions"])
261
+ group("CONFIG options", data["config_options"])
262
+ group("Export formats", data["export_formats"]["all"])
263
+ group("Database types", data["database_types"])
264
+ _console.print("[bold]Variable patterns[/bold]")
265
+ for name, pattern in data["variable_patterns"].items():
266
+ _console.print(f" {pattern:<12} {name}")
267
+
268
+
269
+ def _plugins_data() -> dict[str, list[str]]:
270
+ """Names of the installed plugins, by kind."""
271
+ from execsql.plugins import (
272
+ EXPORTER_GROUP,
273
+ IMPORTER_GROUP,
274
+ METACOMMAND_GROUP,
275
+ _load_entry_points,
276
+ )
277
+
278
+ return {
279
+ "metacommands": [name for name, _ in _load_entry_points(METACOMMAND_GROUP)],
280
+ "exporters": [name for name, _ in _load_entry_points(EXPORTER_GROUP)],
281
+ "importers": [name for name, _ in _load_entry_points(IMPORTER_GROUP)],
282
+ }
283
+
284
+
285
+ def _print_plugins() -> None:
286
+ """Print the installed plugins, or how to write one when there are none."""
287
+ plugins = _plugins_data()
288
+ _console.print("\n[bold cyan]Installed plugins:[/bold cyan]\n")
289
+ if not any(plugins.values()):
290
+ _console.print(" [dim]No plugins found.[/dim]")
291
+ _console.print()
292
+ _console.print(
293
+ " Plugins are discovered via Python entry points.\n"
294
+ " See the execsql documentation for how to create plugins.",
295
+ )
296
+ else:
297
+ for kind, names in plugins.items():
298
+ if names:
299
+ _console.print(f" [bold]{kind.capitalize()}[/bold] ({len(names)}):")
300
+ for name in names:
301
+ _console.print(f" - {name}")
302
+ _console.print()