execsql2 2.5.0__tar.gz → 2.7.1__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 (288) hide show
  1. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/project_context.md +107 -19
  2. {execsql2-2.5.0 → execsql2-2.7.1}/.gitignore +1 -0
  3. {execsql2-2.5.0 → execsql2-2.7.1}/CHANGELOG.md +39 -1
  4. {execsql2-2.5.0 → execsql2-2.7.1}/CLAUDE.md +1 -0
  5. {execsql2-2.5.0 → execsql2-2.7.1}/PKG-INFO +5 -2
  6. {execsql2-2.5.0 → execsql2-2.7.1}/README.md +1 -1
  7. execsql2-2.7.1/docs/about/divergence.md +171 -0
  8. {execsql2-2.5.0 → execsql2-2.7.1}/docs/reference/metacommands.md +16 -1
  9. {execsql2-2.5.0 → execsql2-2.7.1}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +1 -1
  10. {execsql2-2.5.0 → execsql2-2.7.1}/pyproject.toml +8 -4
  11. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/__init__.py +3 -3
  12. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/delimited.py +2 -2
  13. execsql2-2.7.1/src/execsql/exporters/markdown.py +126 -0
  14. execsql2-2.7.1/src/execsql/exporters/xlsx.py +317 -0
  15. execsql2-2.7.1/src/execsql/exporters/yaml.py +87 -0
  16. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/gui/tui.py +132 -0
  17. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/__init__.py +203 -182
  18. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/dispatch.py +11 -0
  19. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/io.py +2 -0
  20. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/io_export.py +75 -0
  21. execsql2-2.7.1/src/execsql/state.py +452 -0
  22. {execsql2-2.5.0 → execsql2-2.7.1}/tests/conftest.py +2 -0
  23. execsql2-2.7.1/tests/db/test_base.py +1953 -0
  24. execsql2-2.7.1/tests/exporters/test_delimited.py +1242 -0
  25. execsql2-2.7.1/tests/exporters/test_markdown.py +302 -0
  26. execsql2-2.7.1/tests/exporters/test_xlsx.py +419 -0
  27. execsql2-2.7.1/tests/exporters/test_yaml.py +140 -0
  28. execsql2-2.7.1/tests/metacommands/test_connect.py +1807 -0
  29. execsql2-2.7.1/tests/test_engine.py +1305 -0
  30. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_state.py +109 -3
  31. {execsql2-2.5.0 → execsql2-2.7.1}/uv.lock +5 -1
  32. {execsql2-2.5.0 → execsql2-2.7.1}/zensical.toml +1 -0
  33. execsql2-2.5.0/src/execsql/state.py +0 -391
  34. execsql2-2.5.0/tests/db/test_base.py +0 -423
  35. execsql2-2.5.0/tests/exporters/test_delimited.py +0 -460
  36. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/dba.md +0 -0
  37. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/herald.md +0 -0
  38. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/inspector.md +0 -0
  39. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/oracle.md +0 -0
  40. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/patcher.md +0 -0
  41. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/qa.md +0 -0
  42. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/agents/scribe.md +0 -0
  43. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/code-oracle.md +0 -0
  44. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/migrate.md +0 -0
  45. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/review-changes.md +0 -0
  46. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/test-module.md +0 -0
  47. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/update-changelog.md +0 -0
  48. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/commands/where-is.md +0 -0
  49. {execsql2-2.5.0 → execsql2-2.7.1}/.claude/state/status.md +0 -0
  50. {execsql2-2.5.0 → execsql2-2.7.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  51. {execsql2-2.5.0 → execsql2-2.7.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  52. {execsql2-2.5.0 → execsql2-2.7.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  53. {execsql2-2.5.0 → execsql2-2.7.1}/.github/workflows/ci-cd.yml +0 -0
  54. {execsql2-2.5.0 → execsql2-2.7.1}/.pre-commit-config.yaml +0 -0
  55. {execsql2-2.5.0 → execsql2-2.7.1}/.pre-commit-hooks.yaml +0 -0
  56. {execsql2-2.5.0 → execsql2-2.7.1}/.python-version +0 -0
  57. {execsql2-2.5.0 → execsql2-2.7.1}/.readthedocs.yaml +0 -0
  58. {execsql2-2.5.0 → execsql2-2.7.1}/CONTRIBUTING.md +0 -0
  59. {execsql2-2.5.0 → execsql2-2.7.1}/LICENSE.txt +0 -0
  60. {execsql2-2.5.0 → execsql2-2.7.1}/NOTICE +0 -0
  61. {execsql2-2.5.0 → execsql2-2.7.1}/SECURITY.md +0 -0
  62. {execsql2-2.5.0 → execsql2-2.7.1}/docs/about/contributors.md +0 -0
  63. {execsql2-2.5.0 → execsql2-2.7.1}/docs/about/copyright.md +0 -0
  64. {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/cli.md +0 -0
  65. {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/db.md +0 -0
  66. {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/exporters.md +0 -0
  67. {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/importers.md +0 -0
  68. {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/index.md +0 -0
  69. {execsql2-2.5.0 → execsql2-2.7.1}/docs/api/metacommands.md +0 -0
  70. {execsql2-2.5.0 → execsql2-2.7.1}/docs/dev/adding_db_adapters.md +0 -0
  71. {execsql2-2.5.0 → execsql2-2.7.1}/docs/dev/adding_exporters.md +0 -0
  72. {execsql2-2.5.0 → execsql2-2.7.1}/docs/dev/adding_importers.md +0 -0
  73. {execsql2-2.5.0 → execsql2-2.7.1}/docs/dev/adding_metacommands.md +0 -0
  74. {execsql2-2.5.0 → execsql2-2.7.1}/docs/dev/architecture.md +0 -0
  75. {execsql2-2.5.0 → execsql2-2.7.1}/docs/getting-started/installation.md +0 -0
  76. {execsql2-2.5.0 → execsql2-2.7.1}/docs/getting-started/requirements.md +0 -0
  77. {execsql2-2.5.0 → execsql2-2.7.1}/docs/getting-started/syntax.md +0 -0
  78. {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/debugging.md +0 -0
  79. {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/documentation.md +0 -0
  80. {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/encoding.md +0 -0
  81. {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/examples.md +0 -0
  82. {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/formatter.md +0 -0
  83. {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/logging.md +0 -0
  84. {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/sql_syntax.md +0 -0
  85. {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/usage.md +0 -0
  86. {execsql2-2.5.0 → execsql2-2.7.1}/docs/guides/using_scripts.md +0 -0
  87. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/Compare_planets.png +0 -0
  88. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/actions.png +0 -0
  89. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/actions2.png +0 -0
  90. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/checkboxes.png +0 -0
  91. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/connect.b64 +0 -0
  92. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/connect.png +0 -0
  93. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/create_conf.png +0 -0
  94. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/data_error1_screenshot.jpg +0 -0
  95. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/entry_form.png +0 -0
  96. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/execsql_console.png +0 -0
  97. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/execsql_logo_01.png +0 -0
  98. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/fatals.png +0 -0
  99. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/logo_small.png +0 -0
  100. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/pause_terminal.png +0 -0
  101. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/pause_terminal_sm.b64 +0 -0
  102. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/pause_terminal_sm.png +0 -0
  103. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/prompt_compare.png +0 -0
  104. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/set_build_commands.jpg +0 -0
  105. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/unit_conversions.b64 +0 -0
  106. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/unit_conversions_029.png +0 -0
  107. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/unmatched.png +0 -0
  108. {execsql2-2.5.0 → execsql2-2.7.1}/docs/images/vim_execsql_highlight.png +0 -0
  109. {execsql2-2.5.0 → execsql2-2.7.1}/docs/index.md +0 -0
  110. {execsql2-2.5.0 → execsql2-2.7.1}/docs/reference/configuration.md +0 -0
  111. {execsql2-2.5.0 → execsql2-2.7.1}/docs/reference/security.md +0 -0
  112. {execsql2-2.5.0 → execsql2-2.7.1}/docs/reference/substitution_vars.md +0 -0
  113. {execsql2-2.5.0 → execsql2-2.7.1}/extras/vscode-execsql/README.md +0 -0
  114. {execsql2-2.5.0 → execsql2-2.7.1}/extras/vscode-execsql/package.json +0 -0
  115. {execsql2-2.5.0 → execsql2-2.7.1}/justfile +0 -0
  116. {execsql2-2.5.0 → execsql2-2.7.1}/scripts/generate_vscode_grammar.py +0 -0
  117. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/__init__.py +0 -0
  118. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/__main__.py +0 -0
  119. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/cli/__init__.py +0 -0
  120. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/cli/dsn.py +0 -0
  121. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/cli/help.py +0 -0
  122. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/cli/run.py +0 -0
  123. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/config.py +0 -0
  124. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/constants.py +0 -0
  125. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/__init__.py +0 -0
  126. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/access.py +0 -0
  127. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/base.py +0 -0
  128. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/dsn.py +0 -0
  129. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/duckdb.py +0 -0
  130. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/factory.py +0 -0
  131. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/firebird.py +0 -0
  132. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/mysql.py +0 -0
  133. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/oracle.py +0 -0
  134. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/postgres.py +0 -0
  135. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/sqlite.py +0 -0
  136. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/db/sqlserver.py +0 -0
  137. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exceptions.py +0 -0
  138. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/base.py +0 -0
  139. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/duckdb.py +0 -0
  140. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/feather.py +0 -0
  141. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/html.py +0 -0
  142. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/json.py +0 -0
  143. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/latex.py +0 -0
  144. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/ods.py +0 -0
  145. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/parquet.py +0 -0
  146. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/pretty.py +0 -0
  147. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/protocol.py +0 -0
  148. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/raw.py +0 -0
  149. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/sqlite.py +0 -0
  150. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/templates.py +0 -0
  151. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/values.py +0 -0
  152. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/xls.py +0 -0
  153. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/xml.py +0 -0
  154. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/exporters/zip.py +0 -0
  155. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/format.py +0 -0
  156. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/gui/__init__.py +0 -0
  157. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/gui/base.py +0 -0
  158. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/gui/console.py +0 -0
  159. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/gui/desktop.py +0 -0
  160. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/__init__.py +0 -0
  161. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/base.py +0 -0
  162. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/csv.py +0 -0
  163. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/feather.py +0 -0
  164. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/ods.py +0 -0
  165. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/importers/xls.py +0 -0
  166. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/conditions.py +0 -0
  167. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/connect.py +0 -0
  168. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/control.py +0 -0
  169. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/data.py +0 -0
  170. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/debug.py +0 -0
  171. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/io_fileops.py +0 -0
  172. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/io_import.py +0 -0
  173. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/io_write.py +0 -0
  174. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/prompt.py +0 -0
  175. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/script_ext.py +0 -0
  176. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/metacommands/system.py +0 -0
  177. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/models.py +0 -0
  178. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/parser.py +0 -0
  179. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/py.typed +0 -0
  180. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/script/__init__.py +0 -0
  181. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/script/control.py +0 -0
  182. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/script/engine.py +0 -0
  183. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/script/variables.py +0 -0
  184. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/types.py +0 -0
  185. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/__init__.py +0 -0
  186. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/auth.py +0 -0
  187. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/crypto.py +0 -0
  188. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/datetime.py +0 -0
  189. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/errors.py +0 -0
  190. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/fileio.py +0 -0
  191. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/gui.py +0 -0
  192. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/mail.py +0 -0
  193. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/numeric.py +0 -0
  194. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/regex.py +0 -0
  195. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/strings.py +0 -0
  196. {execsql2-2.5.0 → execsql2-2.7.1}/src/execsql/utils/timer.py +0 -0
  197. {execsql2-2.5.0 → execsql2-2.7.1}/templates/README.md +0 -0
  198. {execsql2-2.5.0 → execsql2-2.7.1}/templates/config_settings.sqlite +0 -0
  199. {execsql2-2.5.0 → execsql2-2.7.1}/templates/example_config_prompt.sql +0 -0
  200. {execsql2-2.5.0 → execsql2-2.7.1}/templates/execsql.conf +0 -0
  201. {execsql2-2.5.0 → execsql2-2.7.1}/templates/make_config_db.sql +0 -0
  202. {execsql2-2.5.0 → execsql2-2.7.1}/templates/md_compare.sql +0 -0
  203. {execsql2-2.5.0 → execsql2-2.7.1}/templates/md_glossary.sql +0 -0
  204. {execsql2-2.5.0 → execsql2-2.7.1}/templates/md_upsert.sql +0 -0
  205. {execsql2-2.5.0 → execsql2-2.7.1}/templates/pg_compare.sql +0 -0
  206. {execsql2-2.5.0 → execsql2-2.7.1}/templates/pg_glossary.sql +0 -0
  207. {execsql2-2.5.0 → execsql2-2.7.1}/templates/pg_upsert.sql +0 -0
  208. {execsql2-2.5.0 → execsql2-2.7.1}/templates/script_template.sql +0 -0
  209. {execsql2-2.5.0 → execsql2-2.7.1}/templates/ss_compare.sql +0 -0
  210. {execsql2-2.5.0 → execsql2-2.7.1}/templates/ss_glossary.sql +0 -0
  211. {execsql2-2.5.0 → execsql2-2.7.1}/templates/ss_upsert.sql +0 -0
  212. {execsql2-2.5.0 → execsql2-2.7.1}/tests/__init__.py +0 -0
  213. {execsql2-2.5.0 → execsql2-2.7.1}/tests/cli/__init__.py +0 -0
  214. {execsql2-2.5.0 → execsql2-2.7.1}/tests/cli/test_cli.py +0 -0
  215. {execsql2-2.5.0 → execsql2-2.7.1}/tests/cli/test_cli_e2e.py +0 -0
  216. {execsql2-2.5.0 → execsql2-2.7.1}/tests/cli/test_cli_run.py +0 -0
  217. {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/__init__.py +0 -0
  218. {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/test_duckdb.py +0 -0
  219. {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/test_factory.py +0 -0
  220. {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/test_postgres.py +0 -0
  221. {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/test_sqlite.py +0 -0
  222. {execsql2-2.5.0 → execsql2-2.7.1}/tests/db/test_sqlite_extra.py +0 -0
  223. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/__init__.py +0 -0
  224. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_base.py +0 -0
  225. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_db.py +0 -0
  226. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_duckdb_exporter.py +0 -0
  227. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_exporters.py +0 -0
  228. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_feather.py +0 -0
  229. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_html_latex.py +0 -0
  230. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_json.py +0 -0
  231. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_ods.py +0 -0
  232. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_parquet.py +0 -0
  233. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_sqlite_exporter.py +0 -0
  234. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_templates.py +0 -0
  235. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_xls_xlsx.py +0 -0
  236. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_xml.py +0 -0
  237. {execsql2-2.5.0 → execsql2-2.7.1}/tests/exporters/test_zip.py +0 -0
  238. {execsql2-2.5.0 → execsql2-2.7.1}/tests/gui/__init__.py +0 -0
  239. {execsql2-2.5.0 → execsql2-2.7.1}/tests/gui/test_backends.py +0 -0
  240. {execsql2-2.5.0 → execsql2-2.7.1}/tests/importers/__init__.py +0 -0
  241. {execsql2-2.5.0 → execsql2-2.7.1}/tests/importers/test_csv_importer.py +0 -0
  242. {execsql2-2.5.0 → execsql2-2.7.1}/tests/importers/test_feather_importer.py +0 -0
  243. {execsql2-2.5.0 → execsql2-2.7.1}/tests/importers/test_ods_importer.py +0 -0
  244. {execsql2-2.5.0 → execsql2-2.7.1}/tests/importers/test_xls_importer.py +0 -0
  245. {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/__init__.py +0 -0
  246. {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/conftest.py +0 -0
  247. {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/test_dsn.py +0 -0
  248. {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/test_duckdb.py +0 -0
  249. {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/test_mysql.py +0 -0
  250. {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/test_postgres.py +0 -0
  251. {execsql2-2.5.0 → execsql2-2.7.1}/tests/integration/test_sqlite.py +0 -0
  252. {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/__init__.py +0 -0
  253. {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands.py +0 -0
  254. {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_connect.py +0 -0
  255. {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_data.py +0 -0
  256. {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_extended.py +0 -0
  257. {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  258. {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_io.py +0 -0
  259. {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  260. {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  261. {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_system.py +0 -0
  262. {execsql2-2.5.0 → execsql2-2.7.1}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  263. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_config.py +0 -0
  264. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_config_data.py +0 -0
  265. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_constants.py +0 -0
  266. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_exceptions.py +0 -0
  267. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_format.py +0 -0
  268. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_mail.py +0 -0
  269. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_models.py +0 -0
  270. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_package.py +0 -0
  271. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_parser.py +0 -0
  272. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_registry.py +0 -0
  273. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_script.py +0 -0
  274. {execsql2-2.5.0 → execsql2-2.7.1}/tests/test_types.py +0 -0
  275. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/__init__.py +0 -0
  276. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_auth.py +0 -0
  277. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_auth_extra.py +0 -0
  278. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_crypto.py +0 -0
  279. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_datetime.py +0 -0
  280. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_errors.py +0 -0
  281. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_errors_extra.py +0 -0
  282. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_fileio.py +0 -0
  283. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_fileio_extra.py +0 -0
  284. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_numeric.py +0 -0
  285. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_regex.py +0 -0
  286. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_strings.py +0 -0
  287. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_timer.py +0 -0
  288. {execsql2-2.5.0 → execsql2-2.7.1}/tests/utils/test_timer_extra.py +0 -0
@@ -189,10 +189,10 @@ Triggered on: push to `main`, any tag `v*.*.*`, pull requests.
189
189
 
190
190
  ## Versioning
191
191
 
192
- `bump-my-version` manages versions. Current: `2.4.6`. Bump commands:
192
+ `bump-my-version` manages versions. Current: `2.5.0`. Bump commands:
193
193
 
194
- - `just bump-patch` → 2.4.6 → 2.4.7
195
- - `just bump-minor` → 2.4.6 → 2.5.0
194
+ - `just bump-patch` → 2.5.0 → 2.5.1
195
+ - `just bump-minor` → 2.5.0 → 2.6.0
196
196
  Bumps commit + tag. Pre-commit hook runs `uv lock` + stages `uv.lock`.
197
197
 
198
198
  ## Ruff Config
@@ -243,44 +243,132 @@ the foreseeable future.
243
243
  | Docs reorganized (getting-started/reference/guides/about) | 2.4.6 | 2026-03 |
244
244
  | Pre-commit hook for `execsql-format` | 2.4.3 | 2026-03 |
245
245
  | VS Code syntax highlighting extension | 2.4.x | 2026-03 |
246
+ | Docstring coverage 81% on public API | 2.5.0 | 2026-04 |
247
+ | Developer architecture guide with Mermaid diagrams | 2.5.0 | 2026-04 |
248
+ | Cursor context managers (exec_cmd + vacuum) | 2.5.0 | 2026-04 |
249
+ | Exporter Protocol types (QueryExporter, RowsetExporter) | 2.5.0 | 2026-04 |
250
+ | SubVarSet.merge() optimization (O(1) vs O(V)) | 2.5.0 | 2026-04 |
251
+ | GitHub Actions upgraded to Node.js 24 | 2.5.0 | 2026-04 |
252
+ | GitHub issue/PR templates + SECURITY.md | 2.5.0 | 2026-04 |
253
+ | Database ABC already in place (verified) | 2.5.0 | 2026-04 |
254
+ | Dispatch optimization already in place (verified) | 2.5.0 | 2026-04 |
255
+ | PostgreSQL integration tests (9 tests, CI Docker) | 2.5.0 | 2026-04 |
256
+ | MySQL integration tests (9 tests, CI Docker) | 2.5.0 | 2026-04 |
246
257
 
247
258
  ______________________________________________________________________
248
259
 
249
- ### v2.5Remaining Code Quality
260
+ ### v2.6Architecture & Internal Quality
250
261
 
251
- Theme: Complete the remaining v2.2/v2.3 items and continue stabilization.
262
+ - [x] **`state.py` `RuntimeContext` refactor** — 33 mutable globals consolidated into a slotted `RuntimeContext` class with transparent module proxy. `get_context()`/`set_context()` API added. Zero external call-site changes.
263
+ - [x] **`noqa` cleanup in `metacommands/__init__.py`** — removed all 180 redundant `# noqa` comments; `__all__` already satisfies ruff F401.
264
+ - [x] **Coverage push to 86%** — 403 new tests (3010 total) covering `db/base.py` (55→99%), `metacommands/connect.py` (36→100%), `script/engine.py` (77→95%), `exporters/delimited.py` (76→95%). Remaining gap to 90% is in GUI, ODS, and import handlers.
252
265
 
253
- - **Docstring coverage** target 50%+ on public API (focus on `exporters/`, `db/`, `config.py`)
266
+ ### v2.7New Export/Import Formats
254
267
 
255
- ______________________________________________________________________
268
+ - [x] **Parquet import** — already existed as `IMPORT TO table FROM PARQUET file` (verified present).
269
+ - [x] **YAML export** — `FORMAT YAML` via PyYAML, list-of-dicts with native type preservation.
270
+ - [x] **Markdown (GFM) export** — `FORMAT MARKDOWN` / `MD`, pipe tables with alignment and escaping.
271
+ - [x] **Excel (XLSX) multi-sheet export** — `FORMAT XLSX` single + multi-sheet via openpyxl, bold headers, inventory sheet, sheet name deduplication.
256
272
 
257
- ### v2.6Architecture & Performance
273
+ ### v2.8Scripting Power Features
258
274
 
259
- Theme: Internal modernization. May include backward-incompatible internal API changes (public CLI behavior preserved).
275
+ - [ ] **`ASSERT` metacommand** `-- !x! ASSERT <condition> "message"`. Data validation for CI pipelines and sanity checks.
276
+ - [ ] **`--dry-run` improvements** — show SQL with substitution variables expanded, not just raw metacommands.
277
+ - [ ] **Script profiling (`--profile`)** — per-statement execution times, summary report at end. Leverages existing `Timer` infrastructure.
278
+ - [ ] **Parallel execution blocks** — `PARALLEL BEGIN ... PARALLEL END` for independent statements. See design notes below.
260
279
 
261
- - **Database ABC with `@abstractmethod`** replace runtime `DatabaseNotImplementedError` with true abstract methods
262
- - **Cursor context managers** — explicit cursor lifecycle in `execute()`, `select_data()`, `select_rowsource()`
263
- - **Metacommand dispatch optimization** — consider dict/trie lookup instead of O(N) regex scan
264
- - **Variable substitution optimization** — reduce O(V×D) complexity
265
- - **Exporter protocol** — define a `Protocol` or ABC for exporters with a common interface
280
+ ### v2.9Library API & Developer Experience
266
281
 
267
- ______________________________________________________________________
282
+ - [ ] **Programmatic Python API** — `execsql.run(script, db=...)` for notebook/pipeline usage. Depends on `RuntimeContext` refactor.
283
+ - [ ] **TOML configuration** — `execsql.toml` as modern alternative to legacy INI format (coexist initially).
284
+
285
+ ### v2.10 — Testing & CI Hardening
286
+
287
+ - [ ] **Property-based testing (Hypothesis)** — for parsers, type inference, substitution variables.
288
+ - [ ] **Parser fuzzing** — `CondParser` and `NumericParser` handle arbitrary user input; fuzz for edge cases.
289
+ - [ ] **Nightly CI against latest DB driver versions** — catch upstream breakage in psycopg2, pymysql, duckdb, etc.
290
+ - [ ] **CI benchmarks** — track substitution variable and dispatch performance over time.
291
+
292
+ ### v2.11 — Documentation & Community
293
+
294
+ - [ ] **Cookbook / recipes page** — real-world examples: ETL workflows, HTML reports, data validation pipelines.
295
+ - [ ] **Migration guide from upstream execsql** — what changed, what's new, how to switch.
296
+ - [ ] **Interactive tutorial** — guided walkthrough script against a bundled SQLite DB.
268
297
 
269
298
  ### v3.0+ — Future
270
299
 
271
- - **Plugin system** — allow external packages to register exporters, importers, or metacommands
300
+ - [ ] **Plugin system** — entry points for `execsql.exporters`, `execsql.importers`, `execsql.metacommands` allowing external packages to register new handlers.
301
+ - [ ] **LSP / language server** — for the VS Code extension: autocomplete metacommands, validate substitution variables, jump-to-definition for `INCLUDE`d scripts.
272
302
 
273
303
  ______________________________________________________________________
274
304
 
275
305
  ### Ongoing / No-milestone
276
306
 
277
- - PostgreSQL integration tests (requires external server — CI docker service)
278
- - MySQL integration tests (same)
279
- - `savedscripts` memory pruning
280
307
  - Textual TUI polish
281
308
 
282
309
  ______________________________________________________________________
283
310
 
311
+ ### Design Notes: Parallel Execution Blocks
312
+
313
+ **Concept:** Allow users to declare groups of independent SQL statements that
314
+ can run concurrently, reducing wall-clock time for ETL scripts with
315
+ independent work.
316
+
317
+ **Syntax:**
318
+ ```sql
319
+ -- !x! PARALLEL BEGIN [WORKERS=4]
320
+ INSERT INTO summary_a SELECT ... FROM raw_data;
321
+ INSERT INTO summary_b SELECT ... FROM raw_data;
322
+ INSERT INTO summary_c SELECT ... FROM raw_data;
323
+ -- !x! PARALLEL END
324
+ ```
325
+
326
+ **How it would work in the engine:**
327
+
328
+ 1. When `runscripts()` encounters `PARALLEL BEGIN`, the engine enters a
329
+ "collecting" mode (similar to how `LOOP` compiles commands into a
330
+ `CommandList` before executing). Statements are accumulated but not run.
331
+
332
+ 2. On `PARALLEL END`, the collected statements are dispatched to a
333
+ `concurrent.futures.ThreadPoolExecutor` (or `ProcessPoolExecutor`).
334
+ Each statement gets its own database cursor (or connection from
335
+ `DatabasePool`).
336
+
337
+ 3. The main `runscripts()` loop blocks at the `PARALLEL END` until all
338
+ futures complete. Errors from any worker are collected and raised as
339
+ a combined `ErrInfo`.
340
+
341
+ **Key constraints and design decisions:**
342
+
343
+ - **No shared mutable state inside parallel blocks.** Substitution variable
344
+ writes (`SET`), `IF/ELSE`, `LOOP`, `INCLUDE`, and other control-flow
345
+ metacommands are **prohibited** inside `PARALLEL` blocks — only raw SQL
346
+ and simple export metacommands are allowed. The parser rejects anything
347
+ else at compile time.
348
+
349
+ - **Connection handling.** Each parallel worker needs its own cursor or
350
+ connection. `DatabasePool` already exists in `db/base.py` but currently
351
+ manages one connection per named alias. This would need a pool-per-alias
352
+ model (e.g., min/max connections) or each worker opens a fresh connection
353
+ from the same DSN.
354
+
355
+ - **Depends on `RuntimeContext` refactor.** The current `state.py` globals
356
+ (especially `commandliststack`, `if_stack`, `subvars`) are not
357
+ thread-safe. Workers would need isolated read-only snapshots of
358
+ substitution variables and their own cursor state. The `RuntimeContext`
359
+ work in v2.6 is a prerequisite.
360
+
361
+ - **`WORKERS=N`** defaults to `min(len(statements), os.cpu_count())`.
362
+ Configurable via the metacommand or `execsql.toml`.
363
+
364
+ - **Transaction semantics.** Each statement runs in its own implicit
365
+ transaction (autocommit). If the user needs atomicity across the whole
366
+ block, they wrap it in `BEGIN BATCH ... END BATCH` outside the parallel
367
+ block — but that negates parallelism for most backends, so this is
368
+ mainly useful for independent ETL loads.
369
+
370
+ ______________________________________________________________________
371
+
284
372
  ## Open Design Questions
285
373
 
286
374
  ### Distribution / single-file invocation model
@@ -29,6 +29,7 @@ Thumbs.db
29
29
  .pytest_cache/
30
30
  .coverage
31
31
  coverage.xml
32
+ *.py,cover
32
33
  .tox/
33
34
  htmlcov/
34
35
  execsql.log
@@ -13,7 +13,45 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
- ## [2.5.0] - 2026-03-31
16
+ ## [2.7.1] - 2026-04-01
17
+
18
+ ### Fixed
19
+
20
+ - Fix `AttributeError: module 'execsql.state' has no attribute 'dedup_words'` when importing CSV files with `DEDUP_COL_HDRS` enabled — `dedup_words` is now correctly imported from `execsql.utils.strings` instead of accessed through the state module.
21
+
22
+ ______________________________________________________________________
23
+
24
+ ## [2.7.0] - 2026-04-01
25
+
26
+ ### Added
27
+
28
+ - **Markdown export** (`FORMAT MARKDOWN` / `FORMAT MD`) — GitHub-flavored pipe tables with column alignment, pipe/backslash escaping, and zip support. No dependencies required.
29
+ - **YAML export** (`FORMAT YAML`) — list-of-dicts output via PyYAML with native type preservation (int, float, null). Requires `PyYAML` (included in `formats` extras).
30
+ - **XLSX export** (`FORMAT XLSX`) — single-sheet and multi-sheet Excel export via openpyxl with bold headers, native type preservation, sheet name deduplication, and a "Datasheets" inventory sheet. Multi-sheet syntax: `EXPORT table1, table2 TO file.xlsx AS XLSX`.
31
+
32
+ ______________________________________________________________________
33
+
34
+ ## [2.6.0] - 2026-04-01
35
+
36
+ ### Added
37
+
38
+ - Textual TUI `console_save()` — writes console output to a file, matching Tkinter parity.
39
+ - Keyboard shortcut hints on Textual TUI dialog screens — Escape to cancel, Enter to submit, with `Footer` widget on all major dialog screens.
40
+ - `RuntimeContext` class in `state.py` — groups all 33 mutable runtime globals into a single slotted object. Enables isolated contexts for testing and future concurrent execution.
41
+ - `get_context()` / `set_context()` public API for programmatic access to the active runtime context.
42
+ - Divergence from Upstream documentation page (`docs/about/divergence.md`) listing all user-visible changes since the fork.
43
+ - Test coverage raised from 80% to 86% — 403 new tests across `db/base.py`, `metacommands/connect.py`, `script/engine.py`, and `exporters/delimited.py`.
44
+
45
+ ### Changed
46
+
47
+ - `state.py` module now uses a `types.ModuleType` subclass that transparently proxies attribute reads and writes to the active `RuntimeContext` instance. All existing `_state.foo` call sites continue working with zero changes.
48
+ - `reset()` simplified from 40 lines with 7 `global` statements to a clean context replacement (preserving `filewriter`).
49
+ - `initialize()` and `endloop()` rewritten to use `_ctx` directly instead of `global` statements.
50
+ - Removed 180 redundant `# noqa` suppressions from `metacommands/__init__.py` — the existing `__all__` already satisfies ruff F401.
51
+
52
+ ______________________________________________________________________
53
+
54
+ ## [2.5.0] - 2026-04-01
17
55
 
18
56
  ### Added
19
57
 
@@ -51,4 +51,5 @@ A multi-agent system where specialized agents collaborate to improve, extend, de
51
51
  - All code must pass `ruff check` and target Python 3.10+
52
52
  - **Every user-visible change must be reflected in `CHANGELOG.md`** under the `[Unreleased]` section using [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) categories: Added, Changed, Fixed, Removed. Do not leave changelog updates for later — include them in the same commit or PR as the code change.
53
53
  - **Every user-visible change must also update documentation** — review and revise `README.md`, `docs/`, and any other relevant documentation to stay consistent with the code. New CLI options, features, or behavior changes must be reflected in the README Options table, feature list, and the corresponding docs page. Do not leave doc updates for later. Docs are organized into subdirectories: `docs/getting-started/`, `docs/reference/` (configuration, metacommands, substitution_vars, security), `docs/guides/`, `docs/about/`, `docs/api/`, `docs/dev/`.
54
+ - **Every change that diverges from upstream execsql v1.130.1 must update `docs/about/divergence.md`** — this includes new features, changed behavior, security fixes, and removed functionality. The divergence page is the canonical record of how execsql2 differs from the original monolith. Keep entries concise and organized under the existing section headings (Added Features, Changed Behavior, Security and Correctness Fixes, Removed Features).
54
55
  - **After every version bump and push, monitor CI** — push with `git push && git push --tags` to include version tags, then run `gh run list --limit 1` to get the run ID, then `gh run watch <id> --exit-status` to block until it completes. Bump commits trigger PyPI publish and GitHub Release, so failures must be caught and fixed immediately.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.5.0
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 15+ formats including CSV, TSV, JSON, XML, HTML, LaTeX, OpenDocument, Feather, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
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`.
@@ -120,7 +120,7 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
120
120
  # Features
121
121
 
122
122
  - Import data from CSV, TSV, Excel, OpenDocument, Feather, or Parquet files into a database table.
123
- - Export query results in 15+ formats including CSV, TSV, JSON, XML, HTML, LaTeX, OpenDocument, Feather, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
123
+ - 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.
124
124
  - Copy data between databases, including across different DBMS types.
125
125
  - Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
126
126
  - Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`.
@@ -0,0 +1,171 @@
1
+ # Divergence from Upstream
2
+
3
+ execsql2 is a maintained fork of [execsql](https://execsql.readthedocs.io/)
4
+ v1.130.1 by R. Dreas Nielsen. This page documents all user-visible changes
5
+ since the fork was created: new features, changed behavior, security fixes,
6
+ and removed functionality.
7
+
8
+ For a chronological view, see the [Change Log](change_log.md).
9
+
10
+ ______________________________________________________________________
11
+
12
+ ## Added Features
13
+
14
+ ### CLI Options
15
+
16
+ | Flag | Description |
17
+ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | `--version` | Print version and exit (Rich-formatted). |
19
+ | `-c` / `--command` | Execute an inline SQL or metacommand string instead of a script file. |
20
+ | `--dsn` / `--connection-string` | Accept a standard database URL (e.g. `postgresql://user:pass@host/db`). Supports `postgresql`, `mysql`, `mssql`, `oracle`, `firebird`, `sqlite`, and `duckdb` schemes. |
21
+ | `--output-dir` | Set a default base directory for export output files. |
22
+ | `--progress` | Show a Rich progress bar during long-running IMPORT operations. |
23
+ | `--dump-keywords` | Emit all metacommand keywords, conditionals, config options, and export formats as structured JSON. |
24
+ | `--gui-framework` | Select GUI backend: `tkinter` (default) or `textual` (terminal UI). |
25
+ | `--dry-run` | Parse the script and print the full command list without connecting to a database or executing anything. |
26
+
27
+ ### Export Formats
28
+
29
+ | Format | Description |
30
+ | ----------------- | -------------------------------------------------------------------------------------------------------------------- |
31
+ | `PARQUET` | Export query or table results to Apache Parquet via `polars`. |
32
+ | `FEATHER` | Export to Apache Feather/IPC via `polars` + `pyarrow` (upstream used `pandas`). |
33
+ | `YAML` | Export query or table results as a YAML sequence of mappings via `PyYAML`. |
34
+ | `MARKDOWN` / `MD` | Export query or table results as a GitHub-Flavored Markdown (GFM) pipe table. Pure Python, no optional dependencies. |
35
+ | `XLSX` | Export query or table results to an Excel XLSX workbook via `openpyxl` (single or multi-sheet). |
36
+
37
+ ### Metacommands
38
+
39
+ | Metacommand | Description |
40
+ | ---------------------- | --------------------------------------------------------------------- |
41
+ | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
42
+ | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
43
+
44
+ ### Configuration Options
45
+
46
+ New options in `execsql.conf`:
47
+
48
+ | Option | Section | Description |
49
+ | -------------------------- | ----------- | ----------------------------------------------------------------- |
50
+ | `use_keyring` | `[connect]` | Use the OS keyring for credential storage (default: `yes`). |
51
+ | `show_progress` | `[input]` | Enable Rich progress bar for IMPORT (default: `no`). |
52
+ | `import_progress_interval` | `[input]` | Log a status line every N rows during IMPORT (default: `0`). |
53
+ | `log_sql` | `[config]` | Enable SQL audit logging (default: `no`). |
54
+ | `max_log_size_mb` | `[config]` | Rotate the log file at this size in MB (default: `0` = disabled). |
55
+
56
+ ### Tools
57
+
58
+ | Tool | Description |
59
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
60
+ | `execsql-format` | Standalone CLI for normalizing metacommand indentation and uppercasing SQL keywords. Supports `--check` and `--in-place` modes. Also available as a [pre-commit hook](../guides/formatter.md). |
61
+
62
+ ### GUI
63
+
64
+ | Feature | Description |
65
+ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
66
+ | Textual TUI backend | Full terminal-UI backend via the `textual` library. Provides all dialog types (password, pause, message, entry, compare, action, etc.) in the terminal. |
67
+ | Console fallback | Text-only backend that handles GUI calls in headless environments by printing to stdout. |
68
+
69
+ ### Authentication
70
+
71
+ | Feature | Description |
72
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
73
+ | OS keyring integration | When the `keyring` package is installed, passwords are stored in and retrieved from the OS credential store (macOS Keychain, Windows Credential Manager, Linux SecretService). |
74
+ | Keyring retry on auth failure | If a stored password is rejected, the stale entry is deleted, the user is re-prompted, and the new password is saved automatically. |
75
+
76
+ ### Logging Enhancements
77
+
78
+ | Feature | Description |
79
+ | ----------------------------- | ---------------------------------------------------------------------------------- |
80
+ | Per-event ISO 8601 timestamps | `status`, `connect`, `action`, and `user_msg` log entries include a timestamp. |
81
+ | Run duration in exit record | The `exit` log record includes elapsed wall-clock time. |
82
+ | Run ID millisecond precision | Run identifier format changed from `%Y%m%d_%H%M_%S` to `%Y%m%d_%H%M_%S_NNN`. |
83
+ | SQL audit log record | New `sql` record type containing DB name, line number, and query text. |
84
+ | Import progress log | Periodic row-count status lines during IMPORT when `import_progress_interval > 0`. |
85
+
86
+ ### Developer / Packaging
87
+
88
+ | Feature | Description |
89
+ | --------------------------- | --------------------------------------------------------------------------------------------------------------- |
90
+ | VS Code syntax highlighting | Auto-generated `tmLanguage.json` grammar from the dispatch table. |
91
+ | `py.typed` marker | PEP 561 marker enabling downstream static type checking. |
92
+ | Structured keyword registry | `--dump-keywords` introspects the dispatch table and outputs JSON used by the grammar generator and test suite. |
93
+
94
+ ______________________________________________________________________
95
+
96
+ ## Changed Behavior
97
+
98
+ ### CLI Interface
99
+
100
+ The CLI framework changed from `optparse` to [Typer](https://typer.tiangolo.com/) with Rich-formatted help text. All original short flags (`-a` through `-z`) are preserved. The tool can be invoked as either `execsql` or `execsql2`.
101
+
102
+ ### Internal State Management
103
+
104
+ All 33 mutable runtime globals in `state.py` have been consolidated into a `RuntimeContext` object. The module uses a transparent proxy so existing code is unaffected, but the architecture now supports isolated contexts for testing and future concurrent execution.
105
+
106
+ ### Substitution Variables
107
+
108
+ - **Cycle detection** — `substitute_vars()` raises an error after 100 iterations to prevent infinite loops when variables reference each other cyclically. Upstream had no protection.
109
+ - **O(1) substitution** — Variable substitution uses a single combined regex and dict lookup instead of O(V) per-variable regex passes. Behavior is identical; performance is improved.
110
+
111
+ ### Database Adapters
112
+
113
+ - **`Database` is an ABC** — `open_db()` and `exec_cmd()` are abstract methods. Subclasses that omit them raise `TypeError` at instantiation instead of at call time.
114
+ - **Connection timeouts** — PostgreSQL and SQLite adapters accept a connection timeout parameter (default 30 seconds).
115
+ - **DuckDB temporal types** — `TIMESTAMPTZ`, `TIMESTAMP`, `DATE`, `TIME` now map to native DuckDB types instead of `TEXT`.
116
+
117
+ ### Error Handling
118
+
119
+ - **Exception hierarchy** — All custom exceptions inherit from `ExecSqlError`, enabling `except ExecSqlError` to catch any execsql-originated error.
120
+ - **Exception chaining** — All `raise` statements inside `except` blocks preserve the original traceback via `from`.
121
+
122
+ ______________________________________________________________________
123
+
124
+ ## Security and Correctness Fixes
125
+
126
+ These are behavioral changes driven by security or correctness issues in the upstream code.
127
+
128
+ ### Injection Fixes
129
+
130
+ | Area | Fix |
131
+ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
132
+ | Database metadata queries | `schema_exists()`, `table_exists()`, `column_exists()`, `table_columns()`, `view_exists()`, `role_exists()` across all 9 adapters now use parameterized queries. Upstream used string interpolation. |
133
+ | `import_entire_file()` | Column names are quoted with `quote_identifier()` instead of interpolated into INSERT statements. |
134
+ | PostgreSQL `CREATE DATABASE` | Database name and encoding are quoted. COPY delimiter and quote character are validated. |
135
+ | `$SHEETS_TABLES_VALUES` | Sheet names from ODS/XLS imports are escaped before embedding in SQL. |
136
+ | HTTP `Content-Disposition` | Filename is sanitized to prevent HTTP response splitting in SERVE. |
137
+
138
+ ### Template and Export Safety
139
+
140
+ | Area | Fix |
141
+ | ----------------- | ------------------------------------------------------------------------------------------------------ |
142
+ | Jinja2 sandboxing | Templates run in `SandboxedEnvironment` instead of the default `jinja2.Template`. |
143
+ | HTML export | Column headers and cell values are escaped with `html.escape()` to prevent XSS. |
144
+ | XML export | Values are escaped with `xml.sax.saxutils.escape()`. Invalid XML element name characters are replaced. |
145
+ | JSON export | The `description` field uses `json.dumps()` instead of string interpolation. |
146
+
147
+ ### Credential and Logging Safety
148
+
149
+ | Area | Fix |
150
+ | ---------------------------- | ------------------------------------------------------------------------------------------ |
151
+ | ODBC password redaction | Connection strings in log output have `Pwd=***` substituted before logging. |
152
+ | `enc_password` documentation | Prominent warnings that XOR encryption is obfuscation only — keys are hardcoded in source. |
153
+
154
+ ### Bug Fixes
155
+
156
+ | Area | Fix |
157
+ | --------------------------------- | ---------------------------------------------------------------------------------------------------------- |
158
+ | Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`. |
159
+ | MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names. |
160
+ | `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor. |
161
+ | `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop. |
162
+ | Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
163
+
164
+ ______________________________________________________________________
165
+
166
+ ## Removed Features
167
+
168
+ | Feature | Reason |
169
+ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
170
+ | Airspeed template processor | The `airspeed` library (Velocity clone) is unmaintained since ~2018. Use `FORMAT jinja` instead. The `airspeed` value for `template_processor` in `execsql.conf` is no longer accepted. |
171
+ | Python 2 compatibility | All Python 2 constructs (`stringtypes`, `u""` literals, `optparse`, etc.) have been removed. execsql2 requires Python 3.10+. |
@@ -932,7 +932,22 @@ JSON_TS or JSON_TABLESCHEMA
932
932
 
933
933
  LATEX
934
934
 
935
- : Input for the [LaTeΧ](https://www.latex-project.org/) typesetting system. If the "APPEND" keyword is not used, a complete document (of class article) will be written. If the "APPEND" keyword is used, only the table definition will be written to the output file. If the "APPEND" keyword is used and an existing output file contains an \\end directive, the table will be written before that directive rather than at the physical end of the file. Wide or long tables may exceed LaTeΧ's default page size. If the "DESCRIPTION" keyword is used, the given description will be used as the table's caption. Data exported in LaTeX format cannot be written into a zipfile.
935
+ : Input for the [LaTeX](https://www.latex-project.org/) typesetting system. If the "APPEND" keyword is not used, a complete document (of class article) will be written. If the "APPEND" keyword is used, only the table definition will be written to the output file. If the "APPEND" keyword is used and an existing output file contains an \\end directive, the table will be written before that directive rather than at the physical end of the file. Wide or long tables may exceed LaTeΧ's default page size. If the "DESCRIPTION" keyword is used, the given description will be used as the table's caption. Data exported in LaTeX format cannot be written into a zipfile.
936
+
937
+
938
+ MARKDOWN or MD
939
+
940
+ : [GitHub-Flavored Markdown](https://github.github.com/gfm/) pipe table. Column values are aligned and pipe (`|`) and backslash (`\`) characters in data are escaped. If the "DESCRIPTION" keyword is used, the description is written as an HTML comment (`<!-- ... -->`) before the table. If the "APPEND" keyword is used, only the table is appended (no repeated headers). No optional dependencies required.
941
+
942
+
943
+ XLSX
944
+
945
+ : [Excel](https://www.microsoft.com/en-us/microsoft-365/excel) workbook in the Office Open XML format. One or more tables (or views) can be exported to an XLSX workbook. Each table will be exported to a separate worksheet within the workbook, with the first row containing bold column headers. To export multiple tables, their names must be separated by commas. The "APPEND" keyword can be used to add worksheets to an existing workbook. The name of the view or table exported will be used as the worksheet name; if this conflicts with a sheet already in the workbook, a number will be appended to make the sheet name unique. A "Datasheets" inventory sheet is created with author, date, description, and source information for each data sheet. Data types are preserved natively (integers, floats, dates, datetimes, booleans). The `openpyxl` library must be installed (`pip install execsql2[excel]`). Data exported in XLSX format cannot be written into a zipfile.
946
+
947
+
948
+ YAML
949
+
950
+ : [YAML](https://yaml.org/) sequence of mappings. Each row is represented as a mapping (dictionary) with column names as keys. Python data types are preserved — integers remain integers, floats remain floats, and `None` becomes YAML `null`. If the "APPEND" keyword is used, a new YAML document is appended to the file (multi-document stream). The `PyYAML` library must be installed (`pip install execsql2[formats]`). No description text is included in the output even if provided.
936
951
 
937
952
 
938
953
  SQLITE
@@ -118,7 +118,7 @@
118
118
  },
119
119
  "export-formats": {
120
120
  "comment": "Export/import format names",
121
- "match": "(?i)\\b(json_tableschema|cgi-html|feather|json_ts|txt-and|unitsep|duckdb|sqlite|values|latex|plain|hdf5|html|json|tabq|tsvq|b64|csv|ods|raw|tab|tsv|txt|xml|us)\\b",
121
+ "match": "(?i)\\b(json_tableschema|cgi-html|markdown|feather|json_ts|txt-and|unitsep|duckdb|sqlite|values|latex|plain|hdf5|html|json|tabq|tsvq|xlsx|yaml|b64|csv|ods|raw|tab|tsv|txt|xml|md|us)\\b",
122
122
  "name": "support.constant.execsql"
123
123
  },
124
124
  "secondary-operators": {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.5.0"
7
+ version = "2.7.1"
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" }
@@ -56,7 +56,7 @@ firebird = ["firebird-driver"]
56
56
  oracle = ["oracledb"]
57
57
  odbc = ["pyodbc"]
58
58
  # Feature bundles
59
- formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables"]
59
+ formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables", "PyYAML"]
60
60
  auth = ["keyring"]
61
61
  # Convenience groups
62
62
  all-db = [
@@ -147,6 +147,10 @@ ignore = [
147
147
  "UP017", # Use datetime.UTC alias (not available in Python 3.10)
148
148
  ]
149
149
 
150
+ [tool.ruff.lint.per-file-ignores]
151
+ # F822: context attrs are proxied via __getattr__/__setattr__ on the module class, not defined as module-level variables
152
+ "src/execsql/state.py" = ["F822"]
153
+
150
154
  [tool.ruff.format]
151
155
  quote-style = "double"
152
156
  indent-style = "space"
@@ -154,7 +158,7 @@ skip-magic-trailing-comma = false
154
158
  line-ending = "auto"
155
159
 
156
160
  [tool.bumpversion]
157
- current_version = "2.5.0"
161
+ current_version = "2.7.1"
158
162
  commit = true
159
163
  commit_args = "--no-verify"
160
164
  tag = true
@@ -178,7 +182,7 @@ addopts = [
178
182
  "--cov=execsql",
179
183
  "--cov-branch",
180
184
  "--cov-report=xml",
181
- "--cov-fail-under=80",
185
+ "--cov-fail-under=85",
182
186
  "--strict-markers",
183
187
  "--strict-config",
184
188
  "--color=yes",
@@ -9,9 +9,9 @@ handlers can access them via ``_state.write_query_to_csv`` etc. without
9
9
  importing directly from here.
10
10
 
11
11
  Sub-modules: ``base``, ``delimited``, ``json``, ``xml``, ``html``,
12
- ``latex``, ``ods``, ``xls``, ``zip``, ``raw``, ``pretty``, ``values``,
13
- ``templates``, ``feather``, ``parquet``, ``duckdb``, ``sqlite``,
14
- ``protocol``.
12
+ ``latex``, ``markdown``, ``ods``, ``xls``, ``zip``, ``raw``, ``pretty``,
13
+ ``values``, ``templates``, ``feather``, ``parquet``, ``duckdb``,
14
+ ``sqlite``, ``protocol``.
15
15
  """
16
16
 
17
17
  from execsql.exporters.protocol import QueryExporter, RowsetExporter
@@ -27,7 +27,7 @@ from execsql.exceptions import ErrInfo
27
27
  from execsql.models import DataTable
28
28
  from execsql.utils.errors import exception_desc
29
29
  from execsql.utils.fileio import filewriter_close
30
- from execsql.utils.strings import clean_words, fold_words
30
+ from execsql.utils.strings import clean_words, dedup_words, fold_words
31
31
 
32
32
  __all__ = ["LineDelimiter", "CsvFile", "CsvWriter", "DelimitedWriter", "write_delimited_file"]
33
33
 
@@ -677,7 +677,7 @@ class CsvFile(EncodedFile):
677
677
  if conf.fold_col_hdrs != "no":
678
678
  colnames = fold_words(colnames, conf.fold_col_hdrs)
679
679
  if conf.dedup_col_hdrs:
680
- colnames = _state.dedup_words(colnames)
680
+ colnames = dedup_words(colnames)
681
681
  return colnames
682
682
 
683
683
  def column_headers(self) -> list[str]: