execsql2 2.16.1__tar.gz → 2.16.2__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 (319) hide show
  1. {execsql2-2.16.1 → execsql2-2.16.2}/CHANGELOG.md +8 -0
  2. {execsql2-2.16.1 → execsql2-2.16.2}/CONTRIBUTING.md +0 -20
  3. {execsql2-2.16.1 → execsql2-2.16.2}/PKG-INFO +1 -1
  4. {execsql2-2.16.1 → execsql2-2.16.2}/docs/about/divergence.md +1 -0
  5. {execsql2-2.16.1 → execsql2-2.16.2}/pyproject.toml +2 -2
  6. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/script/executor.py +5 -0
  7. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/script/parser.py +9 -1
  8. {execsql2-2.16.1 → execsql2-2.16.2}/tests/cli/test_cli_run.py +173 -1
  9. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_executor.py +20 -0
  10. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_plugins.py +122 -0
  11. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_strings.py +5 -0
  12. {execsql2-2.16.1 → execsql2-2.16.2}/uv.lock +1 -1
  13. {execsql2-2.16.1 → execsql2-2.16.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  14. {execsql2-2.16.1 → execsql2-2.16.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  15. {execsql2-2.16.1 → execsql2-2.16.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  16. {execsql2-2.16.1 → execsql2-2.16.2}/.github/workflows/ci-cd.yml +0 -0
  17. {execsql2-2.16.1 → execsql2-2.16.2}/.gitignore +0 -0
  18. {execsql2-2.16.1 → execsql2-2.16.2}/.pre-commit-config.yaml +0 -0
  19. {execsql2-2.16.1 → execsql2-2.16.2}/.pre-commit-hooks.yaml +0 -0
  20. {execsql2-2.16.1 → execsql2-2.16.2}/.python-version +0 -0
  21. {execsql2-2.16.1 → execsql2-2.16.2}/.readthedocs.yaml +0 -0
  22. {execsql2-2.16.1 → execsql2-2.16.2}/LICENSE.txt +0 -0
  23. {execsql2-2.16.1 → execsql2-2.16.2}/NOTICE +0 -0
  24. {execsql2-2.16.1 → execsql2-2.16.2}/README.md +0 -0
  25. {execsql2-2.16.1 → execsql2-2.16.2}/SECURITY.md +0 -0
  26. {execsql2-2.16.1 → execsql2-2.16.2}/docs/about/contributors.md +0 -0
  27. {execsql2-2.16.1 → execsql2-2.16.2}/docs/about/copyright.md +0 -0
  28. {execsql2-2.16.1 → execsql2-2.16.2}/docs/api/cli.md +0 -0
  29. {execsql2-2.16.1 → execsql2-2.16.2}/docs/api/db.md +0 -0
  30. {execsql2-2.16.1 → execsql2-2.16.2}/docs/api/exporters.md +0 -0
  31. {execsql2-2.16.1 → execsql2-2.16.2}/docs/api/importers.md +0 -0
  32. {execsql2-2.16.1 → execsql2-2.16.2}/docs/api/index.md +0 -0
  33. {execsql2-2.16.1 → execsql2-2.16.2}/docs/api/metacommands.md +0 -0
  34. {execsql2-2.16.1 → execsql2-2.16.2}/docs/dev/adding_db_adapters.md +0 -0
  35. {execsql2-2.16.1 → execsql2-2.16.2}/docs/dev/adding_exporters.md +0 -0
  36. {execsql2-2.16.1 → execsql2-2.16.2}/docs/dev/adding_importers.md +0 -0
  37. {execsql2-2.16.1 → execsql2-2.16.2}/docs/dev/adding_metacommands.md +0 -0
  38. {execsql2-2.16.1 → execsql2-2.16.2}/docs/dev/architecture.md +0 -0
  39. {execsql2-2.16.1 → execsql2-2.16.2}/docs/getting-started/installation.md +0 -0
  40. {execsql2-2.16.1 → execsql2-2.16.2}/docs/getting-started/requirements.md +0 -0
  41. {execsql2-2.16.1 → execsql2-2.16.2}/docs/getting-started/syntax.md +0 -0
  42. {execsql2-2.16.1 → execsql2-2.16.2}/docs/guides/debugging.md +0 -0
  43. {execsql2-2.16.1 → execsql2-2.16.2}/docs/guides/documentation.md +0 -0
  44. {execsql2-2.16.1 → execsql2-2.16.2}/docs/guides/encoding.md +0 -0
  45. {execsql2-2.16.1 → execsql2-2.16.2}/docs/guides/examples.md +0 -0
  46. {execsql2-2.16.1 → execsql2-2.16.2}/docs/guides/formatter.md +0 -0
  47. {execsql2-2.16.1 → execsql2-2.16.2}/docs/guides/logging.md +0 -0
  48. {execsql2-2.16.1 → execsql2-2.16.2}/docs/guides/sql_syntax.md +0 -0
  49. {execsql2-2.16.1 → execsql2-2.16.2}/docs/guides/usage.md +0 -0
  50. {execsql2-2.16.1 → execsql2-2.16.2}/docs/guides/using_scripts.md +0 -0
  51. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/Compare_planets.png +0 -0
  52. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/actions.png +0 -0
  53. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/actions2.png +0 -0
  54. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/checkboxes.png +0 -0
  55. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/connect.b64 +0 -0
  56. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/connect.png +0 -0
  57. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/create_conf.png +0 -0
  58. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/data_error1_screenshot.jpg +0 -0
  59. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/entry_form.png +0 -0
  60. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/execsql_console.png +0 -0
  61. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/execsql_logo_01.png +0 -0
  62. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/fatals.png +0 -0
  63. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/logo_small.png +0 -0
  64. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/pause_terminal.png +0 -0
  65. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/pause_terminal_sm.b64 +0 -0
  66. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/pause_terminal_sm.png +0 -0
  67. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/prompt_compare.png +0 -0
  68. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/set_build_commands.jpg +0 -0
  69. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/unit_conversions.b64 +0 -0
  70. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/unit_conversions_029.png +0 -0
  71. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/unmatched.png +0 -0
  72. {execsql2-2.16.1 → execsql2-2.16.2}/docs/images/vim_execsql_highlight.png +0 -0
  73. {execsql2-2.16.1 → execsql2-2.16.2}/docs/index.md +0 -0
  74. {execsql2-2.16.1 → execsql2-2.16.2}/docs/reference/configuration.md +0 -0
  75. {execsql2-2.16.1 → execsql2-2.16.2}/docs/reference/metacommands.md +0 -0
  76. {execsql2-2.16.1 → execsql2-2.16.2}/docs/reference/security.md +0 -0
  77. {execsql2-2.16.1 → execsql2-2.16.2}/docs/reference/substitution_vars.md +0 -0
  78. {execsql2-2.16.1 → execsql2-2.16.2}/extras/plugin-template/README.md +0 -0
  79. {execsql2-2.16.1 → execsql2-2.16.2}/extras/plugin-template/pyproject.toml +0 -0
  80. {execsql2-2.16.1 → execsql2-2.16.2}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  81. {execsql2-2.16.1 → execsql2-2.16.2}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  82. {execsql2-2.16.1 → execsql2-2.16.2}/extras/vscode-execsql/README.md +0 -0
  83. {execsql2-2.16.1 → execsql2-2.16.2}/extras/vscode-execsql/package.json +0 -0
  84. {execsql2-2.16.1 → execsql2-2.16.2}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  85. {execsql2-2.16.1 → execsql2-2.16.2}/justfile +0 -0
  86. {execsql2-2.16.1 → execsql2-2.16.2}/scripts/generate_vscode_grammar.py +0 -0
  87. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/__init__.py +0 -0
  88. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/__main__.py +0 -0
  89. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/api.py +0 -0
  90. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/cli/__init__.py +0 -0
  91. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/cli/dsn.py +0 -0
  92. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/cli/help.py +0 -0
  93. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/cli/lint.py +0 -0
  94. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/cli/lint_ast.py +0 -0
  95. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/cli/run.py +0 -0
  96. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/config.py +0 -0
  97. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/__init__.py +0 -0
  98. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/access.py +0 -0
  99. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/base.py +0 -0
  100. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/dsn.py +0 -0
  101. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/duckdb.py +0 -0
  102. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/factory.py +0 -0
  103. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/firebird.py +0 -0
  104. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/mysql.py +0 -0
  105. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/oracle.py +0 -0
  106. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/postgres.py +0 -0
  107. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/sqlite.py +0 -0
  108. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/db/sqlserver.py +0 -0
  109. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/debug/__init__.py +0 -0
  110. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/debug/repl.py +0 -0
  111. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exceptions.py +0 -0
  112. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/__init__.py +0 -0
  113. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/base.py +0 -0
  114. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/delimited.py +0 -0
  115. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/duckdb.py +0 -0
  116. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/feather.py +0 -0
  117. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/html.py +0 -0
  118. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/json.py +0 -0
  119. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/latex.py +0 -0
  120. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/markdown.py +0 -0
  121. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/ods.py +0 -0
  122. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/parquet.py +0 -0
  123. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/pretty.py +0 -0
  124. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/protocol.py +0 -0
  125. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/raw.py +0 -0
  126. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/sqlite.py +0 -0
  127. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/templates.py +0 -0
  128. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/values.py +0 -0
  129. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/xls.py +0 -0
  130. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/xlsx.py +0 -0
  131. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/xml.py +0 -0
  132. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/yaml.py +0 -0
  133. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/exporters/zip.py +0 -0
  134. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/format.py +0 -0
  135. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/gui/__init__.py +0 -0
  136. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/gui/base.py +0 -0
  137. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/gui/console.py +0 -0
  138. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/gui/desktop.py +0 -0
  139. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/gui/tui.py +0 -0
  140. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/importers/__init__.py +0 -0
  141. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/importers/base.py +0 -0
  142. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/importers/csv.py +0 -0
  143. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/importers/feather.py +0 -0
  144. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/importers/json.py +0 -0
  145. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/importers/ods.py +0 -0
  146. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/importers/xls.py +0 -0
  147. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/__init__.py +0 -0
  148. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/conditions.py +0 -0
  149. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/connect.py +0 -0
  150. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/control.py +0 -0
  151. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/data.py +0 -0
  152. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/debug.py +0 -0
  153. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/dispatch.py +0 -0
  154. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/io.py +0 -0
  155. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/io_export.py +0 -0
  156. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/io_fileops.py +0 -0
  157. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/io_import.py +0 -0
  158. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/io_write.py +0 -0
  159. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/prompt.py +0 -0
  160. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/script_ext.py +0 -0
  161. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/system.py +0 -0
  162. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/metacommands/upsert.py +0 -0
  163. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/models.py +0 -0
  164. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/parser.py +0 -0
  165. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/plugins.py +0 -0
  166. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/py.typed +0 -0
  167. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/script/__init__.py +0 -0
  168. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/script/ast.py +0 -0
  169. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/script/control.py +0 -0
  170. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/script/engine.py +0 -0
  171. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/script/variables.py +0 -0
  172. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/state.py +0 -0
  173. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/types.py +0 -0
  174. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/__init__.py +0 -0
  175. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/auth.py +0 -0
  176. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/crypto.py +0 -0
  177. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/datetime.py +0 -0
  178. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/errors.py +0 -0
  179. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/fileio.py +0 -0
  180. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/gui.py +0 -0
  181. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/mail.py +0 -0
  182. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/numeric.py +0 -0
  183. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/regex.py +0 -0
  184. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/strings.py +0 -0
  185. {execsql2-2.16.1 → execsql2-2.16.2}/src/execsql/utils/timer.py +0 -0
  186. {execsql2-2.16.1 → execsql2-2.16.2}/templates/README.md +0 -0
  187. {execsql2-2.16.1 → execsql2-2.16.2}/templates/config_settings.sqlite +0 -0
  188. {execsql2-2.16.1 → execsql2-2.16.2}/templates/example_config_prompt.sql +0 -0
  189. {execsql2-2.16.1 → execsql2-2.16.2}/templates/execsql.conf +0 -0
  190. {execsql2-2.16.1 → execsql2-2.16.2}/templates/make_config_db.sql +0 -0
  191. {execsql2-2.16.1 → execsql2-2.16.2}/templates/md_compare.sql +0 -0
  192. {execsql2-2.16.1 → execsql2-2.16.2}/templates/md_glossary.sql +0 -0
  193. {execsql2-2.16.1 → execsql2-2.16.2}/templates/md_upsert.sql +0 -0
  194. {execsql2-2.16.1 → execsql2-2.16.2}/templates/pg_compare.sql +0 -0
  195. {execsql2-2.16.1 → execsql2-2.16.2}/templates/pg_glossary.sql +0 -0
  196. {execsql2-2.16.1 → execsql2-2.16.2}/templates/pg_upsert.sql +0 -0
  197. {execsql2-2.16.1 → execsql2-2.16.2}/templates/script_template.sql +0 -0
  198. {execsql2-2.16.1 → execsql2-2.16.2}/templates/ss_compare.sql +0 -0
  199. {execsql2-2.16.1 → execsql2-2.16.2}/templates/ss_glossary.sql +0 -0
  200. {execsql2-2.16.1 → execsql2-2.16.2}/templates/ss_upsert.sql +0 -0
  201. {execsql2-2.16.1 → execsql2-2.16.2}/tests/__init__.py +0 -0
  202. {execsql2-2.16.1 → execsql2-2.16.2}/tests/cli/__init__.py +0 -0
  203. {execsql2-2.16.1 → execsql2-2.16.2}/tests/cli/test_cli.py +0 -0
  204. {execsql2-2.16.1 → execsql2-2.16.2}/tests/cli/test_cli_e2e.py +0 -0
  205. {execsql2-2.16.1 → execsql2-2.16.2}/tests/cli/test_lint.py +0 -0
  206. {execsql2-2.16.1 → execsql2-2.16.2}/tests/cli/test_ping.py +0 -0
  207. {execsql2-2.16.1 → execsql2-2.16.2}/tests/cli/test_profile.py +0 -0
  208. {execsql2-2.16.1 → execsql2-2.16.2}/tests/conftest.py +0 -0
  209. {execsql2-2.16.1 → execsql2-2.16.2}/tests/db/__init__.py +0 -0
  210. {execsql2-2.16.1 → execsql2-2.16.2}/tests/db/test_base.py +0 -0
  211. {execsql2-2.16.1 → execsql2-2.16.2}/tests/db/test_db_adapters_mocked.py +0 -0
  212. {execsql2-2.16.1 → execsql2-2.16.2}/tests/db/test_dsn.py +0 -0
  213. {execsql2-2.16.1 → execsql2-2.16.2}/tests/db/test_duckdb.py +0 -0
  214. {execsql2-2.16.1 → execsql2-2.16.2}/tests/db/test_factory.py +0 -0
  215. {execsql2-2.16.1 → execsql2-2.16.2}/tests/db/test_postgres.py +0 -0
  216. {execsql2-2.16.1 → execsql2-2.16.2}/tests/db/test_sqlite.py +0 -0
  217. {execsql2-2.16.1 → execsql2-2.16.2}/tests/db/test_sqlite_extra.py +0 -0
  218. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/__init__.py +0 -0
  219. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_base.py +0 -0
  220. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_db.py +0 -0
  221. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_delimited.py +0 -0
  222. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_duckdb_exporter.py +0 -0
  223. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_exporters.py +0 -0
  224. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_feather.py +0 -0
  225. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_html_extended.py +0 -0
  226. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_html_latex.py +0 -0
  227. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_json.py +0 -0
  228. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_json_extended.py +0 -0
  229. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_latex_extended.py +0 -0
  230. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_markdown.py +0 -0
  231. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_ods.py +0 -0
  232. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_parquet.py +0 -0
  233. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_pretty_extended.py +0 -0
  234. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_raw_extended.py +0 -0
  235. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_sqlite_exporter.py +0 -0
  236. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_templates.py +0 -0
  237. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_templates_extended.py +0 -0
  238. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_values_extended.py +0 -0
  239. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_xls_xlsx.py +0 -0
  240. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_xlsx.py +0 -0
  241. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_xml.py +0 -0
  242. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_yaml.py +0 -0
  243. {execsql2-2.16.1 → execsql2-2.16.2}/tests/exporters/test_zip.py +0 -0
  244. {execsql2-2.16.1 → execsql2-2.16.2}/tests/gui/__init__.py +0 -0
  245. {execsql2-2.16.1 → execsql2-2.16.2}/tests/gui/test_backends.py +0 -0
  246. {execsql2-2.16.1 → execsql2-2.16.2}/tests/gui/test_compare_stats.py +0 -0
  247. {execsql2-2.16.1 → execsql2-2.16.2}/tests/gui/test_compute_row_diffs.py +0 -0
  248. {execsql2-2.16.1 → execsql2-2.16.2}/tests/importers/__init__.py +0 -0
  249. {execsql2-2.16.1 → execsql2-2.16.2}/tests/importers/test_base_extended.py +0 -0
  250. {execsql2-2.16.1 → execsql2-2.16.2}/tests/importers/test_csv_edge_cases.py +0 -0
  251. {execsql2-2.16.1 → execsql2-2.16.2}/tests/importers/test_csv_importer.py +0 -0
  252. {execsql2-2.16.1 → execsql2-2.16.2}/tests/importers/test_feather_importer.py +0 -0
  253. {execsql2-2.16.1 → execsql2-2.16.2}/tests/importers/test_json_importer.py +0 -0
  254. {execsql2-2.16.1 → execsql2-2.16.2}/tests/importers/test_ods_importer.py +0 -0
  255. {execsql2-2.16.1 → execsql2-2.16.2}/tests/importers/test_xls_importer.py +0 -0
  256. {execsql2-2.16.1 → execsql2-2.16.2}/tests/integration/__init__.py +0 -0
  257. {execsql2-2.16.1 → execsql2-2.16.2}/tests/integration/conftest.py +0 -0
  258. {execsql2-2.16.1 → execsql2-2.16.2}/tests/integration/test_dsn.py +0 -0
  259. {execsql2-2.16.1 → execsql2-2.16.2}/tests/integration/test_duckdb.py +0 -0
  260. {execsql2-2.16.1 → execsql2-2.16.2}/tests/integration/test_mysql.py +0 -0
  261. {execsql2-2.16.1 → execsql2-2.16.2}/tests/integration/test_postgres.py +0 -0
  262. {execsql2-2.16.1 → execsql2-2.16.2}/tests/integration/test_sqlite.py +0 -0
  263. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/__init__.py +0 -0
  264. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_assert.py +0 -0
  265. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_breakpoint.py +0 -0
  266. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_connect.py +0 -0
  267. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_io_export.py +0 -0
  268. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_io_import.py +0 -0
  269. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_metacommands.py +0 -0
  270. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_metacommands_connect.py +0 -0
  271. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_metacommands_data.py +0 -0
  272. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_metacommands_extended.py +0 -0
  273. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  274. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_metacommands_io.py +0 -0
  275. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  276. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  277. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_metacommands_system.py +0 -0
  278. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  279. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_pg_upsert.py +0 -0
  280. {execsql2-2.16.1 → execsql2-2.16.2}/tests/metacommands/test_row_count.py +0 -0
  281. {execsql2-2.16.1 → execsql2-2.16.2}/tests/scripts/__init__.py +0 -0
  282. {execsql2-2.16.1 → execsql2-2.16.2}/tests/scripts/fixtures/control_flow.sql +0 -0
  283. {execsql2-2.16.1 → execsql2-2.16.2}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  284. {execsql2-2.16.1 → execsql2-2.16.2}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  285. {execsql2-2.16.1 → execsql2-2.16.2}/tests/scripts/fixtures/smoke.sql +0 -0
  286. {execsql2-2.16.1 → execsql2-2.16.2}/tests/scripts/test_sql_scripts.py +0 -0
  287. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_api.py +0 -0
  288. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_ast.py +0 -0
  289. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_ast_parser.py +0 -0
  290. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_config.py +0 -0
  291. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_config_data.py +0 -0
  292. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_config_extended.py +0 -0
  293. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_debug_repl.py +0 -0
  294. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_engine.py +0 -0
  295. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_error_messages.py +0 -0
  296. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_exceptions.py +0 -0
  297. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_format.py +0 -0
  298. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_mail.py +0 -0
  299. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_models.py +0 -0
  300. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_package.py +0 -0
  301. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_parser.py +0 -0
  302. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_registry.py +0 -0
  303. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_script.py +0 -0
  304. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_state.py +0 -0
  305. {execsql2-2.16.1 → execsql2-2.16.2}/tests/test_types.py +0 -0
  306. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/__init__.py +0 -0
  307. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_auth.py +0 -0
  308. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_auth_extra.py +0 -0
  309. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_crypto.py +0 -0
  310. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_datetime.py +0 -0
  311. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_errors.py +0 -0
  312. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_errors_extra.py +0 -0
  313. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_fileio.py +0 -0
  314. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_fileio_extra.py +0 -0
  315. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_numeric.py +0 -0
  316. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_regex.py +0 -0
  317. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_timer.py +0 -0
  318. {execsql2-2.16.1 → execsql2-2.16.2}/tests/utils/test_timer_extra.py +0 -0
  319. {execsql2-2.16.1 → execsql2-2.16.2}/zensical.toml +0 -0
@@ -13,6 +13,14 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.16.2] - 2026-04-30
17
+
18
+ ### Fixed
19
+
20
+ - INCLUDE with quoted paths (e.g., `-- !x! INCLUDE "!!path!!/file.sql"`) now strips the surrounding quotes before resolving the file path. The AST parser captured the full target text including quotes, but the legacy dispatch regex stripped them — quoted INCLUDE paths would fail with "File does not exist" even when the file was present on disk.
21
+
22
+ ______________________________________________________________________
23
+
16
24
  ## [2.16.1] - 2026-04-30
17
25
 
18
26
  ### Fixed
@@ -24,26 +24,6 @@ uv tool install rust-just # via uv
24
24
 
25
25
  Run `just` with no arguments to list all available recipes.
26
26
 
27
- ```
28
- just sync # Sync dependencies from lockfile
29
- just update-hooks # Update pre-commit hooks to latest versions
30
-
31
- just lint # Run ruff linter + formatter
32
- just pre-commit # Run all pre-commit hooks against every file
33
- just test # Run tests against the active Python version
34
- just test-all # Run tests across all supported Python versions (3.10–3.13)
35
- just clean # Remove build artifacts and caches
36
-
37
- just docs # Build the documentation site
38
- just docs-serve # Serve docs locally at http://127.0.0.1:8000
39
-
40
- just bump # Show available version bumps from current version
41
- just bump-patch # Bump patch version (e.g. 2.0.0 → 2.0.1)
42
- just bump-minor # Bump minor version (e.g. 2.0.0 → 2.1.0)
43
- just bump-major # Bump major version (e.g. 2.0.0 → 3.0.0)
44
- just bump-pre 2.0.0a1 # Tag a specific pre-release version
45
- ```
46
-
47
27
  ## Code Quality
48
28
 
49
29
  Linting and formatting are handled by [ruff](https://docs.astral.sh/ruff/):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.16.1
3
+ Version: 2.16.2
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
@@ -312,6 +312,7 @@ These are behavioral changes driven by security or correctness issues in the ups
312
312
  | Temp file creation TOCTOU race | `TempFileMgr.new_temp_fn()` discarded the `NamedTemporaryFile` handle, creating a race window. Now uses `tempfile.mkstemp()` for secure creation. Inherited from upstream. |
313
313
  | `shlex.split` on Windows incorrect mode | Called without `posix=False` on Windows, mishandling backslash-heavy paths in SHELL commands. Inherited from upstream. |
314
314
  | AST executor `~`/`+` variable scoping broken | The AST executor passed `localvars` through function parameters but never pushed `CommandList` frames onto `commandliststack`. Legacy metacommand handlers (`x_sub`, `x_rm_sub`, `xf_sub_defined`, `SUB_LOCAL`, prompt handlers, REPL) access `commandliststack[-1]` for `~` local and `+` outer-scope variables. This caused `~` vars to be invisible to SQL, `SUB_DEFINED(~var)` to always return false, and the REPL `.vars`/`.stack` to show empty state. Fixed by pushing/popping `CommandList` frames in `execute()` and `_execute_script_native()`. |
315
+ | AST parser INCLUDE quoted paths broken | The AST parser captured the full INCLUDE target including surrounding quotes (`"path"`), but the legacy dispatch regex stripped them. Quoted INCLUDE paths failed with "File does not exist" even when the file was present. Fixed by stripping matched quote pairs in the parser. |
315
316
 
316
317
  ______________________________________________________________________
317
318
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.16.1"
7
+ version = "2.16.2"
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.1"
168
+ current_version = "2.16.2"
169
169
  commit = true
170
170
  tag = true
171
171
  tag_name = "v{new_version}"
@@ -760,6 +760,11 @@ def _execute_include_native(
760
760
  effective_locals = _stack_localvars(ctx) or localvars
761
761
  target = substitute_vars(node.target, effective_locals, ctx=ctx).strip()
762
762
 
763
+ # Strip surrounding quotes — the AST parser captures the full target
764
+ # including any quotes, but the legacy dispatch regex stripped them.
765
+ if len(target) >= 2 and target[0] in ('"', "'") and target[-1] == target[0]:
766
+ target = target[1:-1]
767
+
763
768
  # Tilde expansion (matches x_include legacy handler)
764
769
  if len(target) > 1 and target[0] == "~" and target[1] == os.sep:
765
770
  target = str(Path.home() / target[2:])
@@ -95,6 +95,14 @@ _INCLUDE_RX = re.compile(
95
95
  re.I,
96
96
  )
97
97
 
98
+
99
+ def _strip_quotes(s: str) -> str:
100
+ """Strip a matching pair of surrounding quotes from *s*."""
101
+ if len(s) >= 2 and s[0] in ('"', "'") and s[-1] == s[0]:
102
+ return s[1:-1]
103
+ return s
104
+
105
+
98
106
  _EXEC_SCRIPT_RX = re.compile(
99
107
  r"^\s*(?:EXEC(?:UTE)?|RUN)\s+SCRIPT"
100
108
  r"(?P<exists>\s+IF\s+EXISTS)?"
@@ -545,7 +553,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
545
553
  _current_body().append(
546
554
  IncludeDirective(
547
555
  span=SourceSpan(source_name, file_lineno),
548
- target=m.group("target").strip(),
556
+ target=_strip_quotes(m.group("target").strip()),
549
557
  if_exists=m.group("exists") is not None,
550
558
  ),
551
559
  )
@@ -22,7 +22,7 @@ from unittest.mock import MagicMock, patch
22
22
  import pytest
23
23
 
24
24
  import execsql.state as _state
25
- from execsql.cli.run import _connect_initial_db, _print_dry_run, _run
25
+ from execsql.cli.run import _apply_cli_options, _connect_initial_db, _print_dry_run, _run, _seed_early_subvars
26
26
  from execsql.exceptions import ConfigError
27
27
 
28
28
 
@@ -1312,3 +1312,175 @@ class TestPositionalNetworkRouting:
1312
1312
  )
1313
1313
  # server set by DSN, positional should go to db
1314
1314
  assert _state.conf.db == "extra_db"
1315
+
1316
+
1317
+ # ---------------------------------------------------------------------------
1318
+ # _seed_early_subvars — unit tests for exception/filter branches
1319
+ # ---------------------------------------------------------------------------
1320
+
1321
+
1322
+ class TestSeedEarlySubvars:
1323
+ """Unit tests for _seed_early_subvars() covering the sensitive-key filter
1324
+ and the exception-swallowing branch for invalid env-var names."""
1325
+
1326
+ def test_sensitive_key_filtered_out(self):
1327
+ """Env vars whose names contain a sensitive substring are not seeded.
1328
+
1329
+ This exercises the ``continue`` branch (line 211 in run.py) where a key
1330
+ such as ``MY_SECRET_KEY`` is skipped before ``add_substitution`` is called.
1331
+ """
1332
+ with patch.dict("os.environ", {"MY_SECRET_KEY": "s3cr3t"}, clear=False):
1333
+ subvars = _seed_early_subvars()
1334
+ # The sensitive var must not appear with the & prefix.
1335
+ assert subvars.varvalue("&MY_SECRET_KEY") is None
1336
+ assert subvars.varvalue("&my_secret_key") is None
1337
+
1338
+ def test_invalid_env_var_name_is_silently_skipped(self):
1339
+ """An env var whose name produces an invalid substitution key does not raise.
1340
+
1341
+ Keys that contain characters outside word-character range (e.g. a hyphen)
1342
+ produce a variable name like ``&MY-VAR`` which fails SubVarSet.check_var_name().
1343
+ The ``except Exception: pass`` block (lines 214-215) swallows the error.
1344
+ """
1345
+ # Use a key containing a hyphen — illegal in an execsql variable name.
1346
+ with patch.dict("os.environ", {"EXECSQL-INVALID-KEY": "value"}, clear=False):
1347
+ # Must not raise even though add_substitution will raise ErrInfo.
1348
+ subvars = _seed_early_subvars()
1349
+ # The variable should simply be absent from the internal dict rather than
1350
+ # causing a crash. Check the internal dict directly to avoid calling
1351
+ # varvalue() with an invalid key (which would itself raise).
1352
+ assert "&execsql-invalid-key" not in subvars._subs_dict
1353
+
1354
+
1355
+ # ---------------------------------------------------------------------------
1356
+ # _apply_cli_options — unit tests for default-fallback branches
1357
+ # ---------------------------------------------------------------------------
1358
+
1359
+
1360
+ class TestApplyCliOptionsDefaults:
1361
+ """Unit tests for _apply_cli_options() that exercise the default-value
1362
+ fallback branches. These branches are only reachable when conf fields are
1363
+ ``None`` (i.e. not set by ConfigData defaults), which never happens when
1364
+ going through _run() — hence these tests call _apply_cli_options() directly
1365
+ with a hand-crafted SimpleNamespace conf.
1366
+ """
1367
+
1368
+ def _conf(self, **kwargs):
1369
+ """Return a minimal SimpleNamespace conf with all relevant fields set to
1370
+ ``None`` so that every default-fallback branch in _apply_cli_options()
1371
+ becomes reachable."""
1372
+ defaults = {
1373
+ "username": None,
1374
+ "passwd_prompt": True,
1375
+ "db_encoding": None,
1376
+ "script_encoding": None,
1377
+ "output_encoding": None,
1378
+ "import_encoding": None,
1379
+ "import_buffer": None,
1380
+ "make_export_dirs": None,
1381
+ "boolean_int": None,
1382
+ "scan_lines": None,
1383
+ "gui_level": None,
1384
+ "gui_framework": None,
1385
+ "db_type": None,
1386
+ "user_logfile": False,
1387
+ "port": None,
1388
+ "access_username": None,
1389
+ "new_db": False,
1390
+ "export_output_dir": None,
1391
+ "show_progress": False,
1392
+ }
1393
+ defaults.update(kwargs)
1394
+ return SimpleNamespace(**defaults)
1395
+
1396
+ def _call(self, conf, **kwargs):
1397
+ """Invoke _apply_cli_options() with the supplied conf and kwargs,
1398
+ filling in None/False defaults for any omitted parameters."""
1399
+ defaults = {
1400
+ "user": None,
1401
+ "no_passwd": False,
1402
+ "database_encoding": None,
1403
+ "script_encoding": None,
1404
+ "output_encoding": None,
1405
+ "import_encoding": None,
1406
+ "import_buffer": None,
1407
+ "make_dirs": None,
1408
+ "boolean_int": None,
1409
+ "scanlines": None,
1410
+ "use_gui": None,
1411
+ "gui_framework": None,
1412
+ "db_type": None,
1413
+ "user_logfile": False,
1414
+ "port": None,
1415
+ "new_db": False,
1416
+ "output_dir": None,
1417
+ "progress": False,
1418
+ }
1419
+ defaults.update(kwargs)
1420
+ _apply_cli_options(conf, **defaults)
1421
+
1422
+ def test_script_encoding_defaults_to_utf8_when_conf_is_none(self):
1423
+ """When conf.script_encoding is None and no CLI flag is given, default to 'utf8'."""
1424
+ conf = self._conf(script_encoding=None)
1425
+ self._call(conf, script_encoding=None)
1426
+ assert conf.script_encoding == "utf8"
1427
+
1428
+ def test_output_encoding_defaults_to_utf8_when_conf_is_none(self):
1429
+ """When conf.output_encoding is None and no CLI flag is given, default to 'utf8'."""
1430
+ conf = self._conf(output_encoding=None)
1431
+ self._call(conf, output_encoding=None)
1432
+ assert conf.output_encoding == "utf8"
1433
+
1434
+ def test_import_encoding_defaults_to_utf8_when_conf_is_none(self):
1435
+ """When conf.import_encoding is None and no CLI flag is given, default to 'utf8'."""
1436
+ conf = self._conf(import_encoding=None)
1437
+ self._call(conf, import_encoding=None)
1438
+ assert conf.import_encoding == "utf8"
1439
+
1440
+ def test_scan_lines_defaults_to_100_when_conf_is_none(self):
1441
+ """When conf.scan_lines is None and scanlines CLI arg is None, default to 100."""
1442
+ conf = self._conf(scan_lines=None)
1443
+ self._call(conf, scanlines=None)
1444
+ assert conf.scan_lines == 100
1445
+
1446
+ def test_db_type_defaults_to_l_when_conf_is_none(self):
1447
+ """When conf.db_type is None and db_type CLI arg is None, default to 'l' (SQLite)."""
1448
+ conf = self._conf(db_type=None)
1449
+ self._call(conf, db_type=None)
1450
+ assert conf.db_type == "l"
1451
+
1452
+ def test_gui_level_out_of_range_raises_config_error(self):
1453
+ """A gui_level value outside 0-3 raises ConfigError after being set by use_gui."""
1454
+ conf = self._conf(gui_level=None)
1455
+ with pytest.raises(ConfigError, match="Invalid GUI level"):
1456
+ self._call(conf, use_gui="5")
1457
+
1458
+ def test_cli_script_encoding_overrides_none_conf(self):
1459
+ """A CLI-supplied script_encoding wins over None in conf."""
1460
+ conf = self._conf(script_encoding=None)
1461
+ self._call(conf, script_encoding="latin-1")
1462
+ assert conf.script_encoding == "latin-1"
1463
+
1464
+ def test_cli_output_encoding_overrides_none_conf(self):
1465
+ """A CLI-supplied output_encoding wins over None in conf."""
1466
+ conf = self._conf(output_encoding=None)
1467
+ self._call(conf, output_encoding="cp1252")
1468
+ assert conf.output_encoding == "cp1252"
1469
+
1470
+ def test_cli_import_encoding_overrides_none_conf(self):
1471
+ """A CLI-supplied import_encoding wins over None in conf."""
1472
+ conf = self._conf(import_encoding=None)
1473
+ self._call(conf, import_encoding="utf-16")
1474
+ assert conf.import_encoding == "utf-16"
1475
+
1476
+ def test_cli_scanlines_overrides_none_conf(self):
1477
+ """A CLI-supplied scanlines wins over None in conf (no default applied)."""
1478
+ conf = self._conf(scan_lines=None)
1479
+ self._call(conf, scanlines=500)
1480
+ assert conf.scan_lines == 500
1481
+
1482
+ def test_cli_db_type_overrides_none_conf(self):
1483
+ """A CLI-supplied db_type wins over None in conf."""
1484
+ conf = self._conf(db_type=None)
1485
+ self._call(conf, db_type="p")
1486
+ assert conf.db_type == "p"
@@ -616,6 +616,26 @@ class TestInclude:
616
616
  assert result.returncode == 0
617
617
  assert _query_db(tmp_path, "SELECT x FROM t") == [(99,)]
618
618
 
619
+ def test_include_quoted_path(self, tmp_path):
620
+ """INCLUDE with double-quoted path strips quotes correctly."""
621
+ included = tmp_path / "helper.sql"
622
+ included.write_text("INSERT INTO t VALUES (7);")
623
+ script = 'CREATE TABLE t (x INT);\n-- !x! INCLUDE "helper.sql"'
624
+ result = _run_ast(script, tmp_path)
625
+ assert result.returncode == 0, result.stderr
626
+ assert _query_db(tmp_path, "SELECT x FROM t") == [(7,)]
627
+
628
+ def test_include_quoted_path_with_vars(self, tmp_path):
629
+ """INCLUDE with quoted path containing substitution variables."""
630
+ subdir = tmp_path / "lib"
631
+ subdir.mkdir()
632
+ included = subdir / "helper.sql"
633
+ included.write_text("INSERT INTO t VALUES (8);")
634
+ script = f'-- !x! SUB mylib {subdir}\nCREATE TABLE t (x INT);\n-- !x! INCLUDE "!!mylib!!!!$pathsep!!helper.sql"'
635
+ result = _run_ast(script, tmp_path)
636
+ assert result.returncode == 0, result.stderr
637
+ assert _query_db(tmp_path, "SELECT x FROM t") == [(8,)]
638
+
619
639
  def test_include_if_exists_missing(self, tmp_path):
620
640
  """INCLUDE IF EXISTS silently skips missing files."""
621
641
  script = "CREATE TABLE t (x INT);\nINSERT INTO t VALUES (1);\n-- !x! INCLUDE IF EXISTS no_such_file.sql"
@@ -11,9 +11,12 @@ from execsql.plugins import (
11
11
  ImporterEntry,
12
12
  ImporterRegistry,
13
13
  _load_entry_points,
14
+ discover_all_plugins,
14
15
  discover_exporter_plugins,
15
16
  discover_importer_plugins,
16
17
  discover_metacommand_plugins,
18
+ get_exporter_registry,
19
+ get_importer_registry,
17
20
  )
18
21
 
19
22
 
@@ -211,3 +214,122 @@ class TestListPluginsCli:
211
214
  result = runner.invoke(app, ["--list-plugins"], catch_exceptions=False)
212
215
  assert result.exit_code == 0
213
216
  assert "my_awesome_plugin" in result.output
217
+
218
+
219
+ # ---------------------------------------------------------------------------
220
+ # ImporterRegistry — untested methods
221
+ # ---------------------------------------------------------------------------
222
+
223
+
224
+ class TestImporterRegistryMethods:
225
+ def test_formats_returns_sorted_uppercase(self):
226
+ reg = ImporterRegistry()
227
+ reg.add("zebra", import_fn=lambda: None)
228
+ reg.add("alpha", import_fn=lambda: None)
229
+ assert reg.formats() == ["ALPHA", "ZEBRA"]
230
+
231
+ def test_entries_returns_all(self):
232
+ reg = ImporterRegistry()
233
+ reg.add("a", import_fn=lambda: None)
234
+ reg.add("b", import_fn=lambda: None)
235
+ assert len(reg.entries()) == 2
236
+
237
+
238
+ # ---------------------------------------------------------------------------
239
+ # Module-level singletons
240
+ # ---------------------------------------------------------------------------
241
+
242
+
243
+ class TestRegistrySingletons:
244
+ def test_get_exporter_registry_returns_instance(self):
245
+ import execsql.plugins as _plugins
246
+
247
+ # Reset global to ensure we exercise the None-branch.
248
+ original = _plugins._exporter_registry
249
+ _plugins._exporter_registry = None
250
+ try:
251
+ reg = get_exporter_registry()
252
+ assert isinstance(reg, ExporterRegistry)
253
+ # Second call returns the same singleton.
254
+ assert get_exporter_registry() is reg
255
+ finally:
256
+ _plugins._exporter_registry = original
257
+
258
+ def test_get_importer_registry_returns_instance(self):
259
+ import execsql.plugins as _plugins
260
+
261
+ original = _plugins._importer_registry
262
+ _plugins._importer_registry = None
263
+ try:
264
+ reg = get_importer_registry()
265
+ assert isinstance(reg, ImporterRegistry)
266
+ assert get_importer_registry() is reg
267
+ finally:
268
+ _plugins._importer_registry = original
269
+
270
+
271
+ # ---------------------------------------------------------------------------
272
+ # _load_entry_points — error branch when entry_points() itself raises
273
+ # ---------------------------------------------------------------------------
274
+
275
+
276
+ class TestLoadEntryPointsError:
277
+ def test_entry_points_query_failure_returns_empty(self):
278
+ """If entry_points() raises, _load_entry_points should return []."""
279
+ with patch("execsql.plugins.entry_points", side_effect=RuntimeError("registry broken")):
280
+ result = _load_entry_points(METACOMMAND_GROUP)
281
+ assert result == []
282
+
283
+
284
+ # ---------------------------------------------------------------------------
285
+ # discover_all_plugins
286
+ # ---------------------------------------------------------------------------
287
+
288
+
289
+ class TestDiscoverAllPlugins:
290
+ def test_discover_all_plugins_without_mcl_skips_metacommands(self):
291
+ """When mcl is None, metacommand plugins are not attempted."""
292
+ with patch("execsql.plugins.entry_points", return_value=[]):
293
+ result = discover_all_plugins(mcl=None)
294
+ # Key for metacommand group must be absent since mcl is None.
295
+ from execsql.plugins import METACOMMAND_GROUP
296
+
297
+ assert METACOMMAND_GROUP not in result
298
+ # Exporter and importer counts are present.
299
+ from execsql.plugins import EXPORTER_GROUP, IMPORTER_GROUP
300
+
301
+ assert EXPORTER_GROUP in result
302
+ assert IMPORTER_GROUP in result
303
+
304
+ def test_discover_all_plugins_with_mcl_includes_metacommands(self):
305
+ """When mcl is provided, all three plugin types are discovered."""
306
+ mock_mcl = MagicMock()
307
+ with patch("execsql.plugins.entry_points", return_value=[]):
308
+ result = discover_all_plugins(mcl=mock_mcl)
309
+ from execsql.plugins import EXPORTER_GROUP, IMPORTER_GROUP, METACOMMAND_GROUP
310
+
311
+ assert METACOMMAND_GROUP in result
312
+ assert EXPORTER_GROUP in result
313
+ assert IMPORTER_GROUP in result
314
+
315
+ def test_discover_exporter_plugin_registration_error_handled(self):
316
+ """A plugin whose register function raises does not crash discover_exporter_plugins."""
317
+ mock_register = MagicMock(side_effect=ValueError("bad exporter"))
318
+ mock_ep = MagicMock()
319
+ mock_ep.name = "broken_exp"
320
+ mock_ep.load.return_value = mock_register
321
+
322
+ with patch("execsql.plugins.entry_points", return_value=[mock_ep]):
323
+ count = discover_exporter_plugins()
324
+ assert count == 0
325
+
326
+ def test_discover_importer_plugin_registration_error_handled(self):
327
+ """A plugin whose register function raises does not crash discover_importer_plugins."""
328
+ mock_register = MagicMock(side_effect=ValueError("bad importer"))
329
+ mock_ep = MagicMock()
330
+ mock_ep.name = "broken_imp"
331
+ mock_ep.load.return_value = mock_register
332
+
333
+ with patch("execsql.plugins.entry_points", return_value=[mock_ep]):
334
+ count = discover_importer_plugins()
335
+ assert count == 0
@@ -222,3 +222,8 @@ class TestEncodingsMatch:
222
222
 
223
223
  def test_koi8r_aliases(self):
224
224
  assert encodings_match("koi8-r", "koi8r") is True
225
+
226
+ def test_leading_zero_normalization_matches(self):
227
+ # iso-8859-01 and iso-8859-1 differ only in a leading zero — the
228
+ # numeric-leading-zero stripping pass (line 133-137 in run.py) makes them equal.
229
+ assert encodings_match("iso-8859-01", "iso-8859-1") is True
@@ -648,7 +648,7 @@ wheels = [
648
648
 
649
649
  [[package]]
650
650
  name = "execsql2"
651
- version = "2.16.1"
651
+ version = "2.16.2"
652
652
  source = { editable = "." }
653
653
  dependencies = [
654
654
  { name = "python-dateutil" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes