mycli 2.13.2__tar.gz → 2.15.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.13.2 → mycli-2.15.0}/.github/workflows/ci.yml +5 -5
  2. {mycli-2.13.2 → mycli-2.15.0}/.github/workflows/publish.yml +5 -5
  3. {mycli-2.13.2 → mycli-2.15.0}/.github/workflows/typecheck.yml +1 -1
  4. {mycli-2.13.2/mycli.egg-info → mycli-2.15.0}/PKG-INFO +1 -1
  5. {mycli-2.13.2 → mycli-2.15.0}/changelog.md +27 -0
  6. {mycli-2.13.2 → mycli-2.15.0}/mycli/TIPS +4 -0
  7. {mycli-2.13.2 → mycli-2.15.0}/mycli/boundary_tunnel.py +4 -3
  8. {mycli-2.13.2 → mycli-2.15.0}/mycli/clibuffer.py +1 -1
  9. {mycli-2.13.2 → mycli-2.15.0}/mycli/client.py +1 -0
  10. {mycli-2.13.2 → mycli-2.15.0}/mycli/config.py +8 -0
  11. {mycli-2.13.2 → mycli-2.15.0}/mycli/main_modes/repl.py +17 -6
  12. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/completion_engine.py +35 -4
  13. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/special/dsn_aliases.py +4 -1
  14. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/special/favoritequeries.py +122 -6
  15. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/special/iocommands.py +183 -51
  16. {mycli-2.13.2 → mycli-2.15.0}/mycli/sqlexecute.py +1 -1
  17. {mycli-2.13.2 → mycli-2.15.0/mycli.egg-info}/PKG-INFO +1 -1
  18. {mycli-2.13.2 → mycli-2.15.0}/mycli.egg-info/scm_file_list.json +145 -145
  19. mycli-2.15.0/mycli.egg-info/scm_version.json +8 -0
  20. mycli-2.15.0/test/features/fixture_data/help_commands.txt +42 -0
  21. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_clibuffer.py +6 -3
  22. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_client_commands.py +10 -1
  23. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_client_query.py +19 -0
  24. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_completion_engine.py +71 -0
  25. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_config.py +23 -0
  26. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_favoritequeries.py +211 -1
  27. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_main_modes_repl.py +28 -0
  28. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_smart_completion_public_schema_only.py +4 -3
  29. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_special_iocommands.py +438 -1
  30. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_sqlexecute.py +10 -2
  31. mycli-2.13.2/mycli.egg-info/scm_version.json +0 -8
  32. mycli-2.13.2/test/features/fixture_data/help_commands.txt +0 -41
  33. {mycli-2.13.2 → mycli-2.15.0}/.git-blame-ignore-revs +0 -0
  34. {mycli-2.13.2 → mycli-2.15.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  35. {mycli-2.13.2 → mycli-2.15.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  36. {mycli-2.13.2 → mycli-2.15.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  37. {mycli-2.13.2 → mycli-2.15.0}/.github/dependabot.yml +0 -0
  38. {mycli-2.13.2 → mycli-2.15.0}/.github/workflows/lint.yml +0 -0
  39. {mycli-2.13.2 → mycli-2.15.0}/.gitignore +0 -0
  40. {mycli-2.13.2 → mycli-2.15.0}/AGENTS.md +0 -0
  41. {mycli-2.13.2 → mycli-2.15.0}/AUTHORS.rst +0 -0
  42. {mycli-2.13.2 → mycli-2.15.0}/CONTRIBUTING.md +0 -0
  43. {mycli-2.13.2 → mycli-2.15.0}/LICENSE.txt +0 -0
  44. {mycli-2.13.2 → mycli-2.15.0}/MANIFEST.in +0 -0
  45. {mycli-2.13.2 → mycli-2.15.0}/README.md +0 -0
  46. {mycli-2.13.2 → mycli-2.15.0}/SPONSORS.rst +0 -0
  47. {mycli-2.13.2 → mycli-2.15.0}/doc/key_bindings.rst +0 -0
  48. {mycli-2.13.2 → mycli-2.15.0}/doc/llm.md +0 -0
  49. {mycli-2.13.2 → mycli-2.15.0}/doc/screenshots/main.gif +0 -0
  50. {mycli-2.13.2 → mycli-2.15.0}/doc/screenshots/tables.png +0 -0
  51. {mycli-2.13.2 → mycli-2.15.0}/doc/screenshots/total_histogram.png +0 -0
  52. {mycli-2.13.2 → mycli-2.15.0}/doc/transforms.md +0 -0
  53. {mycli-2.13.2 → mycli-2.15.0}/mycli/AUTHORS +0 -0
  54. {mycli-2.13.2 → mycli-2.15.0}/mycli/SPONSORS +0 -0
  55. {mycli-2.13.2 → mycli-2.15.0}/mycli/__init__.py +0 -0
  56. {mycli-2.13.2 → mycli-2.15.0}/mycli/app_state.py +0 -0
  57. {mycli-2.13.2 → mycli-2.15.0}/mycli/cli_runner.py +0 -0
  58. {mycli-2.13.2 → mycli-2.15.0}/mycli/client_commands.py +0 -0
  59. {mycli-2.13.2 → mycli-2.15.0}/mycli/client_connection.py +0 -0
  60. {mycli-2.13.2 → mycli-2.15.0}/mycli/client_query.py +0 -0
  61. {mycli-2.13.2 → mycli-2.15.0}/mycli/clistyle.py +0 -0
  62. {mycli-2.13.2 → mycli-2.15.0}/mycli/clitoolbar.py +0 -0
  63. {mycli-2.13.2 → mycli-2.15.0}/mycli/compat.py +0 -0
  64. {mycli-2.13.2 → mycli-2.15.0}/mycli/completion_refresher.py +0 -0
  65. {mycli-2.13.2 → mycli-2.15.0}/mycli/constants.py +0 -0
  66. {mycli-2.13.2 → mycli-2.15.0}/mycli/key_bindings.py +0 -0
  67. {mycli-2.13.2 → mycli-2.15.0}/mycli/lexer.py +0 -0
  68. {mycli-2.13.2 → mycli-2.15.0}/mycli/main.py +0 -0
  69. {mycli-2.13.2 → mycli-2.15.0}/mycli/main_modes/batch.py +0 -0
  70. {mycli-2.13.2 → mycli-2.15.0}/mycli/main_modes/checkup.py +0 -0
  71. {mycli-2.13.2 → mycli-2.15.0}/mycli/main_modes/completions.py +0 -0
  72. {mycli-2.13.2 → mycli-2.15.0}/mycli/main_modes/execute.py +0 -0
  73. {mycli-2.13.2 → mycli-2.15.0}/mycli/main_modes/list_dsn.py +0 -0
  74. {mycli-2.13.2 → mycli-2.15.0}/mycli/myclirc +0 -0
  75. {mycli-2.13.2 → mycli-2.15.0}/mycli/output.py +0 -0
  76. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/__init__.py +0 -0
  77. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/batch_utils.py +0 -0
  78. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/cli_utils.py +0 -0
  79. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/filepaths.py +0 -0
  80. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/hybrid_redirection.py +0 -0
  81. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/interactive_utils.py +0 -0
  82. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/key_binding_utils.py +0 -0
  83. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/polars_transform.py +0 -0
  84. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/ptoolkit/__init__.py +0 -0
  85. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/ptoolkit/fzf.py +0 -0
  86. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/ptoolkit/history.py +0 -0
  87. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/ptoolkit/utils.py +0 -0
  88. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/special/__init__.py +0 -0
  89. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/special/dbcommands.py +0 -0
  90. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/special/delimitercommand.py +0 -0
  91. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/special/llm.py +0 -0
  92. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/special/main.py +0 -0
  93. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/special/utils.py +0 -0
  94. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/sql_utils.py +0 -0
  95. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/sqlresult.py +0 -0
  96. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/string_utils.py +0 -0
  97. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/tabular_output/__init__.py +0 -0
  98. {mycli-2.13.2 → mycli-2.15.0}/mycli/packages/tabular_output/sql_format.py +0 -0
  99. {mycli-2.13.2 → mycli-2.15.0}/mycli/password_sources.py +0 -0
  100. {mycli-2.13.2 → mycli-2.15.0}/mycli/resources/completions/bash/mycli +0 -0
  101. {mycli-2.13.2 → mycli-2.15.0}/mycli/resources/completions/fish/mycli.fish +0 -0
  102. {mycli-2.13.2 → mycli-2.15.0}/mycli/resources/completions/zsh/_mycli +0 -0
  103. {mycli-2.13.2 → mycli-2.15.0}/mycli/schema_prefetcher.py +0 -0
  104. {mycli-2.13.2 → mycli-2.15.0}/mycli/sqlcompleter.py +0 -0
  105. {mycli-2.13.2 → mycli-2.15.0}/mycli/ssh_tunnel.py +0 -0
  106. {mycli-2.13.2 → mycli-2.15.0}/mycli/types.py +0 -0
  107. {mycli-2.13.2 → mycli-2.15.0}/mycli/vault.py +0 -0
  108. {mycli-2.13.2 → mycli-2.15.0}/mycli.egg-info/SOURCES.txt +0 -0
  109. {mycli-2.13.2 → mycli-2.15.0}/mycli.egg-info/dependency_links.txt +0 -0
  110. {mycli-2.13.2 → mycli-2.15.0}/mycli.egg-info/entry_points.txt +0 -0
  111. {mycli-2.13.2 → mycli-2.15.0}/mycli.egg-info/requires.txt +0 -0
  112. {mycli-2.13.2 → mycli-2.15.0}/mycli.egg-info/top_level.txt +0 -0
  113. {mycli-2.13.2 → mycli-2.15.0}/pyproject.toml +0 -0
  114. {mycli-2.13.2 → mycli-2.15.0}/setup.cfg +0 -0
  115. {mycli-2.13.2 → mycli-2.15.0}/test/__init__.py +0 -0
  116. {mycli-2.13.2 → mycli-2.15.0}/test/features/__init__.py +0 -0
  117. {mycli-2.13.2 → mycli-2.15.0}/test/features/auto_vertical.feature +0 -0
  118. {mycli-2.13.2 → mycli-2.15.0}/test/features/basic_commands.feature +0 -0
  119. {mycli-2.13.2 → mycli-2.15.0}/test/features/connection.feature +0 -0
  120. {mycli-2.13.2 → mycli-2.15.0}/test/features/crud_database.feature +0 -0
  121. {mycli-2.13.2 → mycli-2.15.0}/test/features/crud_table.feature +0 -0
  122. {mycli-2.13.2 → mycli-2.15.0}/test/features/db_utils.py +0 -0
  123. {mycli-2.13.2 → mycli-2.15.0}/test/features/environment.py +0 -0
  124. {mycli-2.13.2 → mycli-2.15.0}/test/features/fixture_utils.py +0 -0
  125. {mycli-2.13.2 → mycli-2.15.0}/test/features/iocommands.feature +0 -0
  126. {mycli-2.13.2 → mycli-2.15.0}/test/features/named_queries.feature +0 -0
  127. {mycli-2.13.2 → mycli-2.15.0}/test/features/specials.feature +0 -0
  128. {mycli-2.13.2 → mycli-2.15.0}/test/features/steps/__init__.py +0 -0
  129. {mycli-2.13.2 → mycli-2.15.0}/test/features/steps/auto_vertical.py +0 -0
  130. {mycli-2.13.2 → mycli-2.15.0}/test/features/steps/basic_commands.py +0 -0
  131. {mycli-2.13.2 → mycli-2.15.0}/test/features/steps/connection.py +0 -0
  132. {mycli-2.13.2 → mycli-2.15.0}/test/features/steps/crud_database.py +0 -0
  133. {mycli-2.13.2 → mycli-2.15.0}/test/features/steps/crud_table.py +0 -0
  134. {mycli-2.13.2 → mycli-2.15.0}/test/features/steps/iocommands.py +0 -0
  135. {mycli-2.13.2 → mycli-2.15.0}/test/features/steps/named_queries.py +0 -0
  136. {mycli-2.13.2 → mycli-2.15.0}/test/features/steps/specials.py +0 -0
  137. {mycli-2.13.2 → mycli-2.15.0}/test/features/steps/utils.py +0 -0
  138. {mycli-2.13.2 → mycli-2.15.0}/test/features/steps/wrappers.py +0 -0
  139. {mycli-2.13.2 → mycli-2.15.0}/test/features/wrappager.py +0 -0
  140. {mycli-2.13.2 → mycli-2.15.0}/test/myclirc +0 -0
  141. {mycli-2.13.2 → mycli-2.15.0}/test/mylogin.cnf +0 -0
  142. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/conftest.py +0 -0
  143. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_app_state.py +0 -0
  144. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_bash_completion.py +0 -0
  145. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_batch_utils.py +0 -0
  146. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_boundary_tunnel.py +0 -0
  147. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_checkup.py +0 -0
  148. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_cli_runner.py +0 -0
  149. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_cli_utils.py +0 -0
  150. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_client.py +0 -0
  151. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_client_connection.py +0 -0
  152. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_clistyle.py +0 -0
  153. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_clitoolbar.py +0 -0
  154. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_completer_use_switch.py +0 -0
  155. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_completion_refresher.py +0 -0
  156. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_delimitercommand.py +0 -0
  157. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_dsn_aliases.py +0 -0
  158. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_filepaths.py +0 -0
  159. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_fish_completion.py +0 -0
  160. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_hybrid_redirection.py +0 -0
  161. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_interactive_utils.py +0 -0
  162. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_key_binding_utils.py +0 -0
  163. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_key_bindings.py +0 -0
  164. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_lexer.py +0 -0
  165. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_main.py +0 -0
  166. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_main_modes_batch.py +0 -0
  167. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_main_modes_execute.py +0 -0
  168. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_main_modes_list_dsn.py +0 -0
  169. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_naive_completion.py +0 -0
  170. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_output.py +0 -0
  171. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_password_sources.py +0 -0
  172. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_polars_transform.py +0 -0
  173. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_ptoolkit_fzf.py +0 -0
  174. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_ptoolkit_history.py +0 -0
  175. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_ptoolkit_utils.py +0 -0
  176. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_schema_prefetcher.py +0 -0
  177. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_special_dbcommands.py +0 -0
  178. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_special_init.py +0 -0
  179. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_special_llm.py +0 -0
  180. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_special_main.py +0 -0
  181. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_special_utils.py +0 -0
  182. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_sql_utils.py +0 -0
  183. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_sqlcompleter.py +0 -0
  184. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_sqlresult.py +0 -0
  185. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_ssh_tunnel.py +0 -0
  186. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_string_utils.py +0 -0
  187. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_tabular_output.py +0 -0
  188. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_vault.py +0 -0
  189. {mycli-2.13.2 → mycli-2.15.0}/test/pytests/test_zsh_completion.py +0 -0
  190. {mycli-2.13.2 → mycli-2.15.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.13.2
3
+ Version: 2.15.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,30 @@
1
+ 2.15.0 (2026/08/20)
2
+ ==============
3
+
4
+ Features
5
+ ---------
6
+ * Add alternative interface `/favorite` for favorite queries.
7
+ * Add `/dsn edit` subcommand to edit an existing DSN.
8
+
9
+
10
+ Bug Fixes
11
+ ---------
12
+ * Lower Boundary tunnel stabilization pause to 0.15 sec.
13
+
14
+
15
+ Documentation
16
+ ---------
17
+ * TIPS: `/favorite run` composes with the `\G` and `\x` terminators.
18
+
19
+
20
+ 2.14.0 (2026/08/15)
21
+ ==============
22
+
23
+ Features
24
+ ---------
25
+ * Revert modal cursor shapes in vi edit mode.
26
+
27
+
1
28
  2.13.2 (2026/08/12)
2
29
  ==============
3
30
 
@@ -64,6 +64,8 @@ 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
71
  /once <filename> appends the next result to <filename>!
@@ -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!
@@ -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,
@@ -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
 
@@ -23,7 +23,6 @@ import prompt_toolkit
23
23
  from prompt_toolkit.application.current import get_app
24
24
  from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, ThreadedAutoSuggest
25
25
  from prompt_toolkit.completion import DynamicCompleter
26
- from prompt_toolkit.cursor_shapes import CursorShape, ModalCursorShapeConfig
27
26
  from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
28
27
  from prompt_toolkit.filters import Condition, has_focus, is_done
29
28
  from prompt_toolkit.formatted_text import (
@@ -106,6 +105,7 @@ _PROMPT_TARGETS: dict[int, 'MyCli'] = {}
106
105
  class ReplState:
107
106
  iterations: int = 0
108
107
  mutating: bool = False
108
+ buffer_text: str | None = None
109
109
 
110
110
 
111
111
  @Condition
@@ -450,6 +450,9 @@ def _output_results(
450
450
  mycli.logger.debug('status: %r', result.status)
451
451
  mycli.logger.debug('command: %r', result.command)
452
452
  threshold = 1000
453
+ if result.command is not None and result.command['name'] == 'set_buffer':
454
+ state.buffer_text = str(result.command['text'])
455
+ continue
453
456
  if result.command is not None and result.command['name'] == 'watch':
454
457
  if watch_count > 0:
455
458
  try:
@@ -608,7 +611,6 @@ def _build_prompt_session(
608
611
  enable_system_prompt=True,
609
612
  enable_suspend=True,
610
613
  editing_mode=editing_mode,
611
- cursor=ModalCursorShapeConfig() if editing_mode == EditingMode.VI else CursorShape.BLOCK,
612
614
  search_ignore_case=True,
613
615
  )
614
616
 
@@ -632,10 +634,19 @@ def _one_iteration(
632
634
  try:
633
635
  assert mycli.prompt_session is not None
634
636
  loaded_message_fn = partial(_get_prompt_message, mycli, mycli.prompt_session.app)
635
- text = mycli.prompt_session.prompt(
636
- inputhook=inputhook,
637
- message=loaded_message_fn,
638
- )
637
+ if state.buffer_text is None:
638
+ text = mycli.prompt_session.prompt(
639
+ inputhook=inputhook,
640
+ message=loaded_message_fn,
641
+ )
642
+ else:
643
+ default = state.buffer_text
644
+ state.buffer_text = None
645
+ text = mycli.prompt_session.prompt(
646
+ default=default,
647
+ inputhook=inputhook,
648
+ message=loaded_message_fn,
649
+ )
639
650
  except KeyboardInterrupt:
640
651
  return
641
652
 
@@ -8,6 +8,7 @@ import sqlparse
8
8
  from sqlparse.sql import Comparison, Identifier, Token, Where
9
9
 
10
10
  from mycli.packages.special.dsn_aliases import DSN_SUBCOMMANDS
11
+ from mycli.packages.special.favoritequeries import FAVORITE_SUBCOMMANDS
11
12
  from mycli.packages.special.main import COMMANDS as SPECIAL_COMMANDS
12
13
  from mycli.packages.special.main import parse_special_command
13
14
  from mycli.packages.sql_utils import extract_tables, find_prev_keyword, last_word
@@ -831,6 +832,36 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
831
832
  if cmd in ["\\llm", "/llm", "\\ai", "/ai"]:
832
833
  return [{"type": "llm"}]
833
834
 
835
+ if cmd.lower() in (r'\favorite', '/favorite'):
836
+ favorite_arguments = _arg.split(maxsplit=1)
837
+ if favorite_arguments and favorite_arguments[0].lower() in ('run', 'eval'):
838
+ if len(favorite_arguments) == 1 and not text[-1].isspace():
839
+ return []
840
+ expansion_arg = favorite_arguments[1] if len(favorite_arguments) == 2 else ''
841
+ return suggest_favorite_query_with_template(text, expansion_arg)
842
+ if favorite_arguments and favorite_arguments[0].lower() == 'save':
843
+ if len(favorite_arguments) == 1 and not text[-1].isspace():
844
+ return []
845
+ if len(favorite_arguments) == 1:
846
+ return [{'type': 'favoritequery'}]
847
+ save_arguments = favorite_arguments[1].split(maxsplit=1)
848
+ if len(save_arguments) == 1 and not text[-1].isspace():
849
+ return [{'type': 'favoritequery'}]
850
+ query = save_arguments[1] if len(save_arguments) == 2 else ''
851
+ if query and text[-1].isspace():
852
+ query += ' '
853
+ return suggest_type(query, query)
854
+ if favorite_arguments and favorite_arguments[0].lower() in ('edit', 'delete'):
855
+ if len(favorite_arguments) == 1:
856
+ return [] if not text[-1].isspace() else [{'type': 'favoritequery'}]
857
+ target_arguments = favorite_arguments[1].split()
858
+ if len(target_arguments) == 1 and not text[-1].isspace():
859
+ return [{'type': 'favoritequery'}]
860
+ return []
861
+ if favorite_arguments and favorite_arguments[0].lower() in FAVORITE_SUBCOMMANDS:
862
+ return []
863
+ return [{'type': 'special_subcommand', 'subcommands': list(FAVORITE_SUBCOMMANDS)}]
864
+
834
865
  if cmd.lower() in (r'\config', '/config'):
835
866
  config_arguments = _arg.split(maxsplit=1)
836
867
  config_subcommands = ['help', 'get', 'search', 'edit']
@@ -848,10 +879,10 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
848
879
 
849
880
  if cmd.lower() in (r'\dsn', '/dsn'):
850
881
  dsn_arguments = _arg.split(maxsplit=1)
851
- completing_delete_target = (len(dsn_arguments) == 1 and text[-1].isspace()) or (len(dsn_arguments) == 2 and not text[-1].isspace())
852
- if dsn_arguments and dsn_arguments[0].lower() == 'delete' and completing_delete_target:
882
+ completing_alias_target = (len(dsn_arguments) == 1 and text[-1].isspace()) or (len(dsn_arguments) == 2 and not text[-1].isspace())
883
+ if dsn_arguments and dsn_arguments[0].lower() in ('edit', 'delete') and completing_alias_target:
853
884
  return [{'type': 'dsn_alias'}]
854
- if dsn_arguments and dsn_arguments[0].lower() == 'delete' and len(dsn_arguments) == 2:
885
+ if dsn_arguments and dsn_arguments[0].lower() in ('edit', 'delete') and len(dsn_arguments) == 2:
855
886
  return []
856
887
  if dsn_arguments and dsn_arguments[0].lower() == 'save':
857
888
  completing_option = (len(dsn_arguments) == 1 and text[-1].isspace()) or (
@@ -872,7 +903,7 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
872
903
  if completing_option:
873
904
  return [{'type': 'special_subcommand', 'subcommands': [option]}]
874
905
  return []
875
- if dsn_arguments and dsn_arguments[0].lower() in DSN_SUBCOMMANDS - {'delete'}:
906
+ if dsn_arguments and dsn_arguments[0].lower() in DSN_SUBCOMMANDS - {'edit', 'delete'}:
876
907
  return []
877
908
  return [{'type': 'special_subcommand', 'subcommands': list(DSN_SUBCOMMANDS)}]
878
909
 
@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
13
13
  if TYPE_CHECKING:
14
14
  from mycli.client import MyCli
15
15
 
16
- DSN_SUBCOMMANDS = {'help', 'list', 'show', 'save', 'delete'}
16
+ DSN_SUBCOMMANDS = {'help', 'list', 'show', 'save', 'edit', 'delete'}
17
17
  INVALID_DSN_ALIAS_ERROR = 'Error: DSN aliases cannot start with a dash.'
18
18
  MISSING = object()
19
19
 
@@ -84,6 +84,9 @@ Examples:
84
84
  │ rocks │ mysql://mycli@localhost/mysql?prompt=%5Cd%3E%5C_ │
85
85
  └───────┴──────────────────────────────────────────────────┘
86
86
 
87
+ # Edit a DSN saved alias in an external editor.
88
+ mysql> /dsn edit rocks
89
+
87
90
  # Delete a DSN alias.
88
91
  mysql> /dsn delete rocks
89
92
  """
@@ -1,23 +1,85 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections.abc import Mapping
3
4
  import logging
4
5
  import os
5
6
  import re
6
- from typing import Any
7
+ from typing import IO, Any
7
8
 
9
+ from configobj import ConfigObjError
8
10
  from jinja2 import meta, nodes
9
11
  from jinja2.sandbox import SandboxedEnvironment
10
12
 
11
- from mycli.config import log, read_config_file
13
+ from mycli.config import log, read_config_file, read_config_files
12
14
 
13
15
  logger = logging.getLogger(__name__)
14
16
 
15
17
  MISSING = object()
18
+ FAVORITE_SUBCOMMANDS = ('help', 'list', 'reload', 'run', 'eval', 'save', 'edit', 'delete')
19
+ FAVORITE_COMMAND_HELP = '''
20
+ Favorite Queries are a way to save frequently used queries
21
+ with a short name.
22
+ Examples:
23
+
24
+ # Save a new favorite query.
25
+ > /favorite save simple SELECT * FROM abc WHERE a IS NOT NULL;
26
+
27
+ # When multi-line mode is on, pressing Return twice is needed to save.
28
+ # This supports multi-statement favorites.
29
+
30
+ # List all favorite queries.
31
+ > /favorite list
32
+ ╒═══════════╤══════════════════════════════════════════════════╕
33
+ │ Name │ Query │
34
+ ╞═══════════╪══════════════════════════════════════════════════╡
35
+ │ simple │ SELECT * FROM abc WHERE a IS NOT NULL │
36
+ │ find_user │ SELECT * FROM users WHERE name = '{{ kv.name }}' │
37
+ ╘═══════════╧══════════════════════════════════════════════════╛
38
+
39
+ # Run a favorite query.
40
+ > /favorite run simple
41
+ ╒════════╤════════╕
42
+ │ a │ b │
43
+ ╞════════╪════════╡
44
+ │ 日本語 │ 日本語 │
45
+ ╘════════╧════════╛
46
+
47
+ # Run a favorite query containing {{ kv.name }} in the template.
48
+ > /favorite run find_user --name=henry
49
+ > /favorite run find_user --name henry
50
+
51
+ # Run a favorite query containing positional parameter $1 in the
52
+ # template.
53
+ > /favorite run find_user henry
54
+
55
+ # Use -- to disambiguate positional parameters such as $1, especially
56
+ # if the positional value starts with a dash.
57
+ > /favorite run query --key=value -- positional-value
58
+ > /favorite run query -- --positional-value-which-looks-like-a-flag--
59
+
60
+ # Expand a favorite query into the command-line buffer without running it.
61
+ > /favorite eval find_user --name=henry
62
+
63
+ # Edit a favorite query in an external editor.
64
+ > /favorite edit simple
65
+
66
+ # Delete a favorite query.
67
+ > /favorite delete simple
68
+ simple: Deleted.
69
+
70
+ # Reload favorite queries from the configuration files.
71
+ > /favorite reload
72
+
73
+ See also the alternative interface /f, /fs, /fd.'''
16
74
 
17
75
  favorite_query_template_environment = SandboxedEnvironment(autoescape=False)
18
76
  favorite_query_variable_pattern = re.compile(r'^[A-Za-z_][A-Za-z0-9_-]*$')
19
77
 
20
78
 
79
+ class FavoriteQueryReloadError(Exception):
80
+ pass
81
+
82
+
21
83
  def analyze_favorite_query_template(query: str) -> tuple[set[str], bool]:
22
84
  """Return statically referenced keys and whether ``kv`` is used dynamically."""
23
85
  parsed_template = favorite_query_template_environment.parse(query)
@@ -116,14 +178,24 @@ Examples:
116
178
  # Delete a favorite query.
117
179
  > /fd simple
118
180
  simple: Deleted.
181
+
182
+ See also the alternative interface /favorite.
119
183
  """
120
184
 
121
185
  # Class-level variable, for convenience to use as a singleton.
122
186
  instance: FavoriteQueries
123
187
 
124
- def __init__(self, config: Any, config_file: str | None = None) -> None:
188
+ def __init__(
189
+ self,
190
+ config: Any,
191
+ config_file: str | None = None,
192
+ shared_favorites_file: str | None = None,
193
+ system_config_files: list[str | IO[str]] | None = None,
194
+ ) -> None:
125
195
  self.config = config
126
196
  self.config_file = config_file
197
+ self.shared_favorites_file = shared_favorites_file
198
+ self.system_config_files = list(system_config_files or [])
127
199
 
128
200
  @classmethod
129
201
  def from_config(
@@ -131,10 +203,10 @@ Examples:
131
203
  config: Any,
132
204
  config_file: str | None = None,
133
205
  shared_favorites_file: str | None = None,
206
+ system_config_files: list[str | IO[str]] | None = None,
134
207
  ) -> FavoriteQueries:
135
- favorites = cls(config, config_file)
136
208
  if not shared_favorites_file:
137
- return favorites
209
+ return cls(config, config_file, system_config_files=system_config_files)
138
210
 
139
211
  shared_favorites_file = os.path.expanduser(shared_favorites_file)
140
212
  if not os.path.isabs(shared_favorites_file):
@@ -143,7 +215,9 @@ Examples:
143
215
  logging.WARNING,
144
216
  f"Shared favorites file path must be absolute: '{shared_favorites_file}'.",
145
217
  )
146
- return favorites
218
+ return cls(config, config_file, system_config_files=system_config_files)
219
+
220
+ favorites = cls(config, config_file, shared_favorites_file, system_config_files)
147
221
 
148
222
  if not os.path.isfile(shared_favorites_file):
149
223
  log(
@@ -164,6 +238,48 @@ Examples:
164
238
  config[cls.section_name].update(configured_queries)
165
239
  return favorites
166
240
 
241
+ def _reload_queries(self, path: str, description: str) -> dict[str, str]:
242
+ expanded_path = os.path.expanduser(path)
243
+ if not os.path.isfile(expanded_path):
244
+ raise FavoriteQueryReloadError(f"unable to read {description} file '{expanded_path}'")
245
+ try:
246
+ config = read_config_file(expanded_path, raise_errors=True)
247
+ except (ConfigObjError, OSError, UnicodeError) as exc:
248
+ raise FavoriteQueryReloadError(
249
+ f"unable to read {description} file '{expanded_path}': {exc}",
250
+ ) from exc
251
+
252
+ assert config is not None
253
+ configured_queries = config.get(self.section_name, {})
254
+ if not isinstance(configured_queries, Mapping) or any(
255
+ not isinstance(name, str) or not isinstance(query, str) for name, query in configured_queries.items()
256
+ ):
257
+ raise FavoriteQueryReloadError(
258
+ f"invalid [{self.section_name}] section in {description} file '{expanded_path}'",
259
+ )
260
+ return dict(configured_queries)
261
+
262
+ def reload(self) -> None:
263
+ if self.config_file is None:
264
+ raise FavoriteQueryReloadError('no user configuration file is configured')
265
+
266
+ user_queries = self._reload_queries(self.config_file, 'user configuration')
267
+ system_config = read_config_files(self.system_config_files, ignore_package_defaults=True)
268
+ system_queries = system_config.get(self.section_name, {})
269
+ if not isinstance(system_queries, Mapping) or any(
270
+ not isinstance(name, str) or not isinstance(query, str) for name, query in system_queries.items()
271
+ ):
272
+ raise FavoriteQueryReloadError(f'invalid [{self.section_name}] section in system configuration files')
273
+ queries: dict[str, str] = {}
274
+ if self.shared_favorites_file is not None:
275
+ try:
276
+ queries.update(self._reload_queries(self.shared_favorites_file, 'shared favorites'))
277
+ except FavoriteQueryReloadError as exc:
278
+ log(logger, logging.WARNING, str(exc))
279
+ queries.update(system_queries)
280
+ queries.update(user_queries)
281
+ self.config[self.section_name] = queries
282
+
167
283
  def _clean_query(self, query: str | None) -> str | None:
168
284
  if not query:
169
285
  return query