mycli 2.14.0__tar.gz → 2.16.0__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 (190) hide show
  1. {mycli-2.14.0 → mycli-2.16.0}/.github/workflows/ci.yml +5 -5
  2. {mycli-2.14.0 → mycli-2.16.0}/.github/workflows/publish.yml +5 -5
  3. {mycli-2.14.0 → mycli-2.16.0}/.github/workflows/typecheck.yml +1 -1
  4. {mycli-2.14.0/mycli.egg-info → mycli-2.16.0}/PKG-INFO +1 -1
  5. {mycli-2.14.0 → mycli-2.16.0}/changelog.md +36 -0
  6. {mycli-2.14.0 → mycli-2.16.0}/mycli/TIPS +8 -4
  7. {mycli-2.14.0 → mycli-2.16.0}/mycli/boundary_tunnel.py +4 -3
  8. {mycli-2.14.0 → mycli-2.16.0}/mycli/clibuffer.py +1 -1
  9. {mycli-2.14.0 → mycli-2.16.0}/mycli/client.py +1 -0
  10. {mycli-2.14.0 → mycli-2.16.0}/mycli/client_commands.py +167 -7
  11. {mycli-2.14.0 → mycli-2.16.0}/mycli/client_query.py +9 -0
  12. {mycli-2.14.0 → mycli-2.16.0}/mycli/config.py +8 -0
  13. {mycli-2.14.0 → mycli-2.16.0}/mycli/main.py +0 -1
  14. {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/execute.py +1 -1
  15. {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/repl.py +163 -5
  16. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/completion_engine.py +73 -4
  17. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/filepaths.py +1 -1
  18. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/dsn_aliases.py +4 -1
  19. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/favoritequeries.py +122 -6
  20. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/iocommands.py +185 -53
  21. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/main.py +1 -1
  22. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/sqlresult.py +3 -1
  23. {mycli-2.14.0 → mycli-2.16.0}/mycli/resources/completions/bash/mycli +1 -1
  24. {mycli-2.14.0 → mycli-2.16.0}/mycli/resources/completions/fish/mycli.fish +1 -1
  25. {mycli-2.14.0 → mycli-2.16.0}/mycli/resources/completions/zsh/_mycli +1 -1
  26. {mycli-2.14.0 → mycli-2.16.0}/mycli/sqlcompleter.py +40 -1
  27. {mycli-2.14.0 → mycli-2.16.0}/mycli/sqlexecute.py +1 -1
  28. {mycli-2.14.0 → mycli-2.16.0/mycli.egg-info}/PKG-INFO +1 -1
  29. mycli-2.16.0/mycli.egg-info/scm_version.json +8 -0
  30. mycli-2.16.0/test/features/fixture_data/help_commands.txt +42 -0
  31. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_clibuffer.py +6 -3
  32. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_client_commands.py +355 -4
  33. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_client_query.py +60 -0
  34. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_completion_engine.py +108 -4
  35. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_config.py +23 -0
  36. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_favoritequeries.py +211 -1
  37. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_filepaths.py +1 -0
  38. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_main.py +2 -2
  39. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_main_modes_execute.py +6 -6
  40. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_main_modes_repl.py +370 -0
  41. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_smart_completion_public_schema_only.py +80 -12
  42. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_iocommands.py +438 -1
  43. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_main.py +1 -1
  44. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_sqlexecute.py +10 -2
  45. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_sqlresult.py +2 -0
  46. mycli-2.14.0/mycli.egg-info/scm_version.json +0 -8
  47. mycli-2.14.0/test/features/fixture_data/help_commands.txt +0 -41
  48. {mycli-2.14.0 → mycli-2.16.0}/.git-blame-ignore-revs +0 -0
  49. {mycli-2.14.0 → mycli-2.16.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  50. {mycli-2.14.0 → mycli-2.16.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  51. {mycli-2.14.0 → mycli-2.16.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  52. {mycli-2.14.0 → mycli-2.16.0}/.github/dependabot.yml +0 -0
  53. {mycli-2.14.0 → mycli-2.16.0}/.github/workflows/lint.yml +0 -0
  54. {mycli-2.14.0 → mycli-2.16.0}/.gitignore +0 -0
  55. {mycli-2.14.0 → mycli-2.16.0}/AGENTS.md +0 -0
  56. {mycli-2.14.0 → mycli-2.16.0}/AUTHORS.rst +0 -0
  57. {mycli-2.14.0 → mycli-2.16.0}/CONTRIBUTING.md +0 -0
  58. {mycli-2.14.0 → mycli-2.16.0}/LICENSE.txt +0 -0
  59. {mycli-2.14.0 → mycli-2.16.0}/MANIFEST.in +0 -0
  60. {mycli-2.14.0 → mycli-2.16.0}/README.md +0 -0
  61. {mycli-2.14.0 → mycli-2.16.0}/SPONSORS.rst +0 -0
  62. {mycli-2.14.0 → mycli-2.16.0}/doc/key_bindings.rst +0 -0
  63. {mycli-2.14.0 → mycli-2.16.0}/doc/llm.md +0 -0
  64. {mycli-2.14.0 → mycli-2.16.0}/doc/screenshots/main.gif +0 -0
  65. {mycli-2.14.0 → mycli-2.16.0}/doc/screenshots/tables.png +0 -0
  66. {mycli-2.14.0 → mycli-2.16.0}/doc/screenshots/total_histogram.png +0 -0
  67. {mycli-2.14.0 → mycli-2.16.0}/doc/transforms.md +0 -0
  68. {mycli-2.14.0 → mycli-2.16.0}/mycli/AUTHORS +0 -0
  69. {mycli-2.14.0 → mycli-2.16.0}/mycli/SPONSORS +0 -0
  70. {mycli-2.14.0 → mycli-2.16.0}/mycli/__init__.py +0 -0
  71. {mycli-2.14.0 → mycli-2.16.0}/mycli/app_state.py +0 -0
  72. {mycli-2.14.0 → mycli-2.16.0}/mycli/cli_runner.py +0 -0
  73. {mycli-2.14.0 → mycli-2.16.0}/mycli/client_connection.py +0 -0
  74. {mycli-2.14.0 → mycli-2.16.0}/mycli/clistyle.py +0 -0
  75. {mycli-2.14.0 → mycli-2.16.0}/mycli/clitoolbar.py +0 -0
  76. {mycli-2.14.0 → mycli-2.16.0}/mycli/compat.py +0 -0
  77. {mycli-2.14.0 → mycli-2.16.0}/mycli/completion_refresher.py +0 -0
  78. {mycli-2.14.0 → mycli-2.16.0}/mycli/constants.py +0 -0
  79. {mycli-2.14.0 → mycli-2.16.0}/mycli/key_bindings.py +0 -0
  80. {mycli-2.14.0 → mycli-2.16.0}/mycli/lexer.py +0 -0
  81. {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/batch.py +0 -0
  82. {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/checkup.py +0 -0
  83. {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/completions.py +0 -0
  84. {mycli-2.14.0 → mycli-2.16.0}/mycli/main_modes/list_dsn.py +0 -0
  85. {mycli-2.14.0 → mycli-2.16.0}/mycli/myclirc +0 -0
  86. {mycli-2.14.0 → mycli-2.16.0}/mycli/output.py +0 -0
  87. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/__init__.py +0 -0
  88. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/batch_utils.py +0 -0
  89. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/cli_utils.py +0 -0
  90. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/hybrid_redirection.py +0 -0
  91. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/interactive_utils.py +0 -0
  92. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/key_binding_utils.py +0 -0
  93. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/polars_transform.py +0 -0
  94. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/ptoolkit/__init__.py +0 -0
  95. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/ptoolkit/fzf.py +0 -0
  96. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/ptoolkit/history.py +0 -0
  97. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/ptoolkit/utils.py +0 -0
  98. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/__init__.py +0 -0
  99. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/dbcommands.py +0 -0
  100. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/delimitercommand.py +0 -0
  101. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/llm.py +0 -0
  102. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/special/utils.py +0 -0
  103. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/sql_utils.py +0 -0
  104. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/string_utils.py +0 -0
  105. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/tabular_output/__init__.py +0 -0
  106. {mycli-2.14.0 → mycli-2.16.0}/mycli/packages/tabular_output/sql_format.py +0 -0
  107. {mycli-2.14.0 → mycli-2.16.0}/mycli/password_sources.py +0 -0
  108. {mycli-2.14.0 → mycli-2.16.0}/mycli/schema_prefetcher.py +0 -0
  109. {mycli-2.14.0 → mycli-2.16.0}/mycli/ssh_tunnel.py +0 -0
  110. {mycli-2.14.0 → mycli-2.16.0}/mycli/types.py +0 -0
  111. {mycli-2.14.0 → mycli-2.16.0}/mycli/vault.py +0 -0
  112. {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/SOURCES.txt +0 -0
  113. {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/dependency_links.txt +0 -0
  114. {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/entry_points.txt +0 -0
  115. {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/requires.txt +0 -0
  116. {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/scm_file_list.json +0 -0
  117. {mycli-2.14.0 → mycli-2.16.0}/mycli.egg-info/top_level.txt +0 -0
  118. {mycli-2.14.0 → mycli-2.16.0}/pyproject.toml +0 -0
  119. {mycli-2.14.0 → mycli-2.16.0}/setup.cfg +0 -0
  120. {mycli-2.14.0 → mycli-2.16.0}/test/__init__.py +0 -0
  121. {mycli-2.14.0 → mycli-2.16.0}/test/features/__init__.py +0 -0
  122. {mycli-2.14.0 → mycli-2.16.0}/test/features/auto_vertical.feature +0 -0
  123. {mycli-2.14.0 → mycli-2.16.0}/test/features/basic_commands.feature +0 -0
  124. {mycli-2.14.0 → mycli-2.16.0}/test/features/connection.feature +0 -0
  125. {mycli-2.14.0 → mycli-2.16.0}/test/features/crud_database.feature +0 -0
  126. {mycli-2.14.0 → mycli-2.16.0}/test/features/crud_table.feature +0 -0
  127. {mycli-2.14.0 → mycli-2.16.0}/test/features/db_utils.py +0 -0
  128. {mycli-2.14.0 → mycli-2.16.0}/test/features/environment.py +0 -0
  129. {mycli-2.14.0 → mycli-2.16.0}/test/features/fixture_utils.py +0 -0
  130. {mycli-2.14.0 → mycli-2.16.0}/test/features/iocommands.feature +0 -0
  131. {mycli-2.14.0 → mycli-2.16.0}/test/features/named_queries.feature +0 -0
  132. {mycli-2.14.0 → mycli-2.16.0}/test/features/specials.feature +0 -0
  133. {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/__init__.py +0 -0
  134. {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/auto_vertical.py +0 -0
  135. {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/basic_commands.py +0 -0
  136. {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/connection.py +0 -0
  137. {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/crud_database.py +0 -0
  138. {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/crud_table.py +0 -0
  139. {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/iocommands.py +0 -0
  140. {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/named_queries.py +0 -0
  141. {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/specials.py +0 -0
  142. {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/utils.py +0 -0
  143. {mycli-2.14.0 → mycli-2.16.0}/test/features/steps/wrappers.py +0 -0
  144. {mycli-2.14.0 → mycli-2.16.0}/test/features/wrappager.py +0 -0
  145. {mycli-2.14.0 → mycli-2.16.0}/test/myclirc +0 -0
  146. {mycli-2.14.0 → mycli-2.16.0}/test/mylogin.cnf +0 -0
  147. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/conftest.py +0 -0
  148. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_app_state.py +0 -0
  149. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_bash_completion.py +0 -0
  150. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_batch_utils.py +0 -0
  151. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_boundary_tunnel.py +0 -0
  152. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_checkup.py +0 -0
  153. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_cli_runner.py +0 -0
  154. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_cli_utils.py +0 -0
  155. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_client.py +0 -0
  156. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_client_connection.py +0 -0
  157. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_clistyle.py +0 -0
  158. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_clitoolbar.py +0 -0
  159. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_completer_use_switch.py +0 -0
  160. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_completion_refresher.py +0 -0
  161. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_delimitercommand.py +0 -0
  162. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_dsn_aliases.py +0 -0
  163. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_fish_completion.py +0 -0
  164. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_hybrid_redirection.py +0 -0
  165. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_interactive_utils.py +0 -0
  166. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_key_binding_utils.py +0 -0
  167. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_key_bindings.py +0 -0
  168. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_lexer.py +0 -0
  169. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_main_modes_batch.py +0 -0
  170. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_main_modes_list_dsn.py +0 -0
  171. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_naive_completion.py +0 -0
  172. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_output.py +0 -0
  173. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_password_sources.py +0 -0
  174. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_polars_transform.py +0 -0
  175. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_ptoolkit_fzf.py +0 -0
  176. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_ptoolkit_history.py +0 -0
  177. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_ptoolkit_utils.py +0 -0
  178. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_schema_prefetcher.py +0 -0
  179. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_dbcommands.py +0 -0
  180. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_init.py +0 -0
  181. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_llm.py +0 -0
  182. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_special_utils.py +0 -0
  183. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_sql_utils.py +0 -0
  184. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_sqlcompleter.py +0 -0
  185. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_ssh_tunnel.py +0 -0
  186. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_string_utils.py +0 -0
  187. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_tabular_output.py +0 -0
  188. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_vault.py +0 -0
  189. {mycli-2.14.0 → mycli-2.16.0}/test/pytests/test_zsh_completion.py +0 -0
  190. {mycli-2.14.0 → mycli-2.16.0}/test/utils.py +0 -0
@@ -24,7 +24,7 @@ jobs:
24
24
  steps:
25
25
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
26
 
27
- - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
27
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
28
28
  with:
29
29
  version: "latest"
30
30
 
@@ -66,7 +66,7 @@ jobs:
66
66
  steps:
67
67
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
68
68
 
69
- - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
69
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
70
70
  with:
71
71
  version: "latest"
72
72
 
@@ -116,7 +116,7 @@ jobs:
116
116
  steps:
117
117
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
118
118
 
119
- - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
119
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
120
120
  with:
121
121
  version: 'latest'
122
122
 
@@ -165,7 +165,7 @@ jobs:
165
165
  run: |
166
166
  mkdir -p /root/mycli
167
167
 
168
- - uses: ubuntu/wsl/.github/actions/wsl-checkout@f861f1083e518354e4c3637b08c0edfcf8bc3d51
168
+ - uses: ubuntu/wsl/.github/actions/wsl-checkout@e2114d0a5ebea62e6b2e1565f175871ed7558d20
169
169
  with:
170
170
  distro: Ubuntu-24.04
171
171
  working-dir: /root/mycli
@@ -219,7 +219,7 @@ jobs:
219
219
  steps:
220
220
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
221
221
 
222
- - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
222
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
223
223
  with:
224
224
  version: "latest"
225
225
 
@@ -29,7 +29,7 @@ jobs:
29
29
 
30
30
  steps:
31
31
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32
- - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
32
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
33
33
  with:
34
34
  version: "latest"
35
35
 
@@ -74,7 +74,7 @@ jobs:
74
74
  steps:
75
75
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
76
76
 
77
- - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
77
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
78
78
  with:
79
79
  version: "latest"
80
80
 
@@ -124,7 +124,7 @@ jobs:
124
124
  steps:
125
125
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
126
126
 
127
- - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
127
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
128
128
  with:
129
129
  version: 'latest'
130
130
 
@@ -173,7 +173,7 @@ jobs:
173
173
  run: |
174
174
  mkdir -p /root/mycli
175
175
 
176
- - uses: ubuntu/wsl/.github/actions/wsl-checkout@f861f1083e518354e4c3637b08c0edfcf8bc3d51
176
+ - uses: ubuntu/wsl/.github/actions/wsl-checkout@e2114d0a5ebea62e6b2e1565f175871ed7558d20
177
177
  with:
178
178
  distro: Ubuntu-24.04
179
179
  working-dir: /root/mycli
@@ -226,7 +226,7 @@ jobs:
226
226
 
227
227
  steps:
228
228
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
229
- - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
229
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
230
230
  with:
231
231
  version: "latest"
232
232
 
@@ -25,7 +25,7 @@ jobs:
25
25
  with:
26
26
  python-version: '3.14'
27
27
 
28
- - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
28
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
29
29
  with:
30
30
  version: 'latest'
31
31
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycli
3
- Version: 2.14.0
3
+ Version: 2.16.0
4
4
  Summary: Rich MySQL terminal client with autocompletion, syntax highlighting, and dataframes
5
5
  Author: Mycli Core Team
6
6
  License-Expression: BSD-3-Clause
@@ -1,3 +1,39 @@
1
+ 2.16.0 (2026/08/22)
2
+ ==============
3
+
4
+ Features
5
+ ---------
6
+ * Advertise beta `--boundary-id` option in helpdoc and completions.
7
+ * Add `/source --special` to allow executing some special commands.
8
+ * Add `/source --show` to display each query before executing it.
9
+ * Add `/source --page` to display all output in one pager session.
10
+
11
+
12
+ Bug Fixes
13
+ ---------
14
+ * Show CLI error on invalid `--execute` containing `/source`.
15
+ * Require `/source` filenames containing spaces to be quoted.
16
+
17
+
18
+ 2.15.0 (2026/08/20)
19
+ ==============
20
+
21
+ Features
22
+ ---------
23
+ * Add alternative interface `/favorite` for favorite queries.
24
+ * Add `/dsn edit` subcommand to edit an existing DSN.
25
+
26
+
27
+ Bug Fixes
28
+ ---------
29
+ * Lower Boundary tunnel stabilization pause to 0.15 sec.
30
+
31
+
32
+ Documentation
33
+ ---------
34
+ * TIPS: `/favorite run` composes with the `\G` and `\x` terminators.
35
+
36
+
1
37
  2.14.0 (2026/08/15)
2
38
  ==============
3
39
 
@@ -54,7 +54,7 @@ copy the previous query to the clipboard with /clip!
54
54
 
55
55
  edit a query in an external editor using <query>\edit!
56
56
 
57
- edit a query in an external editor using /edit <filename>!
57
+ edit a query in an external editor using /edit <file>!
58
58
 
59
59
  /f lists favorite queries; /f <name> executes a favorite!
60
60
 
@@ -64,9 +64,11 @@ edit a query in an external editor using /edit <filename>!
64
64
 
65
65
  Favorite queries support Jinja templates and named arguments!
66
66
 
67
+ Manage favorite queries with /favorite!
68
+
67
69
  /l lists databases!
68
70
 
69
- /once <filename> appends the next result to <filename>!
71
+ /once <file> appends the next result to <file>!
70
72
 
71
73
  /| <command> sends the next result to a subprocess!
72
74
 
@@ -146,6 +148,8 @@ display output vertically using \G at the end of a query!
146
148
 
147
149
  display output in an interactive explorer \x at the end of a query!
148
150
 
151
+ "/favorite run" can be combined with the \G or \x terminators!
152
+
149
153
  run SQL scripts in batch mode using the standard input!
150
154
 
151
155
  indexed columns are tagged with a "*" in completion menus!
@@ -302,9 +306,9 @@ customize password sources/precedence with "password_sources" in ~/.myclirc!
302
306
 
303
307
  redirect query output to a shell command with "$| <command>"!
304
308
 
305
- redirect query output to a CSV file with "$> <filename>"!
309
+ redirect query output to a CSV file with "$> <file>"!
306
310
 
307
- append query output to a CSV file with "$>> <filename>"!
311
+ append query output to a CSV file with "$>> <file>"!
308
312
 
309
313
  run a command after shell redirects with "post_redirect_command" in ~/.myclirc!
310
314
 
@@ -15,7 +15,10 @@ from typing import IO
15
15
 
16
16
  from mycli.compat import WIN
17
17
 
18
- TUNNEL_STABILIZATION_PAUSE = 0.25
18
+ # This should be as small as possible and yet still
19
+ # ward off SSL errors at connection time. We know
20
+ # that 0.1 is too small on at least one Mac.
21
+ TUNNEL_STABILIZATION_PAUSE = 0.15
19
22
 
20
23
 
21
24
  class BoundaryTunnelError(RuntimeError):
@@ -148,8 +151,6 @@ class BoundaryTunnel:
148
151
  self._raise_if_failed()
149
152
  if self._is_listening():
150
153
  self._ready.set()
151
- # todo: if this works to ward off SSL errors at connection time,
152
- # try making the pause as small as possible
153
154
  time.sleep(TUNNEL_STABILIZATION_PAUSE)
154
155
  return
155
156
  time.sleep(0.05)
@@ -29,7 +29,7 @@ def _multiline_exception(text: str) -> bool:
29
29
  # Multi-statement favorite query is a special case. Because there will
30
30
  # be a semicolon separating statements, we can't consider semicolon an
31
31
  # EOL. Let's consider an empty line an EOL instead.
32
- if first_word.startswith(("\\fs", "/fs")):
32
+ if iocommands.is_favorite_save_command(text):
33
33
  return orig.endswith("\n")
34
34
 
35
35
  return (
@@ -122,6 +122,7 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
122
122
  self.config,
123
123
  myclirc,
124
124
  c['main'].get('shared_favorites_file'),
125
+ system_config_files=self.system_config_files,
125
126
  )
126
127
  DsnAliases.instance = DsnAliases.from_config(
127
128
  self.config,
@@ -4,16 +4,21 @@ from collections.abc import Generator, Mapping
4
4
  import logging
5
5
  import os
6
6
  import re
7
+ import shlex
7
8
  from typing import TYPE_CHECKING, Any, cast
8
9
 
9
10
  import click
11
+ import sqlparse
10
12
 
13
+ from mycli.compat import WIN
11
14
  from mycli.config import write_default_config
12
15
  from mycli.main_modes.repl import set_all_external_titles
13
16
  from mycli.packages import special
14
17
  from mycli.packages.batch_utils import statements_from_filehandle
15
18
  from mycli.packages.filepaths import dir_path_exists
16
19
  from mycli.packages.interactive_utils import confirm_destructive_query
20
+ from mycli.packages.special import main as special_main
21
+ from mycli.packages.special.iocommands import expand_favorite_query
17
22
  from mycli.packages.special.main import ArgType, SpecialCommandAlias
18
23
  from mycli.packages.sqlresult import SQLResult
19
24
  from mycli.sqlexecute import SQLExecute
@@ -30,6 +35,29 @@ MISSING_CONFIG_VALUE = object()
30
35
  DSN_CONFIG_VALUE = object()
31
36
  FAVORITES_CONFIG_VALUE = object()
32
37
  HIDDEN_CONFIG_SECTIONS = frozenset({'alias_dsn', 'favorite_queries'})
38
+ INVALID_SOURCE_FILENAME = 'Source accepts exactly one filename; filenames containing spaces must be quoted.'
39
+ SOURCE_SAFE_SPECIAL_COMMANDS = frozenset({
40
+ 'connect',
41
+ 'fd',
42
+ 'fs',
43
+ 'help',
44
+ 'l',
45
+ 'nowarnings',
46
+ 'prompt',
47
+ 'redirectformat',
48
+ 'rehash',
49
+ 'status',
50
+ 'tableformat',
51
+ 'timing',
52
+ 'use',
53
+ 'warnings',
54
+ 'dt',
55
+ })
56
+ SOURCE_SAFE_SUBCOMMANDS = {
57
+ 'config': frozenset({'help', 'get', 'search'}),
58
+ 'dsn': frozenset({'help', 'list', 'show', 'save', 'delete'}),
59
+ 'favorite': frozenset({'help', 'list', 'reload', 'run', 'save', 'delete'}),
60
+ }
33
61
 
34
62
 
35
63
  def _render_config_value(value: Any) -> str:
@@ -38,6 +66,106 @@ def _render_config_value(value: Any) -> str:
38
66
  return str(value)
39
67
 
40
68
 
69
+ def _parse_source_arguments(arg: str) -> tuple[str, bool, bool, bool]:
70
+ allow_special = False
71
+ show_queries = False
72
+ page_output = False
73
+ filename = arg
74
+ while arguments := filename.split(maxsplit=1):
75
+ if arguments[0] == '--special':
76
+ allow_special = True
77
+ elif arguments[0] == '--show':
78
+ show_queries = True
79
+ elif arguments[0] == '--page':
80
+ page_output = True
81
+ else:
82
+ break
83
+ filename = arguments[1] if len(arguments) == 2 else ''
84
+ return filename, allow_special, show_queries, page_output
85
+
86
+
87
+ def _has_unquoted_whitespace(value: str) -> bool:
88
+ quote: str | None = None
89
+ escaped = False
90
+ for character in value:
91
+ if escaped:
92
+ if quote is None and character.isspace():
93
+ return True
94
+ escaped = False
95
+ continue
96
+ if not WIN and character == '\\' and quote != "'":
97
+ escaped = True
98
+ continue
99
+ if character in ("'", '"'):
100
+ if quote is None:
101
+ quote = character
102
+ elif quote == character:
103
+ quote = None
104
+ elif quote is None and character.isspace():
105
+ return True
106
+ return False
107
+
108
+
109
+ def _parse_source_filename(filename: str) -> str:
110
+ if not filename:
111
+ return ''
112
+ if _has_unquoted_whitespace(filename):
113
+ raise ValueError(INVALID_SOURCE_FILENAME)
114
+ try:
115
+ arguments = shlex.split(filename, posix=not WIN)
116
+ except ValueError as error:
117
+ raise ValueError(f'Invalid source filename: {error}.') from None
118
+ if len(arguments) != 1:
119
+ raise ValueError(INVALID_SOURCE_FILENAME)
120
+ parsed_filename = arguments[0]
121
+ if WIN and len(parsed_filename) >= 2 and parsed_filename[0] == parsed_filename[-1] and parsed_filename[0] in ("'", '"'):
122
+ parsed_filename = parsed_filename[1:-1]
123
+ return parsed_filename
124
+
125
+
126
+ def _registered_special_command(query: str) -> tuple[str, str] | None:
127
+ command, _verbosity, arg = special.parse_special_command(query)
128
+ registered = special_main.COMMANDS.get(command)
129
+ if registered is None:
130
+ registered = special_main.COMMANDS.get(command.lower())
131
+ if registered is None:
132
+ return None
133
+ return registered.command.removeprefix('\\').removeprefix('/').lower(), arg
134
+
135
+
136
+ def _favorite_source_command_is_safe(arg: str) -> bool:
137
+ query, _error = expand_favorite_query(arg)
138
+ if query is None:
139
+ return True
140
+ return not any(special.is_special_command(statement.rstrip(';')) for statement in sqlparse.split(query))
141
+
142
+
143
+ def _source_special_command_is_safe(query: str) -> bool:
144
+ parsed = _registered_special_command(query)
145
+ if parsed is None:
146
+ return False
147
+
148
+ command, arg = parsed
149
+ if command == 'f':
150
+ return not arg or _favorite_source_command_is_safe(arg)
151
+ if command in ('fd', 'fs'):
152
+ return True
153
+ if command in SOURCE_SAFE_SPECIAL_COMMANDS:
154
+ return True
155
+
156
+ subcommands = SOURCE_SAFE_SUBCOMMANDS.get(command)
157
+ if subcommands is None:
158
+ return False
159
+ arguments = arg.split(maxsplit=1)
160
+ subcommand = arguments[0].lower() if arguments else 'help'
161
+ if subcommand not in subcommands:
162
+ return False
163
+ if command == 'favorite' and subcommand == 'run':
164
+ run_arg = arguments[1] if len(arguments) == 2 else ''
165
+ return not run_arg or _favorite_source_command_is_safe(run_arg)
166
+ return True
167
+
168
+
41
169
  def _iter_config_values(
42
170
  config: Mapping[str, Any],
43
171
  prefix: str = '',
@@ -143,7 +271,7 @@ class ClientCommandsMixin:
143
271
  special.register_special_command(
144
272
  self.execute_from_file,
145
273
  "source",
146
- "/source <filename>",
274
+ "/source [--special|--show|--page] <file>",
147
275
  "Execute queries from a file.",
148
276
  aliases=[SpecialCommandAlias("\\.", case_sensitive=False)],
149
277
  )
@@ -266,12 +394,20 @@ class ClientCommandsMixin:
266
394
  yield SQLResult(status=msg)
267
395
 
268
396
  def execute_from_file(self, arg: str, **_) -> Generator[SQLResult, None, None]:
269
- if not arg:
270
- yield SQLResult(status="Missing required argument: filename.")
397
+ filename, allow_special, show_queries, page_output = _parse_source_arguments(arg)
398
+ if page_output:
399
+ yield SQLResult(command={'name': 'source_page'})
400
+ try:
401
+ filename = _parse_source_filename(filename)
402
+ except ValueError as error:
403
+ yield SQLResult(status=str(error), is_error=True)
404
+ return
405
+ if not filename:
406
+ yield SQLResult(status="Missing required argument: filename.", is_error=True)
271
407
  return
272
408
 
273
409
  try:
274
- file_h = open(os.path.expanduser(arg))
410
+ file_h = open(os.path.expanduser(filename))
275
411
  except OSError as error:
276
412
  yield SQLResult(status=str(error))
277
413
  return
@@ -288,12 +424,36 @@ class ClientCommandsMixin:
288
424
  yield SQLResult(status=str(error))
289
425
  return
290
426
 
291
- if special.is_special_command(query.rstrip(';')):
292
- yield SQLResult(status='Special commands are not supported in source files.')
293
- return
427
+ special_query = query.rstrip(';')
428
+ if special.is_special_command(special_query):
429
+ if not allow_special:
430
+ yield SQLResult(
431
+ status='Special commands are not supported without /source --special.',
432
+ is_error=True,
433
+ )
434
+ return
435
+ if not _source_special_command_is_safe(special_query):
436
+ command, _verbosity, _arg = special.parse_special_command(special_query)
437
+ yield SQLResult(
438
+ status=f'Special command is never permitted in source files: {command}.',
439
+ is_error=True,
440
+ )
441
+ return
442
+ if show_queries:
443
+ if page_output:
444
+ yield SQLResult(command={'name': 'source_show', 'text': special_query})
445
+ else:
446
+ click.secho(f'> {special_query}')
447
+ yield from self.sqlexecute.run(special_query)
448
+ continue
294
449
 
295
450
  if self.destructive_warning and confirm_destructive_query(self.destructive_keywords, query) is False:
296
451
  continue
452
+ if show_queries:
453
+ if page_output:
454
+ yield SQLResult(command={'name': 'source_show', 'text': query})
455
+ else:
456
+ click.secho(f'> {query}')
297
457
  yield from self.sqlexecute.run(query)
298
458
 
299
459
  def change_prompt_format(self, arg: str, **_) -> list[SQLResult]:
@@ -10,6 +10,10 @@ from mycli.packages.sqlresult import SQLResult
10
10
  from mycli.sqlcompleter import SQLCompleter
11
11
 
12
12
 
13
+ class QueryError(Exception):
14
+ """An error reported while consuming query results."""
15
+
16
+
13
17
  class ClientQueryMixin:
14
18
  if TYPE_CHECKING:
15
19
  schema_prefetcher: Any
@@ -80,6 +84,7 @@ class ClientQueryMixin:
80
84
  query: str,
81
85
  checkpoint: str | None = None,
82
86
  new_line: bool = True,
87
+ raise_on_error: bool = False,
83
88
  ) -> None:
84
89
  """Runs *query*."""
85
90
  assert self.sqlexecute is not None
@@ -91,6 +96,10 @@ class ClientQueryMixin:
91
96
  self.main_formatter.query = query
92
97
  self.redirect_formatter.query = query
93
98
  self.explorer_formatter.query = query
99
+ if result.is_error and raise_on_error:
100
+ message = result.status_plain or 'Query failed.'
101
+ self.log_output(message)
102
+ raise QueryError(message)
94
103
  output = self.format_sqlresult(
95
104
  result,
96
105
  is_expanded=special.is_expanded_output(),
@@ -48,6 +48,7 @@ def read_config_file(
48
48
  f: str | IO[str],
49
49
  list_values: bool = True,
50
50
  preserve_quotes: bool = False,
51
+ raise_errors: bool = False,
51
52
  ) -> ConfigObj | LimiitedQuotePreservingConfigObj | None:
52
53
  """Read a config file.
53
54
 
@@ -57,6 +58,9 @@ def read_config_file(
57
58
  not unquoted. We are disabling list_values when reading MySQL config files
58
59
  so we can correctly interpret commas in passwords.
59
60
 
61
+ Set *raise_errors* to propagate parsing and I/O errors instead of logging
62
+ them and returning a partial config or ``None``.
63
+
60
64
  """
61
65
 
62
66
  if isinstance(f, str):
@@ -68,10 +72,14 @@ def read_config_file(
68
72
  else:
69
73
  config = ConfigObj(f, interpolation=False, encoding="utf8", list_values=list_values)
70
74
  except ConfigObjError as e:
75
+ if raise_errors:
76
+ raise
71
77
  log(logger, logging.WARNING, "Unable to parse line {0} of config file '{1}'.".format(e.line_number, f))
72
78
  log(logger, logging.WARNING, "Using successfully parsed config values.")
73
79
  return e.config
74
80
  except (IOError, OSError) as e:
81
+ if raise_errors:
82
+ raise
75
83
  log(logger, logging.WARNING, "You don't have permission to read config file '{0}'.".format(e.filename))
76
84
  return None
77
85
 
@@ -324,7 +324,6 @@ class CliArgs:
324
324
  boundary_id: str | None = clickdc.option(
325
325
  type=str,
326
326
  help='BETA: open a HashiCorp Boundary tunnel to TARGET_ID and connect through it.',
327
- hidden=True,
328
327
  )
329
328
  checkup: bool = clickdc.option(
330
329
  is_flag=True,
@@ -46,7 +46,7 @@ def main_execute_from_cli(mycli: 'MyCli', cli_args: 'CliArgs') -> int:
46
46
  mycli.logger.warning('Unable to open TTY as stdin.')
47
47
  raise e
48
48
  if execution_confirmed:
49
- mycli.run_query(execute_sql, checkpoint=cli_args.checkpoint)
49
+ mycli.run_query(execute_sql, checkpoint=cli_args.checkpoint, raise_on_error=True)
50
50
  return 0
51
51
  else:
52
52
  return 1