mycli 2.6.1__tar.gz → 2.7.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 (180) hide show
  1. {mycli-2.6.1/mycli.egg-info → mycli-2.7.0}/PKG-INFO +13 -21
  2. {mycli-2.6.1 → mycli-2.7.0}/changelog.md +39 -0
  3. {mycli-2.6.1 → mycli-2.7.0}/mycli/TIPS +10 -0
  4. {mycli-2.6.1 → mycli-2.7.0}/mycli/client.py +5 -1
  5. {mycli-2.6.1 → mycli-2.7.0}/mycli/client_commands.py +1 -1
  6. {mycli-2.6.1 → mycli-2.7.0}/mycli/client_query.py +1 -0
  7. {mycli-2.6.1 → mycli-2.7.0}/mycli/clistyle.py +1 -0
  8. {mycli-2.6.1 → mycli-2.7.0}/mycli/completion_refresher.py +5 -0
  9. {mycli-2.6.1 → mycli-2.7.0}/mycli/main_modes/checkup.py +34 -21
  10. {mycli-2.6.1 → mycli-2.7.0}/mycli/main_modes/repl.py +1 -1
  11. {mycli-2.6.1 → mycli-2.7.0}/mycli/myclirc +7 -0
  12. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/completion_engine.py +25 -5
  13. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/special/iocommands.py +4 -4
  14. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/special/main.py +11 -11
  15. {mycli-2.6.1 → mycli-2.7.0}/mycli/schema_prefetcher.py +8 -0
  16. {mycli-2.6.1 → mycli-2.7.0}/mycli/sqlcompleter.py +63 -7
  17. {mycli-2.6.1 → mycli-2.7.0}/mycli/sqlexecute.py +19 -0
  18. {mycli-2.6.1 → mycli-2.7.0/mycli.egg-info}/PKG-INFO +13 -21
  19. {mycli-2.6.1 → mycli-2.7.0}/mycli.egg-info/requires.txt +12 -20
  20. mycli-2.7.0/mycli.egg-info/scm_version.json +8 -0
  21. {mycli-2.6.1 → mycli-2.7.0}/pyproject.toml +19 -23
  22. {mycli-2.6.1 → mycli-2.7.0}/test/myclirc +7 -0
  23. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_checkup.py +18 -10
  24. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_client.py +30 -0
  25. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_client_query.py +12 -2
  26. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_clistyle.py +12 -1
  27. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_completer_use_switch.py +3 -0
  28. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_completion_engine.py +33 -4
  29. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_completion_refresher.py +4 -0
  30. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_main_modes_repl.py +17 -3
  31. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_naive_completion.py +13 -0
  32. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_schema_prefetcher.py +11 -0
  33. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_smart_completion_public_schema_only.py +113 -0
  34. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_special_main.py +3 -3
  35. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_sqlcompleter.py +39 -0
  36. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_sqlexecute.py +39 -0
  37. mycli-2.6.1/mycli.egg-info/scm_version.json +0 -8
  38. {mycli-2.6.1 → mycli-2.7.0}/.git-blame-ignore-revs +0 -0
  39. {mycli-2.6.1 → mycli-2.7.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  40. {mycli-2.6.1 → mycli-2.7.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  41. {mycli-2.6.1 → mycli-2.7.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  42. {mycli-2.6.1 → mycli-2.7.0}/.github/dependabot.yml +0 -0
  43. {mycli-2.6.1 → mycli-2.7.0}/.github/workflows/ci.yml +0 -0
  44. {mycli-2.6.1 → mycli-2.7.0}/.github/workflows/lint.yml +0 -0
  45. {mycli-2.6.1 → mycli-2.7.0}/.github/workflows/publish.yml +0 -0
  46. {mycli-2.6.1 → mycli-2.7.0}/.github/workflows/typecheck.yml +0 -0
  47. {mycli-2.6.1 → mycli-2.7.0}/.gitignore +0 -0
  48. {mycli-2.6.1 → mycli-2.7.0}/AGENTS.md +0 -0
  49. {mycli-2.6.1 → mycli-2.7.0}/AUTHORS.rst +0 -0
  50. {mycli-2.6.1 → mycli-2.7.0}/CONTRIBUTING.md +0 -0
  51. {mycli-2.6.1 → mycli-2.7.0}/LICENSE.txt +0 -0
  52. {mycli-2.6.1 → mycli-2.7.0}/MANIFEST.in +0 -0
  53. {mycli-2.6.1 → mycli-2.7.0}/README.md +0 -0
  54. {mycli-2.6.1 → mycli-2.7.0}/SPONSORS.rst +0 -0
  55. {mycli-2.6.1 → mycli-2.7.0}/doc/key_bindings.rst +0 -0
  56. {mycli-2.6.1 → mycli-2.7.0}/doc/llm.md +0 -0
  57. {mycli-2.6.1 → mycli-2.7.0}/doc/screenshots/main.gif +0 -0
  58. {mycli-2.6.1 → mycli-2.7.0}/doc/screenshots/tables.png +0 -0
  59. {mycli-2.6.1 → mycli-2.7.0}/doc/screenshots/total_histogram.png +0 -0
  60. {mycli-2.6.1 → mycli-2.7.0}/doc/transforms.md +0 -0
  61. {mycli-2.6.1 → mycli-2.7.0}/mycli/AUTHORS +0 -0
  62. {mycli-2.6.1 → mycli-2.7.0}/mycli/SPONSORS +0 -0
  63. {mycli-2.6.1 → mycli-2.7.0}/mycli/__init__.py +0 -0
  64. {mycli-2.6.1 → mycli-2.7.0}/mycli/app_state.py +0 -0
  65. {mycli-2.6.1 → mycli-2.7.0}/mycli/cli_runner.py +0 -0
  66. {mycli-2.6.1 → mycli-2.7.0}/mycli/clibuffer.py +0 -0
  67. {mycli-2.6.1 → mycli-2.7.0}/mycli/client_connection.py +0 -0
  68. {mycli-2.6.1 → mycli-2.7.0}/mycli/clitoolbar.py +0 -0
  69. {mycli-2.6.1 → mycli-2.7.0}/mycli/compat.py +0 -0
  70. {mycli-2.6.1 → mycli-2.7.0}/mycli/config.py +0 -0
  71. {mycli-2.6.1 → mycli-2.7.0}/mycli/constants.py +0 -0
  72. {mycli-2.6.1 → mycli-2.7.0}/mycli/key_bindings.py +0 -0
  73. {mycli-2.6.1 → mycli-2.7.0}/mycli/lexer.py +0 -0
  74. {mycli-2.6.1 → mycli-2.7.0}/mycli/main.py +0 -0
  75. {mycli-2.6.1 → mycli-2.7.0}/mycli/main_modes/batch.py +0 -0
  76. {mycli-2.6.1 → mycli-2.7.0}/mycli/main_modes/execute.py +0 -0
  77. {mycli-2.6.1 → mycli-2.7.0}/mycli/main_modes/list_dsn.py +0 -0
  78. {mycli-2.6.1 → mycli-2.7.0}/mycli/output.py +0 -0
  79. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/__init__.py +0 -0
  80. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/batch_utils.py +0 -0
  81. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/cli_utils.py +0 -0
  82. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/filepaths.py +0 -0
  83. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/hybrid_redirection.py +0 -0
  84. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/interactive_utils.py +0 -0
  85. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/key_binding_utils.py +0 -0
  86. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/polars_transform.py +0 -0
  87. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/ptoolkit/__init__.py +0 -0
  88. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/ptoolkit/fzf.py +0 -0
  89. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/ptoolkit/history.py +0 -0
  90. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/ptoolkit/utils.py +0 -0
  91. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/special/__init__.py +0 -0
  92. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/special/dbcommands.py +0 -0
  93. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/special/delimitercommand.py +0 -0
  94. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/special/dsn_aliases.py +0 -0
  95. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/special/favoritequeries.py +0 -0
  96. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/special/llm.py +0 -0
  97. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/special/utils.py +0 -0
  98. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/sql_utils.py +0 -0
  99. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/sqlresult.py +0 -0
  100. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/string_utils.py +0 -0
  101. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/tabular_output/__init__.py +0 -0
  102. {mycli-2.6.1 → mycli-2.7.0}/mycli/packages/tabular_output/sql_format.py +0 -0
  103. {mycli-2.6.1 → mycli-2.7.0}/mycli/password_sources.py +0 -0
  104. {mycli-2.6.1 → mycli-2.7.0}/mycli/ssh_tunnel.py +0 -0
  105. {mycli-2.6.1 → mycli-2.7.0}/mycli/types.py +0 -0
  106. {mycli-2.6.1 → mycli-2.7.0}/mycli/vault.py +0 -0
  107. {mycli-2.6.1 → mycli-2.7.0}/mycli.egg-info/SOURCES.txt +0 -0
  108. {mycli-2.6.1 → mycli-2.7.0}/mycli.egg-info/dependency_links.txt +0 -0
  109. {mycli-2.6.1 → mycli-2.7.0}/mycli.egg-info/entry_points.txt +0 -0
  110. {mycli-2.6.1 → mycli-2.7.0}/mycli.egg-info/scm_file_list.json +0 -0
  111. {mycli-2.6.1 → mycli-2.7.0}/mycli.egg-info/top_level.txt +0 -0
  112. {mycli-2.6.1 → mycli-2.7.0}/setup.cfg +0 -0
  113. {mycli-2.6.1 → mycli-2.7.0}/test/__init__.py +0 -0
  114. {mycli-2.6.1 → mycli-2.7.0}/test/features/__init__.py +0 -0
  115. {mycli-2.6.1 → mycli-2.7.0}/test/features/auto_vertical.feature +0 -0
  116. {mycli-2.6.1 → mycli-2.7.0}/test/features/basic_commands.feature +0 -0
  117. {mycli-2.6.1 → mycli-2.7.0}/test/features/connection.feature +0 -0
  118. {mycli-2.6.1 → mycli-2.7.0}/test/features/crud_database.feature +0 -0
  119. {mycli-2.6.1 → mycli-2.7.0}/test/features/crud_table.feature +0 -0
  120. {mycli-2.6.1 → mycli-2.7.0}/test/features/db_utils.py +0 -0
  121. {mycli-2.6.1 → mycli-2.7.0}/test/features/environment.py +0 -0
  122. {mycli-2.6.1 → mycli-2.7.0}/test/features/fixture_data/help_commands.txt +0 -0
  123. {mycli-2.6.1 → mycli-2.7.0}/test/features/fixture_utils.py +0 -0
  124. {mycli-2.6.1 → mycli-2.7.0}/test/features/iocommands.feature +0 -0
  125. {mycli-2.6.1 → mycli-2.7.0}/test/features/named_queries.feature +0 -0
  126. {mycli-2.6.1 → mycli-2.7.0}/test/features/specials.feature +0 -0
  127. {mycli-2.6.1 → mycli-2.7.0}/test/features/steps/__init__.py +0 -0
  128. {mycli-2.6.1 → mycli-2.7.0}/test/features/steps/auto_vertical.py +0 -0
  129. {mycli-2.6.1 → mycli-2.7.0}/test/features/steps/basic_commands.py +0 -0
  130. {mycli-2.6.1 → mycli-2.7.0}/test/features/steps/connection.py +0 -0
  131. {mycli-2.6.1 → mycli-2.7.0}/test/features/steps/crud_database.py +0 -0
  132. {mycli-2.6.1 → mycli-2.7.0}/test/features/steps/crud_table.py +0 -0
  133. {mycli-2.6.1 → mycli-2.7.0}/test/features/steps/iocommands.py +0 -0
  134. {mycli-2.6.1 → mycli-2.7.0}/test/features/steps/named_queries.py +0 -0
  135. {mycli-2.6.1 → mycli-2.7.0}/test/features/steps/specials.py +0 -0
  136. {mycli-2.6.1 → mycli-2.7.0}/test/features/steps/utils.py +0 -0
  137. {mycli-2.6.1 → mycli-2.7.0}/test/features/steps/wrappers.py +0 -0
  138. {mycli-2.6.1 → mycli-2.7.0}/test/features/wrappager.py +0 -0
  139. {mycli-2.6.1 → mycli-2.7.0}/test/mylogin.cnf +0 -0
  140. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/conftest.py +0 -0
  141. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_app_state.py +0 -0
  142. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_batch_utils.py +0 -0
  143. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_cli_runner.py +0 -0
  144. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_cli_utils.py +0 -0
  145. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_clibuffer.py +0 -0
  146. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_client_commands.py +0 -0
  147. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_client_connection.py +0 -0
  148. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_clitoolbar.py +0 -0
  149. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_config.py +0 -0
  150. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_delimitercommand.py +0 -0
  151. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_dsn_aliases.py +0 -0
  152. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_favoritequeries.py +0 -0
  153. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_filepaths.py +0 -0
  154. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_hybrid_redirection.py +0 -0
  155. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_interactive_utils.py +0 -0
  156. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_key_binding_utils.py +0 -0
  157. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_key_bindings.py +0 -0
  158. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_lexer.py +0 -0
  159. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_main.py +0 -0
  160. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_main_modes_batch.py +0 -0
  161. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_main_modes_execute.py +0 -0
  162. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_main_modes_list_dsn.py +0 -0
  163. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_output.py +0 -0
  164. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_password_sources.py +0 -0
  165. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_polars_transform.py +0 -0
  166. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_ptoolkit_fzf.py +0 -0
  167. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_ptoolkit_history.py +0 -0
  168. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_ptoolkit_utils.py +0 -0
  169. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_special_dbcommands.py +0 -0
  170. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_special_init.py +0 -0
  171. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_special_iocommands.py +0 -0
  172. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_special_llm.py +0 -0
  173. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_special_utils.py +0 -0
  174. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_sql_utils.py +0 -0
  175. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_sqlresult.py +0 -0
  176. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_ssh_tunnel.py +0 -0
  177. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_string_utils.py +0 -0
  178. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_tabular_output.py +0 -0
  179. {mycli-2.6.1 → mycli-2.7.0}/test/pytests/test_vault.py +0 -0
  180. {mycli-2.6.1 → mycli-2.7.0}/test/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycli
3
- Version: 2.6.1
3
+ Version: 2.7.0
4
4
  Summary: CLI for MySQL Database. With auto-completion and syntax highlighting.
5
5
  Author: Mycli Core Team
6
6
  License-Expression: BSD-3-Clause
@@ -13,18 +13,18 @@ Requires-Python: >=3.10
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE.txt
15
15
  License-File: AUTHORS.rst
16
- Requires-Dist: click~=8.3.1
16
+ Requires-Dist: click~=8.4.2
17
17
  Requires-Dist: clickdc~=0.1.1
18
- Requires-Dist: cryptography~=46.0.5
19
- Requires-Dist: Pygments~=2.19.2
18
+ Requires-Dist: cryptography~=49.0.0
19
+ Requires-Dist: Pygments~=2.20.0
20
20
  Requires-Dist: prompt_toolkit<4.0.0,>=3.0.41
21
21
  Requires-Dist: PyMySQL~=1.1.2
22
22
  Requires-Dist: sqlparse<0.6.0,>=0.3.0
23
- Requires-Dist: sqlglot~=30.8.0
24
- Requires-Dist: sqlglotc~=30.8.0
23
+ Requires-Dist: sqlglot~=30.12.0
24
+ Requires-Dist: sqlglotc~=30.12.0
25
25
  Requires-Dist: configobj~=5.0.9
26
- Requires-Dist: cli_helpers[styles]~=2.15.0
27
- Requires-Dist: wcwidth~=0.6.0
26
+ Requires-Dist: cli_helpers[styles]~=2.15.1
27
+ Requires-Dist: wcwidth~=0.8.2
28
28
  Requires-Dist: pyperclip~=1.11.0
29
29
  Requires-Dist: pycryptodomex~=3.23.0
30
30
  Requires-Dist: pyfzf~=0.3.1
@@ -32,10 +32,9 @@ Requires-Dist: rapidfuzz~=3.14.3
32
32
  Requires-Dist: keyring~=25.7.0
33
33
  Requires-Dist: yaspin~=3.4.0
34
34
  Provides-Extra: llm
35
- Requires-Dist: llm~=0.30.0; extra == "llm"
36
- Requires-Dist: pydantic_core~=2.41.5; extra == "llm"
37
- Requires-Dist: setuptools==82.*; extra == "llm"
38
- Requires-Dist: pip==26.*; extra == "llm"
35
+ Requires-Dist: llm~=0.31.1; extra == "llm"
36
+ Requires-Dist: setuptools==83.*; extra == "llm"
37
+ Requires-Dist: pip~=26.1.2; extra == "llm"
39
38
  Provides-Extra: dataframe
40
39
  Requires-Dist: polars~=1.42.1; extra == "dataframe"
41
40
  Requires-Dist: altair~=6.2.2; extra == "dataframe"
@@ -45,21 +44,14 @@ Requires-Dist: mycli[dataframe,llm]; extra == "all"
45
44
  Provides-Extra: dev
46
45
  Requires-Dist: behave~=1.3.3; extra == "dev"
47
46
  Requires-Dist: coverage~=7.13.4; extra == "dev"
48
- Requires-Dist: mypy~=1.19.1; extra == "dev"
47
+ Requires-Dist: mypy~=2.3.0; extra == "dev"
49
48
  Requires-Dist: pexpect~=4.9.0; extra == "dev"
50
- Requires-Dist: pytest~=9.0.2; extra == "dev"
49
+ Requires-Dist: pytest~=9.1.1; extra == "dev"
51
50
  Requires-Dist: pytest-cov~=7.0.0; extra == "dev"
52
51
  Requires-Dist: pytest-random-order~=1.2.0; extra == "dev"
53
52
  Requires-Dist: tox~=4.35.0; extra == "dev"
54
53
  Requires-Dist: pdbpp~=0.11.7; extra == "dev"
55
- Requires-Dist: llm~=0.30.0; extra == "dev"
56
- Requires-Dist: pydantic_core~=2.41.5; extra == "dev"
57
- Requires-Dist: setuptools==82.*; extra == "dev"
58
- Requires-Dist: pip==26.*; extra == "dev"
59
54
  Requires-Dist: ruff~=0.15.0; extra == "dev"
60
- Requires-Dist: polars~=1.42.1; extra == "dev"
61
- Requires-Dist: altair~=6.2.2; extra == "dev"
62
- Requires-Dist: vl-convert-python~=1.9.0; extra == "dev"
63
55
  Dynamic: license-file
64
56
 
65
57
  # mycli
@@ -1,3 +1,42 @@
1
+ 2.7.0 (2026/07/25)
2
+ ==============
3
+
4
+ Features
5
+ ---------
6
+ * Show purpose in tabular `--checkup` output.
7
+ * Add optional dependencies to `--checkup`.
8
+ * Improve completions for `/command`s.
9
+ * Highlight indexed columns in completions with a suffix and/or a text style.
10
+
11
+
12
+ Bugfixes
13
+ ---------
14
+ * Ignoring `min_completion_trigger` only applies to first token after `/`.
15
+
16
+
17
+ Documentation
18
+ ---------
19
+ * Add `.|` and `.>` to TIPS.
20
+
21
+
22
+ Internal
23
+ ---------
24
+ * Update `cli_helpers` dependency to v2.15.1
25
+ * Update `click` to v8.4.2
26
+ * Update `cryptography` to v49.0.0
27
+ * Update `pygments` to v2.20.0
28
+ * Update `llm` to v0.31.1
29
+ * Update `setuptools` to v83.x
30
+ * Update `pip` to v26.1.2
31
+ * Update `pytest` to v9.1.1
32
+ * Add dependency cooldown period
33
+ * Exclude `cli_helpers` and `openai` from cooldown
34
+ * Update `sqlglot`/`sqlglotc` to v30.12.0
35
+ * Update `wcwidth` to v0.8.2
36
+ * Remove duplication in dev dependencies.
37
+ * Update `mypy` to v2.3.0.
38
+
39
+
1
40
  2.6.1 (2026/07/24)
2
41
  ==============
3
42
 
@@ -144,6 +144,8 @@ display output in an interactive explorer \x at the end of a query!
144
144
 
145
145
  run SQL scripts in batch mode using the standard input!
146
146
 
147
+ indexed columns are tagged with a "*" in completion menus!
148
+
147
149
  ###
148
150
  ### keystrokes
149
151
  ###
@@ -301,3 +303,11 @@ redirect query output to a CSV file with "$> <filename>"!
301
303
  append query output to a CSV file with "$>> <filename>"!
302
304
 
303
305
  run a command after shell redirects with "post_redirect_command" in ~/.myclirc!
306
+
307
+ ###
308
+ ### transforms
309
+ ###
310
+
311
+ transform or plot query output with a dataframe using ".| df"!
312
+
313
+ save query output to a Parquet file with ".> output.parquet"!
@@ -183,6 +183,7 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
183
183
  self.initialize_logging()
184
184
 
185
185
  keyword_casing = c["main"].get("keyword_casing", "auto")
186
+ indexed_column_suffix = c['main'].get('indexed_column_suffix', '')
186
187
 
187
188
  self.highlight_preview = c['search'].as_bool('highlight_preview')
188
189
 
@@ -191,7 +192,10 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
191
192
  # Initialize completer.
192
193
  self.smart_completion = c["main"].as_bool("smart_completion")
193
194
  self.completer = SQLCompleter(
194
- self.smart_completion, supported_formats=self.main_formatter.supported_formats, keyword_casing=keyword_casing
195
+ self.smart_completion,
196
+ supported_formats=self.main_formatter.supported_formats,
197
+ keyword_casing=keyword_casing,
198
+ indexed_column_suffix=indexed_column_suffix,
195
199
  )
196
200
  self._completer_lock = threading.Lock()
197
201
 
@@ -52,7 +52,7 @@ class ClientCommandsMixin:
52
52
  "rehash",
53
53
  "/rehash",
54
54
  "Refresh auto-completions.",
55
- arg_type=ArgType.NO_QUERY,
55
+ arg_type=ArgType.NO_ARGUMENT,
56
56
  aliases=[SpecialCommandAlias("\\#", case_sensitive=False)],
57
57
  )
58
58
  special.register_special_command(
@@ -52,6 +52,7 @@ class ClientQueryMixin:
52
52
  "smart_completion": self.smart_completion,
53
53
  "supported_formats": self.main_formatter.supported_formats,
54
54
  "keyword_casing": self.completer.keyword_casing,
55
+ "indexed_column_suffix": self.completer.indexed_column_suffix,
55
56
  },
56
57
  )
57
58
 
@@ -15,6 +15,7 @@ logger = logging.getLogger(__name__)
15
15
  TOKEN_TO_PROMPT_STYLE: dict[Token, str] = {
16
16
  Token.Menu.Completions.Completion.Current: "completion-menu.completion.current",
17
17
  Token.Menu.Completions.Completion: "completion-menu.completion",
18
+ Token.Menu.Completions.Completion.Indexed: "completion-menu.completion.indexed",
18
19
  Token.Menu.Completions.Meta.Current: "completion-menu.meta.completion.current",
19
20
  Token.Menu.Completions.Meta: "completion-menu.meta.completion",
20
21
  Token.Menu.Completions.MultiColumnMeta: "completion-menu.multi-column-meta",
@@ -167,6 +167,11 @@ def refresh_tables(completer: SQLCompleter, executor: SQLExecute) -> None:
167
167
  completer.extend_columns(table_columns_dbresult, kind="tables")
168
168
 
169
169
 
170
+ @refresher("indexed_columns")
171
+ def refresh_indexed_columns(completer: SQLCompleter, executor: SQLExecute) -> None:
172
+ completer.extend_indexed_columns(executor.indexed_columns())
173
+
174
+
170
175
  @refresher("foreign_keys")
171
176
  def refresh_foreign_keys(completer: SQLCompleter, executor: SQLExecute) -> None:
172
177
  completer.extend_foreign_keys(executor.foreign_keys())
@@ -6,6 +6,8 @@ import sys
6
6
  import urllib.error
7
7
  import urllib.request
8
8
 
9
+ from tabulate import tabulate
10
+
9
11
  from mycli.constants import REPO_URL
10
12
 
11
13
  PYPI_API_BASE = 'https://pypi.org/pypi'
@@ -24,15 +26,20 @@ def pypi_api_fetch(fragment: str) -> dict:
24
26
 
25
27
  def _dependencies_checkup() -> None:
26
28
  print('\n### Key Python dependencies:\n')
27
- for dependency in [
28
- 'cli_helpers',
29
- 'click',
30
- 'prompt_toolkit',
31
- 'pygments',
32
- 'pymysql',
33
- 'sqlglot',
34
- 'sqlglotc',
35
- 'tabulate',
29
+ table = []
30
+ for dependency, purpose in [
31
+ ('cli_helpers', 'required'),
32
+ ('click', 'required'),
33
+ ('prompt_toolkit', 'required'),
34
+ ('pygments', 'required'),
35
+ ('pymysql', 'required'),
36
+ ('sqlglot', 'required'),
37
+ ('sqlglotc', 'required'),
38
+ ('tabulate', 'required'),
39
+ ('llm', 'optional for /llm command'),
40
+ ('polars', 'optional for .| operator'),
41
+ ('altair', 'optional for .| operator'),
42
+ ('vl-convert-python', 'optional for .| operator'),
36
43
  ]:
37
44
  try:
38
45
  installed_version = importlib.metadata.version(dependency)
@@ -40,32 +47,38 @@ def _dependencies_checkup() -> None:
40
47
  installed_version = None
41
48
  pypi_profile = pypi_api_fetch(f'/{dependency}/json')
42
49
  latest_version = pypi_profile.get('info', {}).get('version', None)
43
- print(f'{dependency} version {installed_version} (latest {latest_version})')
50
+ table.append([dependency, installed_version, latest_version, purpose])
51
+ print(tabulate(table, headers=['library', 'version', 'latest', 'purpose']))
44
52
 
45
53
 
46
54
  def _executables_checkup() -> None:
47
55
  print('\n### External executables:\n')
48
- for executable in [
49
- 'less',
50
- 'fzf',
51
- 'pygmentize',
56
+ table = []
57
+ for executable, purpose in [
58
+ ('less', 'required for paging'),
59
+ ('fzf', r'optional for history search and \x'),
60
+ ('pygmentize', 'optional for history search'),
52
61
  ]:
62
+ # executable, purpose = external
53
63
  if shutil.which(executable):
54
- print(f'The "{executable}" executable was found — good!')
64
+ table.append([executable, 'found', purpose])
55
65
  else:
56
- print(f'The recommended "{executable}" executable was not found — some functionality will suffer.')
66
+ table.append([executable, 'MISSING', purpose])
67
+ print(tabulate(table, headers=['executable', 'status', 'purpose']))
57
68
 
58
69
 
59
70
  def _environment_checkup() -> None:
60
71
  print('\n### Environment variables:\n')
61
- for variable in [
62
- 'EDITOR',
63
- 'VISUAL',
72
+ table = []
73
+ for variable, purpose in [
74
+ ('EDITOR', r'optional for \edit and C-x C-e'),
75
+ ('VISUAL', r'optional for \edit and C-x C-e'),
64
76
  ]:
65
77
  if value := os.environ.get(variable):
66
- print(f'The ${variable} environment variable was set to "{value}" — good!')
78
+ table.append([f'${variable}', value, purpose])
67
79
  else:
68
- print(f'The ${variable} environment variable was not set — some functionality will suffer.')
80
+ table.append([f'${variable}', 'UNSET', purpose])
81
+ print(tabulate(table, headers=['variable', 'setting', 'purpose']))
69
82
 
70
83
 
71
84
  def _configuration_checkup(mycli) -> None:
@@ -112,7 +112,7 @@ def complete_while_typing_filter() -> bool:
112
112
  return True
113
113
  app = get_app()
114
114
  text = app.current_buffer.text.lstrip()
115
- if text.startswith('/') and not text.startswith('/*'):
115
+ if text.startswith('/') and not text.startswith('/*') and ' ' not in text:
116
116
  return True
117
117
  text_len = len(text)
118
118
  if text_len < MIN_COMPLETION_TRIGGER:
@@ -9,6 +9,12 @@ show_warnings = False
9
9
  # possible completions will be listed.
10
10
  smart_completion = True
11
11
 
12
+ # Text appended to indexed column names in the completion menu. This text is
13
+ # not inserted into the query. Leave empty to disable the marker. Quote values
14
+ # containing spaces, commas, or comment characters.
15
+ # Alternative: ' [indexed]'
16
+ indexed_column_suffix = *
17
+
12
18
  # Minimum characters typed before offering completion suggestions. Forward
13
19
  # slash for a command is an exception which always offers completions.
14
20
  # Suggestion: 3.
@@ -411,6 +417,7 @@ default_username_field = username
411
417
  # Completion menus
412
418
  completion-menu.completion.current = 'bg:#ffffff #000000'
413
419
  completion-menu.completion = 'bg:#008888 #ffffff'
420
+ completion-menu.completion.indexed = 'bg:#008888 #ffffff'
414
421
  completion-menu.meta.completion.current = 'bg:#44aaaa #000000'
415
422
  completion-menu.meta.completion = 'bg:#448888 #ffffff'
416
423
  completion-menu.multi-column-meta = 'bg:#aaffff #000000'
@@ -71,6 +71,10 @@ def _keyword_suggestions() -> list[Suggestion]:
71
71
  return [{'type': 'keyword'}]
72
72
 
73
73
 
74
+ def _special_suggestions() -> list[Suggestion]:
75
+ return [{'type': 'special'}]
76
+
77
+
74
78
  def _keyword_and_special_suggestions() -> list[Suggestion]:
75
79
  return [{'type': 'keyword'}, {'type': 'special'}]
76
80
 
@@ -144,8 +148,11 @@ def _emit_none_token(_ctx: SuggestContext) -> list[Suggestion]:
144
148
  return _keyword_suggestions()
145
149
 
146
150
 
147
- def _emit_blank_token(_ctx: SuggestContext) -> list[Suggestion]:
148
- return _keyword_and_special_suggestions()
151
+ def _emit_blank_token(ctx: SuggestContext) -> list[Suggestion]:
152
+ if ctx.text_before_cursor.startswith('/'):
153
+ return _special_suggestions()
154
+ else:
155
+ return _keyword_and_special_suggestions()
149
156
 
150
157
 
151
158
  def _emit_star(_ctx: SuggestContext) -> list[Suggestion]:
@@ -793,7 +800,8 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
793
800
  {"type": "view", "schema": []},
794
801
  {"type": "schema"},
795
802
  ]
796
- elif cmd.lower() in [
803
+
804
+ if cmd.lower() in [
797
805
  r'\.',
798
806
  r'/.',
799
807
  'source',
@@ -806,14 +814,16 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
806
814
  '/tee',
807
815
  ]:
808
816
  return [{"type": "file_name"}]
817
+
809
818
  # todo: why is \edit case-sensitive?
810
- elif cmd in [
819
+ if cmd in [
811
820
  r'\e',
812
821
  '/e',
813
822
  r'\edit',
814
823
  '/edit',
815
824
  ]:
816
825
  return [{"type": "file_name"}]
826
+
817
827
  if cmd in ["\\llm", "/llm", "\\ai", "/ai"]:
818
828
  return [{"type": "llm"}]
819
829
 
@@ -828,7 +838,17 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
828
838
  return []
829
839
  return [{'type': 'special_subcommand', 'subcommands': list(DSN_SUBCOMMANDS)}]
830
840
 
831
- return [{"type": "keyword"}, {"type": "special"}]
841
+ if cmd.lower() in [
842
+ 'help',
843
+ '/help',
844
+ r'\help',
845
+ '?',
846
+ '/?',
847
+ r'\?',
848
+ ]:
849
+ return [{"type": "keyword"}, {"type": "special"}]
850
+
851
+ return []
832
852
 
833
853
 
834
854
  def suggest_based_on_last_token(
@@ -98,7 +98,7 @@ def is_show_warnings_enabled() -> bool:
98
98
  'warnings',
99
99
  '/warnings',
100
100
  'Enable automatic warnings display.',
101
- arg_type=ArgType.NO_QUERY,
101
+ arg_type=ArgType.NO_ARGUMENT,
102
102
  case_sensitive=True,
103
103
  aliases=[SpecialCommandAlias('\\W', case_sensitive=True)],
104
104
  )
@@ -113,7 +113,7 @@ def enable_show_warnings() -> Generator[SQLResult, None, None]:
113
113
  'nowarnings',
114
114
  '/nowarnings',
115
115
  'Disable automatic warnings display.',
116
- arg_type=ArgType.NO_QUERY,
116
+ arg_type=ArgType.NO_ARGUMENT,
117
117
  case_sensitive=True,
118
118
  aliases=[SpecialCommandAlias('\\w', case_sensitive=True)],
119
119
  )
@@ -152,7 +152,7 @@ def set_pager(arg: str, **_) -> list[SQLResult]:
152
152
  "nopager",
153
153
  "/nopager",
154
154
  "Disable pager; print to stdout.",
155
- arg_type=ArgType.NO_QUERY,
155
+ arg_type=ArgType.NO_ARGUMENT,
156
156
  case_sensitive=True,
157
157
  aliases=[SpecialCommandAlias("\\n", case_sensitive=True)],
158
158
  )
@@ -165,7 +165,7 @@ def disable_pager() -> list[SQLResult]:
165
165
  "\\timing",
166
166
  "/timing",
167
167
  "Toggle timing of queries.",
168
- arg_type=ArgType.NO_QUERY,
168
+ arg_type=ArgType.NO_ARGUMENT,
169
169
  case_sensitive=True,
170
170
  aliases=[SpecialCommandAlias("\\t", case_sensitive=True)],
171
171
  )
@@ -27,7 +27,7 @@ CASE_INSENSITIVE_COMMANDS: set[str] = set()
27
27
 
28
28
 
29
29
  class ArgType(Enum):
30
- NO_QUERY = 0
30
+ NO_ARGUMENT = 0
31
31
  PARSED_QUERY = 1
32
32
  RAW_QUERY = 2
33
33
 
@@ -205,7 +205,7 @@ def execute(cur: Cursor, sql: str) -> list[SQLResult]:
205
205
  if command.lower().startswith(("help", "/help", "\\?", "/?", "?")) and arg:
206
206
  return show_keyword_help(cur=cur, arg=arg)
207
207
 
208
- if special_cmd.arg_type == ArgType.NO_QUERY:
208
+ if special_cmd.arg_type == ArgType.NO_ARGUMENT:
209
209
  return special_cmd.handler()
210
210
  elif special_cmd.arg_type == ArgType.PARSED_QUERY:
211
211
  return special_cmd.handler(cur=cur, arg=arg, command_verbosity=(command_verbosity == CommandVerbosity.VERBOSE))
@@ -219,7 +219,7 @@ def execute(cur: Cursor, sql: str) -> list[SQLResult]:
219
219
  "help",
220
220
  "/help [term]",
221
221
  "Show this table, or search for help on a term.",
222
- arg_type=ArgType.NO_QUERY,
222
+ arg_type=ArgType.NO_ARGUMENT,
223
223
  aliases=[SpecialCommandAlias("\\?", case_sensitive=False), SpecialCommandAlias("?", case_sensitive=False)],
224
224
  )
225
225
  def show_help(*_args) -> list[SQLResult]:
@@ -283,7 +283,7 @@ def show_keyword_help(cur: Cursor, arg: str) -> list[SQLResult]:
283
283
  return _show_mysql_help(cur, keyword)
284
284
 
285
285
 
286
- @special_command('\\bug', '/bug', 'File a bug on GitHub.', arg_type=ArgType.NO_QUERY)
286
+ @special_command('\\bug', '/bug', 'File a bug on GitHub.', arg_type=ArgType.NO_ARGUMENT)
287
287
  def file_bug(*_args) -> list[SQLResult]:
288
288
  webbrowser.open_new_tab(ISSUES_URL)
289
289
  return [SQLResult(status=f'{ISSUES_URL} — press "New Issue"')]
@@ -293,14 +293,14 @@ def file_bug(*_args) -> list[SQLResult]:
293
293
  "exit",
294
294
  "/exit",
295
295
  "Exit.",
296
- arg_type=ArgType.NO_QUERY,
296
+ arg_type=ArgType.NO_ARGUMENT,
297
297
  aliases=[SpecialCommandAlias("\\q", case_sensitive=False)],
298
298
  )
299
299
  @special_command(
300
300
  "quit",
301
301
  "/quit",
302
302
  "Quit.",
303
- arg_type=ArgType.NO_QUERY,
303
+ arg_type=ArgType.NO_ARGUMENT,
304
304
  aliases=[SpecialCommandAlias("\\q", case_sensitive=False)],
305
305
  )
306
306
  def quit_(*_args):
@@ -311,7 +311,7 @@ def quit_(*_args):
311
311
  "\\edit",
312
312
  "/edit <filename> | <query>\\edit",
313
313
  "Edit query with editor (uses $VISUAL or $EDITOR).",
314
- arg_type=ArgType.NO_QUERY,
314
+ arg_type=ArgType.NO_ARGUMENT,
315
315
  case_sensitive=True,
316
316
  aliases=[SpecialCommandAlias("\\e", case_sensitive=True)],
317
317
  )
@@ -319,14 +319,14 @@ def quit_(*_args):
319
319
  "\\clip",
320
320
  "/clip | <query>\\clip",
321
321
  "Copy query to the system clipboard.",
322
- arg_type=ArgType.NO_QUERY,
322
+ arg_type=ArgType.NO_ARGUMENT,
323
323
  case_sensitive=True,
324
324
  )
325
325
  @special_command(
326
326
  "\\G",
327
327
  "<query>\\G",
328
328
  "Display query results vertically.",
329
- arg_type=ArgType.NO_QUERY,
329
+ arg_type=ArgType.NO_ARGUMENT,
330
330
  case_sensitive=True,
331
331
  backslash_only=True,
332
332
  )
@@ -334,7 +334,7 @@ def quit_(*_args):
334
334
  "\\g",
335
335
  "<query>\\g",
336
336
  "Display query results (mnemonic: go).",
337
- arg_type=ArgType.NO_QUERY,
337
+ arg_type=ArgType.NO_ARGUMENT,
338
338
  case_sensitive=True,
339
339
  backslash_only=True,
340
340
  )
@@ -342,7 +342,7 @@ def quit_(*_args):
342
342
  "\\x",
343
343
  "<query>\\x",
344
344
  "Display query results in an explorer rather than a pager.",
345
- arg_type=ArgType.NO_QUERY,
345
+ arg_type=ArgType.NO_ARGUMENT,
346
346
  case_sensitive=True,
347
347
  backslash_only=True,
348
348
  )
@@ -174,6 +174,7 @@ class SchemaPrefetcher:
174
174
  def _prefetch_one(self, executor: SQLExecute, schema: str) -> None:
175
175
  _logger.debug('prefetching schema %r', schema)
176
176
  table_rows = list(executor.table_columns(schema=schema))
177
+ indexed_rows = list(executor.indexed_columns(schema=schema))
177
178
  fk_rows = list(executor.foreign_keys(schema=schema))
178
179
  enum_rows = list(executor.enum_values(schema=schema))
179
180
  func_rows = list(executor.functions(schema=schema))
@@ -189,6 +190,12 @@ class SchemaPrefetcher:
189
190
  cols = table_columns.setdefault(esc_table, ['*'])
190
191
  cols.append(esc_col)
191
192
 
193
+ indexed_columns: dict[str, set[str]] = {}
194
+ for table, column in indexed_rows:
195
+ esc_table = completer.escape_name(table)
196
+ esc_col = completer.escape_name(column)
197
+ indexed_columns.setdefault(esc_table, set()).add(esc_col)
198
+
192
199
  fk_tables: dict[str, set[str]] = {}
193
200
  fk_relations: list[tuple[str, str, str, str]] = []
194
201
  for table, col, ref_table, ref_col in fk_rows:
@@ -224,6 +231,7 @@ class SchemaPrefetcher:
224
231
  live_completer.load_schema_metadata(
225
232
  schema=schema,
226
233
  table_columns=table_columns,
234
+ indexed_columns=indexed_columns,
227
235
  foreign_keys=fk_payload,
228
236
  enum_values=enum_values,
229
237
  functions=functions,