execsql2 2.5.0__py3-none-any.whl → 2.7.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.
- execsql/exporters/__init__.py +3 -3
- execsql/exporters/delimited.py +2 -2
- execsql/exporters/markdown.py +126 -0
- execsql/exporters/xlsx.py +317 -0
- execsql/exporters/yaml.py +87 -0
- execsql/gui/tui.py +132 -0
- execsql/metacommands/__init__.py +203 -182
- execsql/metacommands/dispatch.py +11 -0
- execsql/metacommands/io.py +2 -0
- execsql/metacommands/io_export.py +75 -0
- execsql/state.py +261 -200
- {execsql2-2.5.0.dist-info → execsql2-2.7.1.dist-info}/METADATA +5 -2
- {execsql2-2.5.0.dist-info → execsql2-2.7.1.dist-info}/RECORD +32 -29
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.5.0.data → execsql2-2.7.1.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.5.0.dist-info → execsql2-2.7.1.dist-info}/WHEEL +0 -0
- {execsql2-2.5.0.dist-info → execsql2-2.7.1.dist-info}/entry_points.txt +0 -0
- {execsql2-2.5.0.dist-info → execsql2-2.7.1.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.5.0.dist-info → execsql2-2.7.1.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:
|
|
@@ -105,156 +103,238 @@ __all__ = [
|
|
|
105
103
|
"endloop",
|
|
106
104
|
"reset",
|
|
107
105
|
"initialize",
|
|
106
|
+
# New public API
|
|
107
|
+
"RuntimeContext",
|
|
108
|
+
"get_context",
|
|
109
|
+
"set_context",
|
|
108
110
|
]
|
|
109
111
|
|
|
110
112
|
# ---------------------------------------------------------------------------
|
|
111
|
-
#
|
|
113
|
+
# Compile-time constants — immutable after module load, stay in __dict__
|
|
112
114
|
# ---------------------------------------------------------------------------
|
|
113
115
|
|
|
114
|
-
# Configuration data, initialized in main()
|
|
115
|
-
conf: ConfigData | None = None
|
|
116
|
-
|
|
117
|
-
# Default encodings
|
|
118
|
-
logfile_encoding: str = "utf8" # Should never be changed; is not configurable.
|
|
119
|
-
|
|
120
|
-
# ---------------------------------------------------------------------------
|
|
121
|
-
# Runtime state variables
|
|
122
|
-
# ---------------------------------------------------------------------------
|
|
123
|
-
|
|
124
|
-
# The last command run. This should be a ScriptCmd object.
|
|
125
|
-
last_command: ScriptCmd | None = None
|
|
126
|
-
|
|
127
|
-
# The last user password entered via 'get_password()'
|
|
128
|
-
upass: str | None = None
|
|
129
|
-
|
|
130
116
|
# A compiled regex to match prefixed regular expressions, used to check
|
|
131
117
|
# for unsubstituted variables.
|
|
132
118
|
varlike = re.compile(r"!![$@&~#]?\w+!!", re.I)
|
|
133
119
|
|
|
134
|
-
# A WriteSpec object for messages to be written when the program halts due to an error.
|
|
135
|
-
err_halt_writespec: WriteSpec | None = None
|
|
136
|
-
|
|
137
|
-
# A MailSpec object for email to be sent when the program halts due to an error.
|
|
138
|
-
err_halt_email: MailSpec | None = None
|
|
139
|
-
|
|
140
|
-
# A ScriptExecSpec object for a script to be executed when the program halts due to an error.
|
|
141
|
-
err_halt_exec: ScriptExecSpec | None = None
|
|
142
|
-
|
|
143
|
-
# A WriteSpec object for messages to be written when the program halts due to user cancellation.
|
|
144
|
-
cancel_halt_writespec: WriteSpec | None = None
|
|
145
|
-
|
|
146
|
-
# A MailSpec object for email to be sent when the program halts due to user cancellation.
|
|
147
|
-
cancel_halt_mailspec: MailSpec | None = None
|
|
148
|
-
|
|
149
|
-
# A ScriptExecSpec object for a script to be executed when the program halts due to user cancellation.
|
|
150
|
-
cancel_halt_exec: ScriptExecSpec | None = None
|
|
151
|
-
|
|
152
|
-
# A stack of the CommandList objects currently in the queue to be executed.
|
|
153
|
-
commandliststack: list[CommandList] = []
|
|
154
|
-
|
|
155
|
-
# A dictionary of CommandList objects (ordinarily created by BEGIN/END SCRIPT metacommands).
|
|
156
|
-
savedscripts: dict[str, CommandList] = {}
|
|
157
|
-
|
|
158
|
-
# A stack of CommandList objects used when compiling the statements within a loop.
|
|
159
|
-
loopcommandstack: list[CommandList] = []
|
|
160
|
-
|
|
161
|
-
# A global flag to indicate that commands should be compiled into the topmost entry
|
|
162
|
-
# in the loopcommandstack rather than executed.
|
|
163
|
-
compiling_loop: bool = False
|
|
164
|
-
|
|
165
120
|
# Compiled regex for END LOOP metacommand, which is immediate.
|
|
166
121
|
endloop_rx = re.compile(r"^\s*END\s+LOOP\s*$", re.I)
|
|
167
122
|
|
|
168
|
-
# 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.
|
|
169
125
|
loop_rx = re.compile(r"\s*LOOP\s+", re.I)
|
|
170
126
|
|
|
171
|
-
# Nesting counter, to ensure loops are only ended when nesting level is zero.
|
|
172
|
-
loop_nest_level: int = 0
|
|
173
|
-
|
|
174
|
-
# A count of all of the commands run.
|
|
175
|
-
cmds_run: int = 0
|
|
176
|
-
|
|
177
127
|
# Pattern for deferred substitution, e.g.: "!{somevar}!"
|
|
178
128
|
defer_rx = re.compile(r"(!{([$@&~#]?[a-z0-9_]+)}!)", re.I)
|
|
179
129
|
|
|
180
130
|
# The string type (str in Python 3).
|
|
181
131
|
stringtypes: type = str
|
|
182
132
|
|
|
183
|
-
# The execution log object; set at startup.
|
|
184
|
-
exec_log: Logger | None = None
|
|
185
|
-
|
|
186
|
-
# The substitution variable set; set at startup.
|
|
187
|
-
subvars: SubVarSet | None = None
|
|
188
|
-
|
|
189
|
-
# The program execution status tracker; set at startup.
|
|
190
|
-
status: StatObj | None = None
|
|
191
|
-
|
|
192
133
|
# ---------------------------------------------------------------------------
|
|
193
|
-
#
|
|
134
|
+
# Version numbers (parsed from package __version__)
|
|
194
135
|
# ---------------------------------------------------------------------------
|
|
195
136
|
|
|
196
|
-
|
|
197
|
-
|
|
137
|
+
try:
|
|
138
|
+
from execsql import __version__ as _pkg_version
|
|
198
139
|
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
201
148
|
|
|
202
|
-
# Elapsed-time tracker (Timer instance).
|
|
203
|
-
timer: Timer | None = None
|
|
204
149
|
|
|
205
|
-
#
|
|
206
|
-
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# RuntimeContext — holds all mutable state for a single execsql session
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
207
153
|
|
|
208
|
-
|
|
209
|
-
|
|
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
|
+
"""
|
|
210
205
|
|
|
211
|
-
|
|
212
|
-
|
|
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
|
+
)
|
|
213
246
|
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
216
291
|
|
|
217
|
-
# Metacommand dispatch table (MetaCommandList instance).
|
|
218
|
-
metacommandlist: MetaCommandList | None = None
|
|
219
292
|
|
|
220
|
-
#
|
|
221
|
-
|
|
293
|
+
# ---------------------------------------------------------------------------
|
|
294
|
+
# Module proxy — transparently delegates context attr access to _ctx
|
|
295
|
+
# ---------------------------------------------------------------------------
|
|
222
296
|
|
|
223
|
-
# Asynchronous file-writer subprocess (FileWriter instance).
|
|
224
|
-
filewriter: FileWriter | None = None
|
|
225
297
|
|
|
226
|
-
|
|
227
|
-
|
|
298
|
+
class _StateModule(types.ModuleType):
|
|
299
|
+
"""Module subclass that proxies mutable state attributes to the active RuntimeContext."""
|
|
228
300
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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}")
|
|
232
305
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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)
|
|
236
311
|
|
|
237
|
-
|
|
238
|
-
|
|
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)
|
|
239
327
|
|
|
240
|
-
_vparts = _pkg_version.split(".")
|
|
241
|
-
primary_vno: int = int(_vparts[0]) if len(_vparts) > 0 else 0
|
|
242
|
-
secondary_vno: int = int(_vparts[1]) if len(_vparts) > 1 else 0
|
|
243
|
-
tertiary_vno: int = int(_vparts[2]) if len(_vparts) > 2 else 0
|
|
244
|
-
except Exception:
|
|
245
|
-
primary_vno = 0
|
|
246
|
-
secondary_vno = 0
|
|
247
|
-
tertiary_vno = 0
|
|
248
328
|
|
|
249
329
|
# ---------------------------------------------------------------------------
|
|
250
|
-
# Utility functions
|
|
330
|
+
# Utility functions — use _ctx directly (LOAD_GLOBAL bypasses the proxy)
|
|
251
331
|
# ---------------------------------------------------------------------------
|
|
252
332
|
|
|
253
333
|
|
|
254
334
|
def xcmd_test(teststr: str) -> bool:
|
|
255
335
|
"""Evaluate a conditional test string and return a boolean result."""
|
|
256
|
-
import execsql.parser as _parser
|
|
257
336
|
import execsql.exceptions as _exc
|
|
337
|
+
import execsql.parser as _parser
|
|
258
338
|
|
|
259
339
|
result = _parser.CondParser(teststr).parse().eval()
|
|
260
340
|
if result is not None:
|
|
@@ -266,80 +346,60 @@ def endloop() -> None:
|
|
|
266
346
|
"""Complete the current loop being compiled and push it onto the command stack."""
|
|
267
347
|
import execsql.exceptions as _exc
|
|
268
348
|
|
|
269
|
-
|
|
270
|
-
if len(loopcommandstack) == 0:
|
|
349
|
+
if len(_ctx.loopcommandstack) == 0:
|
|
271
350
|
raise _exc.ErrInfo("error", other_msg="END LOOP metacommand without a matching preceding LOOP metacommand.")
|
|
272
|
-
compiling_loop = False
|
|
273
|
-
commandliststack.append(loopcommandstack[-1])
|
|
274
|
-
loopcommandstack.pop()
|
|
351
|
+
_ctx.compiling_loop = False
|
|
352
|
+
_ctx.commandliststack.append(_ctx.loopcommandstack[-1])
|
|
353
|
+
_ctx.loopcommandstack.pop()
|
|
275
354
|
|
|
276
355
|
|
|
277
356
|
# ---------------------------------------------------------------------------
|
|
278
|
-
#
|
|
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
|
|
279
379
|
# ---------------------------------------------------------------------------
|
|
280
380
|
|
|
281
381
|
|
|
282
382
|
def reset() -> None:
|
|
283
|
-
"""Reset all
|
|
383
|
+
"""Reset all mutable state to initial values.
|
|
284
384
|
|
|
285
|
-
Intended for use in tests.
|
|
286
|
-
|
|
287
|
-
|
|
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).
|
|
288
388
|
"""
|
|
289
|
-
global
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
global exec_log, subvars, status, if_stack, counters, timer
|
|
294
|
-
global output, dbs, tempfiles, export_metadata
|
|
295
|
-
global metacommandlist, conditionallist, filewriter
|
|
296
|
-
global gui_console, gui_manager_queue, gui_manager_thread
|
|
297
|
-
|
|
298
|
-
# Mutable containers — clear in-place (no rebind needed)
|
|
299
|
-
commandliststack.clear()
|
|
300
|
-
loopcommandstack.clear()
|
|
301
|
-
savedscripts.clear()
|
|
302
|
-
|
|
303
|
-
# Scalar flags and counters
|
|
304
|
-
compiling_loop = False
|
|
305
|
-
loop_nest_level = 0
|
|
306
|
-
cmds_run = 0
|
|
389
|
+
global _ctx
|
|
390
|
+
|
|
391
|
+
# Preserve filewriter — it's atexit-managed and must survive resets.
|
|
392
|
+
old_fw = _ctx.filewriter
|
|
307
393
|
|
|
308
394
|
# Close open database connections before discarding the pool.
|
|
309
|
-
if dbs is not None:
|
|
395
|
+
if _ctx.dbs is not None:
|
|
310
396
|
try:
|
|
311
|
-
dbs.closeall()
|
|
397
|
+
_ctx.dbs.closeall()
|
|
312
398
|
except Exception:
|
|
313
399
|
pass
|
|
314
400
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
last_command = None
|
|
318
|
-
upass = None
|
|
319
|
-
err_halt_writespec = None
|
|
320
|
-
err_halt_email = None
|
|
321
|
-
err_halt_exec = None
|
|
322
|
-
cancel_halt_writespec = None
|
|
323
|
-
cancel_halt_mailspec = None
|
|
324
|
-
cancel_halt_exec = None
|
|
325
|
-
exec_log = None
|
|
326
|
-
subvars = None
|
|
327
|
-
status = None
|
|
328
|
-
if_stack = None
|
|
329
|
-
counters = None
|
|
330
|
-
timer = None
|
|
331
|
-
output = None
|
|
332
|
-
dbs = None
|
|
333
|
-
tempfiles = None
|
|
334
|
-
export_metadata = None
|
|
335
|
-
metacommandlist = None
|
|
336
|
-
conditionallist = None
|
|
337
|
-
# filewriter is a multiprocessing.Process managed by atexit — do NOT null
|
|
338
|
-
# it here. Nulling it while the subprocess is alive creates two competing
|
|
339
|
-
# consumers on the shared fw_input queue, causing test-to-test races.
|
|
340
|
-
gui_console = None
|
|
341
|
-
gui_manager_queue = None
|
|
342
|
-
gui_manager_thread = None
|
|
401
|
+
_ctx = RuntimeContext()
|
|
402
|
+
_ctx.filewriter = old_fw
|
|
343
403
|
|
|
344
404
|
|
|
345
405
|
def initialize(
|
|
@@ -367,25 +427,26 @@ def initialize(
|
|
|
367
427
|
(script path, subprocess queues, local class definitions). Those are
|
|
368
428
|
assigned directly in ``_run()`` before and after this call.
|
|
369
429
|
"""
|
|
370
|
-
global conf, if_stack, counters, timer, dbs, tempfiles
|
|
371
|
-
global export_metadata, metacommandlist, conditionallist
|
|
372
|
-
|
|
373
|
-
# These names are re-exported at the bottom of this module (after this
|
|
374
|
-
# function definition), so they are guaranteed to be available by the time
|
|
375
|
-
# initialize() is called from cli._run(). Using the module-level names
|
|
376
|
-
# avoids F811 "redefinition of unused name" from local imports.
|
|
377
|
-
import execsql.script as _script
|
|
378
|
-
import execsql.utils.timer as _timer_mod
|
|
379
430
|
import execsql.db.base as _db_base
|
|
380
|
-
import execsql.utils.fileio as _fileio_mod
|
|
381
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
|
+
# ---------------------------------------------------------------------------
|
|
382
450
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
counters = _script.CounterVars()
|
|
386
|
-
timer = _timer_mod.Timer()
|
|
387
|
-
dbs = _db_base.DatabasePool()
|
|
388
|
-
tempfiles = _fileio_mod.TempFileMgr()
|
|
389
|
-
export_metadata = _exporters_base.ExportMetadata()
|
|
390
|
-
metacommandlist = dispatch_table
|
|
391
|
-
conditionallist = conditional_table
|
|
451
|
+
_ctx = RuntimeContext()
|
|
452
|
+
sys.modules[__name__].__class__ = _StateModule
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.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: Repository, https://github.com/geocoug/execsql
|
|
6
6
|
Project-URL: Issues, https://github.com/geocoug/execsql/issues
|
|
@@ -54,6 +54,7 @@ Requires-Dist: polars; extra == 'all'
|
|
|
54
54
|
Requires-Dist: psycopg2-binary; extra == 'all'
|
|
55
55
|
Requires-Dist: pymysql; extra == 'all'
|
|
56
56
|
Requires-Dist: pyodbc; extra == 'all'
|
|
57
|
+
Requires-Dist: pyyaml; extra == 'all'
|
|
57
58
|
Requires-Dist: tables; extra == 'all'
|
|
58
59
|
Requires-Dist: xlrd; extra == 'all'
|
|
59
60
|
Provides-Extra: all-db
|
|
@@ -76,6 +77,7 @@ Requires-Dist: openpyxl; extra == 'dev'
|
|
|
76
77
|
Requires-Dist: polars; extra == 'dev'
|
|
77
78
|
Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
|
|
78
79
|
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
80
|
+
Requires-Dist: pyyaml; extra == 'dev'
|
|
79
81
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
80
82
|
Requires-Dist: tables; extra == 'dev'
|
|
81
83
|
Requires-Dist: tox-uv>=1.13.1; extra == 'dev'
|
|
@@ -91,6 +93,7 @@ Requires-Dist: jinja2; extra == 'formats'
|
|
|
91
93
|
Requires-Dist: odfpy; extra == 'formats'
|
|
92
94
|
Requires-Dist: openpyxl; extra == 'formats'
|
|
93
95
|
Requires-Dist: polars; extra == 'formats'
|
|
96
|
+
Requires-Dist: pyyaml; extra == 'formats'
|
|
94
97
|
Requires-Dist: tables; extra == 'formats'
|
|
95
98
|
Requires-Dist: xlrd; extra == 'formats'
|
|
96
99
|
Provides-Extra: mssql
|
|
@@ -227,7 +230,7 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
|
|
|
227
230
|
# Features
|
|
228
231
|
|
|
229
232
|
- Import data from CSV, TSV, Excel, OpenDocument, Feather, or Parquet files into a database table.
|
|
230
|
-
- Export query results in
|
|
233
|
+
- 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.
|
|
231
234
|
- Copy data between databases, including across different DBMS types.
|
|
232
235
|
- Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
|
|
233
236
|
- Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`.
|