execsql2 2.23.1__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.
- execsql/cli/__init__.py +17 -586
- execsql/cli/application.py +309 -0
- execsql/cli/commands/__init__.py +13 -0
- execsql/cli/commands/config.py +205 -0
- execsql/cli/commands/format.py +100 -0
- execsql/cli/commands/init.py +135 -0
- execsql/cli/commands/lint.py +233 -0
- execsql/cli/commands/listing.py +81 -0
- execsql/cli/commands/run.py +451 -0
- execsql/cli/dispatch.py +66 -0
- execsql/cli/help.py +175 -24
- execsql/cli/lint.py +513 -76
- execsql/cli/options.py +108 -0
- execsql/cli/run.py +41 -14
- execsql/config.py +251 -154
- execsql/config_validation.py +282 -0
- execsql/data/execsql.conf.template +24 -1
- execsql/data/script_header.sql +23 -0
- execsql/db/factory.py +2 -2
- execsql/debug/repl.py +51 -193
- execsql/exceptions.py +9 -0
- execsql/format.py +87 -75
- execsql/interactive.py +232 -0
- execsql/manifest.py +189 -0
- execsql/metacommands/conditions.py +1 -1
- execsql/metacommands/control.py +2 -3
- execsql/metacommands/effects.py +124 -0
- execsql/script/__init__.py +0 -9
- execsql/script/ast.py +1 -1
- execsql/script/engine.py +6 -93
- execsql/script/executor.py +153 -34
- execsql/state.py +11 -2
- execsql/state.pyi +3 -0
- execsql/utils/color.py +18 -0
- execsql/utils/errors.py +11 -1
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/README.md +1 -1
- {execsql2-2.23.1.dist-info → execsql2-2.24.0.dist-info}/METADATA +111 -58
- {execsql2-2.23.1.dist-info → execsql2-2.24.0.dist-info}/RECORD +55 -39
- execsql2-2.24.0.dist-info/entry_points.txt +2 -0
- execsql2-2.23.1.dist-info/entry_points.txt +0 -3
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.24.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.23.1.dist-info → execsql2-2.24.0.dist-info}/WHEEL +0 -0
- {execsql2-2.23.1.dist-info → execsql2-2.24.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.23.1.dist-info → execsql2-2.24.0.dist-info}/licenses/NOTICE +0 -0
execsql/cli/options.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Options shared by more than one command, each defined once.
|
|
2
|
+
|
|
3
|
+
A command declares a shared option by annotating a parameter with one of the
|
|
4
|
+
types below, so ``run`` and ``ping`` cannot drift apart on a flag's spelling,
|
|
5
|
+
metavar or help text. Options that belong to a single command stay in that
|
|
6
|
+
command's module.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Annotated
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ConfigFileOpt",
|
|
18
|
+
"DbTypeOpt",
|
|
19
|
+
"DsnOpt",
|
|
20
|
+
"NoPasswdOpt",
|
|
21
|
+
"OutputFormat",
|
|
22
|
+
"OutputFormatOpt",
|
|
23
|
+
"PortOpt",
|
|
24
|
+
"ScriptEncodingOpt",
|
|
25
|
+
"UserOpt",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
# -- Connection (run, ping) ----------------------------------------------------
|
|
29
|
+
|
|
30
|
+
DbTypeOpt = Annotated[
|
|
31
|
+
str | None,
|
|
32
|
+
typer.Option(
|
|
33
|
+
"-t",
|
|
34
|
+
"--type",
|
|
35
|
+
metavar="{a,d,p,s,l,m,k,o,f}",
|
|
36
|
+
help=(
|
|
37
|
+
"Database type: a=MS-Access, p=PostgreSQL, "
|
|
38
|
+
"s=SQL Server, l=SQLite, m=MySQL/MariaDB, "
|
|
39
|
+
"k=DuckDB, o=Oracle, f=Firebird, "
|
|
40
|
+
"d=DSN."
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
DsnOpt = Annotated[
|
|
46
|
+
str | None,
|
|
47
|
+
typer.Option(
|
|
48
|
+
"--dsn",
|
|
49
|
+
"--connection-string",
|
|
50
|
+
metavar="URL",
|
|
51
|
+
help=(
|
|
52
|
+
"Database connection URL, e.g. postgresql://user:pass@host:5432/db. "
|
|
53
|
+
"Supported schemes: postgresql, mysql, mssql, oracle, firebird, sqlite, duckdb. "
|
|
54
|
+
"Overrides -t/-u/-p and positional server/db args."
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
UserOpt = Annotated[str | None, typer.Option("-u", "--user", help="Database user name.")]
|
|
60
|
+
|
|
61
|
+
PortOpt = Annotated[int | None, typer.Option("-p", "--port", help="Database server port.")]
|
|
62
|
+
|
|
63
|
+
NoPasswdOpt = Annotated[
|
|
64
|
+
bool,
|
|
65
|
+
typer.Option("-w", "--no-passwd", help="Skip password prompt when user is specified."),
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
# -- Configuration (run, ping, config) -----------------------------------------
|
|
69
|
+
|
|
70
|
+
ConfigFileOpt = Annotated[
|
|
71
|
+
str | None,
|
|
72
|
+
typer.Option(
|
|
73
|
+
"--config",
|
|
74
|
+
metavar="FILE",
|
|
75
|
+
help=(
|
|
76
|
+
"Path to an execsql configuration file. "
|
|
77
|
+
"Loaded after the implicit search paths so its values take precedence. "
|
|
78
|
+
"The file may chain additional configs via its [config] section."
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
# -- Script files (run, format, lint) ------------------------------------------
|
|
84
|
+
|
|
85
|
+
ScriptEncodingOpt = Annotated[
|
|
86
|
+
str | None,
|
|
87
|
+
typer.Option(
|
|
88
|
+
"-f",
|
|
89
|
+
"--script-encoding",
|
|
90
|
+
metavar="NAME",
|
|
91
|
+
help="Character encoding of the script file. Default: [encoding] script from a config file, else UTF-8.",
|
|
92
|
+
),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
# -- Machine-readable output (ping, config, list) ------------------------------
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class OutputFormat(str, Enum):
|
|
99
|
+
"""Output formats for commands that report data rather than check files."""
|
|
100
|
+
|
|
101
|
+
text = "text"
|
|
102
|
+
json = "json"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
OutputFormatOpt = Annotated[
|
|
106
|
+
OutputFormat,
|
|
107
|
+
typer.Option("--output-format", help="text for people; json for tools."),
|
|
108
|
+
]
|
execsql/cli/run.py
CHANGED
|
@@ -33,7 +33,6 @@ __all__ = ["_connect_initial_db", "_ping_db", "_print_dry_run", "_print_profile"
|
|
|
33
33
|
|
|
34
34
|
# Lint helper — imported lazily inside _run() to keep start-up cost low, but
|
|
35
35
|
# re-exported here so that tests and callers can reach it via cli.run.
|
|
36
|
-
from execsql.cli.lint import _print_lint_results # noqa: F401 — re-export (used by cli/__init__.py)
|
|
37
36
|
|
|
38
37
|
|
|
39
38
|
# ---------------------------------------------------------------------------
|
|
@@ -289,6 +288,21 @@ def _load_config(
|
|
|
289
288
|
return ConfigData(script_path, subvars, config_file=config_file)
|
|
290
289
|
|
|
291
290
|
|
|
291
|
+
def _tool_config(config_file: str | None) -> ConfigData:
|
|
292
|
+
"""The config ``execsql format`` and ``execsql lint`` read.
|
|
293
|
+
|
|
294
|
+
Read once per invocation, from the system, user and working-directory
|
|
295
|
+
locations plus *config_file* — not per script, so a script directory's
|
|
296
|
+
own ``execsql.conf`` applies only when passed with ``--config``.
|
|
297
|
+
"""
|
|
298
|
+
return _load_config(None, _seed_early_subvars(), config_file)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _configured_script_encoding(conf: ConfigData) -> str | None:
|
|
302
|
+
"""``[encoding] script`` from *conf*, or ``None`` when no config file sets it."""
|
|
303
|
+
return conf.script_encoding if "script_encoding" in conf.sources else None
|
|
304
|
+
|
|
305
|
+
|
|
292
306
|
def _seed_script_subvars(subvars: SubVarSet, script_name: str | None) -> None:
|
|
293
307
|
"""Add substitution variables that depend on the script path."""
|
|
294
308
|
from execsql.utils.errors import file_size_date
|
|
@@ -492,8 +506,9 @@ def _setup_logging(
|
|
|
492
506
|
use_gui: str | None,
|
|
493
507
|
no_passwd: bool,
|
|
494
508
|
import_buffer: int | None,
|
|
509
|
+
named_vars: list[tuple[str, str]] | None = None,
|
|
495
510
|
) -> Logger:
|
|
496
|
-
"""Create the execution logger, log initial info, and seed ``$RUN_ID
|
|
511
|
+
"""Create the execution logger, log initial info, and seed ``$RUN_ID``, ``-a`` and ``--var`` values."""
|
|
497
512
|
from execsql.utils.errors import file_size_date
|
|
498
513
|
|
|
499
514
|
opts_dict = {
|
|
@@ -550,6 +565,13 @@ def _setup_logging(
|
|
|
550
565
|
logger.log_status_info(
|
|
551
566
|
f"Command-line substitution variable assignment: {var} set to {{***}}",
|
|
552
567
|
)
|
|
568
|
+
# --var values: named, set after config files so they win over
|
|
569
|
+
# [variables]; a SUB in the script can still reassign them. Values are
|
|
570
|
+
# redacted for the same reason as -a's.
|
|
571
|
+
for name, value in named_vars or ():
|
|
572
|
+
subvars.add_substitution(name, value)
|
|
573
|
+
logger.add_redaction_value(value)
|
|
574
|
+
logger.log_status_info(f"Command-line substitution variable assignment: {name} set to {{***}}")
|
|
553
575
|
|
|
554
576
|
return logger
|
|
555
577
|
|
|
@@ -587,12 +609,13 @@ def _run(
|
|
|
587
609
|
profile: bool = False,
|
|
588
610
|
profile_limit: int = 20,
|
|
589
611
|
ping: bool = False,
|
|
590
|
-
lint: bool = False,
|
|
591
612
|
debug: bool = False,
|
|
592
613
|
no_system_cmd: bool = False,
|
|
593
614
|
no_rm_file: bool = False,
|
|
594
615
|
no_serve: bool = False,
|
|
595
616
|
config_file: str | None = None,
|
|
617
|
+
named_vars: list[tuple[str, str]] | None = None,
|
|
618
|
+
manifest_path: str | None = None,
|
|
596
619
|
) -> None:
|
|
597
620
|
"""Initialise state, connect to the database, load the script, and run it.
|
|
598
621
|
|
|
@@ -605,12 +628,6 @@ def _run(
|
|
|
605
628
|
connection details (DBMS name, server version, and location), and calls
|
|
606
629
|
:func:`_ping_db` which raises ``SystemExit(0)``. No script is loaded or
|
|
607
630
|
executed. *script_name* and *command* may both be ``None`` in ping mode.
|
|
608
|
-
|
|
609
|
-
When *lint* is ``True``, the script is parsed and statically analysed for
|
|
610
|
-
structural issues (unmatched IF/ENDIF/LOOP/BATCH blocks, potentially
|
|
611
|
-
undefined variables, missing INCLUDE files, empty scripts) without
|
|
612
|
-
connecting to a database or executing anything. Exits with code 0 if no
|
|
613
|
-
errors were found, or code 1 if errors were found.
|
|
614
631
|
"""
|
|
615
632
|
import execsql.state as _state
|
|
616
633
|
|
|
@@ -698,6 +715,8 @@ def _run(
|
|
|
698
715
|
if sub_vars:
|
|
699
716
|
for n, repl in enumerate(sub_vars):
|
|
700
717
|
_state.subvars.add_substitution(f"$ARG_{n + 1}", repl)
|
|
718
|
+
for name, value in named_vars or ():
|
|
719
|
+
_state.subvars.add_substitution(name, value)
|
|
701
720
|
_ast_tree = _load_script(command, script_name, conf.script_encoding)
|
|
702
721
|
_print_dry_run(_ast_tree)
|
|
703
722
|
raise SystemExit(0)
|
|
@@ -713,6 +732,14 @@ def _run(
|
|
|
713
732
|
_state.dbs.add("initial", db)
|
|
714
733
|
_ping_db(db) # raises SystemExit
|
|
715
734
|
|
|
735
|
+
manifest = None
|
|
736
|
+
if manifest_path:
|
|
737
|
+
from execsql import manifest as _manifest
|
|
738
|
+
|
|
739
|
+
variables = [f"$ARG_{n + 1}" for n in range(len(sub_vars or ()))] + [name for name, _ in named_vars or ()]
|
|
740
|
+
manifest = _manifest.start(manifest_path, script_name, variables)
|
|
741
|
+
manifest.config_files = list(conf.files_read)
|
|
742
|
+
|
|
716
743
|
import execsql.utils.fileio as _fileio
|
|
717
744
|
|
|
718
745
|
if _state.filewriter is None or not _state.filewriter.is_alive():
|
|
@@ -758,6 +785,7 @@ def _run(
|
|
|
758
785
|
use_gui=use_gui,
|
|
759
786
|
no_passwd=no_passwd,
|
|
760
787
|
import_buffer=import_buffer,
|
|
788
|
+
named_vars=named_vars,
|
|
761
789
|
)
|
|
762
790
|
|
|
763
791
|
# ------------------------------------------------------------------
|
|
@@ -765,11 +793,6 @@ def _run(
|
|
|
765
793
|
# ------------------------------------------------------------------
|
|
766
794
|
_ast_tree = _load_script(command, script_name, conf.script_encoding)
|
|
767
795
|
|
|
768
|
-
# ------------------------------------------------------------------
|
|
769
|
-
# NOTE: --lint is handled as an early exit in cli/__init__.py (AST
|
|
770
|
-
# linter) before _run() is called. No lint code path here.
|
|
771
|
-
# ------------------------------------------------------------------
|
|
772
|
-
|
|
773
796
|
# ------------------------------------------------------------------
|
|
774
797
|
# Start GUI console if requested
|
|
775
798
|
# ------------------------------------------------------------------
|
|
@@ -794,6 +817,8 @@ def _run(
|
|
|
794
817
|
_state.dbs.add("initial", db)
|
|
795
818
|
|
|
796
819
|
_state.exec_log.log_db_connect(db)
|
|
820
|
+
if manifest is not None:
|
|
821
|
+
manifest.set_database(db)
|
|
797
822
|
_state.subvars.add_substitution("$CURRENT_DBMS", db.type.dbms_id)
|
|
798
823
|
_state.subvars.add_substitution("$CURRENT_DATABASE", db.name())
|
|
799
824
|
_state.subvars.add_substitution("$DB_SERVER", db.server_name)
|
|
@@ -822,6 +847,8 @@ def _run(
|
|
|
822
847
|
|
|
823
848
|
if _ast_tree is not None:
|
|
824
849
|
_execute_script_ast(_ast_tree, conf, profile=profile, profile_limit=profile_limit)
|
|
850
|
+
if manifest is not None:
|
|
851
|
+
manifest.finish(0)
|
|
825
852
|
|
|
826
853
|
|
|
827
854
|
# ---------------------------------------------------------------------------
|