execsql2 2.4.5__py3-none-any.whl → 2.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- execsql/cli/__init__.py +14 -0
- execsql/cli/dsn.py +2 -0
- execsql/cli/help.py +2 -0
- execsql/cli/run.py +4 -2
- execsql/constants.py +11 -0
- execsql/db/access.py +20 -0
- execsql/db/base.py +4 -0
- execsql/db/dsn.py +11 -8
- execsql/db/duckdb.py +12 -8
- execsql/db/firebird.py +17 -8
- execsql/db/mysql.py +13 -8
- execsql/db/oracle.py +22 -8
- execsql/db/postgres.py +21 -9
- execsql/db/sqlite.py +18 -8
- execsql/db/sqlserver.py +14 -8
- execsql/exporters/__init__.py +6 -1
- execsql/exporters/base.py +2 -0
- execsql/exporters/delimited.py +10 -0
- execsql/exporters/protocol.py +128 -0
- execsql/exporters/xls.py +8 -0
- execsql/format.py +3 -1
- execsql/gui/__init__.py +2 -0
- execsql/gui/base.py +2 -0
- execsql/gui/console.py +2 -0
- execsql/gui/desktop.py +1 -0
- execsql/gui/tui.py +134 -0
- execsql/importers/base.py +1 -0
- execsql/importers/csv.py +2 -0
- execsql/importers/feather.py +2 -0
- execsql/importers/ods.py +1 -0
- execsql/importers/xls.py +1 -0
- execsql/metacommands/__init__.py +386 -180
- execsql/metacommands/dispatch.py +2 -0
- execsql/metacommands/io.py +41 -0
- execsql/models.py +17 -0
- execsql/parser.py +41 -0
- execsql/script/control.py +2 -0
- execsql/script/engine.py +19 -0
- execsql/script/variables.py +9 -5
- execsql/state.py +312 -199
- execsql/types.py +46 -0
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/METADATA +2 -2
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/RECORD +62 -61
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.4.5.data → execsql2-2.6.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/WHEEL +0 -0
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.4.5.dist-info → execsql2-2.6.0.dist-info}/licenses/NOTICE +0 -0
execsql/state.py
CHANGED
|
@@ -12,27 +12,25 @@ and then access attributes (``_state.conf``, ``_state.subvars``, etc.)
|
|
|
12
12
|
inside function/method bodies — never at class-definition time — to avoid
|
|
13
13
|
circular-import issues at load time.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
``
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
``endloop()`` (finalise a compiled loop).
|
|
30
|
-
|
|
31
|
-
All re-exports have been removed. Each module now imports directly from
|
|
32
|
-
its source module rather than accessing names via ``_state``.
|
|
15
|
+
Internally, all mutable state lives on a :class:`RuntimeContext` instance
|
|
16
|
+
(``_ctx``). The module's ``__class__`` is swapped to a custom
|
|
17
|
+
:class:`types.ModuleType` subclass that transparently proxies attribute
|
|
18
|
+
reads and writes to the active context. This means:
|
|
19
|
+
|
|
20
|
+
- External code continues to use ``_state.conf``, ``_state.subvars = ...``,
|
|
21
|
+
etc. with zero changes.
|
|
22
|
+
- Functions *within* this module use ``_ctx.conf``, ``_ctx.subvars``, etc.
|
|
23
|
+
directly, because Python's ``LOAD_GLOBAL`` / ``STORE_GLOBAL`` bytecodes
|
|
24
|
+
access ``__dict__`` directly and do not trigger ``__getattr__`` /
|
|
25
|
+
``__setattr__`` on the module class.
|
|
26
|
+
|
|
27
|
+
Use :func:`get_context` / :func:`set_context` to obtain or replace the
|
|
28
|
+
active context programmatically.
|
|
33
29
|
"""
|
|
34
30
|
|
|
35
31
|
import re
|
|
32
|
+
import sys
|
|
33
|
+
import types
|
|
36
34
|
from typing import TYPE_CHECKING, Any
|
|
37
35
|
|
|
38
36
|
if TYPE_CHECKING:
|
|
@@ -55,154 +53,288 @@ if TYPE_CHECKING:
|
|
|
55
53
|
from execsql.utils.mail import MailSpec
|
|
56
54
|
from execsql.utils.timer import Timer
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
# Configuration / encoding
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
__all__ = [
|
|
57
|
+
# Configuration / encoding
|
|
58
|
+
"conf",
|
|
59
|
+
"logfile_encoding",
|
|
60
|
+
# Runtime state
|
|
61
|
+
"last_command",
|
|
62
|
+
"upass",
|
|
63
|
+
"varlike",
|
|
64
|
+
"err_halt_writespec",
|
|
65
|
+
"err_halt_email",
|
|
66
|
+
"err_halt_exec",
|
|
67
|
+
"cancel_halt_writespec",
|
|
68
|
+
"cancel_halt_mailspec",
|
|
69
|
+
"cancel_halt_exec",
|
|
70
|
+
"commandliststack",
|
|
71
|
+
"savedscripts",
|
|
72
|
+
"loopcommandstack",
|
|
73
|
+
"compiling_loop",
|
|
74
|
+
"endloop_rx",
|
|
75
|
+
"loop_rx",
|
|
76
|
+
"loop_nest_level",
|
|
77
|
+
"cmds_run",
|
|
78
|
+
"defer_rx",
|
|
79
|
+
"stringtypes",
|
|
80
|
+
"exec_log",
|
|
81
|
+
"subvars",
|
|
82
|
+
"status",
|
|
83
|
+
# Lazy singletons
|
|
84
|
+
"if_stack",
|
|
85
|
+
"counters",
|
|
86
|
+
"timer",
|
|
87
|
+
"output",
|
|
88
|
+
"dbs",
|
|
89
|
+
"tempfiles",
|
|
90
|
+
"export_metadata",
|
|
91
|
+
"metacommandlist",
|
|
92
|
+
"conditionallist",
|
|
93
|
+
"filewriter",
|
|
94
|
+
"gui_console",
|
|
95
|
+
"gui_manager_queue",
|
|
96
|
+
"gui_manager_thread",
|
|
97
|
+
# Version
|
|
98
|
+
"primary_vno",
|
|
99
|
+
"secondary_vno",
|
|
100
|
+
"tertiary_vno",
|
|
101
|
+
# Functions
|
|
102
|
+
"xcmd_test",
|
|
103
|
+
"endloop",
|
|
104
|
+
"reset",
|
|
105
|
+
"initialize",
|
|
106
|
+
# New public API
|
|
107
|
+
"RuntimeContext",
|
|
108
|
+
"get_context",
|
|
109
|
+
"set_context",
|
|
110
|
+
]
|
|
67
111
|
|
|
68
112
|
# ---------------------------------------------------------------------------
|
|
69
|
-
#
|
|
113
|
+
# Compile-time constants — immutable after module load, stay in __dict__
|
|
70
114
|
# ---------------------------------------------------------------------------
|
|
71
115
|
|
|
72
|
-
# The last command run. This should be a ScriptCmd object.
|
|
73
|
-
last_command: ScriptCmd | None = None
|
|
74
|
-
|
|
75
|
-
# The last user password entered via 'get_password()'
|
|
76
|
-
upass: str | None = None
|
|
77
|
-
|
|
78
116
|
# A compiled regex to match prefixed regular expressions, used to check
|
|
79
117
|
# for unsubstituted variables.
|
|
80
118
|
varlike = re.compile(r"!![$@&~#]?\w+!!", re.I)
|
|
81
119
|
|
|
82
|
-
# A WriteSpec object for messages to be written when the program halts due to an error.
|
|
83
|
-
err_halt_writespec: WriteSpec | None = None
|
|
84
|
-
|
|
85
|
-
# A MailSpec object for email to be sent when the program halts due to an error.
|
|
86
|
-
err_halt_email: MailSpec | None = None
|
|
87
|
-
|
|
88
|
-
# A ScriptExecSpec object for a script to be executed when the program halts due to an error.
|
|
89
|
-
err_halt_exec: ScriptExecSpec | None = None
|
|
90
|
-
|
|
91
|
-
# A WriteSpec object for messages to be written when the program halts due to user cancellation.
|
|
92
|
-
cancel_halt_writespec: WriteSpec | None = None
|
|
93
|
-
|
|
94
|
-
# A MailSpec object for email to be sent when the program halts due to user cancellation.
|
|
95
|
-
cancel_halt_mailspec: MailSpec | None = None
|
|
96
|
-
|
|
97
|
-
# A ScriptExecSpec object for a script to be executed when the program halts due to user cancellation.
|
|
98
|
-
cancel_halt_exec: ScriptExecSpec | None = None
|
|
99
|
-
|
|
100
|
-
# A stack of the CommandList objects currently in the queue to be executed.
|
|
101
|
-
commandliststack: list[CommandList] = []
|
|
102
|
-
|
|
103
|
-
# A dictionary of CommandList objects (ordinarily created by BEGIN/END SCRIPT metacommands).
|
|
104
|
-
savedscripts: dict[str, CommandList] = {}
|
|
105
|
-
|
|
106
|
-
# A stack of CommandList objects used when compiling the statements within a loop.
|
|
107
|
-
loopcommandstack: list[CommandList] = []
|
|
108
|
-
|
|
109
|
-
# A global flag to indicate that commands should be compiled into the topmost entry
|
|
110
|
-
# in the loopcommandstack rather than executed.
|
|
111
|
-
compiling_loop: bool = False
|
|
112
|
-
|
|
113
120
|
# Compiled regex for END LOOP metacommand, which is immediate.
|
|
114
121
|
endloop_rx = re.compile(r"^\s*END\s+LOOP\s*$", re.I)
|
|
115
122
|
|
|
116
|
-
# Compiled regex for *start of* LOOP metacommand, for testing while compiling
|
|
123
|
+
# Compiled regex for *start of* LOOP metacommand, for testing while compiling
|
|
124
|
+
# commands within a loop.
|
|
117
125
|
loop_rx = re.compile(r"\s*LOOP\s+", re.I)
|
|
118
126
|
|
|
119
|
-
# Nesting counter, to ensure loops are only ended when nesting level is zero.
|
|
120
|
-
loop_nest_level: int = 0
|
|
121
|
-
|
|
122
|
-
# A count of all of the commands run.
|
|
123
|
-
cmds_run: int = 0
|
|
124
|
-
|
|
125
127
|
# Pattern for deferred substitution, e.g.: "!{somevar}!"
|
|
126
128
|
defer_rx = re.compile(r"(!{([$@&~#]?[a-z0-9_]+)}!)", re.I)
|
|
127
129
|
|
|
128
130
|
# The string type (str in Python 3).
|
|
129
131
|
stringtypes: type = str
|
|
130
132
|
|
|
131
|
-
# The execution log object; set at startup.
|
|
132
|
-
exec_log: Logger | None = None
|
|
133
|
-
|
|
134
|
-
# The substitution variable set; set at startup.
|
|
135
|
-
subvars: SubVarSet | None = None
|
|
136
|
-
|
|
137
|
-
# The program execution status tracker; set at startup.
|
|
138
|
-
status: StatObj | None = None
|
|
139
|
-
|
|
140
133
|
# ---------------------------------------------------------------------------
|
|
141
|
-
#
|
|
134
|
+
# Version numbers (parsed from package __version__)
|
|
142
135
|
# ---------------------------------------------------------------------------
|
|
143
136
|
|
|
144
|
-
|
|
145
|
-
|
|
137
|
+
try:
|
|
138
|
+
from execsql import __version__ as _pkg_version
|
|
146
139
|
|
|
147
|
-
|
|
148
|
-
|
|
140
|
+
_vparts = _pkg_version.split(".")
|
|
141
|
+
primary_vno: int = int(_vparts[0]) if len(_vparts) > 0 else 0
|
|
142
|
+
secondary_vno: int = int(_vparts[1]) if len(_vparts) > 1 else 0
|
|
143
|
+
tertiary_vno: int = int(_vparts[2]) if len(_vparts) > 2 else 0
|
|
144
|
+
except Exception:
|
|
145
|
+
primary_vno = 0
|
|
146
|
+
secondary_vno = 0
|
|
147
|
+
tertiary_vno = 0
|
|
149
148
|
|
|
150
|
-
# Elapsed-time tracker (Timer instance).
|
|
151
|
-
timer: Timer | None = None
|
|
152
149
|
|
|
153
|
-
#
|
|
154
|
-
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# RuntimeContext — holds all mutable state for a single execsql session
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
155
153
|
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
_CONTEXT_ATTRS: frozenset[str] = frozenset(
|
|
155
|
+
{
|
|
156
|
+
# Configuration
|
|
157
|
+
"conf",
|
|
158
|
+
"logfile_encoding",
|
|
159
|
+
# Runtime flags
|
|
160
|
+
"last_command",
|
|
161
|
+
"upass",
|
|
162
|
+
"err_halt_writespec",
|
|
163
|
+
"err_halt_email",
|
|
164
|
+
"err_halt_exec",
|
|
165
|
+
"cancel_halt_writespec",
|
|
166
|
+
"cancel_halt_mailspec",
|
|
167
|
+
"cancel_halt_exec",
|
|
168
|
+
# Execution stack
|
|
169
|
+
"commandliststack",
|
|
170
|
+
"savedscripts",
|
|
171
|
+
"loopcommandstack",
|
|
172
|
+
"compiling_loop",
|
|
173
|
+
"loop_nest_level",
|
|
174
|
+
"cmds_run",
|
|
175
|
+
# I/O
|
|
176
|
+
"exec_log",
|
|
177
|
+
"subvars",
|
|
178
|
+
"status",
|
|
179
|
+
"output",
|
|
180
|
+
"filewriter",
|
|
181
|
+
# Lazy singletons
|
|
182
|
+
"if_stack",
|
|
183
|
+
"counters",
|
|
184
|
+
"timer",
|
|
185
|
+
"dbs",
|
|
186
|
+
"tempfiles",
|
|
187
|
+
"export_metadata",
|
|
188
|
+
"metacommandlist",
|
|
189
|
+
"conditionallist",
|
|
190
|
+
# GUI
|
|
191
|
+
"gui_console",
|
|
192
|
+
"gui_manager_queue",
|
|
193
|
+
"gui_manager_thread",
|
|
194
|
+
},
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class RuntimeContext:
|
|
199
|
+
"""All mutable runtime state for a single execsql execution session.
|
|
200
|
+
|
|
201
|
+
A fresh instance provides clean default values identical to the original
|
|
202
|
+
module-level declarations. Use :func:`get_context` to obtain the active
|
|
203
|
+
instance, or :func:`set_context` to replace it.
|
|
204
|
+
"""
|
|
158
205
|
|
|
159
|
-
|
|
160
|
-
|
|
206
|
+
__slots__ = (
|
|
207
|
+
# Configuration
|
|
208
|
+
"conf",
|
|
209
|
+
"logfile_encoding",
|
|
210
|
+
# Runtime flags
|
|
211
|
+
"last_command",
|
|
212
|
+
"upass",
|
|
213
|
+
"err_halt_writespec",
|
|
214
|
+
"err_halt_email",
|
|
215
|
+
"err_halt_exec",
|
|
216
|
+
"cancel_halt_writespec",
|
|
217
|
+
"cancel_halt_mailspec",
|
|
218
|
+
"cancel_halt_exec",
|
|
219
|
+
# Execution stack
|
|
220
|
+
"commandliststack",
|
|
221
|
+
"savedscripts",
|
|
222
|
+
"loopcommandstack",
|
|
223
|
+
"compiling_loop",
|
|
224
|
+
"loop_nest_level",
|
|
225
|
+
"cmds_run",
|
|
226
|
+
# I/O
|
|
227
|
+
"exec_log",
|
|
228
|
+
"subvars",
|
|
229
|
+
"status",
|
|
230
|
+
"output",
|
|
231
|
+
"filewriter",
|
|
232
|
+
# Lazy singletons
|
|
233
|
+
"if_stack",
|
|
234
|
+
"counters",
|
|
235
|
+
"timer",
|
|
236
|
+
"dbs",
|
|
237
|
+
"tempfiles",
|
|
238
|
+
"export_metadata",
|
|
239
|
+
"metacommandlist",
|
|
240
|
+
"conditionallist",
|
|
241
|
+
# GUI
|
|
242
|
+
"gui_console",
|
|
243
|
+
"gui_manager_queue",
|
|
244
|
+
"gui_manager_thread",
|
|
245
|
+
)
|
|
161
246
|
|
|
162
|
-
|
|
163
|
-
|
|
247
|
+
def __init__(self) -> None:
|
|
248
|
+
# Configuration
|
|
249
|
+
self.conf: ConfigData | None = None
|
|
250
|
+
self.logfile_encoding: str = "utf8"
|
|
251
|
+
|
|
252
|
+
# Runtime flags
|
|
253
|
+
self.last_command: ScriptCmd | None = None
|
|
254
|
+
self.upass: str | None = None
|
|
255
|
+
self.err_halt_writespec: WriteSpec | None = None
|
|
256
|
+
self.err_halt_email: MailSpec | None = None
|
|
257
|
+
self.err_halt_exec: ScriptExecSpec | None = None
|
|
258
|
+
self.cancel_halt_writespec: WriteSpec | None = None
|
|
259
|
+
self.cancel_halt_mailspec: MailSpec | None = None
|
|
260
|
+
self.cancel_halt_exec: ScriptExecSpec | None = None
|
|
261
|
+
|
|
262
|
+
# Execution stack
|
|
263
|
+
self.commandliststack: list[CommandList] = []
|
|
264
|
+
self.savedscripts: dict[str, CommandList] = {}
|
|
265
|
+
self.loopcommandstack: list[CommandList] = []
|
|
266
|
+
self.compiling_loop: bool = False
|
|
267
|
+
self.loop_nest_level: int = 0
|
|
268
|
+
self.cmds_run: int = 0
|
|
269
|
+
|
|
270
|
+
# I/O
|
|
271
|
+
self.exec_log: Logger | None = None
|
|
272
|
+
self.subvars: SubVarSet | None = None
|
|
273
|
+
self.status: StatObj | None = None
|
|
274
|
+
self.output: WriteHooks | None = None
|
|
275
|
+
self.filewriter: FileWriter | None = None
|
|
276
|
+
|
|
277
|
+
# Lazy singletons
|
|
278
|
+
self.if_stack: IfLevels | None = None
|
|
279
|
+
self.counters: CounterVars | None = None
|
|
280
|
+
self.timer: Timer | None = None
|
|
281
|
+
self.dbs: DatabasePool | None = None
|
|
282
|
+
self.tempfiles: TempFileMgr | None = None
|
|
283
|
+
self.export_metadata: ExportMetadata | None = None
|
|
284
|
+
self.metacommandlist: MetaCommandList | None = None
|
|
285
|
+
self.conditionallist: MetaCommandList | None = None
|
|
286
|
+
|
|
287
|
+
# GUI
|
|
288
|
+
self.gui_console: Any = None
|
|
289
|
+
self.gui_manager_queue: _mp.Queue | None = None
|
|
290
|
+
self.gui_manager_thread: _threading.Thread | None = None
|
|
164
291
|
|
|
165
|
-
# Metacommand dispatch table (MetaCommandList instance).
|
|
166
|
-
metacommandlist: MetaCommandList | None = None
|
|
167
292
|
|
|
168
|
-
#
|
|
169
|
-
|
|
293
|
+
# ---------------------------------------------------------------------------
|
|
294
|
+
# Module proxy — transparently delegates context attr access to _ctx
|
|
295
|
+
# ---------------------------------------------------------------------------
|
|
170
296
|
|
|
171
|
-
# Asynchronous file-writer subprocess (FileWriter instance).
|
|
172
|
-
filewriter: FileWriter | None = None
|
|
173
297
|
|
|
174
|
-
|
|
175
|
-
|
|
298
|
+
class _StateModule(types.ModuleType):
|
|
299
|
+
"""Module subclass that proxies mutable state attributes to the active RuntimeContext."""
|
|
176
300
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
301
|
+
def __getattr__(self, name: str) -> Any:
|
|
302
|
+
if name in _CONTEXT_ATTRS:
|
|
303
|
+
return getattr(self.__dict__["_ctx"], name)
|
|
304
|
+
raise AttributeError(f"module {self.__name__!r} has no attribute {name!r}")
|
|
180
305
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
306
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
307
|
+
if name in _CONTEXT_ATTRS:
|
|
308
|
+
setattr(self.__dict__["_ctx"], name, value)
|
|
309
|
+
else:
|
|
310
|
+
super().__setattr__(name, value)
|
|
184
311
|
|
|
185
|
-
|
|
186
|
-
|
|
312
|
+
def __delattr__(self, name: str) -> None:
|
|
313
|
+
if name in _CONTEXT_ATTRS:
|
|
314
|
+
# Reset to the fresh-context default. Needed for
|
|
315
|
+
# unittest.mock.patch compatibility: patch checks
|
|
316
|
+
# ``name in target.__dict__`` to decide whether to restore
|
|
317
|
+
# via setattr (local) or delattr (non-local). Since context
|
|
318
|
+
# attrs live on _ctx, not __dict__, patch takes the delattr
|
|
319
|
+
# path. We reset to the default rather than truly deleting.
|
|
320
|
+
_defaults = RuntimeContext()
|
|
321
|
+
setattr(self.__dict__["_ctx"], name, getattr(_defaults, name))
|
|
322
|
+
else:
|
|
323
|
+
super().__delattr__(name)
|
|
324
|
+
|
|
325
|
+
def __dir__(self) -> list[str]:
|
|
326
|
+
return sorted(set(super().__dir__()) | _CONTEXT_ATTRS)
|
|
187
327
|
|
|
188
|
-
_vparts = _pkg_version.split(".")
|
|
189
|
-
primary_vno: int = int(_vparts[0]) if len(_vparts) > 0 else 0
|
|
190
|
-
secondary_vno: int = int(_vparts[1]) if len(_vparts) > 1 else 0
|
|
191
|
-
tertiary_vno: int = int(_vparts[2]) if len(_vparts) > 2 else 0
|
|
192
|
-
except Exception:
|
|
193
|
-
primary_vno = 0
|
|
194
|
-
secondary_vno = 0
|
|
195
|
-
tertiary_vno = 0
|
|
196
328
|
|
|
197
329
|
# ---------------------------------------------------------------------------
|
|
198
|
-
# Utility functions
|
|
330
|
+
# Utility functions — use _ctx directly (LOAD_GLOBAL bypasses the proxy)
|
|
199
331
|
# ---------------------------------------------------------------------------
|
|
200
332
|
|
|
201
333
|
|
|
202
334
|
def xcmd_test(teststr: str) -> bool:
|
|
203
335
|
"""Evaluate a conditional test string and return a boolean result."""
|
|
204
|
-
import execsql.parser as _parser
|
|
205
336
|
import execsql.exceptions as _exc
|
|
337
|
+
import execsql.parser as _parser
|
|
206
338
|
|
|
207
339
|
result = _parser.CondParser(teststr).parse().eval()
|
|
208
340
|
if result is not None:
|
|
@@ -214,80 +346,60 @@ def endloop() -> None:
|
|
|
214
346
|
"""Complete the current loop being compiled and push it onto the command stack."""
|
|
215
347
|
import execsql.exceptions as _exc
|
|
216
348
|
|
|
217
|
-
|
|
218
|
-
if len(loopcommandstack) == 0:
|
|
349
|
+
if len(_ctx.loopcommandstack) == 0:
|
|
219
350
|
raise _exc.ErrInfo("error", other_msg="END LOOP metacommand without a matching preceding LOOP metacommand.")
|
|
220
|
-
compiling_loop = False
|
|
221
|
-
commandliststack.append(loopcommandstack[-1])
|
|
222
|
-
loopcommandstack.pop()
|
|
351
|
+
_ctx.compiling_loop = False
|
|
352
|
+
_ctx.commandliststack.append(_ctx.loopcommandstack[-1])
|
|
353
|
+
_ctx.loopcommandstack.pop()
|
|
223
354
|
|
|
224
355
|
|
|
225
356
|
# ---------------------------------------------------------------------------
|
|
226
|
-
#
|
|
357
|
+
# Context management
|
|
358
|
+
# ---------------------------------------------------------------------------
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def get_context() -> RuntimeContext:
|
|
362
|
+
"""Return the active :class:`RuntimeContext`."""
|
|
363
|
+
return _ctx
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def set_context(ctx: RuntimeContext) -> None:
|
|
367
|
+
"""Replace the active :class:`RuntimeContext`.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
ctx: The new context to install. All subsequent ``_state.foo``
|
|
371
|
+
accesses will resolve against this instance.
|
|
372
|
+
"""
|
|
373
|
+
global _ctx
|
|
374
|
+
_ctx = ctx
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# ---------------------------------------------------------------------------
|
|
378
|
+
# Initialization and reset
|
|
227
379
|
# ---------------------------------------------------------------------------
|
|
228
380
|
|
|
229
381
|
|
|
230
382
|
def reset() -> None:
|
|
231
|
-
"""Reset all
|
|
383
|
+
"""Reset all mutable state to initial values.
|
|
232
384
|
|
|
233
|
-
Intended for use in tests.
|
|
234
|
-
|
|
235
|
-
|
|
385
|
+
Intended for use in tests. Creates a fresh :class:`RuntimeContext`,
|
|
386
|
+
preserving only the ``filewriter`` subprocess (which is ``atexit``-managed
|
|
387
|
+
and must not be discarded while alive).
|
|
236
388
|
"""
|
|
237
|
-
global
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
global exec_log, subvars, status, if_stack, counters, timer
|
|
242
|
-
global output, dbs, tempfiles, export_metadata
|
|
243
|
-
global metacommandlist, conditionallist, filewriter
|
|
244
|
-
global gui_console, gui_manager_queue, gui_manager_thread
|
|
245
|
-
|
|
246
|
-
# Mutable containers — clear in-place (no rebind needed)
|
|
247
|
-
commandliststack.clear()
|
|
248
|
-
loopcommandstack.clear()
|
|
249
|
-
savedscripts.clear()
|
|
250
|
-
|
|
251
|
-
# Scalar flags and counters
|
|
252
|
-
compiling_loop = False
|
|
253
|
-
loop_nest_level = 0
|
|
254
|
-
cmds_run = 0
|
|
389
|
+
global _ctx
|
|
390
|
+
|
|
391
|
+
# Preserve filewriter — it's atexit-managed and must survive resets.
|
|
392
|
+
old_fw = _ctx.filewriter
|
|
255
393
|
|
|
256
394
|
# Close open database connections before discarding the pool.
|
|
257
|
-
if dbs is not None:
|
|
395
|
+
if _ctx.dbs is not None:
|
|
258
396
|
try:
|
|
259
|
-
dbs.closeall()
|
|
397
|
+
_ctx.dbs.closeall()
|
|
260
398
|
except Exception:
|
|
261
399
|
pass
|
|
262
400
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
last_command = None
|
|
266
|
-
upass = None
|
|
267
|
-
err_halt_writespec = None
|
|
268
|
-
err_halt_email = None
|
|
269
|
-
err_halt_exec = None
|
|
270
|
-
cancel_halt_writespec = None
|
|
271
|
-
cancel_halt_mailspec = None
|
|
272
|
-
cancel_halt_exec = None
|
|
273
|
-
exec_log = None
|
|
274
|
-
subvars = None
|
|
275
|
-
status = None
|
|
276
|
-
if_stack = None
|
|
277
|
-
counters = None
|
|
278
|
-
timer = None
|
|
279
|
-
output = None
|
|
280
|
-
dbs = None
|
|
281
|
-
tempfiles = None
|
|
282
|
-
export_metadata = None
|
|
283
|
-
metacommandlist = None
|
|
284
|
-
conditionallist = None
|
|
285
|
-
# filewriter is a multiprocessing.Process managed by atexit — do NOT null
|
|
286
|
-
# it here. Nulling it while the subprocess is alive creates two competing
|
|
287
|
-
# consumers on the shared fw_input queue, causing test-to-test races.
|
|
288
|
-
gui_console = None
|
|
289
|
-
gui_manager_queue = None
|
|
290
|
-
gui_manager_thread = None
|
|
401
|
+
_ctx = RuntimeContext()
|
|
402
|
+
_ctx.filewriter = old_fw
|
|
291
403
|
|
|
292
404
|
|
|
293
405
|
def initialize(
|
|
@@ -315,25 +427,26 @@ def initialize(
|
|
|
315
427
|
(script path, subprocess queues, local class definitions). Those are
|
|
316
428
|
assigned directly in ``_run()`` before and after this call.
|
|
317
429
|
"""
|
|
318
|
-
global conf, if_stack, counters, timer, dbs, tempfiles
|
|
319
|
-
global export_metadata, metacommandlist, conditionallist
|
|
320
|
-
|
|
321
|
-
# These names are re-exported at the bottom of this module (after this
|
|
322
|
-
# function definition), so they are guaranteed to be available by the time
|
|
323
|
-
# initialize() is called from cli._run(). Using the module-level names
|
|
324
|
-
# avoids F811 "redefinition of unused name" from local imports.
|
|
325
|
-
import execsql.script as _script
|
|
326
|
-
import execsql.utils.timer as _timer_mod
|
|
327
430
|
import execsql.db.base as _db_base
|
|
328
|
-
import execsql.utils.fileio as _fileio_mod
|
|
329
431
|
import execsql.exporters.base as _exporters_base
|
|
432
|
+
import execsql.script as _script
|
|
433
|
+
import execsql.utils.fileio as _fileio_mod
|
|
434
|
+
import execsql.utils.timer as _timer_mod
|
|
435
|
+
|
|
436
|
+
_ctx.conf = config
|
|
437
|
+
_ctx.if_stack = _script.IfLevels()
|
|
438
|
+
_ctx.counters = _script.CounterVars()
|
|
439
|
+
_ctx.timer = _timer_mod.Timer()
|
|
440
|
+
_ctx.dbs = _db_base.DatabasePool()
|
|
441
|
+
_ctx.tempfiles = _fileio_mod.TempFileMgr()
|
|
442
|
+
_ctx.export_metadata = _exporters_base.ExportMetadata()
|
|
443
|
+
_ctx.metacommandlist = dispatch_table
|
|
444
|
+
_ctx.conditionallist = conditional_table
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
# ---------------------------------------------------------------------------
|
|
448
|
+
# Bootstrap — create the initial context and swap the module class
|
|
449
|
+
# ---------------------------------------------------------------------------
|
|
330
450
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
counters = _script.CounterVars()
|
|
334
|
-
timer = _timer_mod.Timer()
|
|
335
|
-
dbs = _db_base.DatabasePool()
|
|
336
|
-
tempfiles = _fileio_mod.TempFileMgr()
|
|
337
|
-
export_metadata = _exporters_base.ExportMetadata()
|
|
338
|
-
metacommandlist = dispatch_table
|
|
339
|
-
conditionallist = conditional_table
|
|
451
|
+
_ctx = RuntimeContext()
|
|
452
|
+
sys.modules[__name__].__class__ = _StateModule
|