mycli 2.24.0__tar.gz → 2.24.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. {mycli-2.24.0 → mycli-2.24.2}/.github/workflows/publish.yml +1 -0
  2. {mycli-2.24.0/mycli.egg-info → mycli-2.24.2}/PKG-INFO +1 -1
  3. {mycli-2.24.0 → mycli-2.24.2}/changelog.md +19 -0
  4. {mycli-2.24.0 → mycli-2.24.2}/mycli/myclirc +3 -3
  5. {mycli-2.24.0 → mycli-2.24.2}/mycli/sqlcompleter.py +27 -14
  6. {mycli-2.24.0 → mycli-2.24.2/mycli.egg-info}/PKG-INFO +1 -1
  7. {mycli-2.24.0 → mycli-2.24.2}/mycli.egg-info/scm_version.json +2 -2
  8. {mycli-2.24.0 → mycli-2.24.2}/test/myclirc +3 -3
  9. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_client.py +1 -1
  10. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_sqlcompleter.py +123 -15
  11. {mycli-2.24.0 → mycli-2.24.2}/.git-blame-ignore-revs +0 -0
  12. {mycli-2.24.0 → mycli-2.24.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  13. {mycli-2.24.0 → mycli-2.24.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  14. {mycli-2.24.0 → mycli-2.24.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  15. {mycli-2.24.0 → mycli-2.24.2}/.github/dependabot.yml +0 -0
  16. {mycli-2.24.0 → mycli-2.24.2}/.github/workflows/ci.yml +0 -0
  17. {mycli-2.24.0 → mycli-2.24.2}/.github/workflows/lint.yml +0 -0
  18. {mycli-2.24.0 → mycli-2.24.2}/.github/workflows/typecheck.yml +0 -0
  19. {mycli-2.24.0 → mycli-2.24.2}/.gitignore +0 -0
  20. {mycli-2.24.0 → mycli-2.24.2}/AGENTS.md +0 -0
  21. {mycli-2.24.0 → mycli-2.24.2}/AUTHORS.rst +0 -0
  22. {mycli-2.24.0 → mycli-2.24.2}/CONTRIBUTING.md +0 -0
  23. {mycli-2.24.0 → mycli-2.24.2}/LICENSE.txt +0 -0
  24. {mycli-2.24.0 → mycli-2.24.2}/MANIFEST.in +0 -0
  25. {mycli-2.24.0 → mycli-2.24.2}/README.md +0 -0
  26. {mycli-2.24.0 → mycli-2.24.2}/SPONSORS.rst +0 -0
  27. {mycli-2.24.0 → mycli-2.24.2}/doc/key_bindings.rst +0 -0
  28. {mycli-2.24.0 → mycli-2.24.2}/doc/llm.md +0 -0
  29. {mycli-2.24.0 → mycli-2.24.2}/doc/screenshots/main.gif +0 -0
  30. {mycli-2.24.0 → mycli-2.24.2}/doc/screenshots/tables.png +0 -0
  31. {mycli-2.24.0 → mycli-2.24.2}/doc/screenshots/total_histogram.png +0 -0
  32. {mycli-2.24.0 → mycli-2.24.2}/doc/transforms.md +0 -0
  33. {mycli-2.24.0 → mycli-2.24.2}/docker/Dockerfile +0 -0
  34. {mycli-2.24.0 → mycli-2.24.2}/docker/resources/test_db-1.0.7.tar.gz +0 -0
  35. {mycli-2.24.0 → mycli-2.24.2}/mycli/AUTHORS +0 -0
  36. {mycli-2.24.0 → mycli-2.24.2}/mycli/SPONSORS +0 -0
  37. {mycli-2.24.0 → mycli-2.24.2}/mycli/TIPS +0 -0
  38. {mycli-2.24.0 → mycli-2.24.2}/mycli/__init__.py +0 -0
  39. {mycli-2.24.0 → mycli-2.24.2}/mycli/app_state.py +0 -0
  40. {mycli-2.24.0 → mycli-2.24.2}/mycli/boundary_tunnel.py +0 -0
  41. {mycli-2.24.0 → mycli-2.24.2}/mycli/cli_runner.py +0 -0
  42. {mycli-2.24.0 → mycli-2.24.2}/mycli/clibuffer.py +0 -0
  43. {mycli-2.24.0 → mycli-2.24.2}/mycli/client.py +0 -0
  44. {mycli-2.24.0 → mycli-2.24.2}/mycli/client_commands.py +0 -0
  45. {mycli-2.24.0 → mycli-2.24.2}/mycli/client_connection.py +0 -0
  46. {mycli-2.24.0 → mycli-2.24.2}/mycli/client_query.py +0 -0
  47. {mycli-2.24.0 → mycli-2.24.2}/mycli/clistyle.py +0 -0
  48. {mycli-2.24.0 → mycli-2.24.2}/mycli/clitoolbar.py +0 -0
  49. {mycli-2.24.0 → mycli-2.24.2}/mycli/compat.py +0 -0
  50. {mycli-2.24.0 → mycli-2.24.2}/mycli/completion_refresher.py +0 -0
  51. {mycli-2.24.0 → mycli-2.24.2}/mycli/config.py +0 -0
  52. {mycli-2.24.0 → mycli-2.24.2}/mycli/constants.py +0 -0
  53. {mycli-2.24.0 → mycli-2.24.2}/mycli/key_bindings.py +0 -0
  54. {mycli-2.24.0 → mycli-2.24.2}/mycli/kubectl_tunnel.py +0 -0
  55. {mycli-2.24.0 → mycli-2.24.2}/mycli/lexer.py +0 -0
  56. {mycli-2.24.0 → mycli-2.24.2}/mycli/main.py +0 -0
  57. {mycli-2.24.0 → mycli-2.24.2}/mycli/main_modes/batch.py +0 -0
  58. {mycli-2.24.0 → mycli-2.24.2}/mycli/main_modes/checkup.py +0 -0
  59. {mycli-2.24.0 → mycli-2.24.2}/mycli/main_modes/completions.py +0 -0
  60. {mycli-2.24.0 → mycli-2.24.2}/mycli/main_modes/execute.py +0 -0
  61. {mycli-2.24.0 → mycli-2.24.2}/mycli/main_modes/list_dsn.py +0 -0
  62. {mycli-2.24.0 → mycli-2.24.2}/mycli/main_modes/repl.py +0 -0
  63. {mycli-2.24.0 → mycli-2.24.2}/mycli/output.py +0 -0
  64. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/__init__.py +0 -0
  65. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/batch_utils.py +0 -0
  66. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/cli_utils.py +0 -0
  67. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/completion_engine.py +0 -0
  68. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/filepaths.py +0 -0
  69. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/hybrid_redirection.py +0 -0
  70. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/interactive_utils.py +0 -0
  71. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/key_binding_utils.py +0 -0
  72. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/polars_completion.py +0 -0
  73. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/polars_transform.py +0 -0
  74. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/ptoolkit/__init__.py +0 -0
  75. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/ptoolkit/fzf.py +0 -0
  76. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/ptoolkit/history.py +0 -0
  77. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/ptoolkit/utils.py +0 -0
  78. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/special/__init__.py +0 -0
  79. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/special/dbcommands.py +0 -0
  80. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/special/delimitercommand.py +0 -0
  81. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/special/dsn_aliases.py +0 -0
  82. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/special/favoritequeries.py +0 -0
  83. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/special/iocommands.py +0 -0
  84. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/special/llm.py +0 -0
  85. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/special/main.py +0 -0
  86. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/special/source.py +0 -0
  87. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/special/utils.py +0 -0
  88. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/sql_utils.py +0 -0
  89. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/sqlresult.py +0 -0
  90. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/string_utils.py +0 -0
  91. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/tabular_output/__init__.py +0 -0
  92. {mycli-2.24.0 → mycli-2.24.2}/mycli/packages/tabular_output/sql_format.py +0 -0
  93. {mycli-2.24.0 → mycli-2.24.2}/mycli/password_sources.py +0 -0
  94. {mycli-2.24.0 → mycli-2.24.2}/mycli/resources/completions/bash/mycli +0 -0
  95. {mycli-2.24.0 → mycli-2.24.2}/mycli/resources/completions/fish/mycli.fish +0 -0
  96. {mycli-2.24.0 → mycli-2.24.2}/mycli/resources/completions/zsh/_mycli +0 -0
  97. {mycli-2.24.0 → mycli-2.24.2}/mycli/schema_prefetcher.py +0 -0
  98. {mycli-2.24.0 → mycli-2.24.2}/mycli/sqlexecute.py +0 -0
  99. {mycli-2.24.0 → mycli-2.24.2}/mycli/ssh_tunnel.py +0 -0
  100. {mycli-2.24.0 → mycli-2.24.2}/mycli/types.py +0 -0
  101. {mycli-2.24.0 → mycli-2.24.2}/mycli/vault.py +0 -0
  102. {mycli-2.24.0 → mycli-2.24.2}/mycli.egg-info/SOURCES.txt +0 -0
  103. {mycli-2.24.0 → mycli-2.24.2}/mycli.egg-info/dependency_links.txt +0 -0
  104. {mycli-2.24.0 → mycli-2.24.2}/mycli.egg-info/entry_points.txt +0 -0
  105. {mycli-2.24.0 → mycli-2.24.2}/mycli.egg-info/requires.txt +0 -0
  106. {mycli-2.24.0 → mycli-2.24.2}/mycli.egg-info/scm_file_list.json +0 -0
  107. {mycli-2.24.0 → mycli-2.24.2}/mycli.egg-info/top_level.txt +0 -0
  108. {mycli-2.24.0 → mycli-2.24.2}/pyproject.toml +0 -0
  109. {mycli-2.24.0 → mycli-2.24.2}/setup.cfg +0 -0
  110. {mycli-2.24.0 → mycli-2.24.2}/test/__init__.py +0 -0
  111. {mycli-2.24.0 → mycli-2.24.2}/test/features/__init__.py +0 -0
  112. {mycli-2.24.0 → mycli-2.24.2}/test/features/auto_vertical.feature +0 -0
  113. {mycli-2.24.0 → mycli-2.24.2}/test/features/basic_commands.feature +0 -0
  114. {mycli-2.24.0 → mycli-2.24.2}/test/features/connection.feature +0 -0
  115. {mycli-2.24.0 → mycli-2.24.2}/test/features/crud_database.feature +0 -0
  116. {mycli-2.24.0 → mycli-2.24.2}/test/features/crud_table.feature +0 -0
  117. {mycli-2.24.0 → mycli-2.24.2}/test/features/db_utils.py +0 -0
  118. {mycli-2.24.0 → mycli-2.24.2}/test/features/environment.py +0 -0
  119. {mycli-2.24.0 → mycli-2.24.2}/test/features/fixture_data/help_commands.txt +0 -0
  120. {mycli-2.24.0 → mycli-2.24.2}/test/features/fixture_utils.py +0 -0
  121. {mycli-2.24.0 → mycli-2.24.2}/test/features/iocommands.feature +0 -0
  122. {mycli-2.24.0 → mycli-2.24.2}/test/features/named_queries.feature +0 -0
  123. {mycli-2.24.0 → mycli-2.24.2}/test/features/specials.feature +0 -0
  124. {mycli-2.24.0 → mycli-2.24.2}/test/features/steps/__init__.py +0 -0
  125. {mycli-2.24.0 → mycli-2.24.2}/test/features/steps/auto_vertical.py +0 -0
  126. {mycli-2.24.0 → mycli-2.24.2}/test/features/steps/basic_commands.py +0 -0
  127. {mycli-2.24.0 → mycli-2.24.2}/test/features/steps/connection.py +0 -0
  128. {mycli-2.24.0 → mycli-2.24.2}/test/features/steps/crud_database.py +0 -0
  129. {mycli-2.24.0 → mycli-2.24.2}/test/features/steps/crud_table.py +0 -0
  130. {mycli-2.24.0 → mycli-2.24.2}/test/features/steps/iocommands.py +0 -0
  131. {mycli-2.24.0 → mycli-2.24.2}/test/features/steps/named_queries.py +0 -0
  132. {mycli-2.24.0 → mycli-2.24.2}/test/features/steps/specials.py +0 -0
  133. {mycli-2.24.0 → mycli-2.24.2}/test/features/steps/utils.py +0 -0
  134. {mycli-2.24.0 → mycli-2.24.2}/test/features/steps/wrappers.py +0 -0
  135. {mycli-2.24.0 → mycli-2.24.2}/test/features/wrappager.py +0 -0
  136. {mycli-2.24.0 → mycli-2.24.2}/test/mylogin.cnf +0 -0
  137. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/conftest.py +0 -0
  138. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_app_state.py +0 -0
  139. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_bash_completion.py +0 -0
  140. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_batch_utils.py +0 -0
  141. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_boundary_tunnel.py +0 -0
  142. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_checkup.py +0 -0
  143. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_cli_runner.py +0 -0
  144. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_cli_utils.py +0 -0
  145. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_clibuffer.py +0 -0
  146. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_client_commands.py +0 -0
  147. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_client_connection.py +0 -0
  148. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_client_query.py +0 -0
  149. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_clistyle.py +0 -0
  150. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_clitoolbar.py +0 -0
  151. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_completer_use_switch.py +0 -0
  152. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_completion_engine.py +0 -0
  153. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_completion_refresher.py +0 -0
  154. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_config.py +0 -0
  155. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_delimitercommand.py +0 -0
  156. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_dsn_aliases.py +0 -0
  157. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_favoritequeries.py +0 -0
  158. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_filepaths.py +0 -0
  159. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_fish_completion.py +0 -0
  160. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_hybrid_redirection.py +0 -0
  161. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_interactive_utils.py +0 -0
  162. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_key_binding_utils.py +0 -0
  163. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_key_bindings.py +0 -0
  164. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_kubectl_tunnel.py +0 -0
  165. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_main.py +0 -0
  166. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_main_modes_batch.py +0 -0
  167. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_main_modes_execute.py +0 -0
  168. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_main_modes_list_dsn.py +0 -0
  169. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_main_modes_repl.py +0 -0
  170. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_naive_completion.py +0 -0
  171. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_output.py +0 -0
  172. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_password_sources.py +0 -0
  173. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_polars_completion.py +0 -0
  174. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_polars_transform.py +0 -0
  175. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_ptoolkit_fzf.py +0 -0
  176. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_ptoolkit_history.py +0 -0
  177. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_ptoolkit_utils.py +0 -0
  178. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_schema_prefetcher.py +0 -0
  179. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_smart_completion_public_schema_only.py +0 -0
  180. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_special_dbcommands.py +0 -0
  181. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_special_init.py +0 -0
  182. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_special_iocommands.py +0 -0
  183. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_special_llm.py +0 -0
  184. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_special_main.py +0 -0
  185. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_special_source.py +0 -0
  186. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_special_utils.py +0 -0
  187. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_sql_utils.py +0 -0
  188. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_sqlexecute.py +0 -0
  189. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_sqlresult.py +0 -0
  190. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_ssh_tunnel.py +0 -0
  191. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_string_utils.py +0 -0
  192. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_tabular_output.py +0 -0
  193. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_vault.py +0 -0
  194. {mycli-2.24.0 → mycli-2.24.2}/test/pytests/test_zsh_completion.py +0 -0
  195. {mycli-2.24.0 → mycli-2.24.2}/test/utils.py +0 -0
@@ -281,6 +281,7 @@ jobs:
281
281
  packages: write
282
282
  attestations: write
283
283
  id-token: write
284
+ artifact-metadata: write
284
285
 
285
286
  steps:
286
287
  - name: Checkout repository
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycli
3
- Version: 2.24.0
3
+ Version: 2.24.2
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,22 @@
1
+ 2.24.2 (2026/09/12)
2
+ ==============
3
+
4
+ Bug Fixes
5
+ --------
6
+ * Revert `docker` output-type on ghcr.io images.
7
+ * Set `artifact-metadata: write` in `publish.yml`.
8
+
9
+
10
+
11
+ 2.24.1 (2026/09/12)
12
+ ==============
13
+
14
+ Bug Fixes
15
+ --------
16
+ * Omitted completion-candidate methods are no longer applied at all.
17
+ * Set `docker` output-type on ghcr.io images.
18
+
19
+
1
20
  2.24.0 (2026/09/12)
2
21
  ==============
3
22
 
@@ -9,9 +9,9 @@ show_warnings = False
9
9
  # possible completions will be listed.
10
10
  smart_completion = True
11
11
 
12
- # Methods to find completion candidates, highest priority first. Omitted
13
- # methods follow in the default order; empty uses the default. Filename
14
- # and Polars completions retain their own dedicated ordering.
12
+ # Methods to find completion candidates, in priority order. Empty means the
13
+ # default list. Filename matching requires perfect and/or slash_words to be
14
+ # listed. Polars completions uses its own methods.
15
15
  # * perfect - exact leading match
16
16
  # * regex - ordered charater match with limited intervening spans ("rgx" matches "regex")
17
17
  # * under_words - like regex but follows underscores ("uw" matches "under_words")
@@ -985,7 +985,7 @@ class SQLCompleter(Completer):
985
985
  if len(set(order)) != len(order) or any(name not in default_order for name in order):
986
986
  self.completion_config_errors.append('Invalid completion_match_order; using the default order.')
987
987
  order = ()
988
- self.completion_match_order = order + tuple(name for name in default_order if name not in order)
988
+ self.completion_match_order = order or default_order
989
989
  self._match_priorities: dict[int, int] = {
990
990
  Fuzziness[name.upper()]: priority for priority, name in enumerate(self.completion_match_order)
991
991
  }
@@ -1337,12 +1337,12 @@ class SQLCompleter(Completer):
1337
1337
  def find_fuzzy_match(
1338
1338
  self,
1339
1339
  item: str,
1340
- pattern: re.Pattern[str],
1340
+ pattern: re.Pattern[str] | None,
1341
1341
  under_words_text: list[str],
1342
1342
  case_words_text: list[str],
1343
1343
  ) -> int | None:
1344
1344
  for name in self.completion_match_order:
1345
- if name == 'regex' and pattern.search(item.lower()):
1345
+ if name == 'regex' and pattern is not None and pattern.search(item.lower()):
1346
1346
  return Fuzziness.REGEX
1347
1347
  if name == 'under_words':
1348
1348
  under_words_item = [x for x in item.lower().split('_') if x]
@@ -1362,17 +1362,22 @@ class SQLCompleter(Completer):
1362
1362
  collection: Collection[Any],
1363
1363
  ) -> list[tuple[str, int]]:
1364
1364
  completions: list[tuple[str, int]] = []
1365
- regex = f'.{{0,{self.regex_match_distance}}}?'.join(map(re.escape, text))
1366
- pattern = re.compile(f'({regex})')
1367
- under_words_text = [x for x in text.split('_') if x]
1368
- case_words_text = re.split(_CASE_CHANGE_PAT, last)
1365
+ pattern = None
1366
+ if 'regex' in self.completion_match_order:
1367
+ regex = f'.{{0,{self.regex_match_distance}}}?'.join(map(re.escape, text))
1368
+ pattern = re.compile(f'({regex})')
1369
+ under_words_text = [x for x in text.split('_') if x] if 'under_words' in self.completion_match_order else []
1370
+ case_words_text = re.split(_CASE_CHANGE_PAT, last) if 'camel_case' in self.completion_match_order else []
1369
1371
 
1370
1372
  for item in collection:
1371
1373
  fuzziness = self.find_fuzzy_match(item, pattern, under_words_text, case_words_text)
1374
+ if 'perfect' in self.completion_match_order and self._matches_prefix(item, text):
1375
+ if fuzziness is None or self._match_priorities[Fuzziness.PERFECT] < self._match_priorities[fuzziness]:
1376
+ fuzziness = Fuzziness.PERFECT
1372
1377
  if fuzziness is not None:
1373
1378
  completions.append((item, fuzziness))
1374
1379
 
1375
- if len(text) >= self.rapidfuzz_min_length:
1380
+ if 'rapidfuzz' in self.completion_match_order and len(text) >= self.rapidfuzz_min_length:
1376
1381
  rapidfuzz_matches = rapidfuzz.process.extract(
1377
1382
  text,
1378
1383
  collection,
@@ -1397,17 +1402,22 @@ class SQLCompleter(Completer):
1397
1402
 
1398
1403
  return completions
1399
1404
 
1405
+ def _matches_prefix(self, candidate: str, text: str) -> bool:
1406
+ if not text.startswith('`'):
1407
+ candidate = self._strip_backticks(candidate)
1408
+ return candidate.lower().startswith(text)
1409
+
1400
1410
  def find_perfect_matches(
1401
1411
  self,
1402
1412
  text: str,
1403
1413
  collection: Collection[Any],
1404
1414
  start_only: bool,
1405
1415
  ) -> list[tuple[str, int]]:
1416
+ if 'perfect' not in self.completion_match_order:
1417
+ return []
1406
1418
  completions: list[tuple[str, int]] = []
1407
- match_end_limit = len(text) if start_only else None
1408
1419
  for item in collection:
1409
- match_point = item.lower().find(text, 0, match_end_limit)
1410
- if match_point >= 0:
1420
+ if self._matches_prefix(item, text) if start_only else text in item.lower():
1411
1421
  completions.append((item, Fuzziness.PERFECT))
1412
1422
  return completions
1413
1423
 
@@ -1882,7 +1892,7 @@ class SQLCompleter(Completer):
1882
1892
  tiebreaker = tiebreaker_key(candidate)
1883
1893
  if not text_for_len:
1884
1894
  return (0, rank, tiebreaker, 0)
1885
- elif candidate.lower().startswith(text_for_len):
1895
+ elif self._matches_prefix(candidate, text_for_len):
1886
1896
  # Preserve the shorter-prefix fallback for equal frecency scores.
1887
1897
  length = -1000 + len(candidate) if self.completion_tiebreaker == 'frecency' else 0
1888
1898
  return (0, 0, tiebreaker, length)
@@ -1929,10 +1939,13 @@ class SQLCompleter(Completer):
1929
1939
 
1930
1940
  """
1931
1941
  if '/' in word:
1932
- for path in suggest_path_by_prefix(word, sql_only=sql_only):
1933
- yield (path, Fuzziness.SLASH_WORDS)
1942
+ if 'slash_words' in self.completion_match_order:
1943
+ for path in suggest_path_by_prefix(word, sql_only=sql_only):
1944
+ yield (path, Fuzziness.SLASH_WORDS)
1934
1945
  return
1935
1946
 
1947
+ if 'perfect' not in self.completion_match_order:
1948
+ return
1936
1949
  # todo position is ignored, but may need to be used
1937
1950
  base_path, last_path, position = parse_path(word)
1938
1951
  paths = suggest_path(word, sql_only=sql_only)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycli
3
- Version: 2.24.0
3
+ Version: 2.24.2
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,7 +1,7 @@
1
1
  {
2
- "tag": "2.24.0",
2
+ "tag": "2.24.2",
3
3
  "distance": 0,
4
- "node": "gf969367e7367f166722f1dc45f2dc8480156fb25",
4
+ "node": "g1f6ba0fccb060350e1de72eb81f436f106beec02",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-09-12"
@@ -9,9 +9,9 @@ show_warnings = False
9
9
  # possible completions will be listed.
10
10
  smart_completion = True
11
11
 
12
- # Methods to find completion candidates, highest priority first. Omitted
13
- # methods follow in the default order; empty uses the default. Filename
14
- # and Polars completions retain their own dedicated ordering.
12
+ # Methods to find completion candidates, in priority order. Empty means the
13
+ # default list. Filename matching requires perfect and/or slash_words to be
14
+ # listed. Polars completions uses its own methods.
15
15
  # * perfect - exact leading match
16
16
  # * regex - ordered charater match with limited intervening spans ("rgx" matches "regex")
17
17
  # * under_words - like regex but follows underscores ("uw" matches "under_words")
@@ -33,7 +33,7 @@ def test_init_configures_completion_ranking(monkeypatch: pytest.MonkeyPatch, tmp
33
33
 
34
34
  cli = MyCli(myclirc=myclirc)
35
35
 
36
- assert cli.completer.completion_match_order == ('camel_case', 'under_words', 'perfect', 'regex', 'slash_words', 'rapidfuzz')
36
+ assert cli.completer.completion_match_order == ('camel_case', 'under_words')
37
37
 
38
38
 
39
39
  @pytest.mark.parametrize(
@@ -1,5 +1,6 @@
1
1
  # type: ignore
2
2
 
3
+ from pathlib import Path
3
4
  import re
4
5
  from types import SimpleNamespace
5
6
  from unittest.mock import Mock
@@ -153,7 +154,7 @@ def test_find_fuzzy_matches_skips_rapidfuzz_for_short_text(monkeypatch) -> None:
153
154
  raise AssertionError('rapidfuzz should not be called')
154
155
 
155
156
  monkeypatch.setattr(mycli.sqlcompleter.rapidfuzz.process, 'extract', fail_extract)
156
- completer = SQLCompleter()
157
+ completer = SQLCompleter(completion_match_order=('rapidfuzz',))
157
158
  matches = completer.find_fuzzy_matches('sel', 'sel', ['SELECT'])
158
159
 
159
160
  assert matches == []
@@ -326,7 +327,7 @@ def test_find_matches_supports_substring_matching() -> None:
326
327
  def test_find_matches_quotes_identifiers_when_text_starts_with_backtick() -> None:
327
328
  matches = collect_matches('`us', ['users'])
328
329
 
329
- assert matches == [('`users`', Fuzziness.REGEX)]
330
+ assert matches == [('`users`', Fuzziness.PERFECT)]
330
331
 
331
332
 
332
333
  def test_find_matches_quotes_identifiers_when_cursor_is_inside_backticks() -> None:
@@ -336,7 +337,7 @@ def test_find_matches_quotes_identifiers_when_cursor_is_inside_backticks() -> No
336
337
  text_before_cursor='select `uu',
337
338
  )
338
339
 
339
- assert matches == [('`uuid`', Fuzziness.REGEX)]
340
+ assert matches == [('`uuid`', Fuzziness.PERFECT)]
340
341
 
341
342
 
342
343
  def test_find_matches_preserves_asterisk_inside_backticks() -> None:
@@ -346,14 +347,14 @@ def test_find_matches_preserves_asterisk_inside_backticks() -> None:
346
347
  text_before_cursor='select `*',
347
348
  )
348
349
 
349
- assert matches == [('*', Fuzziness.REGEX)]
350
+ assert matches == [('*', Fuzziness.PERFECT)]
350
351
 
351
352
 
352
353
  def test_find_matches_finds_regex_matches() -> None:
353
354
  matches = collect_matches('sel', ['SELECT', 'foo_select_bar'])
354
355
 
355
356
  assert matches == [
356
- ('SELECT', Fuzziness.REGEX),
357
+ ('SELECT', Fuzziness.PERFECT),
357
358
  ('foo_select_bar', Fuzziness.REGEX),
358
359
  ]
359
360
 
@@ -386,7 +387,7 @@ def test_find_matches_skips_rapidfuzz_for_short_text(monkeypatch) -> None:
386
387
 
387
388
  matches = collect_matches('sel', ['SELECT'])
388
389
 
389
- assert matches == [('SELECT', Fuzziness.REGEX)]
390
+ assert matches == [('SELECT', Fuzziness.PERFECT)]
390
391
 
391
392
 
392
393
  def test_find_matches_filters_short_rapidfuzz_candidates(monkeypatch) -> None:
@@ -404,10 +405,10 @@ def test_find_matches_filters_short_rapidfuzz_candidates(monkeypatch) -> None:
404
405
  @pytest.mark.parametrize(
405
406
  ('orig_text', 'collection', 'casing', 'expected'),
406
407
  [
407
- ('sel', ['SELECT'], 'auto', [('select', Fuzziness.REGEX)]),
408
- ('SEL', ['select'], 'auto', [('SELECT', Fuzziness.REGEX)]),
409
- ('sel', ['select'], 'upper', [('SELECT', Fuzziness.REGEX)]),
410
- ('SEL', ['SELECT'], 'lower', [('select', Fuzziness.REGEX)]),
408
+ ('sel', ['SELECT'], 'auto', [('select', Fuzziness.PERFECT)]),
409
+ ('SEL', ['select'], 'auto', [('SELECT', Fuzziness.PERFECT)]),
410
+ ('sel', ['select'], 'upper', [('SELECT', Fuzziness.PERFECT)]),
411
+ ('SEL', ['SELECT'], 'lower', [('select', Fuzziness.PERFECT)]),
411
412
  ],
412
413
  )
413
414
  def test_find_matches_applies_casing(
@@ -494,12 +495,119 @@ def test_completion_match_order_defaults_and_validation(order: tuple[str, ...])
494
495
  def test_completion_match_order_normalizes_partial_list() -> None:
495
496
  completer = SQLCompleter(completion_match_order=(' CAMEL_CASE ', 'under_words'))
496
497
 
497
- assert completer.completion_match_order == ('camel_case', 'under_words', 'perfect', 'regex', 'slash_words', 'rapidfuzz')
498
+ assert completer.completion_match_order == ('camel_case', 'under_words')
499
+
500
+
501
+ @pytest.mark.parametrize(
502
+ ('method', 'text', 'candidate'),
503
+ [
504
+ ('perfect', 'sel', 'select'),
505
+ ('regex', 'slt', 'select'),
506
+ ('under_words', 'us_de_fu', 'user_defined_function'),
507
+ ('camel_case', 'TiZoTrTy', 'TimeZoneTransitionType'),
508
+ ('rapidfuzz', 'abcd', 'abce'),
509
+ ],
510
+ )
511
+ def test_only_enabled_method_contributes_candidates(method: str, text: str, candidate: str) -> None:
512
+ enabled = SQLCompleter(completion_match_order=(method,))
513
+ disabled = SQLCompleter(completion_match_order=('slash_words',))
514
+
515
+ assert enabled.find_fuzzy_matches(text, text.lower(), [candidate]) == [(candidate, Fuzziness[method.upper()])]
516
+ assert disabled.find_fuzzy_matches(text, text.lower(), [candidate]) == []
517
+
518
+
519
+ def test_perfect_only_does_not_run_fuzzy_matchers(monkeypatch: pytest.MonkeyPatch) -> None:
520
+ completer = SQLCompleter(completion_match_order=('perfect',))
521
+ monkeypatch.setattr(mycli.sqlcompleter.re, 'compile', pytest.fail)
522
+ monkeypatch.setattr(mycli.sqlcompleter.re, 'split', pytest.fail)
523
+ monkeypatch.setattr(completer, 'word_parts_match', pytest.fail)
524
+ monkeypatch.setattr(mycli.sqlcompleter.rapidfuzz.process, 'extract', pytest.fail)
525
+
526
+ assert completer.find_fuzzy_matches('sele', 'sele', ['select', 'user_select']) == [('select', Fuzziness.PERFECT)]
527
+
528
+
529
+ @pytest.mark.parametrize('order', [('perfect', 'regex'), ('regex', 'perfect')])
530
+ def test_enabled_prefix_methods_follow_configured_priority(order: tuple[str, ...]) -> None:
531
+ completer = SQLCompleter(completion_match_order=order)
532
+
533
+ assert list(completer.find_matches('sel', ['select'])) == [('select', Fuzziness[order[0].upper()])]
534
+
535
+
536
+ @pytest.mark.parametrize('smart', [True, False])
537
+ @pytest.mark.parametrize(('order', 'expected'), [(('perfect',), ['select']), (('slash_words',), [])])
538
+ def test_sql_completion_with_restricted_methods(
539
+ monkeypatch: pytest.MonkeyPatch, smart: bool, order: tuple[str, ...], expected: list[str]
540
+ ) -> None:
541
+ completer = make_completer(smart_completion=smart, completion_match_order=order)
542
+ completer.keywords = ['select', 'user_select']
543
+ completer.all_completions = {'select', 'user_select'}
544
+ monkeypatch.setattr(mycli.sqlcompleter, 'suggest_type', lambda *args: [{'type': 'keyword'}])
545
+
546
+ assert [c.text for c in completer.get_completions(Document('sel'), None)] == expected
547
+
548
+
549
+ def test_nonfuzzy_completion_requires_perfect() -> None:
550
+ completer = SQLCompleter(completion_match_order=('regex',))
551
+
552
+ assert list(completer.find_matches('sel', ['select'], fuzzy=False)) == []
553
+
554
+
555
+ @pytest.mark.parametrize('smart', [True, False])
556
+ @pytest.mark.parametrize(('name', 'prefix'), [('order items', 'ord'), ('select', 'SEL')])
557
+ @pytest.mark.parametrize('quote', ['', '`'])
558
+ def test_perfect_only_completes_quoted_table_names(smart: bool, name: str, prefix: str, quote: str) -> None:
559
+ completer = SQLCompleter(smart_completion=smart, completion_match_order=('perfect',))
560
+ completer.extend_schemata('test')
561
+ completer.set_dbname('test')
562
+ completer.extend_relations([(name,)], kind='tables')
563
+ token = quote + prefix
564
+
565
+ matches = list(completer.get_completions(Document(f'SELECT * FROM {token}'), None))
566
+
567
+ assert any(c.text == f'`{name}`' and c.start_position == -len(token) for c in matches)
568
+
569
+
570
+ @pytest.mark.parametrize(('name', 'prefix'), [('order total', 'ord'), ('from', 'fro')])
571
+ def test_perfect_only_completes_quoted_column_names(name: str, prefix: str) -> None:
572
+ completer = SQLCompleter(completion_match_order=('perfect',))
573
+ completer.extend_schemata('test')
574
+ completer.set_dbname('test')
575
+ completer.extend_relations([('orders',)], kind='tables')
576
+ completer.extend_columns([('orders', name)], kind='tables')
577
+
578
+ matches = list(completer.get_completions(Document(f'SELECT * FROM orders WHERE {prefix}'), None))
579
+
580
+ assert any(c.text == f'`{name}`' and c.start_position == -len(prefix) for c in matches)
581
+
582
+
583
+ @pytest.mark.parametrize('fuzzy', [True, False])
584
+ def test_perfect_only_does_not_match_inside_quoted_names(fuzzy: bool) -> None:
585
+ completer = SQLCompleter(completion_match_order=('perfect',))
586
+
587
+ assert list(completer.find_matches('der', ['`order items`'], fuzzy=fuzzy, start_only=True)) == []
588
+
589
+
590
+ @pytest.mark.parametrize(('word', 'order'), [('./fi', ('perfect',)), ('fi', ('slash_words',))])
591
+ def test_disabled_file_methods_do_not_access_filesystem(monkeypatch: pytest.MonkeyPatch, word: str, order: tuple[str, ...]) -> None:
592
+ completer = SQLCompleter(completion_match_order=order)
593
+ monkeypatch.setattr(mycli.sqlcompleter, 'suggest_path_by_prefix', pytest.fail)
594
+ monkeypatch.setattr(mycli.sqlcompleter, 'suggest_path', pytest.fail)
595
+
596
+ assert list(completer.find_files(word)) == []
597
+
598
+
599
+ @pytest.mark.parametrize(('word', 'method'), [('./fi', 'slash_words'), ('fi', 'perfect')])
600
+ def test_enabled_file_method_completes_paths(monkeypatch: pytest.MonkeyPatch, tmp_path: Path, word: str, method: str) -> None:
601
+ monkeypatch.chdir(tmp_path)
602
+ (tmp_path / 'file.sql').touch()
603
+ completer = SQLCompleter(completion_match_order=(method,))
604
+
605
+ assert list(completer.find_files(word)) == [('./file.sql' if '/' in word else 'file.sql', Fuzziness[method.upper()])]
498
606
 
499
607
 
500
608
  @pytest.mark.parametrize('preferred', ['regex', 'under_words', 'camel_case'])
501
609
  def test_overlapping_matches_use_configured_priority(preferred: str) -> None:
502
- completer = SQLCompleter(completion_match_order=(preferred,))
610
+ completer = SQLCompleter(completion_match_order=(preferred, 'perfect'))
503
611
 
504
612
  matches = list(completer.find_matches('al', ['alphabet']))
505
613
 
@@ -507,7 +615,7 @@ def test_overlapping_matches_use_configured_priority(preferred: str) -> None:
507
615
 
508
616
 
509
617
  def test_rapidfuzz_can_replace_an_overlapping_category(monkeypatch: pytest.MonkeyPatch) -> None:
510
- completer = SQLCompleter(completion_match_order=('rapidfuzz',))
618
+ completer = SQLCompleter(completion_match_order=('rapidfuzz', 'regex'))
511
619
  monkeypatch.setattr(
512
620
  mycli.sqlcompleter.rapidfuzz.process,
513
621
  'extract',
@@ -520,7 +628,7 @@ def test_rapidfuzz_can_replace_an_overlapping_category(monkeypatch: pytest.Monke
520
628
  @pytest.mark.parametrize('tiebreaker', ['frecency', 'length', 'lexicographic'])
521
629
  def test_prefix_priority_precedes_frecency(monkeypatch: pytest.MonkeyPatch, tiebreaker: str) -> None:
522
630
  completer = make_completer(
523
- completion_match_order=('rapidfuzz',), frecency_provider=lambda: {'alpha': 100.0}, completion_tiebreaker=tiebreaker
631
+ completion_match_order=('rapidfuzz', 'regex'), frecency_provider=lambda: {'alpha': 100.0}, completion_tiebreaker=tiebreaker
524
632
  )
525
633
  monkeypatch.setattr(mycli.sqlcompleter, 'suggest_type', lambda *args: [{'type': 'keyword'}])
526
634
  monkeypatch.setattr(completer, 'find_matches', lambda *args, **kwargs: [('alpha', Fuzziness.RAPIDFUZZ), ('prefix', Fuzziness.REGEX)])
@@ -529,7 +637,7 @@ def test_prefix_priority_precedes_frecency(monkeypatch: pytest.MonkeyPatch, tieb
529
637
 
530
638
 
531
639
  def test_custom_match_priority_sorts_candidates(monkeypatch: pytest.MonkeyPatch) -> None:
532
- completer = make_completer(completion_match_order=('under_words',))
640
+ completer = make_completer(completion_match_order=('under_words', 'regex'))
533
641
  monkeypatch.setattr(mycli.sqlcompleter, 'suggest_type', lambda *args: [{'type': 'keyword'}])
534
642
  monkeypatch.setattr(completer, 'find_matches', lambda *args, **kwargs: [('alpha', Fuzziness.REGEX), ('bravo', Fuzziness.UNDER_WORDS)])
535
643
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes