execsql2 2.16.15__tar.gz → 2.16.16__tar.gz

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 (324) hide show
  1. {execsql2-2.16.15 → execsql2-2.16.16}/.github/workflows/ci-cd.yml +23 -12
  2. {execsql2-2.16.15 → execsql2-2.16.16}/.pre-commit-config.yaml +1 -0
  3. {execsql2-2.16.15 → execsql2-2.16.16}/CHANGELOG.md +19 -0
  4. {execsql2-2.16.15 → execsql2-2.16.16}/PKG-INFO +1 -1
  5. {execsql2-2.16.15 → execsql2-2.16.16}/docs/about/divergence.md +2 -0
  6. {execsql2-2.16.15 → execsql2-2.16.16}/docs/reference/configuration.md +9 -0
  7. {execsql2-2.16.15 → execsql2-2.16.16}/docs/reference/security.md +30 -0
  8. {execsql2-2.16.15 → execsql2-2.16.16}/pyproject.toml +2 -2
  9. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/api.py +4 -0
  10. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/cli/__init__.py +156 -134
  11. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/cli/help.py +10 -1
  12. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/cli/run.py +4 -0
  13. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/config.py +2 -0
  14. execsql2-2.16.16/src/execsql/data/execsql.conf.template +327 -0
  15. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/sqlite.py +47 -43
  16. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/system.py +10 -9
  17. execsql2-2.16.16/templates/execsql.conf +327 -0
  18. {execsql2-2.16.15 → execsql2-2.16.16}/tests/cli/test_cli.py +24 -0
  19. {execsql2-2.16.15 → execsql2-2.16.16}/tests/conftest.py +2 -0
  20. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_metacommands_extended.py +5 -3
  21. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_metacommands_system.py +15 -4
  22. execsql2-2.16.16/tests/utils/__init__.py +0 -0
  23. {execsql2-2.16.15 → execsql2-2.16.16}/uv.lock +1 -1
  24. execsql2-2.16.15/.github/PULL_REQUEST_TEMPLATE.md +0 -19
  25. execsql2-2.16.15/templates/execsql.conf +0 -287
  26. {execsql2-2.16.15 → execsql2-2.16.16}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  27. {execsql2-2.16.15 → execsql2-2.16.16}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  28. {execsql2-2.16.15 → execsql2-2.16.16}/.gitignore +0 -0
  29. {execsql2-2.16.15 → execsql2-2.16.16}/.pre-commit-hooks.yaml +0 -0
  30. {execsql2-2.16.15 → execsql2-2.16.16}/.python-version +0 -0
  31. {execsql2-2.16.15 → execsql2-2.16.16}/.readthedocs.yaml +0 -0
  32. {execsql2-2.16.15 → execsql2-2.16.16}/CONTRIBUTING.md +0 -0
  33. {execsql2-2.16.15 → execsql2-2.16.16}/LICENSE.txt +0 -0
  34. {execsql2-2.16.15 → execsql2-2.16.16}/NOTICE +0 -0
  35. {execsql2-2.16.15 → execsql2-2.16.16}/README.md +0 -0
  36. {execsql2-2.16.15 → execsql2-2.16.16}/SECURITY.md +0 -0
  37. {execsql2-2.16.15 → execsql2-2.16.16}/docs/about/contributors.md +0 -0
  38. {execsql2-2.16.15 → execsql2-2.16.16}/docs/about/copyright.md +0 -0
  39. {execsql2-2.16.15 → execsql2-2.16.16}/docs/api/cli.md +0 -0
  40. {execsql2-2.16.15 → execsql2-2.16.16}/docs/api/db.md +0 -0
  41. {execsql2-2.16.15 → execsql2-2.16.16}/docs/api/exporters.md +0 -0
  42. {execsql2-2.16.15 → execsql2-2.16.16}/docs/api/importers.md +0 -0
  43. {execsql2-2.16.15 → execsql2-2.16.16}/docs/api/index.md +0 -0
  44. {execsql2-2.16.15 → execsql2-2.16.16}/docs/api/metacommands.md +0 -0
  45. {execsql2-2.16.15 → execsql2-2.16.16}/docs/dev/adding_db_adapters.md +0 -0
  46. {execsql2-2.16.15 → execsql2-2.16.16}/docs/dev/adding_exporters.md +0 -0
  47. {execsql2-2.16.15 → execsql2-2.16.16}/docs/dev/adding_importers.md +0 -0
  48. {execsql2-2.16.15 → execsql2-2.16.16}/docs/dev/adding_metacommands.md +0 -0
  49. {execsql2-2.16.15 → execsql2-2.16.16}/docs/dev/architecture.md +0 -0
  50. {execsql2-2.16.15 → execsql2-2.16.16}/docs/getting-started/installation.md +0 -0
  51. {execsql2-2.16.15 → execsql2-2.16.16}/docs/getting-started/requirements.md +0 -0
  52. {execsql2-2.16.15 → execsql2-2.16.16}/docs/getting-started/syntax.md +0 -0
  53. {execsql2-2.16.15 → execsql2-2.16.16}/docs/guides/debugging.md +0 -0
  54. {execsql2-2.16.15 → execsql2-2.16.16}/docs/guides/documentation.md +0 -0
  55. {execsql2-2.16.15 → execsql2-2.16.16}/docs/guides/encoding.md +0 -0
  56. {execsql2-2.16.15 → execsql2-2.16.16}/docs/guides/examples.md +0 -0
  57. {execsql2-2.16.15 → execsql2-2.16.16}/docs/guides/formatter.md +0 -0
  58. {execsql2-2.16.15 → execsql2-2.16.16}/docs/guides/logging.md +0 -0
  59. {execsql2-2.16.15 → execsql2-2.16.16}/docs/guides/sql_syntax.md +0 -0
  60. {execsql2-2.16.15 → execsql2-2.16.16}/docs/guides/usage.md +0 -0
  61. {execsql2-2.16.15 → execsql2-2.16.16}/docs/guides/using_scripts.md +0 -0
  62. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/Compare_planets.png +0 -0
  63. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/actions.png +0 -0
  64. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/actions2.png +0 -0
  65. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/checkboxes.png +0 -0
  66. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/connect.b64 +0 -0
  67. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/connect.png +0 -0
  68. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/create_conf.png +0 -0
  69. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/data_error1_screenshot.jpg +0 -0
  70. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/entry_form.png +0 -0
  71. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/execsql_console.png +0 -0
  72. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/execsql_logo_01.png +0 -0
  73. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/fatals.png +0 -0
  74. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/logo_small.png +0 -0
  75. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/pause_terminal.png +0 -0
  76. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/pause_terminal_sm.b64 +0 -0
  77. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/pause_terminal_sm.png +0 -0
  78. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/prompt_compare.png +0 -0
  79. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/set_build_commands.jpg +0 -0
  80. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/unit_conversions.b64 +0 -0
  81. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/unit_conversions_029.png +0 -0
  82. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/unmatched.png +0 -0
  83. {execsql2-2.16.15 → execsql2-2.16.16}/docs/images/vim_execsql_highlight.png +0 -0
  84. {execsql2-2.16.15 → execsql2-2.16.16}/docs/index.md +0 -0
  85. {execsql2-2.16.15 → execsql2-2.16.16}/docs/reference/metacommands.md +0 -0
  86. {execsql2-2.16.15 → execsql2-2.16.16}/docs/reference/substitution_vars.md +0 -0
  87. {execsql2-2.16.15 → execsql2-2.16.16}/extras/plugin-template/README.md +0 -0
  88. {execsql2-2.16.15 → execsql2-2.16.16}/extras/plugin-template/pyproject.toml +0 -0
  89. {execsql2-2.16.15 → execsql2-2.16.16}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  90. {execsql2-2.16.15 → execsql2-2.16.16}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  91. {execsql2-2.16.15 → execsql2-2.16.16}/extras/vscode-execsql/README.md +0 -0
  92. {execsql2-2.16.15 → execsql2-2.16.16}/extras/vscode-execsql/package.json +0 -0
  93. {execsql2-2.16.15 → execsql2-2.16.16}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  94. {execsql2-2.16.15 → execsql2-2.16.16}/justfile +0 -0
  95. {execsql2-2.16.15 → execsql2-2.16.16}/scripts/generate_vscode_grammar.py +0 -0
  96. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/__init__.py +0 -0
  97. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/__main__.py +0 -0
  98. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/cli/dsn.py +0 -0
  99. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/cli/lint.py +0 -0
  100. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/cli/lint_ast.py +0 -0
  101. {execsql2-2.16.15/tests → execsql2-2.16.16/src/execsql/data}/__init__.py +0 -0
  102. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/__init__.py +0 -0
  103. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/access.py +0 -0
  104. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/base.py +0 -0
  105. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/dsn.py +0 -0
  106. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/duckdb.py +0 -0
  107. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/factory.py +0 -0
  108. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/firebird.py +0 -0
  109. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/mysql.py +0 -0
  110. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/oracle.py +0 -0
  111. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/postgres.py +0 -0
  112. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/db/sqlserver.py +0 -0
  113. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/debug/__init__.py +0 -0
  114. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/debug/repl.py +0 -0
  115. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exceptions.py +0 -0
  116. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/__init__.py +0 -0
  117. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/base.py +0 -0
  118. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/delimited.py +0 -0
  119. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/duckdb.py +0 -0
  120. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/feather.py +0 -0
  121. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/html.py +0 -0
  122. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/json.py +0 -0
  123. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/latex.py +0 -0
  124. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/markdown.py +0 -0
  125. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/ods.py +0 -0
  126. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/parquet.py +0 -0
  127. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/pretty.py +0 -0
  128. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/protocol.py +0 -0
  129. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/raw.py +0 -0
  130. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/sqlite.py +0 -0
  131. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/templates.py +0 -0
  132. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/values.py +0 -0
  133. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/xls.py +0 -0
  134. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/xlsx.py +0 -0
  135. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/xml.py +0 -0
  136. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/yaml.py +0 -0
  137. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/exporters/zip.py +0 -0
  138. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/format.py +0 -0
  139. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/gui/__init__.py +0 -0
  140. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/gui/base.py +0 -0
  141. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/gui/console.py +0 -0
  142. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/gui/desktop.py +0 -0
  143. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/gui/tui.py +0 -0
  144. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/importers/__init__.py +0 -0
  145. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/importers/base.py +0 -0
  146. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/importers/csv.py +0 -0
  147. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/importers/feather.py +0 -0
  148. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/importers/json.py +0 -0
  149. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/importers/ods.py +0 -0
  150. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/importers/xls.py +0 -0
  151. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/__init__.py +0 -0
  152. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/conditions.py +0 -0
  153. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/connect.py +0 -0
  154. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/control.py +0 -0
  155. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/data.py +0 -0
  156. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/debug.py +0 -0
  157. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/dispatch.py +0 -0
  158. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/io.py +0 -0
  159. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/io_export.py +0 -0
  160. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/io_fileops.py +0 -0
  161. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/io_import.py +0 -0
  162. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/io_write.py +0 -0
  163. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/prompt.py +0 -0
  164. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/script_ext.py +0 -0
  165. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/metacommands/upsert.py +0 -0
  166. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/models.py +0 -0
  167. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/parser.py +0 -0
  168. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/plugins.py +0 -0
  169. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/py.typed +0 -0
  170. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/script/__init__.py +0 -0
  171. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/script/ast.py +0 -0
  172. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/script/control.py +0 -0
  173. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/script/engine.py +0 -0
  174. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/script/executor.py +0 -0
  175. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/script/parser.py +0 -0
  176. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/script/variables.py +0 -0
  177. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/state.py +0 -0
  178. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/types.py +0 -0
  179. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/__init__.py +0 -0
  180. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/auth.py +0 -0
  181. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/crypto.py +0 -0
  182. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/datetime.py +0 -0
  183. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/errors.py +0 -0
  184. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/fileio.py +0 -0
  185. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/gui.py +0 -0
  186. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/mail.py +0 -0
  187. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/numeric.py +0 -0
  188. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/regex.py +0 -0
  189. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/strings.py +0 -0
  190. {execsql2-2.16.15 → execsql2-2.16.16}/src/execsql/utils/timer.py +0 -0
  191. {execsql2-2.16.15 → execsql2-2.16.16}/templates/README.md +0 -0
  192. {execsql2-2.16.15 → execsql2-2.16.16}/templates/config_settings.sqlite +0 -0
  193. {execsql2-2.16.15 → execsql2-2.16.16}/templates/example_config_prompt.sql +0 -0
  194. {execsql2-2.16.15 → execsql2-2.16.16}/templates/make_config_db.sql +0 -0
  195. {execsql2-2.16.15 → execsql2-2.16.16}/templates/md_compare.sql +0 -0
  196. {execsql2-2.16.15 → execsql2-2.16.16}/templates/md_glossary.sql +0 -0
  197. {execsql2-2.16.15 → execsql2-2.16.16}/templates/md_upsert.sql +0 -0
  198. {execsql2-2.16.15 → execsql2-2.16.16}/templates/pg_compare.sql +0 -0
  199. {execsql2-2.16.15 → execsql2-2.16.16}/templates/pg_glossary.sql +0 -0
  200. {execsql2-2.16.15 → execsql2-2.16.16}/templates/pg_upsert.sql +0 -0
  201. {execsql2-2.16.15 → execsql2-2.16.16}/templates/script_template.sql +0 -0
  202. {execsql2-2.16.15 → execsql2-2.16.16}/templates/ss_compare.sql +0 -0
  203. {execsql2-2.16.15 → execsql2-2.16.16}/templates/ss_glossary.sql +0 -0
  204. {execsql2-2.16.15 → execsql2-2.16.16}/templates/ss_upsert.sql +0 -0
  205. {execsql2-2.16.15/tests/cli → execsql2-2.16.16/tests}/__init__.py +0 -0
  206. {execsql2-2.16.15/tests/db → execsql2-2.16.16/tests/cli}/__init__.py +0 -0
  207. {execsql2-2.16.15 → execsql2-2.16.16}/tests/cli/test_cli_e2e.py +0 -0
  208. {execsql2-2.16.15 → execsql2-2.16.16}/tests/cli/test_cli_run.py +0 -0
  209. {execsql2-2.16.15 → execsql2-2.16.16}/tests/cli/test_lint.py +0 -0
  210. {execsql2-2.16.15 → execsql2-2.16.16}/tests/cli/test_ping.py +0 -0
  211. {execsql2-2.16.15 → execsql2-2.16.16}/tests/cli/test_profile.py +0 -0
  212. {execsql2-2.16.15/tests/exporters → execsql2-2.16.16/tests/db}/__init__.py +0 -0
  213. {execsql2-2.16.15 → execsql2-2.16.16}/tests/db/test_base.py +0 -0
  214. {execsql2-2.16.15 → execsql2-2.16.16}/tests/db/test_db_adapters_mocked.py +0 -0
  215. {execsql2-2.16.15 → execsql2-2.16.16}/tests/db/test_dsn.py +0 -0
  216. {execsql2-2.16.15 → execsql2-2.16.16}/tests/db/test_duckdb.py +0 -0
  217. {execsql2-2.16.15 → execsql2-2.16.16}/tests/db/test_factory.py +0 -0
  218. {execsql2-2.16.15 → execsql2-2.16.16}/tests/db/test_postgres.py +0 -0
  219. {execsql2-2.16.15 → execsql2-2.16.16}/tests/db/test_sqlite.py +0 -0
  220. {execsql2-2.16.15 → execsql2-2.16.16}/tests/db/test_sqlite_extra.py +0 -0
  221. {execsql2-2.16.15/tests/gui → execsql2-2.16.16/tests/exporters}/__init__.py +0 -0
  222. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_base.py +0 -0
  223. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_db.py +0 -0
  224. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_delimited.py +0 -0
  225. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_duckdb_exporter.py +0 -0
  226. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_exporters.py +0 -0
  227. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_feather.py +0 -0
  228. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_html_extended.py +0 -0
  229. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_html_latex.py +0 -0
  230. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_json.py +0 -0
  231. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_json_extended.py +0 -0
  232. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_latex_extended.py +0 -0
  233. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_markdown.py +0 -0
  234. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_ods.py +0 -0
  235. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_parquet.py +0 -0
  236. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_pretty_extended.py +0 -0
  237. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_raw_extended.py +0 -0
  238. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_sqlite_exporter.py +0 -0
  239. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_templates.py +0 -0
  240. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_templates_extended.py +0 -0
  241. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_values_extended.py +0 -0
  242. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_xls_xlsx.py +0 -0
  243. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_xlsx.py +0 -0
  244. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_xml.py +0 -0
  245. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_yaml.py +0 -0
  246. {execsql2-2.16.15 → execsql2-2.16.16}/tests/exporters/test_zip.py +0 -0
  247. {execsql2-2.16.15/tests/importers → execsql2-2.16.16/tests/gui}/__init__.py +0 -0
  248. {execsql2-2.16.15 → execsql2-2.16.16}/tests/gui/test_backends.py +0 -0
  249. {execsql2-2.16.15 → execsql2-2.16.16}/tests/gui/test_compare_stats.py +0 -0
  250. {execsql2-2.16.15 → execsql2-2.16.16}/tests/gui/test_compute_row_diffs.py +0 -0
  251. {execsql2-2.16.15/tests/integration → execsql2-2.16.16/tests/importers}/__init__.py +0 -0
  252. {execsql2-2.16.15 → execsql2-2.16.16}/tests/importers/test_base_extended.py +0 -0
  253. {execsql2-2.16.15 → execsql2-2.16.16}/tests/importers/test_csv_edge_cases.py +0 -0
  254. {execsql2-2.16.15 → execsql2-2.16.16}/tests/importers/test_csv_importer.py +0 -0
  255. {execsql2-2.16.15 → execsql2-2.16.16}/tests/importers/test_feather_importer.py +0 -0
  256. {execsql2-2.16.15 → execsql2-2.16.16}/tests/importers/test_json_importer.py +0 -0
  257. {execsql2-2.16.15 → execsql2-2.16.16}/tests/importers/test_ods_importer.py +0 -0
  258. {execsql2-2.16.15 → execsql2-2.16.16}/tests/importers/test_xls_importer.py +0 -0
  259. {execsql2-2.16.15/tests/metacommands → execsql2-2.16.16/tests/integration}/__init__.py +0 -0
  260. {execsql2-2.16.15 → execsql2-2.16.16}/tests/integration/conftest.py +0 -0
  261. {execsql2-2.16.15 → execsql2-2.16.16}/tests/integration/test_dsn.py +0 -0
  262. {execsql2-2.16.15 → execsql2-2.16.16}/tests/integration/test_duckdb.py +0 -0
  263. {execsql2-2.16.15 → execsql2-2.16.16}/tests/integration/test_mysql.py +0 -0
  264. {execsql2-2.16.15 → execsql2-2.16.16}/tests/integration/test_postgres.py +0 -0
  265. {execsql2-2.16.15 → execsql2-2.16.16}/tests/integration/test_sqlite.py +0 -0
  266. {execsql2-2.16.15/tests/scripts → execsql2-2.16.16/tests/metacommands}/__init__.py +0 -0
  267. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_assert.py +0 -0
  268. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_breakpoint.py +0 -0
  269. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_connect.py +0 -0
  270. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_io_export.py +0 -0
  271. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_io_import.py +0 -0
  272. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_metacommands.py +0 -0
  273. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_metacommands_connect.py +0 -0
  274. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_metacommands_data.py +0 -0
  275. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  276. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_metacommands_io.py +0 -0
  277. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  278. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  279. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  280. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_pg_upsert.py +0 -0
  281. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_row_count.py +0 -0
  282. {execsql2-2.16.15 → execsql2-2.16.16}/tests/metacommands/test_show_scripts.py +0 -0
  283. {execsql2-2.16.15/tests/utils → execsql2-2.16.16/tests/scripts}/__init__.py +0 -0
  284. {execsql2-2.16.15 → execsql2-2.16.16}/tests/scripts/fixtures/control_flow.sql +0 -0
  285. {execsql2-2.16.15 → execsql2-2.16.16}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  286. {execsql2-2.16.15 → execsql2-2.16.16}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  287. {execsql2-2.16.15 → execsql2-2.16.16}/tests/scripts/fixtures/smoke.sql +0 -0
  288. {execsql2-2.16.15 → execsql2-2.16.16}/tests/scripts/test_sql_scripts.py +0 -0
  289. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_api.py +0 -0
  290. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_ast.py +0 -0
  291. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_ast_parser.py +0 -0
  292. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_config.py +0 -0
  293. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_config_data.py +0 -0
  294. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_config_extended.py +0 -0
  295. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_debug_repl.py +0 -0
  296. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_engine.py +0 -0
  297. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_error_messages.py +0 -0
  298. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_exceptions.py +0 -0
  299. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_executor.py +0 -0
  300. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_format.py +0 -0
  301. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_mail.py +0 -0
  302. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_models.py +0 -0
  303. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_package.py +0 -0
  304. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_parser.py +0 -0
  305. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_parser_params.py +0 -0
  306. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_plugins.py +0 -0
  307. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_registry.py +0 -0
  308. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_script.py +0 -0
  309. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_state.py +0 -0
  310. {execsql2-2.16.15 → execsql2-2.16.16}/tests/test_types.py +0 -0
  311. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_auth.py +0 -0
  312. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_auth_extra.py +0 -0
  313. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_crypto.py +0 -0
  314. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_datetime.py +0 -0
  315. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_errors.py +0 -0
  316. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_errors_extra.py +0 -0
  317. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_fileio.py +0 -0
  318. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_fileio_extra.py +0 -0
  319. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_numeric.py +0 -0
  320. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_regex.py +0 -0
  321. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_strings.py +0 -0
  322. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_timer.py +0 -0
  323. {execsql2-2.16.15 → execsql2-2.16.16}/tests/utils/test_timer_extra.py +0 -0
  324. {execsql2-2.16.15 → execsql2-2.16.16}/zensical.toml +0 -0
@@ -14,6 +14,20 @@ concurrency:
14
14
  cancel-in-progress: true
15
15
 
16
16
  jobs:
17
+ lint:
18
+ name: lint
19
+ runs-on: ubuntu-latest
20
+ permissions:
21
+ contents: read
22
+ steps:
23
+ - uses: actions/checkout@v6
24
+ - uses: actions/setup-python@v6
25
+ with:
26
+ python-version: "3.13"
27
+ - run: python -m pip install ruff
28
+ - run: ruff check src/ tests/
29
+ - run: ruff format --check src/ tests/
30
+
17
31
  tests:
18
32
  strategy:
19
33
  fail-fast: false
@@ -43,7 +57,8 @@ jobs:
43
57
  uses: actions/cache@v5
44
58
  with:
45
59
  path: ~/.cache/pip
46
- key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }}
60
+ key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{
61
+ hashFiles('**/pyproject.toml') }}
47
62
  restore-keys: |
48
63
  ${{ runner.os }}-pip-${{ matrix.python-version }}-
49
64
  - name: Install dependencies
@@ -86,10 +101,8 @@ jobs:
86
101
  ports:
87
102
  - 5432:5432
88
103
  options: >-
89
- --health-cmd pg_isready
90
- --health-interval 10s
91
- --health-timeout 5s
92
- --health-retries 5
104
+ --health-cmd pg_isready --health-interval 10s
105
+ --health-timeout 5s --health-retries 5
93
106
  mysql:
94
107
  image: mysql:8
95
108
  env:
@@ -100,10 +113,8 @@ jobs:
100
113
  ports:
101
114
  - 3306:3306
102
115
  options: >-
103
- --health-cmd "mysqladmin ping"
104
- --health-interval 10s
105
- --health-timeout 5s
106
- --health-retries 5
116
+ --health-cmd "mysqladmin ping" --health-interval 10s
117
+ --health-timeout 5s --health-retries 5
107
118
  steps:
108
119
  - name: Check out repository code
109
120
  uses: actions/checkout@v6
@@ -122,7 +133,7 @@ jobs:
122
133
  build:
123
134
  name: Build distribution 📦
124
135
  runs-on: ubuntu-latest
125
- needs: [tests, integration-tests]
136
+ needs: [ lint, tests, integration-tests ]
126
137
  if: startsWith(github.ref, 'refs/tags/v')
127
138
  permissions:
128
139
  contents: read
@@ -147,7 +158,7 @@ jobs:
147
158
 
148
159
  publish:
149
160
  name: PyPI Publish 🚀
150
- needs: [build]
161
+ needs: [ build ]
151
162
  if: startsWith(github.ref, 'refs/tags/v')
152
163
  runs-on: ubuntu-latest
153
164
  environment:
@@ -170,7 +181,7 @@ jobs:
170
181
 
171
182
  generate-release:
172
183
  name: Generate GitHub Release
173
- needs: [build]
184
+ needs: [ build ]
174
185
  if: startsWith(github.ref, 'refs/tags/v')
175
186
  runs-on: ubuntu-latest
176
187
  permissions:
@@ -48,6 +48,7 @@ repos:
48
48
  exclude: |
49
49
  (?x)^(
50
50
  \.claude/.*|
51
+ AUDIT\.md|
51
52
  docs/api/.*|
52
53
  docs/reference/metacommands\.md|
53
54
  docs/guides/documentation\.md|
@@ -13,6 +13,25 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.16.16] - 2026-05-02
17
+
18
+ ### Added
19
+
20
+ - `--init-config` CLI flag to print a default `execsql.conf` template (with all options commented out and documented) to stdout. Use `execsql --init-config > execsql.conf` to bootstrap a configuration file.
21
+ - `--no-system-cmd` CLI flag to disable SYSTEM_CMD/SHELL metacommand execution. Scripts that use SHELL will fail with a clear error when this flag is active. Also configurable via `allow_system_cmd = No` in `execsql.conf` `[config]` section. The library API exposes the same control via `allow_system_cmd=False`.
22
+
23
+ ### Changed
24
+
25
+ - CLI `--help` output now logically groups related options: connection, encoding, import/export, execution, GUI, configuration, and information.
26
+ - SYSTEM_CMD now uses `subprocess.run()` instead of the deprecated `subprocess.call()`.
27
+ - `execsql.conf` template updated: added missing options (`use_keyring`, `gui_framework`, `allow_system_cmd`, `log_sql`, `max_log_size_mb`, `show_progress`, `import_progress_interval`, `macos_config_file`), fixed incorrect defaults (`password_prompt`, `new_db`, `scan_lines`), added DuckDB (`k`) to database types, modernized all comments.
28
+
29
+ ### Fixed
30
+
31
+ - SYSTEM_CMD no longer wraps arguments containing `&` in spurious double quotes. The previous behavior (a Windows `cmd.exe` workaround inherited from the upstream monolith) injected literal `"` characters into subprocess arguments, which could cause commands to fail or behave unexpectedly on non-`cmd.exe` targets.
32
+
33
+ ______________________________________________________________________
34
+
16
35
  ## [2.16.15] - 2026-05-02
17
36
 
18
37
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.16.15
3
+ Version: 2.16.16
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
@@ -31,6 +31,8 @@ ______________________________________________________________________
31
31
  | `--lint` | Parse the script and perform static analysis without connecting to a database. Reports unmatched IF/ENDIF, LOOP/END LOOP, and BEGIN BATCH/END BATCH blocks (errors); potentially undefined `!!$VAR!!` references (warnings); missing INCLUDE file targets (warnings); and unknown `EXECUTE SCRIPT` targets (warnings). Variable analysis uses two passes so definition order does not matter. The linter descends into named script blocks reached via `EXECUTE SCRIPT` / `EXEC SCRIPT` / `RUN SCRIPT`, reads `SUB_INI` INI files at lint time, recognizes `SUB_EMPTY` / `SUB_ADD` / `SUB_APPEND` / `SUBDATA` as definitions, suppresses false warnings for `$COUNTER_N`, and auto-discovers built-in system variables from the installed source. Exits 0 if no errors, 1 if errors found. |
32
32
  | `--parse-tree` | Parse the script into an Abstract Syntax Tree and print a visual tree showing block nesting (IF/LOOP/BATCH/SCRIPT), source line ranges, compound conditions (ANDIF/ORIF), and all metacommands. Does not connect to a database or execute anything. Useful for understanding script structure and verifying the parser handles a script correctly. |
33
33
  | `--list-plugins` | List all discovered plugins (metacommands, exporters, importers) from Python entry points and exit. Plugins extend execsql via `execsql.metacommands`, `execsql.exporters`, and `execsql.importers` entry point groups. |
34
+ | `--no-system-cmd` | Disable the `SYSTEM_CMD` (SHELL) metacommand. Scripts that use SHELL will fail with a clear error. Also configurable via `allow_system_cmd = No` in `execsql.conf` `[config]` section, or `allow_system_cmd=False` in the library API. |
35
+ | `--init-config` | Print a default `execsql.conf` template to stdout with all options commented out and documented. Redirect to a file to bootstrap a configuration: `execsql --init-config > execsql.conf`. |
34
36
 
35
37
  ### Export Formats
36
38
 
@@ -15,6 +15,12 @@ Configuration data is read from these files in the order listed above. Informati
15
15
 
16
16
  An explicit configuration file can also be specified with the `--config FILE` command-line option. This file is loaded **after** all four implicit search paths, so its values take precedence over system, user, script-directory, and working-directory config files. CLI arguments still override everything. The `--config` file may chain additional configs via its `[config]` section, just like any other config file.
17
17
 
18
+ To generate a starter configuration file with all options commented out and documented, use:
19
+
20
+ ```bash
21
+ execsql --init-config > execsql.conf
22
+ ```
23
+
18
24
  In addition, *execsql* will read additional configuration files if they are specified in any of the standard configuration files ([see below](#config_config)).
19
25
 
20
26
  Configuration files use the [INI](https://en.wikipedia.org/wiki/INI_file) file format. Section names are case sensitive and must be all in lowercase. Property names are not case sensitive. Property values are read as-is and may or may not be case sensitive, depending on their use. Comments can be included in configuration files; each comment line must start with the "#" character.
@@ -329,6 +335,9 @@ The section and property names that may be used in a configuration file are list
329
335
  `max_log_size_mb` { #max_log_size_mb }
330
336
  : Maximum size of the log file in megabytes before it is rotated. When set to a positive integer, the log file is rotated to `.1` before a new run appends to it if the file size exceeds the configured threshold. The default is `0` (disabled — no rotation).
331
337
 
338
+ `allow_system_cmd` { #allow_system_cmd }
339
+ : When set to "No", the `SYSTEM_CMD` (SHELL) metacommand is disabled. Any script that attempts to execute an OS command will fail with an error. The default is "Yes". This can also be set via the `--no-system-cmd` CLI flag or `allow_system_cmd=False` in the library API. See [Security — Disabling SYSTEM_CMD](security.md#disable_system_cmd) for details.
340
+
332
341
  `log_datavars` { #conf_log_datavars }
333
342
  : A value of 'Yes' or 'No' to control whether data variables that are created by the [SELECT_SUB](metacommands.md#select_sub), [PROMPT SELECT_SUB](metacommands.md#prompt_selsub) and [PROMPT ACTION](metacommands.md#prompt_action) metacommands are written to *execsql*'s [log file](../guides/logging.md#logging). By default, this is set to 'Yes', so that all data variable assignments are logged. The performance of scripts that make extensive use of these metacommands (e.g., [Example 27](../guides/examples.md#example27)) can be improved by setting this to 'No'.
334
343
 
@@ -21,6 +21,36 @@ Variable substitution is applied to the command string before execution, so any
21
21
 
22
22
  If `outdir` is derived from user input, validate or sanitize it before use in a `SYSTEM_CMD` command.
23
23
 
24
+ !!! note "Windows `.bat`/`.cmd` files"
25
+
26
+ On Windows, when the target of a `SYSTEM_CMD` is a `.bat` or `.cmd` file, Windows may invoke `cmd.exe` to interpret it. In this case, the `&` character in arguments can be interpreted as a command separator by `cmd.exe`, even though the subprocess is not launched with `shell=True`. Avoid passing untrusted data containing `&` to batch files on Windows.
27
+
28
+ ### Disabling SYSTEM_CMD { #disable_system_cmd }
29
+
30
+ To prevent scripts from executing OS commands entirely, use the `--no-system-cmd` CLI flag:
31
+
32
+ ```bash
33
+ execsql --no-system-cmd script.sql mydb
34
+ ```
35
+
36
+ Any script that uses `SYSTEM_CMD` or `SHELL` will fail with an error. This is useful for CI pipelines, shared execution environments, or running semi-trusted scripts where shell access is not appropriate.
37
+
38
+ The same restriction can be set permanently in `execsql.conf`:
39
+
40
+ ```ini
41
+ [config]
42
+ allow_system_cmd = No
43
+ ```
44
+
45
+ The library API provides the same control:
46
+
47
+ ```python
48
+ import execsql
49
+ result = execsql.run("script.sql", dsn="sqlite:///my.db", allow_system_cmd=False)
50
+ ```
51
+
52
+ The `--no-system-cmd` CLI flag always takes precedence — if the flag is passed, `SYSTEM_CMD` is disabled regardless of the config file setting.
53
+
24
54
  ## Credential Handling { #credentials }
25
55
 
26
56
  ### Interactive password prompts
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.16.15"
7
+ version = "2.16.16"
8
8
  description = "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."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { file = "LICENSE.txt" }
@@ -165,7 +165,7 @@ skip-magic-trailing-comma = false
165
165
  line-ending = "auto"
166
166
 
167
167
  [tool.bumpversion]
168
- current_version = "2.16.15"
168
+ current_version = "2.16.16"
169
169
  commit = true
170
170
  tag = true
171
171
  tag_name = "v{new_version}"
@@ -316,6 +316,7 @@ def run(
316
316
  encoding: str = "utf-8",
317
317
  halt_on_error: bool = True,
318
318
  new_db: bool = False,
319
+ allow_system_cmd: bool = True,
319
320
  ) -> ScriptResult:
320
321
  """Execute a SQL script and return the result.
321
322
 
@@ -337,6 +338,8 @@ def run(
337
338
  error. If ``False``, capture errors and continue.
338
339
  new_db: If ``True``, create the database if it does not exist
339
340
  (SQLite, PostgreSQL, DuckDB).
341
+ allow_system_cmd: If ``False``, the SYSTEM_CMD (SHELL) metacommand
342
+ is disabled and will raise an error if encountered.
340
343
 
341
344
  Returns:
342
345
  A :class:`ScriptResult` with execution outcome, timing, errors,
@@ -434,6 +437,7 @@ def run(
434
437
  ctx.subvars = subvars
435
438
  ctx.status = StatObj()
436
439
  ctx.status.halt_on_err = halt_on_error
440
+ conf.allow_system_cmd = allow_system_cmd
437
441
  ctx.conf = conf
438
442
 
439
443
  # Capture output to a buffer (suppress stdout/stderr)
@@ -20,7 +20,7 @@ import typer
20
20
 
21
21
  from execsql import __version__
22
22
  from execsql.cli.dsn import _parse_connection_string, _SCHEME_TO_DBTYPE # noqa: F401 — re-export
23
- from execsql.cli.help import _console, _err_console, _print_encodings, _print_metacommands # noqa: F401 — re-export
23
+ from execsql.cli.help import _console, _err_console, _init_config, _print_encodings, _print_metacommands # noqa: F401 — re-export
24
24
  from execsql.cli.run import _connect_initial_db, _run # noqa: F401 — re-export
25
25
  from execsql.exceptions import ConfigError, ErrInfo
26
26
 
@@ -29,6 +29,7 @@ __all__ = [
29
29
  "_connect_initial_db",
30
30
  "_console",
31
31
  "_err_console",
32
+ "_init_config",
32
33
  "_legacy_main",
33
34
  "_parse_connection_string",
34
35
  "_print_encodings",
@@ -72,28 +73,55 @@ def main(
72
73
  "name (client-server DBs) or a database file path (file-based DBs)."
73
74
  ),
74
75
  ),
75
- # Named options — grouped to mirror the original argparse interface
76
- sub_vars: list[str] | None = typer.Option(
76
+ # -- Connection --------------------------------------------------------
77
+ db_type: str | None = typer.Option(
77
78
  None,
78
- "-a",
79
- "--assign-arg",
80
- metavar="VALUE",
81
- help="Define the replacement string for a substitution variable [cyan]\\$ARG_x[/cyan].",
79
+ "-t",
80
+ "--type",
81
+ metavar="{a,d,p,s,l,m,k,o,f}",
82
+ help=(
83
+ "Database type: [bold]a[/bold]=MS-Access, [bold]p[/bold]=PostgreSQL, "
84
+ "[bold]s[/bold]=SQL Server, [bold]l[/bold]=SQLite, [bold]m[/bold]=MySQL/MariaDB, "
85
+ "[bold]k[/bold]=DuckDB, [bold]o[/bold]=Oracle, [bold]f[/bold]=Firebird, "
86
+ "[bold]d[/bold]=DSN."
87
+ ),
82
88
  ),
83
- boolean_int: str | None = typer.Option(
89
+ dsn: str | None = typer.Option(
84
90
  None,
85
- "-b",
86
- "--boolean-int",
87
- metavar="{0,1,t,f,y,n}",
88
- help="Treat integers 0 and 1 as boolean values.",
91
+ "--dsn",
92
+ "--connection-string",
93
+ metavar="URL",
94
+ help=(
95
+ "Database connection URL, e.g. [cyan]postgresql://user:pass@host:5432/db[/cyan]. "
96
+ "Supported schemes: postgresql, mysql, mssql, oracle, firebird, sqlite, duckdb. "
97
+ "Overrides [cyan]-t[/cyan]/[cyan]-u[/cyan]/[cyan]-p[/cyan] and positional server/db args."
98
+ ),
89
99
  ),
90
- make_dirs: str | None = typer.Option(
100
+ user: str | None = typer.Option(
91
101
  None,
92
- "-d",
93
- "--directories",
94
- metavar="{0,1,t,f,y,n}",
95
- help="Auto-create directories for EXPORT metacommand. [dim]n=no (default), y=yes[/dim]",
102
+ "-u",
103
+ "--user",
104
+ help="Database user name.",
96
105
  ),
106
+ port: int | None = typer.Option(
107
+ None,
108
+ "-p",
109
+ "--port",
110
+ help="Database server port.",
111
+ ),
112
+ no_passwd: bool = typer.Option(
113
+ False,
114
+ "-w",
115
+ "--no-passwd",
116
+ help="Skip password prompt when user is specified.",
117
+ ),
118
+ new_db: bool = typer.Option(
119
+ False,
120
+ "-n",
121
+ "--new-db",
122
+ help="Create a new SQLite or Postgres database if it does not exist.",
123
+ ),
124
+ # -- Encoding ----------------------------------------------------------
97
125
  database_encoding: str | None = typer.Option(
98
126
  None,
99
127
  "-e",
@@ -118,35 +146,13 @@ def main(
118
146
  "--import-encoding",
119
147
  help="Encoding for data files used with IMPORT.",
120
148
  ),
121
- user_logfile: bool = typer.Option(
122
- False,
123
- "-l",
124
- "--user-logfile",
125
- help="Write a log file to [cyan]~/execsql.log[/cyan].",
126
- ),
127
- metacommands: bool = typer.Option(
128
- False,
129
- "-m",
130
- "--metacommands",
131
- help="List metacommands and exit.",
132
- ),
133
- new_db: bool = typer.Option(
134
- False,
135
- "-n",
136
- "--new-db",
137
- help="Create a new SQLite or Postgres database if it does not exist.",
138
- ),
139
- online_help: bool = typer.Option(
140
- False,
141
- "-o",
142
- "--online-help",
143
- help="Open the online documentation in the default browser.",
144
- ),
145
- port: int | None = typer.Option(
149
+ # -- Import/Export -----------------------------------------------------
150
+ import_buffer: int | None = typer.Option(
146
151
  None,
147
- "-p",
148
- "--port",
149
- help="Database server port.",
152
+ "-z",
153
+ "--import-buffer",
154
+ metavar="KB",
155
+ help="Import buffer size in KB. [dim]Default: 32[/dim]",
150
156
  ),
151
157
  scanlines: int | None = typer.Option(
152
158
  None,
@@ -155,59 +161,36 @@ def main(
155
161
  metavar="N",
156
162
  help="Lines to scan for IMPORT format detection. [dim]0 = scan entire file.[/dim]",
157
163
  ),
158
- db_type: str | None = typer.Option(
164
+ boolean_int: str | None = typer.Option(
159
165
  None,
160
- "-t",
161
- "--type",
162
- metavar="{a,d,p,s,l,m,k,o,f}",
163
- help=(
164
- "Database type: [bold]a[/bold]=MS-Access, [bold]p[/bold]=PostgreSQL, "
165
- "[bold]s[/bold]=SQL Server, [bold]l[/bold]=SQLite, [bold]m[/bold]=MySQL/MariaDB, "
166
- "[bold]k[/bold]=DuckDB, [bold]o[/bold]=Oracle, [bold]f[/bold]=Firebird, "
167
- "[bold]d[/bold]=DSN."
168
- ),
166
+ "-b",
167
+ "--boolean-int",
168
+ metavar="{0,1,t,f,y,n}",
169
+ help="Treat integers 0 and 1 as boolean values.",
169
170
  ),
170
- user: str | None = typer.Option(
171
+ make_dirs: str | None = typer.Option(
171
172
  None,
172
- "-u",
173
- "--user",
174
- help="Database user name.",
173
+ "-d",
174
+ "--directories",
175
+ metavar="{0,1,t,f,y,n}",
176
+ help="Auto-create directories for EXPORT metacommand. [dim]n=no (default), y=yes[/dim]",
175
177
  ),
176
- use_gui: str | None = typer.Option(
178
+ output_dir: str | None = typer.Option(
177
179
  None,
178
- "-v",
179
- "--visible-prompts",
180
- metavar="{0,1,2,3}",
180
+ "--output-dir",
181
+ metavar="DIR",
181
182
  help=(
182
- "GUI level: [bold]0[/bold]=none (default), [bold]1[/bold]=GUI for password/pause, "
183
- "[bold]2[/bold]=GUI for password/pause + DB selection, [bold]3[/bold]=full GUI console."
183
+ "Default base directory for EXPORT output files. "
184
+ "Relative paths in EXPORT metacommands are joined to this directory. "
185
+ "Absolute paths and [cyan]stdout[/cyan] are unaffected."
184
186
  ),
185
187
  ),
186
- gui_framework: str | None = typer.Option(
187
- None,
188
- "--gui-framework",
189
- metavar="{tkinter,textual}",
190
- help="GUI framework to use with [cyan]--visible-prompts[/cyan]. [dim]Default: tkinter[/dim]",
191
- ),
192
- no_passwd: bool = typer.Option(
193
- False,
194
- "-w",
195
- "--no-passwd",
196
- help="Skip password prompt when user is specified.",
197
- ),
198
- encodings: bool = typer.Option(
188
+ progress: bool = typer.Option(
199
189
  False,
200
- "-y",
201
- "--encodings",
202
- help="List available encoding names and exit.",
203
- ),
204
- import_buffer: int | None = typer.Option(
205
- None,
206
- "-z",
207
- "--import-buffer",
208
- metavar="KB",
209
- help="Import buffer size in KB. [dim]Default: 32[/dim]",
190
+ "--progress",
191
+ help="Show a progress bar for long-running IMPORT operations.",
210
192
  ),
193
+ # -- Execution ---------------------------------------------------------
211
194
  command: str | None = typer.Option(
212
195
  None,
213
196
  "-c",
@@ -221,7 +204,7 @@ def main(
221
204
  dry_run: bool = typer.Option(
222
205
  False,
223
206
  "--dry-run",
224
- help=("Parse the script and print the command list without connecting to a database or executing anything."),
207
+ help="Parse the script and print the command list without connecting to a database or executing anything.",
225
208
  ),
226
209
  lint: bool = typer.Option(
227
210
  False,
@@ -232,51 +215,23 @@ def main(
232
215
  "and missing INCLUDE files (warnings). Exits 0 if no errors, 1 if errors found."
233
216
  ),
234
217
  ),
235
- ping: bool = typer.Option(
218
+ parse_tree: bool = typer.Option(
236
219
  False,
237
- "--ping",
238
- help=(
239
- "Test database connectivity and exit. "
240
- "Prints connection details and the server version on success (exit 0), "
241
- "or the error message on failure (exit 1). "
242
- "No script file is required."
243
- ),
244
- ),
245
- dsn: str | None = typer.Option(
246
- None,
247
- "--dsn",
248
- "--connection-string",
249
- metavar="URL",
250
- help=(
251
- "Database connection URL, e.g. [cyan]postgresql://user:pass@host:5432/db[/cyan]. "
252
- "Supported schemes: postgresql, mysql, mssql, oracle, firebird, sqlite, duckdb. "
253
- "Overrides [cyan]-t[/cyan]/[cyan]-u[/cyan]/[cyan]-p[/cyan] and positional server/db args."
254
- ),
255
- ),
256
- output_dir: str | None = typer.Option(
257
- None,
258
- "--output-dir",
259
- metavar="DIR",
220
+ "--parse-tree",
260
221
  help=(
261
- "Default base directory for EXPORT output files. "
262
- "Relative paths in EXPORT metacommands are joined to this directory. "
263
- "Absolute paths and [cyan]stdout[/cyan] are unaffected."
222
+ "Parse the script into an abstract syntax tree and print the tree structure. "
223
+ "Does not connect to a database or execute anything."
264
224
  ),
265
225
  ),
266
- progress: bool = typer.Option(
267
- False,
268
- "--progress",
269
- help="Show a progress bar for long-running IMPORT operations.",
270
- ),
271
- dump_keywords: bool = typer.Option(
226
+ debug: bool = typer.Option(
272
227
  False,
273
- "--dump-keywords",
274
- help="Dump all metacommand keywords as JSON and exit.",
228
+ "--debug",
229
+ help="Start in step-through debug mode. The debug REPL pauses before each statement.",
275
230
  ),
276
- list_plugins: bool = typer.Option(
231
+ no_system_cmd: bool = typer.Option(
277
232
  False,
278
- "--list-plugins",
279
- help="List all discovered plugins (metacommands, exporters, importers) and exit.",
233
+ "--no-system-cmd",
234
+ help="Disable the SYSTEM_CMD (SHELL) metacommand. Scripts that use SHELL will fail with an error.",
280
235
  ),
281
236
  profile: bool = typer.Option(
282
237
  False,
@@ -288,6 +243,24 @@ def main(
288
243
  "--profile-limit",
289
244
  help="Number of top statements to show in the --profile timing summary (default: 20).",
290
245
  ),
246
+ # -- GUI ---------------------------------------------------------------
247
+ use_gui: str | None = typer.Option(
248
+ None,
249
+ "-v",
250
+ "--visible-prompts",
251
+ metavar="{0,1,2,3}",
252
+ help=(
253
+ "GUI level: [bold]0[/bold]=none (default), [bold]1[/bold]=GUI for password/pause, "
254
+ "[bold]2[/bold]=GUI for password/pause + DB selection, [bold]3[/bold]=full GUI console."
255
+ ),
256
+ ),
257
+ gui_framework: str | None = typer.Option(
258
+ None,
259
+ "--gui-framework",
260
+ metavar="{tkinter,textual}",
261
+ help="GUI framework to use with [cyan]--visible-prompts[/cyan]. [dim]Default: tkinter[/dim]",
262
+ ),
263
+ # -- Configuration -----------------------------------------------------
291
264
  config_file: str | None = typer.Option(
292
265
  None,
293
266
  "--config",
@@ -298,18 +271,62 @@ def main(
298
271
  "The file may chain additional configs via its [cyan][config][/cyan] section."
299
272
  ),
300
273
  ),
301
- parse_tree: bool = typer.Option(
274
+ init_config: bool = typer.Option(
302
275
  False,
303
- "--parse-tree",
276
+ "--init-config",
277
+ help="Print a default [cyan]execsql.conf[/cyan] template to stdout and exit.",
278
+ ),
279
+ sub_vars: list[str] | None = typer.Option(
280
+ None,
281
+ "-a",
282
+ "--assign-arg",
283
+ metavar="VALUE",
284
+ help="Define the replacement string for a substitution variable [cyan]\\$ARG_x[/cyan].",
285
+ ),
286
+ user_logfile: bool = typer.Option(
287
+ False,
288
+ "-l",
289
+ "--user-logfile",
290
+ help="Write a log file to [cyan]~/execsql.log[/cyan].",
291
+ ),
292
+ # -- Information -------------------------------------------------------
293
+ metacommands: bool = typer.Option(
294
+ False,
295
+ "-m",
296
+ "--metacommands",
297
+ help="List metacommands and exit.",
298
+ ),
299
+ encodings: bool = typer.Option(
300
+ False,
301
+ "-y",
302
+ "--encodings",
303
+ help="List available encoding names and exit.",
304
+ ),
305
+ dump_keywords: bool = typer.Option(
306
+ False,
307
+ "--dump-keywords",
308
+ help="Dump all metacommand keywords as JSON and exit.",
309
+ ),
310
+ list_plugins: bool = typer.Option(
311
+ False,
312
+ "--list-plugins",
313
+ help="List all discovered plugins (metacommands, exporters, importers) and exit.",
314
+ ),
315
+ ping: bool = typer.Option(
316
+ False,
317
+ "--ping",
304
318
  help=(
305
- "Parse the script into an abstract syntax tree and print the tree structure. "
306
- "Does not connect to a database or execute anything."
319
+ "Test database connectivity and exit. "
320
+ "Prints connection details and the server version on success (exit 0), "
321
+ "or the error message on failure (exit 1). "
322
+ "No script file is required."
307
323
  ),
308
324
  ),
309
- debug: bool = typer.Option(
325
+ online_help: bool = typer.Option(
310
326
  False,
311
- "--debug",
312
- help="Start in step-through debug mode. The debug REPL pauses before each statement.",
327
+ "-o",
328
+ "--online-help",
329
+ help="Open the online documentation in the default browser.",
313
330
  ),
314
331
  version: bool | None = typer.Option(
315
332
  None,
@@ -340,6 +357,10 @@ def main(
340
357
  _print_encodings()
341
358
  raise typer.Exit()
342
359
 
360
+ if init_config:
361
+ _init_config()
362
+ raise typer.Exit()
363
+
343
364
  if dump_keywords:
344
365
  import json as _json
345
366
 
@@ -580,6 +601,7 @@ def main(
580
601
  ping=ping,
581
602
  lint=lint,
582
603
  debug=debug,
604
+ no_system_cmd=no_system_cmd,
583
605
  config_file=config_file,
584
606
  )
585
607
 
@@ -11,7 +11,7 @@ from encodings.aliases import aliases as codec_dict
11
11
  from rich.console import Console
12
12
  from rich.table import Table
13
13
 
14
- __all__ = ["_console", "_err_console", "_print_encodings", "_print_metacommands"]
14
+ __all__ = ["_console", "_err_console", "_init_config", "_print_encodings", "_print_metacommands"]
15
15
 
16
16
  _console = Console()
17
17
  _err_console = Console(stderr=True)
@@ -77,6 +77,15 @@ _SKIP_FROM_DISPATCH = {
77
77
  }
78
78
 
79
79
 
80
+ def _init_config() -> None:
81
+ """Print the default execsql.conf template to stdout."""
82
+ import importlib.resources
83
+ import sys
84
+
85
+ template = importlib.resources.files("execsql.data").joinpath("execsql.conf.template").read_text(encoding="utf-8")
86
+ sys.stdout.write(template)
87
+
88
+
80
89
  def _print_metacommands() -> None:
81
90
  """Print the metacommands table using Rich.
82
91