execsql2 2.20.0__tar.gz → 2.21.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 (373) hide show
  1. {execsql2-2.20.0 → execsql2-2.21.1}/.github/workflows/ci-cd.yml +2 -2
  2. {execsql2-2.20.0 → execsql2-2.21.1}/CHANGELOG.md +16 -0
  3. {execsql2-2.20.0 → execsql2-2.21.1}/PKG-INFO +7 -7
  4. {execsql2-2.20.0 → execsql2-2.21.1}/README.md +1 -1
  5. {execsql2-2.20.0 → execsql2-2.21.1}/docs/about/divergence.md +1 -0
  6. {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/adding_db_adapters.md +2 -2
  7. {execsql2-2.20.0 → execsql2-2.21.1}/docs/getting-started/requirements.md +10 -10
  8. {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/formatter.md +1 -1
  9. {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/usage.md +1 -1
  10. {execsql2-2.20.0 → execsql2-2.21.1}/docs/reference/metacommands.md +1 -1
  11. {execsql2-2.20.0 → execsql2-2.21.1}/pyproject.toml +5 -5
  12. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/factory.py +1 -1
  13. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/postgres.py +28 -18
  14. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_postgres.py +23 -16
  15. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_postgres_inprocess.py +5 -5
  16. {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/test_postgres.py +6 -6
  17. {execsql2-2.20.0 → execsql2-2.21.1}/uv.lock +97 -71
  18. {execsql2-2.20.0 → execsql2-2.21.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  19. {execsql2-2.20.0 → execsql2-2.21.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  20. {execsql2-2.20.0 → execsql2-2.21.1}/.github/dependabot.yml +0 -0
  21. {execsql2-2.20.0 → execsql2-2.21.1}/.gitignore +0 -0
  22. {execsql2-2.20.0 → execsql2-2.21.1}/.pre-commit-config.yaml +0 -0
  23. {execsql2-2.20.0 → execsql2-2.21.1}/.pre-commit-hooks.yaml +0 -0
  24. {execsql2-2.20.0 → execsql2-2.21.1}/.python-version +0 -0
  25. {execsql2-2.20.0 → execsql2-2.21.1}/.readthedocs.yaml +0 -0
  26. {execsql2-2.20.0 → execsql2-2.21.1}/CONTRIBUTING.md +0 -0
  27. {execsql2-2.20.0 → execsql2-2.21.1}/LICENSE.txt +0 -0
  28. {execsql2-2.20.0 → execsql2-2.21.1}/NOTICE +0 -0
  29. {execsql2-2.20.0 → execsql2-2.21.1}/SECURITY.md +0 -0
  30. {execsql2-2.20.0 → execsql2-2.21.1}/docs/about/contributors.md +0 -0
  31. {execsql2-2.20.0 → execsql2-2.21.1}/docs/about/copyright.md +0 -0
  32. {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/cli.md +0 -0
  33. {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/db.md +0 -0
  34. {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/exporters.md +0 -0
  35. {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/importers.md +0 -0
  36. {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/index.md +0 -0
  37. {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/metacommands.md +0 -0
  38. {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/adding_exporters.md +0 -0
  39. {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/adding_importers.md +0 -0
  40. {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/adding_metacommands.md +0 -0
  41. {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/architecture.md +0 -0
  42. {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/releasing.md +0 -0
  43. {execsql2-2.20.0 → execsql2-2.21.1}/docs/getting-started/installation.md +0 -0
  44. {execsql2-2.20.0 → execsql2-2.21.1}/docs/getting-started/syntax.md +0 -0
  45. {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/debugging.md +0 -0
  46. {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/documentation.md +0 -0
  47. {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/encoding.md +0 -0
  48. {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/examples.md +0 -0
  49. {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/logging.md +0 -0
  50. {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/sql_syntax.md +0 -0
  51. {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/using_scripts.md +0 -0
  52. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/Compare_planets.png +0 -0
  53. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/actions.png +0 -0
  54. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/actions2.png +0 -0
  55. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/checkboxes.png +0 -0
  56. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/connect.b64 +0 -0
  57. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/connect.png +0 -0
  58. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/create_conf.png +0 -0
  59. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/data_error1_screenshot.jpg +0 -0
  60. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/entry_form.png +0 -0
  61. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/execsql_console.png +0 -0
  62. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/execsql_logo_01.png +0 -0
  63. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/fatals.png +0 -0
  64. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/logo_small.png +0 -0
  65. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/pause_terminal.png +0 -0
  66. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/pause_terminal_sm.b64 +0 -0
  67. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/pause_terminal_sm.png +0 -0
  68. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/prompt_compare.png +0 -0
  69. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/set_build_commands.jpg +0 -0
  70. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/unit_conversions.b64 +0 -0
  71. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/unit_conversions_029.png +0 -0
  72. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/unmatched.png +0 -0
  73. {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/vim_execsql_highlight.png +0 -0
  74. {execsql2-2.20.0 → execsql2-2.21.1}/docs/index.md +0 -0
  75. {execsql2-2.20.0 → execsql2-2.21.1}/docs/reference/configuration.md +0 -0
  76. {execsql2-2.20.0 → execsql2-2.21.1}/docs/reference/security.md +0 -0
  77. {execsql2-2.20.0 → execsql2-2.21.1}/docs/reference/substitution_vars.md +0 -0
  78. {execsql2-2.20.0 → execsql2-2.21.1}/extras/plugin-template/README.md +0 -0
  79. {execsql2-2.20.0 → execsql2-2.21.1}/extras/plugin-template/pyproject.toml +0 -0
  80. {execsql2-2.20.0 → execsql2-2.21.1}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  81. {execsql2-2.20.0 → execsql2-2.21.1}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  82. {execsql2-2.20.0 → execsql2-2.21.1}/extras/vscode-execsql/README.md +0 -0
  83. {execsql2-2.20.0 → execsql2-2.21.1}/extras/vscode-execsql/package.json +0 -0
  84. {execsql2-2.20.0 → execsql2-2.21.1}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  85. {execsql2-2.20.0 → execsql2-2.21.1}/justfile +0 -0
  86. {execsql2-2.20.0 → execsql2-2.21.1}/scripts/generate_vscode_grammar.py +0 -0
  87. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/__init__.py +0 -0
  88. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/__main__.py +0 -0
  89. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/api.py +0 -0
  90. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/cli/__init__.py +0 -0
  91. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/cli/dsn.py +0 -0
  92. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/cli/help.py +0 -0
  93. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/cli/lint.py +0 -0
  94. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/cli/run.py +0 -0
  95. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/config.py +0 -0
  96. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/data/__init__.py +0 -0
  97. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/data/execsql.conf.template +0 -0
  98. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/__init__.py +0 -0
  99. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/access.py +0 -0
  100. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/base.py +0 -0
  101. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/dsn.py +0 -0
  102. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/duckdb.py +0 -0
  103. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/firebird.py +0 -0
  104. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/mysql.py +0 -0
  105. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/oracle.py +0 -0
  106. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/sqlite.py +0 -0
  107. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/sqlserver.py +0 -0
  108. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/debug/__init__.py +0 -0
  109. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/debug/repl.py +0 -0
  110. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exceptions.py +0 -0
  111. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/__init__.py +0 -0
  112. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/base.py +0 -0
  113. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/delimited.py +0 -0
  114. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/duckdb.py +0 -0
  115. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/feather.py +0 -0
  116. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/html.py +0 -0
  117. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/json.py +0 -0
  118. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/latex.py +0 -0
  119. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/markdown.py +0 -0
  120. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/ods.py +0 -0
  121. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/parquet.py +0 -0
  122. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/pretty.py +0 -0
  123. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/protocol.py +0 -0
  124. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/raw.py +0 -0
  125. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/sqlite.py +0 -0
  126. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/templates.py +0 -0
  127. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/values.py +0 -0
  128. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/xls.py +0 -0
  129. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/xlsx.py +0 -0
  130. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/xml.py +0 -0
  131. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/yaml.py +0 -0
  132. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/zip.py +0 -0
  133. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/format.py +0 -0
  134. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/gui/__init__.py +0 -0
  135. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/gui/base.py +0 -0
  136. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/gui/console.py +0 -0
  137. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/gui/desktop.py +0 -0
  138. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/gui/tui.py +0 -0
  139. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/__init__.py +0 -0
  140. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/base.py +0 -0
  141. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/csv.py +0 -0
  142. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/feather.py +0 -0
  143. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/json.py +0 -0
  144. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/ods.py +0 -0
  145. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/xls.py +0 -0
  146. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/__init__.py +0 -0
  147. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/conditions.py +0 -0
  148. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/connect.py +0 -0
  149. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/control.py +0 -0
  150. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/data.py +0 -0
  151. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/debug.py +0 -0
  152. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/dispatch.py +0 -0
  153. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/io.py +0 -0
  154. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/io_export.py +0 -0
  155. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/io_fileops.py +0 -0
  156. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/io_import.py +0 -0
  157. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/io_write.py +0 -0
  158. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/prompt.py +0 -0
  159. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/script_ext.py +0 -0
  160. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/system.py +0 -0
  161. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/upsert.py +0 -0
  162. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/models.py +0 -0
  163. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/parser.py +0 -0
  164. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/plugins.py +0 -0
  165. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/py.typed +0 -0
  166. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/__init__.py +0 -0
  167. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/ast.py +0 -0
  168. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/control.py +0 -0
  169. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/engine.py +0 -0
  170. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/executor.py +0 -0
  171. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/parser.py +0 -0
  172. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/variables.py +0 -0
  173. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/state.py +0 -0
  174. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/types.py +0 -0
  175. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/__init__.py +0 -0
  176. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/auth.py +0 -0
  177. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/crypto.py +0 -0
  178. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/datetime.py +0 -0
  179. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/errors.py +0 -0
  180. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/fileio.py +0 -0
  181. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/gui.py +0 -0
  182. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/mail.py +0 -0
  183. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/numeric.py +0 -0
  184. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/regex.py +0 -0
  185. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/strings.py +0 -0
  186. {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/timer.py +0 -0
  187. {execsql2-2.20.0 → execsql2-2.21.1}/templates/README.md +0 -0
  188. {execsql2-2.20.0 → execsql2-2.21.1}/templates/config_settings.sqlite +0 -0
  189. {execsql2-2.20.0 → execsql2-2.21.1}/templates/example_config_prompt.sql +0 -0
  190. {execsql2-2.20.0 → execsql2-2.21.1}/templates/make_config_db.sql +0 -0
  191. {execsql2-2.20.0 → execsql2-2.21.1}/templates/md_compare.sql +0 -0
  192. {execsql2-2.20.0 → execsql2-2.21.1}/templates/md_glossary.sql +0 -0
  193. {execsql2-2.20.0 → execsql2-2.21.1}/templates/md_upsert.sql +0 -0
  194. {execsql2-2.20.0 → execsql2-2.21.1}/templates/pg_compare.sql +0 -0
  195. {execsql2-2.20.0 → execsql2-2.21.1}/templates/pg_glossary.sql +0 -0
  196. {execsql2-2.20.0 → execsql2-2.21.1}/templates/pg_upsert.sql +0 -0
  197. {execsql2-2.20.0 → execsql2-2.21.1}/templates/script_template.sql +0 -0
  198. {execsql2-2.20.0 → execsql2-2.21.1}/templates/ss_compare.sql +0 -0
  199. {execsql2-2.20.0 → execsql2-2.21.1}/templates/ss_glossary.sql +0 -0
  200. {execsql2-2.20.0 → execsql2-2.21.1}/templates/ss_upsert.sql +0 -0
  201. {execsql2-2.20.0 → execsql2-2.21.1}/tests/__init__.py +0 -0
  202. {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/__init__.py +0 -0
  203. {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_cli.py +0 -0
  204. {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_cli_e2e.py +0 -0
  205. {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_cli_run.py +0 -0
  206. {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_lint.py +0 -0
  207. {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_ping.py +0 -0
  208. {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_profile.py +0 -0
  209. {execsql2-2.20.0 → execsql2-2.21.1}/tests/conftest.py +0 -0
  210. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/__init__.py +0 -0
  211. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_access_windows.py +0 -0
  212. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_base.py +0 -0
  213. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_db_adapters_mocked.py +0 -0
  214. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_db_adapters_mocked_extra.py +0 -0
  215. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_dsn.py +0 -0
  216. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_duckdb.py +0 -0
  217. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_factory.py +0 -0
  218. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_mysql_case_folding.py +0 -0
  219. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_mysql_inprocess.py +0 -0
  220. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_sqlite.py +0 -0
  221. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_sqlite_extra.py +0 -0
  222. {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_sqlserver_inprocess.py +0 -0
  223. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/__init__.py +0 -0
  224. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_base.py +0 -0
  225. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_db.py +0 -0
  226. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_delimited.py +0 -0
  227. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_duckdb_exporter.py +0 -0
  228. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_exporters.py +0 -0
  229. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_feather.py +0 -0
  230. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_html_extended.py +0 -0
  231. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_html_latex.py +0 -0
  232. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_json.py +0 -0
  233. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_json_extended.py +0 -0
  234. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_latex_extended.py +0 -0
  235. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_markdown.py +0 -0
  236. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_ods.py +0 -0
  237. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_ods_export.py +0 -0
  238. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_parquet.py +0 -0
  239. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_pretty_extended.py +0 -0
  240. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_raw_extended.py +0 -0
  241. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_sqlite_exporter.py +0 -0
  242. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_templates.py +0 -0
  243. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_templates_extended.py +0 -0
  244. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_values_extended.py +0 -0
  245. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_xls_xlsx.py +0 -0
  246. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_xlsx.py +0 -0
  247. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_xml.py +0 -0
  248. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_yaml.py +0 -0
  249. {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_zip.py +0 -0
  250. {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/__init__.py +0 -0
  251. {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_backends.py +0 -0
  252. {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_backends_extended.py +0 -0
  253. {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_compare_stats.py +0 -0
  254. {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_compute_row_diffs.py +0 -0
  255. {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_desktop_dialogs.py +0 -0
  256. {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_tui_pilot.py +0 -0
  257. {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_tui_pilot_complex.py +0 -0
  258. {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_utils_gui_extended.py +0 -0
  259. {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/__init__.py +0 -0
  260. {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_base_extended.py +0 -0
  261. {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_csv_edge_cases.py +0 -0
  262. {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_csv_importer.py +0 -0
  263. {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_feather_importer.py +0 -0
  264. {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_json_importer.py +0 -0
  265. {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_ods_importer.py +0 -0
  266. {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_xls_importer.py +0 -0
  267. {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/__init__.py +0 -0
  268. {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/conftest.py +0 -0
  269. {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/test_dsn.py +0 -0
  270. {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/test_duckdb.py +0 -0
  271. {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/test_mysql.py +0 -0
  272. {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/test_sqlite.py +0 -0
  273. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/__init__.py +0 -0
  274. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_assert.py +0 -0
  275. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_breakpoint.py +0 -0
  276. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_conditions_extra.py +0 -0
  277. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_connect.py +0 -0
  278. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_io_export.py +0 -0
  279. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_io_import.py +0 -0
  280. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands.py +0 -0
  281. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_connect.py +0 -0
  282. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_data.py +0 -0
  283. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_extended.py +0 -0
  284. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  285. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_io.py +0 -0
  286. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  287. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  288. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_system.py +0 -0
  289. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  290. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_pg_upsert.py +0 -0
  291. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_prompt.py +0 -0
  292. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_row_count.py +0 -0
  293. {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_show_scripts.py +0 -0
  294. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/__init__.py +0 -0
  295. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
  296. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
  297. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
  298. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/audit_smoke.sql +0 -0
  299. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/config_runtime.sql +0 -0
  300. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/control_flow.sql +0 -0
  301. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
  302. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/error_handling.sql +0 -0
  303. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
  304. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/import_options.sql +0 -0
  305. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/includes/helper.sql +0 -0
  306. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/includes.sql +0 -0
  307. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/io_formats.sql +0 -0
  308. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  309. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/multi_db.sql +0 -0
  310. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
  311. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  312. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/predicates.sql +0 -0
  313. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/scripts.sql +0 -0
  314. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/smoke.sql +0 -0
  315. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
  316. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
  317. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
  318. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/template_export.sql +0 -0
  319. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/timer.sql +0 -0
  320. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/transactions.sql +0 -0
  321. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/write_create_table.sql +0 -0
  322. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
  323. {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/test_sql_scripts.py +0 -0
  324. {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/__init__.py +0 -0
  325. {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_env_detection.py +0 -0
  326. {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_expansion_bomb.py +0 -0
  327. {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_import_regex_hardening.py +0 -0
  328. {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_log_redaction.py +0 -0
  329. {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_odbc_injection.py +0 -0
  330. {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_path_containment.py +0 -0
  331. {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_substitution_injection.py +0 -0
  332. {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_zip_bomb.py +0 -0
  333. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_api.py +0 -0
  334. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_api_extended.py +0 -0
  335. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_ast.py +0 -0
  336. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_ast_parser.py +0 -0
  337. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_bundled_templates.py +0 -0
  338. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_config.py +0 -0
  339. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_config_data.py +0 -0
  340. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_config_extended.py +0 -0
  341. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_debug_repl.py +0 -0
  342. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_engine.py +0 -0
  343. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_error_messages.py +0 -0
  344. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_exceptions.py +0 -0
  345. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_executor.py +0 -0
  346. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_executor_inprocess.py +0 -0
  347. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_format.py +0 -0
  348. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_mail.py +0 -0
  349. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_models.py +0 -0
  350. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_package.py +0 -0
  351. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_parser.py +0 -0
  352. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_parser_params.py +0 -0
  353. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_plugins.py +0 -0
  354. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_registry.py +0 -0
  355. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_script.py +0 -0
  356. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_state.py +0 -0
  357. {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_types.py +0 -0
  358. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/__init__.py +0 -0
  359. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_auth.py +0 -0
  360. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_auth_extra.py +0 -0
  361. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_crypto.py +0 -0
  362. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_datetime.py +0 -0
  363. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_encodedfile_context.py +0 -0
  364. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_errors.py +0 -0
  365. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_errors_extra.py +0 -0
  366. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_fileio.py +0 -0
  367. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_fileio_extra.py +0 -0
  368. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_numeric.py +0 -0
  369. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_regex.py +0 -0
  370. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_strings.py +0 -0
  371. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_timer.py +0 -0
  372. {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_timer_extra.py +0 -0
  373. {execsql2-2.20.0 → execsql2-2.21.1}/zensical.toml +0 -0
@@ -86,7 +86,7 @@ jobs:
86
86
  tox -e py
87
87
  - name: Upload coverage to Codecov
88
88
  if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'
89
- uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
89
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
90
90
  with:
91
91
  token: ${{ secrets.CODECOV_TOKEN }}
92
92
  files: coverage.xml
@@ -167,7 +167,7 @@ jobs:
167
167
  run: |
168
168
  python -m pytest --cov=execsql --cov-branch --cov-report=xml --cov-fail-under=86
169
169
  - name: Upload coverage to Codecov
170
- uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
170
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
171
171
  with:
172
172
  token: ${{ secrets.CODECOV_TOKEN }}
173
173
  files: coverage.xml
@@ -13,6 +13,22 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.21.1] - 2026-06-24
17
+
18
+ ### Fixed
19
+
20
+ - PostgreSQL: `EXPORT` and other repeated queries against objects a script drops/recreates or alters no longer fail with `FeatureNotSupported: cached plan must not change result type`. psycopg3's automatic server-side prepared statements are now disabled (`prepare_threshold=None`), restoring the psycopg2 behavior.
21
+
22
+ ______________________________________________________________________
23
+
24
+ ## [2.21.0] - 2026-06-24
25
+
26
+ ### Changed
27
+
28
+ - PostgreSQL connections now use psycopg3 (install the `postgres` extra, which pulls `psycopg[binary]`) instead of psycopg2. The `PG_UPSERT` metacommand now requires `pg-upsert>=1.23.0`.
29
+
30
+ ______________________________________________________________________
31
+
16
32
  ## [2.20.0] - 2026-06-11
17
33
 
18
34
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.20.0
3
+ Version: 2.21.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: Homepage, https://execsql2.readthedocs.io
6
6
  Project-URL: Repository, https://github.com/geocoug/execsql
@@ -52,9 +52,9 @@ Requires-Dist: keyring>=25.0; extra == 'all'
52
52
  Requires-Dist: odfpy>=1.4; extra == 'all'
53
53
  Requires-Dist: openpyxl>=3.1; extra == 'all'
54
54
  Requires-Dist: oracledb>=3.0; extra == 'all'
55
- Requires-Dist: pg-upsert>=1.22.1; extra == 'all'
55
+ Requires-Dist: pg-upsert>=1.23.0; extra == 'all'
56
56
  Requires-Dist: polars>=1.0; extra == 'all'
57
- Requires-Dist: psycopg2-binary>=2.9; extra == 'all'
57
+ Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'all'
58
58
  Requires-Dist: pymysql>=1.1; extra == 'all'
59
59
  Requires-Dist: pyodbc>=5.0; extra == 'all'
60
60
  Requires-Dist: pyyaml>=6.0; extra == 'all'
@@ -66,7 +66,7 @@ Provides-Extra: all-db
66
66
  Requires-Dist: duckdb>=1.0; extra == 'all-db'
67
67
  Requires-Dist: firebird-driver>=1.10; extra == 'all-db'
68
68
  Requires-Dist: oracledb>=3.0; extra == 'all-db'
69
- Requires-Dist: psycopg2-binary>=2.9; extra == 'all-db'
69
+ Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'all-db'
70
70
  Requires-Dist: pymysql>=1.1; extra == 'all-db'
71
71
  Requires-Dist: pyodbc>=5.0; extra == 'all-db'
72
72
  Provides-Extra: auth
@@ -124,9 +124,9 @@ Requires-Dist: pyodbc>=5.0; extra == 'odbc'
124
124
  Provides-Extra: oracle
125
125
  Requires-Dist: oracledb>=3.0; extra == 'oracle'
126
126
  Provides-Extra: postgres
127
- Requires-Dist: psycopg2-binary>=2.9; extra == 'postgres'
127
+ Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'postgres'
128
128
  Provides-Extra: upsert
129
- Requires-Dist: pg-upsert>=1.22.1; extra == 'upsert'
129
+ Requires-Dist: pg-upsert>=1.23.0; extra == 'upsert'
130
130
  Description-Content-Type: text/markdown
131
131
 
132
132
  > [!NOTE]
@@ -403,7 +403,7 @@ execsql-format --no-sql --in-place scripts/
403
403
  ```yaml
404
404
  repos:
405
405
  - repo: https://github.com/geocoug/execsql
406
- rev: v2.20.0
406
+ rev: v2.21.1
407
407
  hooks:
408
408
  - id: execsql-format
409
409
  ```
@@ -272,7 +272,7 @@ execsql-format --no-sql --in-place scripts/
272
272
  ```yaml
273
273
  repos:
274
274
  - repo: https://github.com/geocoug/execsql
275
- rev: v2.20.0
275
+ rev: v2.21.1
276
276
  hooks:
277
277
  - id: execsql-format
278
278
  ```
@@ -210,6 +210,7 @@ All 33 mutable runtime globals in `state.py` have been consolidated into a `Runt
210
210
 
211
211
  ### Database Adapters
212
212
 
213
+ - **PostgreSQL driver is psycopg3** — the PostgreSQL adapter uses `psycopg` (psycopg3, installed via the `psycopg[binary]` extra) instead of upstream's `psycopg2`. The connection now passes the libpq `dbname` keyword (psycopg3 rejects psycopg2's `database`), `VACUUM` toggles `conn.autocommit` instead of the removed `set_session()`, server-side `COPY` import uses psycopg3's `cursor.copy()` context manager instead of `copy_expert()`, and binary `IMPORT` sends raw `bytes` instead of `psycopg2.Binary()`. This also makes `db.conn` directly usable by the `PG_UPSERT` metacommand's pg-upsert ≥1.23.0 dependency, which itself requires a psycopg3 connection. The connection sets `prepare_threshold=None` to disable psycopg3's automatic server-side prepared statements, which psycopg2 never used; without this, re-running a query (e.g. via `EXPORT`) against an object the script drops/recreates or alters raises `FeatureNotSupported: cached plan must not change result type`.
213
214
  - **`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.
214
215
  - **Connection timeouts** — PostgreSQL and SQLite adapters accept a connection timeout parameter (default 30 seconds).
215
216
  - **DuckDB temporal types** — `TIMESTAMPTZ`, `TIMESTAMP`, `DATE`, `TIME` now map to native DuckDB types instead of `TEXT`.
@@ -59,7 +59,7 @@ class MyDBDatabase(Database):
59
59
  self.need_passwd = False
60
60
  self.encoding = "UTF-8"
61
61
  self.encode_commands = False
62
- self.paramstr = "?" # placeholder style: "?" for most drivers, "%s" for psycopg2
62
+ self.paramstr = "?" # placeholder style: "?" for most drivers, "%s" for psycopg
63
63
  self.conn = None
64
64
  self.autocommit = True
65
65
  self.open_db()
@@ -137,7 +137,7 @@ These are the instance attributes and methods you must configure correctly:
137
137
  | Attribute / Method | Type | Purpose |
138
138
  | ---------------------- | ----------------- | --------------------------------------------------------------------------------- |
139
139
  | `self.type` | `DbType` | DBMS type token (e.g., `dbt_sqlite`). Controls quoting and type-mapping. |
140
- | `self.paramstr` | `str` | SQL parameter placeholder: `"?"` (most drivers) or `"%s"` (psycopg2, PyMySQL). |
140
+ | `self.paramstr` | `str` | SQL parameter placeholder: `"?"` (most drivers) or `"%s"` (psycopg, PyMySQL). |
141
141
  | `self.encoding` | `str` | Database character encoding. Detect from the database on connect if possible. |
142
142
  | `self.encode_commands` | `bool` | `True` if SQL strings should be encoded before passing to the driver. |
143
143
  | `self.autocommit` | `bool` | `True` means the driver commits automatically; `False` requires explicit commits. |
@@ -38,16 +38,16 @@ The specific libraries installed by each extra are:
38
38
 
39
39
  ### Database drivers
40
40
 
41
- | Database / Format | Extra | Library |
42
- | ----------------- | ---------- | ------------------------------------------------------------ |
43
- | PostgreSQL | `postgres` | [psycopg2-binary](https://pypi.org/project/psycopg2-binary/) |
44
- | MySQL / MariaDB | `mysql` | [pymysql](https://pypi.org/project/PyMySQL/) |
45
- | MS SQL Server | `mssql` | [pyodbc](https://pypi.org/project/pyodbc/) |
46
- | DuckDB | `duckdb` | [duckdb](https://pypi.org/project/duckdb/) |
47
- | Firebird | `firebird` | [firebird-driver](https://pypi.org/project/firebird-driver/) |
48
- | Oracle | `oracle` | [oracledb](https://pypi.org/project/oracledb/) |
49
- | ODBC DSN | `odbc` | [pyodbc](https://pypi.org/project/pyodbc/) |
50
- | SQLite | — | Built-in (`sqlite3` standard library) |
41
+ | Database / Format | Extra | Library |
42
+ | ----------------- | ---------- | --------------------------------------------------------------- |
43
+ | PostgreSQL | `postgres` | [psycopg[binary]](https://pypi.org/project/psycopg/) (psycopg3) |
44
+ | MySQL / MariaDB | `mysql` | [pymysql](https://pypi.org/project/PyMySQL/) |
45
+ | MS SQL Server | `mssql` | [pyodbc](https://pypi.org/project/pyodbc/) |
46
+ | DuckDB | `duckdb` | [duckdb](https://pypi.org/project/duckdb/) |
47
+ | Firebird | `firebird` | [firebird-driver](https://pypi.org/project/firebird-driver/) |
48
+ | Oracle | `oracle` | [oracledb](https://pypi.org/project/oracledb/) |
49
+ | ODBC DSN | `odbc` | [pyodbc](https://pypi.org/project/pyodbc/) |
50
+ | SQLite | — | Built-in (`sqlite3` standard library) |
51
51
 
52
52
  ### `formats` bundle
53
53
 
@@ -160,7 +160,7 @@ select id,name,created_at from users where active = true order by name;
160
160
  ```yaml
161
161
  repos:
162
162
  - repo: https://github.com/geocoug/execsql
163
- rev: v2.20.0
163
+ rev: v2.21.1
164
164
  hooks:
165
165
  - id: execsql-format
166
166
  ```
@@ -82,7 +82,7 @@ When a DSN is used as a data source, *execsql* has no information about the feat
82
82
 
83
83
  ## PostgreSQL-Compatible Databases
84
84
 
85
- Amazon [Redshift](https://docs.aws.amazon.com/redshift/index.html) is built on PostgreSQL 8.0.3 and [CockroachDB](https://www.cockroachlabs.com/) uses the *psycopg2* library for connections from Python. Both of these databases can therefore potentially be used with *execsql* by specifying the database type as Postgres (i.e., db_type=p).
85
+ Amazon [Redshift](https://docs.aws.amazon.com/redshift/index.html) is built on PostgreSQL 8.0.3 and [CockroachDB](https://www.cockroachlabs.com/) uses the *psycopg* library for connections from Python. Both of these databases can therefore potentially be used with *execsql* by specifying the database type as Postgres (i.e., db_type=p).
86
86
 
87
87
  The following Postgres-specific features that are used by *execsql* may function differently or not at all in other Postgres-compatible databases:
88
88
 
@@ -2368,7 +2368,7 @@ For the full list of temporary objects and their schemas, see the [pg-upsert Tem
2368
2368
  PG_VACUUM <vacuum arguments>
2369
2369
  ```
2370
2370
 
2371
- Runs the 'vacuum' command on the current database if the current DBMS is Postgres. The 'vacuum' command will not execute successfully as a SQL command because it requires a change in the configuration of the (psycopg2) connection. This metacommand makes that change, runs the 'vacuum' metacommand, and restores the connection configuration to its default setting.
2371
+ Runs the 'vacuum' command on the current database if the current DBMS is Postgres. The 'vacuum' command will not execute successfully as a SQL command because it requires a change in the configuration of the (psycopg) connection. This metacommand makes that change, runs the 'vacuum' metacommand, and restores the connection configuration to its default setting.
2372
2372
 
2373
2373
  This metacommand has no effect if the current DBMS is not PostgreSQL.
2374
2374
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.20.0"
7
+ version = "2.21.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" }
@@ -48,7 +48,7 @@ dependencies = [
48
48
 
49
49
  [project.optional-dependencies]
50
50
  # Database drivers — conservative floors matching uv.lock; patches allowed.
51
- postgres = ["psycopg2-binary>=2.9"]
51
+ postgres = ["psycopg[binary]>=3.1,<4"]
52
52
  mysql = ["pymysql>=1.1"]
53
53
  mssql = ["pyodbc>=5.0"]
54
54
  duckdb = ["duckdb>=1.0"]
@@ -69,12 +69,12 @@ formats = [
69
69
  auth = ["keyring>=25.0"]
70
70
  auth-plaintext = ["keyring>=25.0", "keyrings.alt>=5.0"]
71
71
  auth-encrypted = ["keyring>=25.0", "keyrings.alt>=5.0", "pycryptodome>=3.20"]
72
- upsert = ["pg-upsert>=1.22.1"]
72
+ upsert = ["pg-upsert>=1.23.0"]
73
73
  map = ["tkintermapview>=1.29"]
74
74
  formatter = ["sqlglot>=25.0"]
75
75
  # Convenience groups
76
76
  all-db = [
77
- "psycopg2-binary>=2.9",
77
+ "psycopg[binary]>=3.1,<4",
78
78
  "pymysql>=1.1",
79
79
  "pyodbc>=5.0",
80
80
  "duckdb>=1.0",
@@ -178,7 +178,7 @@ skip-magic-trailing-comma = false
178
178
  line-ending = "auto"
179
179
 
180
180
  [tool.bumpversion]
181
- current_version = "2.20.0"
181
+ current_version = "2.21.1"
182
182
  commit = true
183
183
  tag = true
184
184
  tag_name = "v{new_version}"
@@ -63,7 +63,7 @@ def db_Postgres(
63
63
  new_db: bool = False,
64
64
  password: str | None = None,
65
65
  ) -> PostgresDatabase:
66
- """Open a new PostgreSQL connection via psycopg2."""
66
+ """Open a new PostgreSQL connection via psycopg (psycopg3)."""
67
67
  return PostgresDatabase(server_name, database_name, user, pw_needed, port, new_db=new_db, password=password)
68
68
 
69
69
 
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  """
4
4
  PostgreSQL database adapter for execsql.
5
5
 
6
- Implements :class:`PostgresDatabase`. Uses ``psycopg2`` for the
6
+ Implements :class:`PostgresDatabase`. Uses ``psycopg`` (psycopg3) for the
7
7
  connection, supports schema-qualified tables, server-side ``COPY`` for
8
8
  fast IMPORT, ``CREATE DATABASE`` when ``new_db=True``, ``ROLE_EXISTS``,
9
9
  and the ``PG_VACUUM`` metacommand (``vacuum()`` method). Corresponds to
@@ -26,7 +26,7 @@ DEFAULT_CONNECT_TIMEOUT = 30 # seconds
26
26
 
27
27
 
28
28
  class PostgresDatabase(Database):
29
- """PostgreSQL adapter using psycopg2, with schema support, server-side COPY, and keyring auth."""
29
+ """PostgreSQL adapter using psycopg (psycopg3), with schema support, server-side COPY, and keyring auth."""
30
30
 
31
31
  def __init__(
32
32
  self,
@@ -41,10 +41,11 @@ class PostgresDatabase(Database):
41
41
  connect_timeout: int = DEFAULT_CONNECT_TIMEOUT,
42
42
  ) -> None:
43
43
  try:
44
- import psycopg2 # noqa: F401
44
+ import psycopg # noqa: F401
45
45
  except Exception:
46
46
  fatal_error(
47
- "The psycopg2 module is required to connect to PostgreSQL. See http://initd.org/psycopg/",
47
+ "The psycopg module (psycopg3) is required to connect to PostgreSQL. "
48
+ "See https://www.psycopg.org/psycopg3/",
48
49
  )
49
50
  from execsql.types import dbt_postgres
50
51
 
@@ -73,25 +74,33 @@ class PostgresDatabase(Database):
73
74
 
74
75
  def open_db(self) -> None:
75
76
  """Open a connection to the PostgreSQL database."""
76
- import psycopg2
77
+ import psycopg
77
78
 
78
79
  def db_conn(db: PostgresDatabase, db_name: str):
79
80
  try:
81
+ # prepare_threshold=None disables psycopg3's automatic server-side
82
+ # prepared statements. execsql re-runs the same query text (e.g. via
83
+ # EXPORT) against objects that scripts may drop/recreate or alter
84
+ # between runs; a cached plan whose result type then changes triggers
85
+ # PostgreSQL's "cached plan must not change result type" error. psycopg2
86
+ # never auto-prepared, so this preserves backwards-compatible behavior.
80
87
  if db.user and db.password:
81
- return psycopg2.connect(
88
+ return psycopg.connect(
82
89
  host=str(db.server_name),
83
- database=str(db_name),
90
+ dbname=str(db_name),
84
91
  port=db.port,
85
92
  user=db.user,
86
93
  password=db.password,
87
94
  connect_timeout=db.connect_timeout,
95
+ prepare_threshold=None,
88
96
  )
89
97
  else:
90
- return psycopg2.connect(
98
+ return psycopg.connect(
91
99
  host=str(db.server_name),
92
- database=db_name,
100
+ dbname=db_name,
93
101
  port=db.port,
94
102
  connect_timeout=db.connect_timeout,
103
+ prepare_threshold=None,
95
104
  )
96
105
  except Exception as e:
97
106
  msg = (
@@ -148,7 +157,7 @@ class PostgresDatabase(Database):
148
157
  msg = f"Failed to open PostgreSQL database {self.db_name} on {self.server_name}"
149
158
  raise ErrInfo(type="exception", exception_msg=exception_desc(), other_msg=msg) from e
150
159
  # (Re)set the encoding to match the database.
151
- self.encoding = self.conn.encoding
160
+ self.encoding = self.conn.info.encoding
152
161
 
153
162
  def exec_cmd(self, querycommand: str) -> None:
154
163
  """Execute a stored function by name."""
@@ -242,13 +251,13 @@ class PostgresDatabase(Database):
242
251
  but should not be exposed to untrusted input.
243
252
  """
244
253
  self.commit()
245
- self.conn.set_session(autocommit=True)
254
+ self.conn.autocommit = True
246
255
  curs = self.conn.cursor()
247
256
  try:
248
257
  curs.execute(f"VACUUM {argstring};")
249
258
  finally:
250
259
  curs.close()
251
- self.conn.set_session(autocommit=False)
260
+ self.conn.autocommit = False
252
261
 
253
262
  def import_tabular_file(
254
263
  self,
@@ -290,7 +299,7 @@ class PostgresDatabase(Database):
290
299
  import_cols = [self.type.quoted(col) for col in import_cols]
291
300
  csv_file_cols_q = [self.type.quoted(col) for col in csv_file_cols]
292
301
  input_col_list = ",".join(import_cols)
293
- # If encodings match, use copy_expert.
302
+ # If encodings match, use server-side COPY.
294
303
  # If encodings don't match, and the file encoding isn't recognized by CSV, read as CSV.
295
304
  enc_xlates = {
296
305
  "cp1252": "win1252",
@@ -319,7 +328,7 @@ class PostgresDatabase(Database):
319
328
  and not _state.conf.trim_strings
320
329
  and not _state.conf.replace_newlines
321
330
  ):
322
- # Use Postgres' COPY FROM method via psycopg2's copy_expert() method.
331
+ # Use Postgres' COPY FROM method via psycopg3's cursor.copy() context manager.
323
332
  rf = csv_file_obj.open("rt")
324
333
  if skipheader:
325
334
  next(rf)
@@ -346,7 +355,9 @@ class PostgresDatabase(Database):
346
355
  )
347
356
  with self._cursor() as curs:
348
357
  try:
349
- curs.copy_expert(copy_cmd, rf, _state.conf.import_buffer)
358
+ with curs.copy(copy_cmd) as copy:
359
+ while chunk := rf.read(_state.conf.import_buffer):
360
+ copy.write(chunk)
350
361
  except ErrInfo:
351
362
  raise
352
363
  except Exception as e:
@@ -461,12 +472,11 @@ class PostgresDatabase(Database):
461
472
  file_name: str,
462
473
  ) -> None:
463
474
  """Import an entire binary file into a single column of a table."""
464
- import psycopg2
465
-
466
475
  with open(file_name, "rb") as f:
467
476
  filedata = f.read()
468
477
  sq_name = self.schema_qualified_table_name(schema_name, table_name)
469
478
  quoted_col = self.quote_identifier(column_name)
470
479
  sql = f"insert into {sq_name} ({quoted_col}) values ({self.paramsubs(1)});"
471
480
  with self._cursor() as curs:
472
- curs.execute(sql, (psycopg2.Binary(filedata),))
481
+ # psycopg3 sends ``bytes`` to a ``bytea`` column directly; no Binary() wrapper.
482
+ curs.execute(sql, (filedata,))
@@ -1,9 +1,10 @@
1
1
  """
2
2
  Tests for execsql.db.postgres — PostgresDatabase adapter.
3
3
 
4
- These tests mock psycopg2 entirely so they can run without a PostgreSQL
5
- server or the psycopg2 package installed. They verify that the
6
- connect_timeout parameter is correctly threaded through to psycopg2.connect().
4
+ These tests mock psycopg (psycopg3) entirely so they can run without a
5
+ PostgreSQL server or the psycopg package installed. They verify that the
6
+ connect_timeout parameter is correctly threaded through to psycopg.connect(),
7
+ and that the libpq ``dbname`` keyword (not psycopg2's ``database``) is used.
7
8
  """
8
9
 
9
10
  from __future__ import annotations
@@ -19,11 +20,11 @@ from execsql.db.postgres import DEFAULT_CONNECT_TIMEOUT
19
20
  # ---------------------------------------------------------------------------
20
21
 
21
22
 
22
- def _make_mock_psycopg2():
23
- """Return a mock psycopg2 module whose connect() returns a usable conn."""
23
+ def _make_mock_psycopg():
24
+ """Return a mock psycopg module whose connect() returns a usable conn."""
24
25
  mock_mod = MagicMock()
25
26
  mock_conn = MagicMock()
26
- mock_conn.encoding = "UTF8"
27
+ mock_conn.info.encoding = "UTF8"
27
28
  mock_mod.connect.return_value = mock_conn
28
29
  return mock_mod
29
30
 
@@ -34,11 +35,11 @@ def _make_mock_psycopg2():
34
35
 
35
36
 
36
37
  class TestPostgresConnectTimeout:
37
- @patch.dict("sys.modules", {"psycopg2": _make_mock_psycopg2()})
38
+ @patch.dict("sys.modules", {"psycopg": _make_mock_psycopg()})
38
39
  def test_default_connect_timeout(self):
39
40
  import sys
40
41
 
41
- mock_psycopg2 = sys.modules["psycopg2"]
42
+ mock_psycopg = sys.modules["psycopg"]
42
43
  from execsql.db.postgres import PostgresDatabase
43
44
 
44
45
  db = PostgresDatabase(
@@ -48,15 +49,18 @@ class TestPostgresConnectTimeout:
48
49
  password="pass",
49
50
  )
50
51
  assert db.connect_timeout == DEFAULT_CONNECT_TIMEOUT
51
- # Verify psycopg2.connect was called with connect_timeout=30
52
- call_kwargs = mock_psycopg2.connect.call_args[1]
52
+ # Verify psycopg.connect was called with connect_timeout=30
53
+ call_kwargs = mock_psycopg.connect.call_args[1]
53
54
  assert call_kwargs["connect_timeout"] == DEFAULT_CONNECT_TIMEOUT
55
+ # psycopg3 requires the libpq ``dbname`` keyword, not psycopg2's ``database``.
56
+ assert call_kwargs["dbname"] == "testdb"
57
+ assert "database" not in call_kwargs
54
58
 
55
- @patch.dict("sys.modules", {"psycopg2": _make_mock_psycopg2()})
59
+ @patch.dict("sys.modules", {"psycopg": _make_mock_psycopg()})
56
60
  def test_custom_connect_timeout(self):
57
61
  import sys
58
62
 
59
- mock_psycopg2 = sys.modules["psycopg2"]
63
+ mock_psycopg = sys.modules["psycopg"]
60
64
  from execsql.db.postgres import PostgresDatabase
61
65
 
62
66
  db = PostgresDatabase(
@@ -67,14 +71,14 @@ class TestPostgresConnectTimeout:
67
71
  connect_timeout=10,
68
72
  )
69
73
  assert db.connect_timeout == 10
70
- call_kwargs = mock_psycopg2.connect.call_args[1]
74
+ call_kwargs = mock_psycopg.connect.call_args[1]
71
75
  assert call_kwargs["connect_timeout"] == 10
72
76
 
73
- @patch.dict("sys.modules", {"psycopg2": _make_mock_psycopg2()})
77
+ @patch.dict("sys.modules", {"psycopg": _make_mock_psycopg()})
74
78
  def test_connect_timeout_without_credentials(self):
75
79
  import sys
76
80
 
77
- mock_psycopg2 = sys.modules["psycopg2"]
81
+ mock_psycopg = sys.modules["psycopg"]
78
82
  from execsql.db.postgres import PostgresDatabase
79
83
 
80
84
  db = PostgresDatabase(
@@ -84,5 +88,8 @@ class TestPostgresConnectTimeout:
84
88
  connect_timeout=5,
85
89
  )
86
90
  assert db.connect_timeout == 5
87
- call_kwargs = mock_psycopg2.connect.call_args[1]
91
+ call_kwargs = mock_psycopg.connect.call_args[1]
88
92
  assert call_kwargs["connect_timeout"] == 5
93
+ # Credential-less path still uses ``dbname``.
94
+ assert call_kwargs["dbname"] == "testdb"
95
+ assert "database" not in call_kwargs
@@ -7,7 +7,7 @@ subprocess* — subprocess execution yields no coverage credit, so those
7
7
  ~199 missed lines stay uncovered.
8
8
 
9
9
  The module is skipped when:
10
- - psycopg2 is not installed, or
10
+ - psycopg (psycopg3) is not installed, or
11
11
  - the test PostgreSQL instance (localhost:5432, database=execsql_test,
12
12
  user=execsql, password=execsql) is not reachable.
13
13
 
@@ -30,7 +30,7 @@ import execsql.state as _state
30
30
  # Skip the entire module when the driver or server is unavailable
31
31
  # ---------------------------------------------------------------------------
32
32
 
33
- psycopg2 = pytest.importorskip("psycopg2", reason="psycopg2 not installed")
33
+ psycopg = pytest.importorskip("psycopg", reason="psycopg (psycopg3) not installed")
34
34
 
35
35
  _PG_KW: dict = {
36
36
  "host": os.environ.get("EXECSQL_PG_HOST", "localhost"),
@@ -44,7 +44,7 @@ _PG_KW: dict = {
44
44
 
45
45
  def _pg_reachable() -> bool:
46
46
  try:
47
- psycopg2.connect(**_PG_KW).close()
47
+ psycopg.connect(**_PG_KW).close()
48
48
  return True
49
49
  except Exception:
50
50
  return False
@@ -306,7 +306,7 @@ class TestDataAccess:
306
306
  def test_select_data_bad_sql_raises(self, db):
307
307
  # base.Database.select_data() re-raises the driver exception directly
308
308
  # after rolling back; it does not wrap in ErrInfo.
309
- with pytest.raises(psycopg2.Error):
309
+ with pytest.raises(psycopg.Error):
310
310
  db.select_data("SELECT * FROM execsql_no_such_table_xyz;")
311
311
 
312
312
  def test_select_rowsource_iterates(self, db, fresh_table):
@@ -357,7 +357,7 @@ class TestExecCmd:
357
357
  db.commit()
358
358
 
359
359
  def test_exec_cmd_unknown_function_rolls_back(self, db):
360
- with pytest.raises(psycopg2.Error):
360
+ with pytest.raises(psycopg.Error):
361
361
  db.exec_cmd("execsql_no_such_function_xyz")
362
362
 
363
363
 
@@ -6,7 +6,7 @@ the backend (db_type = p). Each test writes a minimal execsql.conf, creates a
6
6
  outcomes (tables created, data inserted, files exported, etc.).
7
7
 
8
8
  The entire module is skipped when:
9
- - psycopg2 is not installed, OR
9
+ - psycopg (psycopg3) is not installed, OR
10
10
  - the test PostgreSQL instance (localhost:5432, database=execsql_test,
11
11
  user=execsql, password=execsql) is not reachable.
12
12
  """
@@ -23,10 +23,10 @@ import pytest
23
23
  from tests.integration.conftest import write_script
24
24
 
25
25
  # ---------------------------------------------------------------------------
26
- # Module-level skip: psycopg2 availability + server reachability
26
+ # Module-level skip: psycopg (psycopg3) availability + server reachability
27
27
  # ---------------------------------------------------------------------------
28
28
 
29
- psycopg2 = pytest.importorskip("psycopg2", reason="psycopg2 package required")
29
+ psycopg = pytest.importorskip("psycopg", reason="psycopg (psycopg3) package required")
30
30
 
31
31
  _PG_CONNECT_KWARGS: dict = {
32
32
  "host": os.environ.get("EXECSQL_PG_HOST", "localhost"),
@@ -41,7 +41,7 @@ _PG_CONNECT_KWARGS: dict = {
41
41
  def _pg_is_reachable() -> bool:
42
42
  """Return True if the test PostgreSQL instance is connectable."""
43
43
  try:
44
- conn = psycopg2.connect(**_PG_CONNECT_KWARGS)
44
+ conn = psycopg.connect(**_PG_CONNECT_KWARGS)
45
45
  conn.close()
46
46
  return True
47
47
  except Exception: # noqa: BLE001
@@ -101,7 +101,7 @@ def _run_execsql_pg(tmp_path, script_path, extra_args=None, timeout=30):
101
101
 
102
102
  def _query_pg(sql: str, params=None):
103
103
  """Open a connection to the test PostgreSQL database, run *sql*, and return all rows."""
104
- conn = psycopg2.connect(**_PG_CONNECT_KWARGS)
104
+ conn = psycopg.connect(**_PG_CONNECT_KWARGS)
105
105
  try:
106
106
  cur = conn.cursor()
107
107
  cur.execute(sql, params)
@@ -112,7 +112,7 @@ def _query_pg(sql: str, params=None):
112
112
 
113
113
  def _exec_pg(sql: str, params=None):
114
114
  """Execute a non-SELECT statement against the test PostgreSQL database."""
115
- conn = psycopg2.connect(**_PG_CONNECT_KWARGS)
115
+ conn = psycopg.connect(**_PG_CONNECT_KWARGS)
116
116
  try:
117
117
  conn.autocommit = True
118
118
  cur = conn.cursor()