mycli 2.27.0__tar.gz → 2.28.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 (210) hide show
  1. {mycli-2.27.0 → mycli-2.28.0}/.github/workflows/ci.yml +7 -7
  2. {mycli-2.27.0 → mycli-2.28.0}/.github/workflows/publish.yml +17 -11
  3. {mycli-2.27.0 → mycli-2.28.0}/.github/workflows/typecheck.yml +1 -1
  4. {mycli-2.27.0 → mycli-2.28.0}/AGENTS.md +7 -0
  5. {mycli-2.27.0/mycli.egg-info → mycli-2.28.0}/PKG-INFO +1 -2
  6. {mycli-2.27.0 → mycli-2.28.0}/changelog.md +33 -0
  7. mycli-2.28.0/doc/cookbook.md +71 -0
  8. mycli-2.28.0/doc/known_bugs.md +61 -0
  9. {mycli-2.27.0 → mycli-2.28.0}/docker/resources/test_db-1.0.7.tar.gz +1 -1
  10. {mycli-2.27.0 → mycli-2.28.0}/mycli/constants.py +1 -0
  11. {mycli-2.27.0 → mycli-2.28.0}/mycli/key_bindings.py +3 -3
  12. {mycli-2.27.0 → mycli-2.28.0}/mycli/main_modes/checkup.py +3 -3
  13. {mycli-2.27.0 → mycli-2.28.0}/mycli/main_modes/repl.py +30 -23
  14. {mycli-2.27.0 → mycli-2.28.0}/mycli/output.py +5 -1
  15. mycli-2.28.0/mycli/packages/fzf.py +78 -0
  16. mycli-2.27.0/mycli/packages/ptoolkit/fzf.py → mycli-2.28.0/mycli/packages/ptoolkit/fzf_history.py +5 -15
  17. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/special/__init__.py +0 -8
  18. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/special/iocommands.py +4 -4
  19. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/sqlresult.py +9 -1
  20. {mycli-2.27.0 → mycli-2.28.0}/mycli/query_runner.py +27 -12
  21. {mycli-2.27.0 → mycli-2.28.0}/mycli/sqlexecute.py +15 -7
  22. {mycli-2.27.0 → mycli-2.28.0/mycli.egg-info}/PKG-INFO +1 -2
  23. {mycli-2.27.0 → mycli-2.28.0}/mycli.egg-info/SOURCES.txt +6 -2
  24. {mycli-2.27.0 → mycli-2.28.0}/mycli.egg-info/requires.txt +0 -1
  25. {mycli-2.27.0 → mycli-2.28.0}/mycli.egg-info/scm_file_list.json +6 -2
  26. mycli-2.28.0/mycli.egg-info/scm_version.json +8 -0
  27. {mycli-2.27.0 → mycli-2.28.0}/pyproject.toml +6 -3
  28. {mycli-2.27.0 → mycli-2.28.0}/test/features/basic_commands.feature +2 -2
  29. {mycli-2.27.0 → mycli-2.28.0}/test/features/iocommands.feature +3 -3
  30. {mycli-2.27.0 → mycli-2.28.0}/test/features/steps/basic_commands.py +4 -4
  31. {mycli-2.27.0 → mycli-2.28.0}/test/features/steps/iocommands.py +1 -1
  32. {mycli-2.27.0 → mycli-2.28.0}/test/features/steps/named_queries.py +17 -17
  33. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_checkup.py +2 -2
  34. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_client_commands.py +1 -1
  35. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_completion_engine.py +39 -23
  36. mycli-2.28.0/test/pytests/test_fzf.py +126 -0
  37. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_hybrid_redirection.py +1 -1
  38. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_key_bindings.py +2 -2
  39. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_main.py +6 -6
  40. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_main_modes_repl.py +144 -11
  41. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_output.py +83 -1
  42. mycli-2.27.0/test/pytests/test_ptoolkit_fzf.py → mycli-2.28.0/test/pytests/test_ptoolkit_fzf_history.py +35 -37
  43. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_query_runner.py +131 -3
  44. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_smart_completion_public_schema_only.py +19 -18
  45. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_special_dbcommands.py +6 -6
  46. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_special_init.py +1 -1
  47. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_special_iocommands.py +27 -27
  48. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_special_llm.py +20 -20
  49. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_special_main.py +12 -12
  50. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_sql_utils.py +8 -8
  51. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_sqlexecute.py +39 -1
  52. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_sqlresult.py +24 -0
  53. {mycli-2.27.0 → mycli-2.28.0}/test/utils.py +12 -0
  54. mycli-2.27.0/mycli.egg-info/scm_version.json +0 -8
  55. {mycli-2.27.0 → mycli-2.28.0}/.git-blame-ignore-revs +0 -0
  56. {mycli-2.27.0 → mycli-2.28.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  57. {mycli-2.27.0 → mycli-2.28.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  58. {mycli-2.27.0 → mycli-2.28.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  59. {mycli-2.27.0 → mycli-2.28.0}/.github/dependabot.yml +0 -0
  60. {mycli-2.27.0 → mycli-2.28.0}/.github/workflows/lint.yml +0 -0
  61. {mycli-2.27.0 → mycli-2.28.0}/.gitignore +0 -0
  62. {mycli-2.27.0 → mycli-2.28.0}/AUTHORS.md +0 -0
  63. {mycli-2.27.0 → mycli-2.28.0}/CONTRIBUTING.md +0 -0
  64. {mycli-2.27.0 → mycli-2.28.0}/LICENSE.txt +0 -0
  65. {mycli-2.27.0 → mycli-2.28.0}/MANIFEST.in +0 -0
  66. {mycli-2.27.0 → mycli-2.28.0}/README.md +0 -0
  67. {mycli-2.27.0 → mycli-2.28.0}/SPONSORS.md +0 -0
  68. {mycli-2.27.0 → mycli-2.28.0}/doc/key_bindings.md +0 -0
  69. {mycli-2.27.0 → mycli-2.28.0}/doc/llm.md +0 -0
  70. {mycli-2.27.0 → mycli-2.28.0}/doc/screenshots/main.gif +0 -0
  71. {mycli-2.27.0 → mycli-2.28.0}/doc/screenshots/polars_describe.png +0 -0
  72. {mycli-2.27.0 → mycli-2.28.0}/doc/screenshots/polars_group_by.png +0 -0
  73. {mycli-2.27.0 → mycli-2.28.0}/doc/screenshots/polars_histogram.png +0 -0
  74. {mycli-2.27.0 → mycli-2.28.0}/doc/screenshots/polars_pivot.png +0 -0
  75. {mycli-2.27.0 → mycli-2.28.0}/doc/screenshots/tables.png +0 -0
  76. {mycli-2.27.0 → mycli-2.28.0}/doc/transforms.md +0 -0
  77. {mycli-2.27.0 → mycli-2.28.0}/docker/Dockerfile +0 -0
  78. {mycli-2.27.0 → mycli-2.28.0}/docker/resources/test_db_license.md +0 -0
  79. {mycli-2.27.0 → mycli-2.28.0}/mycli/AUTHORS +0 -0
  80. {mycli-2.27.0 → mycli-2.28.0}/mycli/SPONSORS +0 -0
  81. {mycli-2.27.0 → mycli-2.28.0}/mycli/TIPS +0 -0
  82. {mycli-2.27.0 → mycli-2.28.0}/mycli/__init__.py +0 -0
  83. {mycli-2.27.0 → mycli-2.28.0}/mycli/app_state.py +0 -0
  84. {mycli-2.27.0 → mycli-2.28.0}/mycli/boundary_tunnel.py +0 -0
  85. {mycli-2.27.0 → mycli-2.28.0}/mycli/cli_runner.py +0 -0
  86. {mycli-2.27.0 → mycli-2.28.0}/mycli/clibuffer.py +0 -0
  87. {mycli-2.27.0 → mycli-2.28.0}/mycli/client.py +0 -0
  88. {mycli-2.27.0 → mycli-2.28.0}/mycli/client_commands.py +0 -0
  89. {mycli-2.27.0 → mycli-2.28.0}/mycli/client_connection.py +0 -0
  90. {mycli-2.27.0 → mycli-2.28.0}/mycli/client_query.py +0 -0
  91. {mycli-2.27.0 → mycli-2.28.0}/mycli/clistyle.py +0 -0
  92. {mycli-2.27.0 → mycli-2.28.0}/mycli/clitoolbar.py +0 -0
  93. {mycli-2.27.0 → mycli-2.28.0}/mycli/compat.py +0 -0
  94. {mycli-2.27.0 → mycli-2.28.0}/mycli/completion_refresher.py +0 -0
  95. {mycli-2.27.0 → mycli-2.28.0}/mycli/config.py +0 -0
  96. {mycli-2.27.0 → mycli-2.28.0}/mycli/keyring_utils.py +0 -0
  97. {mycli-2.27.0 → mycli-2.28.0}/mycli/kubectl_tunnel.py +0 -0
  98. {mycli-2.27.0 → mycli-2.28.0}/mycli/lexer.py +0 -0
  99. {mycli-2.27.0 → mycli-2.28.0}/mycli/macos_keychain.py +0 -0
  100. {mycli-2.27.0 → mycli-2.28.0}/mycli/main.py +0 -0
  101. {mycli-2.27.0 → mycli-2.28.0}/mycli/main_modes/batch.py +0 -0
  102. {mycli-2.27.0 → mycli-2.28.0}/mycli/main_modes/completions.py +0 -0
  103. {mycli-2.27.0 → mycli-2.28.0}/mycli/main_modes/execute.py +0 -0
  104. {mycli-2.27.0 → mycli-2.28.0}/mycli/main_modes/list_dsn.py +0 -0
  105. {mycli-2.27.0 → mycli-2.28.0}/mycli/myclirc +0 -0
  106. {mycli-2.27.0/test/features/steps → mycli-2.28.0/mycli/packages}/__init__.py +0 -0
  107. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/batch_utils.py +0 -0
  108. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/cli_utils.py +0 -0
  109. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/completion_engine.py +0 -0
  110. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/filepaths.py +0 -0
  111. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/hybrid_redirection.py +0 -0
  112. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/interactive_utils.py +0 -0
  113. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/key_binding_utils.py +0 -0
  114. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/polars_completion.py +0 -0
  115. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/polars_transform.py +0 -0
  116. {mycli-2.27.0/test/features → mycli-2.28.0/mycli/packages/ptoolkit}/__init__.py +0 -0
  117. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/ptoolkit/history.py +0 -0
  118. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/ptoolkit/utils.py +0 -0
  119. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/special/dbcommands.py +0 -0
  120. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/special/delimitercommand.py +0 -0
  121. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/special/dsn_aliases.py +0 -0
  122. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/special/favoritequeries.py +0 -0
  123. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/special/llm.py +0 -0
  124. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/special/main.py +0 -0
  125. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/special/source.py +0 -0
  126. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/special/utils.py +0 -0
  127. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/sql_utils.py +0 -0
  128. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/string_utils.py +0 -0
  129. {mycli-2.27.0/test → mycli-2.28.0/mycli/packages/tabular_output}/__init__.py +0 -0
  130. {mycli-2.27.0 → mycli-2.28.0}/mycli/packages/tabular_output/sql_format.py +0 -0
  131. {mycli-2.27.0 → mycli-2.28.0}/mycli/password_sources.py +0 -0
  132. {mycli-2.27.0 → mycli-2.28.0}/mycli/resources/completions/bash/mycli +0 -0
  133. {mycli-2.27.0 → mycli-2.28.0}/mycli/resources/completions/fish/mycli.fish +0 -0
  134. {mycli-2.27.0 → mycli-2.28.0}/mycli/resources/completions/zsh/_mycli +0 -0
  135. {mycli-2.27.0 → mycli-2.28.0}/mycli/schema_prefetcher.py +0 -0
  136. {mycli-2.27.0 → mycli-2.28.0}/mycli/sqlcompleter.py +0 -0
  137. {mycli-2.27.0 → mycli-2.28.0}/mycli/ssh_tunnel.py +0 -0
  138. {mycli-2.27.0 → mycli-2.28.0}/mycli/types.py +0 -0
  139. {mycli-2.27.0 → mycli-2.28.0}/mycli/vault.py +0 -0
  140. {mycli-2.27.0 → mycli-2.28.0}/mycli.egg-info/dependency_links.txt +0 -0
  141. {mycli-2.27.0 → mycli-2.28.0}/mycli.egg-info/entry_points.txt +0 -0
  142. {mycli-2.27.0 → mycli-2.28.0}/mycli.egg-info/top_level.txt +0 -0
  143. {mycli-2.27.0 → mycli-2.28.0}/setup.cfg +0 -0
  144. {mycli-2.27.0/mycli/packages/tabular_output → mycli-2.28.0/test}/__init__.py +0 -0
  145. {mycli-2.27.0/mycli/packages/ptoolkit → mycli-2.28.0/test/features}/__init__.py +0 -0
  146. {mycli-2.27.0 → mycli-2.28.0}/test/features/auto_vertical.feature +0 -0
  147. {mycli-2.27.0 → mycli-2.28.0}/test/features/connection.feature +0 -0
  148. {mycli-2.27.0 → mycli-2.28.0}/test/features/crud_database.feature +0 -0
  149. {mycli-2.27.0 → mycli-2.28.0}/test/features/crud_table.feature +0 -0
  150. {mycli-2.27.0 → mycli-2.28.0}/test/features/db_utils.py +0 -0
  151. {mycli-2.27.0 → mycli-2.28.0}/test/features/environment.py +0 -0
  152. {mycli-2.27.0 → mycli-2.28.0}/test/features/fixture_data/help_commands.txt +0 -0
  153. {mycli-2.27.0 → mycli-2.28.0}/test/features/fixture_utils.py +0 -0
  154. {mycli-2.27.0 → mycli-2.28.0}/test/features/named_queries.feature +0 -0
  155. {mycli-2.27.0 → mycli-2.28.0}/test/features/specials.feature +0 -0
  156. {mycli-2.27.0/mycli/packages → mycli-2.28.0/test/features/steps}/__init__.py +0 -0
  157. {mycli-2.27.0 → mycli-2.28.0}/test/features/steps/auto_vertical.py +0 -0
  158. {mycli-2.27.0 → mycli-2.28.0}/test/features/steps/connection.py +0 -0
  159. {mycli-2.27.0 → mycli-2.28.0}/test/features/steps/crud_database.py +0 -0
  160. {mycli-2.27.0 → mycli-2.28.0}/test/features/steps/crud_table.py +0 -0
  161. {mycli-2.27.0 → mycli-2.28.0}/test/features/steps/specials.py +0 -0
  162. {mycli-2.27.0 → mycli-2.28.0}/test/features/steps/utils.py +0 -0
  163. {mycli-2.27.0 → mycli-2.28.0}/test/features/steps/wrappers.py +0 -0
  164. {mycli-2.27.0 → mycli-2.28.0}/test/features/wrappager.py +0 -0
  165. {mycli-2.27.0 → mycli-2.28.0}/test/myclirc +0 -0
  166. {mycli-2.27.0 → mycli-2.28.0}/test/mylogin.cnf +0 -0
  167. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/conftest.py +0 -0
  168. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_app_state.py +0 -0
  169. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_bash_completion.py +0 -0
  170. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_batch_utils.py +0 -0
  171. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_boundary_tunnel.py +0 -0
  172. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_cli_runner.py +0 -0
  173. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_cli_utils.py +0 -0
  174. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_clibuffer.py +0 -0
  175. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_client.py +0 -0
  176. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_client_connection.py +0 -0
  177. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_client_query.py +0 -0
  178. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_clistyle.py +0 -0
  179. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_clitoolbar.py +0 -0
  180. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_completer_use_switch.py +0 -0
  181. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_completion_refresher.py +0 -0
  182. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_config.py +0 -0
  183. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_delimitercommand.py +0 -0
  184. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_dsn_aliases.py +0 -0
  185. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_favoritequeries.py +0 -0
  186. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_filepaths.py +0 -0
  187. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_fish_completion.py +0 -0
  188. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_interactive_utils.py +0 -0
  189. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_key_binding_utils.py +0 -0
  190. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_keyring_utils.py +0 -0
  191. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_kubectl_tunnel.py +0 -0
  192. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_macos_keychain.py +0 -0
  193. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_main_modes_batch.py +0 -0
  194. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_main_modes_execute.py +0 -0
  195. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_main_modes_list_dsn.py +0 -0
  196. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_naive_completion.py +0 -0
  197. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_password_sources.py +0 -0
  198. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_polars_completion.py +0 -0
  199. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_polars_transform.py +0 -0
  200. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_ptoolkit_history.py +0 -0
  201. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_ptoolkit_utils.py +0 -0
  202. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_schema_prefetcher.py +0 -0
  203. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_special_source.py +0 -0
  204. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_special_utils.py +0 -0
  205. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_sqlcompleter.py +0 -0
  206. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_ssh_tunnel.py +0 -0
  207. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_string_utils.py +0 -0
  208. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_tabular_output.py +0 -0
  209. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_vault.py +0 -0
  210. {mycli-2.27.0 → mycli-2.28.0}/test/pytests/test_zsh_completion.py +0 -0
@@ -38,7 +38,7 @@ jobs:
38
38
  sudo /etc/init.d/mysql start
39
39
 
40
40
  - name: Install dependencies
41
- run: uv sync --all-extras -p ${{ matrix.python-version }}
41
+ run: uv sync --extra dev,llm,dataframe -p ${{ matrix.python-version }}
42
42
 
43
43
  - name: Wait for MySQL connection
44
44
  run: |
@@ -52,7 +52,7 @@ jobs:
52
52
  PYTEST_HOST: 127.0.0.1
53
53
  TERM: xterm
54
54
  run: |
55
- uv run tox -e py${{ matrix.python-version }}
55
+ ./.venv/bin/tox -e py${{ matrix.python-version }}
56
56
 
57
57
  tests-macos:
58
58
  name: Tests macOS
@@ -84,7 +84,7 @@ jobs:
84
84
  brew services start mysql
85
85
 
86
86
  - name: Install dependencies
87
- run: uv sync --all-extras -p ${{ matrix.python-version }}
87
+ run: uv sync --extra dev,llm,dataframe -p ${{ matrix.python-version }}
88
88
 
89
89
  - name: Wait for MySQL connection
90
90
  run: |
@@ -102,7 +102,7 @@ jobs:
102
102
  PYTEST_HOST: 127.0.0.1
103
103
  TERM: xterm
104
104
  run: |
105
- uv run tox -e py${{ matrix.python-version }}
105
+ ./.venv/bin/tox -e py${{ matrix.python-version }}
106
106
 
107
107
  tests-windows:
108
108
  name: Tests Windows
@@ -127,7 +127,7 @@ jobs:
127
127
 
128
128
  - name: Install dependencies
129
129
  shell: cmd
130
- run: uv sync --all-extras -p ${{ matrix.python-version }}
130
+ run: uv sync --extra dev,llm,dataframe -p ${{ matrix.python-version }}
131
131
 
132
132
  - name: Set up MySQL
133
133
  uses: shogo82148/actions-setup-mysql@083ec148d28e62f2b24b8f4541693dfe1d50488d # v1.54.0
@@ -182,7 +182,7 @@ jobs:
182
182
  run: |
183
183
  export PATH="$HOME/.local/bin:$PATH"
184
184
  cd /root/mycli
185
- uv sync --all-extras --python 3.14
185
+ uv sync --extra dev,llm,dataframe --python 3.14
186
186
 
187
187
  - name: Start MySQL in WSL
188
188
  shell: wsl-bash {0}
@@ -248,7 +248,7 @@ jobs:
248
248
  PYTEST_HOST: 127.0.0.1
249
249
  TERM: xterm
250
250
  run: |
251
- uv run tox -e py3.14
251
+ ./.venv/bin/tox -e py3.14
252
252
 
253
253
  docker-x86-build:
254
254
  name: Build x86 Docker Image
@@ -46,7 +46,7 @@ jobs:
46
46
  sudo /etc/init.d/mysql start
47
47
 
48
48
  - name: Install dependencies
49
- run: uv sync --all-extras -p ${{ matrix.python-version }}
49
+ run: uv sync --extra dev,llm,dataframe -p ${{ matrix.python-version }}
50
50
 
51
51
  - name: Wait for MySQL connection
52
52
  run: |
@@ -54,16 +54,22 @@ jobs:
54
54
  sleep 5
55
55
  done
56
56
 
57
+ # arguably this should be made identical to CI for PRs
58
+ # better would be a composite GH action
59
+ - name: Run Style Checks
60
+ run: ./.venv/bin/tox -e style
61
+
62
+ # arguably this should be made identical to CI for PRs
63
+ # better would be a composite GH action
64
+ - name: Run Typechecker
65
+ run: ./.venv/bin/tox -e typecheck
66
+
57
67
  - name: Pytest / behave
58
68
  env:
59
69
  PYTEST_PASSWORD: root
60
70
  PYTEST_HOST: 127.0.0.1
61
71
  run: |
62
- uv run tox -e py${{ matrix.python-version }}
63
-
64
- # arguably this should be made identical to CI for PRs
65
- - name: Run Style Checks
66
- run: uv run tox -e style
72
+ ./.venv/bin/tox -e py${{ matrix.python-version }}
67
73
 
68
74
  tests-macos:
69
75
  name: Tests macOS
@@ -95,7 +101,7 @@ jobs:
95
101
  brew services start mysql
96
102
 
97
103
  - name: Install dependencies
98
- run: uv sync --all-extras -p ${{ matrix.python-version }}
104
+ run: uv sync --extra dev,llm,dataframe -p ${{ matrix.python-version }}
99
105
 
100
106
  - name: Wait for MySQL connection
101
107
  run: |
@@ -113,7 +119,7 @@ jobs:
113
119
  PYTEST_HOST: 127.0.0.1
114
120
  TERM: xterm
115
121
  run: |
116
- uv run tox -e py${{ matrix.python-version }}
122
+ ./.venv/bin/tox -e py${{ matrix.python-version }}
117
123
 
118
124
  tests-windows:
119
125
  name: Tests Windows
@@ -138,7 +144,7 @@ jobs:
138
144
 
139
145
  - name: Install dependencies
140
146
  shell: cmd
141
- run: uv sync --all-extras -p ${{ matrix.python-version }}
147
+ run: uv sync --extra dev,llm,dataframe -p ${{ matrix.python-version }}
142
148
 
143
149
  - name: Set up MySQL
144
150
  uses: shogo82148/actions-setup-mysql@083ec148d28e62f2b24b8f4541693dfe1d50488d # v1.54.0
@@ -193,7 +199,7 @@ jobs:
193
199
  run: |
194
200
  export PATH="$HOME/.local/bin:$PATH"
195
201
  cd /root/mycli
196
- uv sync --all-extras --python 3.14
202
+ uv sync --extra dev,llm,dataframe --python 3.14
197
203
 
198
204
  - name: Start MySQL in WSL
199
205
  shell: wsl-bash {0}
@@ -240,7 +246,7 @@ jobs:
240
246
  python-version: '3.14'
241
247
 
242
248
  - name: Install dependencies
243
- run: uv sync --all-extras -p 3.14
249
+ run: uv sync --extra llm,dataframe -p 3.14
244
250
 
245
251
  - name: Build
246
252
  run: uv build
@@ -30,7 +30,7 @@ jobs:
30
30
  version: 'latest'
31
31
 
32
32
  - name: Install dependencies
33
- run: uv sync --all-extras
33
+ run: uv sync --extra dev,llm,dataframe
34
34
 
35
35
  - name: Run mypy
36
36
  run: |
@@ -8,6 +8,12 @@ A command line client for MySQL with auto-completion and syntax highlighting.
8
8
  ├── .github/ # GitHub Actions and configuration
9
9
  ├── pyproject.toml # project configuration
10
10
  ├── doc/ # documentation
11
+ ├── doc/cookbook.md # recipes for how to configure and use mycli
12
+ ├── doc/key_bindings.md # documentation of key binding behaviors
13
+ ├── doc/known_bugs.md # documentation of known bugs and limitations
14
+ ├── doc/llm.md # documentation on how to use LLM features
15
+ ├── doc/transforms.md # documentation on how to use dataframe transform/plot features
16
+ ├── doc/screenshots/ # screenshots for documentation
11
17
  ├── mycli/ # application source
12
18
  ├── mycli/__init__.py # provides version number
13
19
  ├── mycli/app_state.py # `AppStateMixin` application state mixin and related functions
@@ -44,6 +50,7 @@ A command line client for MySQL with auto-completion and syntax highlighting.
44
50
  ├── mycli/packages/cli_utils.py # utilities for parsing CLI arguments
45
51
  ├── mycli/packages/completion_engine.py # implementation of completion suggestions
46
52
  ├── mycli/packages/filepaths.py # utilities for files, including completion suggestions
53
+ ├── mycli/packages/fzf.py # general interface to fzf chooser
47
54
  ├── mycli/packages/hybrid_redirection.py # implementation of shell-style redirects
48
55
  ├── mycli/packages/interactive_utils.py # utilities for confirming on destructive statements
49
56
  ├── mycli/packages/key_binding_utils.py # handlers for key bindings and related special commands
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycli
3
- Version: 2.27.0
3
+ Version: 2.28.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
@@ -41,7 +41,6 @@ Requires-Dist: cli_helpers[styles]~=2.15.1
41
41
  Requires-Dist: wcwidth~=0.8.3
42
42
  Requires-Dist: pyperclip~=1.11.0
43
43
  Requires-Dist: pycryptodomex~=3.23.0
44
- Requires-Dist: pyfzf~=0.3.1
45
44
  Requires-Dist: rapidfuzz~=3.14.5
46
45
  Requires-Dist: keyring~=25.7.0
47
46
  Requires-Dist: jinja2~=3.1.6
@@ -1,3 +1,36 @@
1
+ v2.28.0 (2026/10/02)
2
+ ==============
3
+
4
+ Features
5
+ --------
6
+ * Add "streaming" query state when `--unbuffered` is in effect.
7
+
8
+
9
+ Bug Fixes
10
+ --------
11
+ * Correct row counts when `--unbuffered` is in effect.
12
+ * Don't let `--unbuffered` override the state-update interval.
13
+ * Fix a brief blankout of query state feedback on large results.
14
+ * Note that `sqlglotc` is optional for performance in `--checkup`.
15
+ * Make imports less lazy, to load special commands under Python 3.15.
16
+
17
+
18
+ Documentation
19
+ --------
20
+ * Add `doc/known_bugs.md` for known bugs and limitations.
21
+ * Add `doc/cookbook.md` for recipes on how to use mycli.
22
+
23
+
24
+ Internal
25
+ --------
26
+ * Replace `pyfzf` dependency with internal implementation.
27
+ * Use the `uv` outside of the `tox` environment when testing.
28
+ * Use forward-slash forms of special commands in the behave test suite.
29
+ * Use forward-slash forms of special commands in the pytest test suite.
30
+ * Add a `tox` typecheck command.
31
+ * Add a typecheck step to the `publish.yml` workflow.
32
+
33
+
1
34
  v2.27.0 (2026/09/29)
2
35
  ==============
3
36
 
@@ -0,0 +1,71 @@
1
+ # Cookbook
2
+
3
+ Recipes for configuring and using mycli.
4
+
5
+ ## faster table rendering
6
+
7
+ A substantial amount of time is spent rendering tables in `tabulate` when
8
+ the number of rows returned is say 100K or more. The reason is that
9
+ `tabulate` scans every row in pure Python to determine cell width.
10
+
11
+ One workaround for faster viewing of large results is to configure the output
12
+ format to CSV in `~/.myclirc`:
13
+
14
+ ```ini
15
+ [main]
16
+ table_format = csv
17
+ ```
18
+
19
+ and to configure the pager to a fast CSV viewer such as [csvlens](https://github.com/ys-l/csvlens):
20
+
21
+ ```ini
22
+ [main]
23
+ pager = csvlens
24
+ ```
25
+
26
+ which has the effect of moving cell-width calculations from Python to Rust.
27
+
28
+ ## keyring
29
+
30
+ Use of the system keyring was left disabled in the default `~/.myclirc` to avoid
31
+ silently changing default behaviors. But it is recommended to enable it!
32
+
33
+ ```ini
34
+ [main]
35
+ use_keyring = auto
36
+ ```
37
+
38
+ Sources eligible for the keyring can also be configured with
39
+
40
+ ```ini
41
+ [main]
42
+ keyring_sources = prompt, literal, file, environment, dsn, login_path, fallback
43
+ ```
44
+
45
+ ## completions trigger
46
+
47
+ Mycli's default completions trigger the display of candidates at the first
48
+ character, which might be distracting for some.
49
+
50
+ For more of an industry-standard experience, in which completions are deferred
51
+ until more information is typed, set `min_completion_trigger` to a small value
52
+ in `~/.myclirc`:
53
+
54
+ ```ini
55
+ [main]
56
+ min_completion_trigger = 3
57
+ ```
58
+
59
+ ## DSN environment variables
60
+
61
+ If using a tool such as [mise](https://github.com/jdx/mise) or [direnv](https://github.com/direnv/direnv) to manage
62
+ environment variables, it may be convenient to enable this setting in
63
+ `~/.myclirc`:
64
+
65
+ ```ini
66
+ [main]
67
+ expand_dsn_alias_env_vars = True
68
+ ```
69
+
70
+ which allows environment variables in the form `${VAR}` to be substituted
71
+ in DSNs.
@@ -0,0 +1,61 @@
1
+ # Known Bugs
2
+
3
+ These are known bugs or limitations in mycli which it would be welcome to
4
+ fix.
5
+
6
+ ## custom delimiters
7
+
8
+ A custom delimiter (query terminator) string may be set with the `/delimiter`
9
+ special command, but the custom delimiter is not respected in all parts of
10
+ mycli's code.
11
+
12
+ ## completions after backtick
13
+
14
+ Completions after a backtick character are too exuberant. Example:
15
+
16
+ ```sql
17
+ SELECT `<tab>
18
+ ```
19
+
20
+ will offer more completions than
21
+
22
+ ```sql
23
+ SELECT <tab>
24
+ ```
25
+
26
+ ## Windows limitations
27
+
28
+ Mycli is only partially tested on Windows, and several features such as
29
+ filename completion after `/source` may not work as expected.
30
+
31
+ ## double-dash with shell redirections
32
+
33
+ The interpretation of `--` may not be consistent with `$|`. The double-dahs
34
+ is inherently ambiguous, being either a SQL comment initiator, or a common
35
+ part of a shell command. The rules for resolving the ambiguity should be
36
+ simple, and better documented.
37
+
38
+ ## enum completions
39
+
40
+ Completions on enum values may fail if the values contain spaces.
41
+
42
+ ## keyring support
43
+
44
+ If "Always Allow" is chosen on Mac, then all Python applications will have
45
+ access to the credential.
46
+
47
+ When making connections via a kubectl tunnel, the keyring is disabled, because
48
+ the Kubernetes context is not known.
49
+
50
+ ## explorer
51
+
52
+ There is a trailing blank line in the explorer choices.
53
+
54
+ ## prompt efficiency
55
+
56
+ The prompt and toolbar are updated more often than needed, even with caching.
57
+
58
+ ## history search preview highlighting
59
+
60
+ When syntax highlighting with `pygmentize` is enabled for history search previews,
61
+ the colors do not match the user's preferences in `~/.myclirc`.
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
- name: mycli-2.27.0/docker/resources/test_db-1.0.7.tar.gz
2
+ name: mycli-2.28.0/docker/resources/test_db-1.0.7.tar.gz
3
3
  size: 35607473 bytes
4
4
  sha256: c44c140f352f35d47fdb65df60f52b779ef552822fad6c4efcfa7b134c3faf84
@@ -59,6 +59,7 @@ TTY_ERASE_LINE = '\r\x1b[2K'
59
59
 
60
60
  class QueryState(enum.StrEnum):
61
61
  RENDERING = 'rendering'
62
+ STREAMING = 'streaming'
62
63
  TRANSFORMING = 'transforming'
63
64
  UNKNOWN = 'unknown'
64
65
  INITIAL = 'finding state'
@@ -19,7 +19,7 @@ from prompt_toolkit.selection import SelectionType
19
19
 
20
20
  from mycli.constants import DOCS_URL
21
21
  from mycli.packages import key_binding_utils
22
- from mycli.packages.ptoolkit.fzf import search_history
22
+ from mycli.packages.ptoolkit.fzf_history import search_history
23
23
  from mycli.packages.ptoolkit.utils import safe_invalidate_display
24
24
 
25
25
  _logger = logging.getLogger(__name__)
@@ -43,9 +43,9 @@ def print_f1_help():
43
43
  app.print_text('\n')
44
44
  app.print_text([
45
45
  ('', 'Inline help — type "'),
46
- ('bold', 'help'),
46
+ ('bold', '/help'),
47
47
  ('', '" or "'),
48
- ('bold', r'\?'),
48
+ ('bold', r'/?'),
49
49
  ('', '"\n'),
50
50
  ])
51
51
  app.print_text([
@@ -34,8 +34,8 @@ def _dependencies_checkup() -> None:
34
34
  ('pygments', 'required'),
35
35
  ('pymysql', 'required'),
36
36
  ('sqlglot', 'required'),
37
- ('sqlglotc', 'required'),
38
37
  ('tabulate', 'required'),
38
+ ('sqlglotc', 'optional for performance'),
39
39
  ('llm', 'optional for /llm command'),
40
40
  ('polars', 'optional for .| operator'),
41
41
  ('altair', 'optional for .| operator'),
@@ -71,8 +71,8 @@ def _environment_checkup() -> None:
71
71
  print('\n### Environment variables:\n')
72
72
  table = []
73
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'),
74
+ ('EDITOR', r'optional for /edit and C-x C-e'),
75
+ ('VISUAL', r'optional for /edit and C-x C-e'),
76
76
  ]:
77
77
  if value := os.environ.get(variable):
78
78
  table.append([f'${variable}', value, purpose])
@@ -530,33 +530,40 @@ def _output_results(
530
530
  else:
531
531
  max_width = None
532
532
 
533
- formatted = mycli.format_sqlresult(
534
- result,
535
- is_expanded=special.is_expanded_output(),
536
- is_redirected=special.is_redirected(),
537
- null_string=mycli.null_string,
538
- numeric_alignment=mycli.numeric_alignment,
539
- binary_display=mycli.binary_display,
540
- max_width=max_width,
541
- )
533
+ runner = runner_for(mycli)
534
+ with runner.rendering() if runner else nullcontext():
535
+ formatted = mycli.format_sqlresult(
536
+ result,
537
+ is_expanded=special.is_expanded_output(),
538
+ is_redirected=special.is_redirected(),
539
+ null_string=mycli.null_string,
540
+ numeric_alignment=mycli.numeric_alignment,
541
+ binary_display=mycli.binary_display,
542
+ max_width=max_width,
543
+ )
542
544
 
543
- duration = time.time() - start
544
- try:
545
- if result_count > 0:
546
- mycli.echo('')
545
+ duration = time.time() - start
547
546
  try:
548
- mycli.output(formatted, result)
547
+ if result_count > 0:
548
+ if runner:
549
+ runner.stop_rendering()
550
+ mycli.echo('')
551
+ try:
552
+ mycli.output(formatted, result)
553
+ except KeyboardInterrupt:
554
+ if raise_interrupts:
555
+ raise
556
+ finally:
557
+ if runner:
558
+ runner.stop_rendering()
559
+ if mycli.beep_after_seconds > 0 and duration >= mycli.beep_after_seconds:
560
+ assert mycli.prompt_session is not None
561
+ mycli.prompt_session.output.bell()
562
+ if special.is_timing_enabled():
563
+ mycli.output_timing(f'Time: {duration:0.03f}s')
549
564
  except KeyboardInterrupt:
550
565
  if raise_interrupts:
551
566
  raise
552
- if mycli.beep_after_seconds > 0 and duration >= mycli.beep_after_seconds:
553
- assert mycli.prompt_session is not None
554
- mycli.prompt_session.output.bell()
555
- if special.is_timing_enabled():
556
- mycli.output_timing(f'Time: {duration:0.03f}s')
557
- except KeyboardInterrupt:
558
- if raise_interrupts:
559
- raise
560
567
 
561
568
  start = time.time()
562
569
  result_count += 1
@@ -1137,7 +1144,7 @@ def _tips_picker() -> str:
1137
1144
  except FileNotFoundError:
1138
1145
  pass
1139
1146
 
1140
- return random.choice(tips) if tips else r'\? or "help" for help!'
1147
+ return random.choice(tips) if tips else r'/? or /help for help!'
1141
1148
 
1142
1149
 
1143
1150
  def _configure_editor(mycli: 'MyCli') -> None:
@@ -344,7 +344,11 @@ class OutputMixin(MyCliState):
344
344
  if result.postamble:
345
345
  output = itertools.chain(output, [result.postamble])
346
346
 
347
- return output
347
+ def finalized_output() -> Generator[str, None, None]:
348
+ yield from output
349
+ result.finalize_status()
350
+
351
+ return itertools.chain(finalized_output())
348
352
 
349
353
  def get_reserved_space(self) -> int:
350
354
  """Get the number of lines to reserve for the completion menu."""
@@ -0,0 +1,78 @@
1
+ """Inspired by
2
+ https://github.com/nk412/pyfzf
3
+ and the fork
4
+ https://github.com/purarue/pyfzf
5
+
6
+ This code is trivially short and very far from the originals, but the MIT license
7
+ header from pyfzf is included anyway.
8
+ """
9
+
10
+ # Portions Copyright (c) 2022 Nagarjuna Kumarappan
11
+ #
12
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ # of this software and associated documentation files (the "Software"), to deal
14
+ # in the Software without restriction, including without limitation the rights
15
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ # copies of the Software, and to permit persons to whom the Software is
17
+ # furnished to do so, subject to the following conditions:
18
+ #
19
+ # The above copyright notice and this permission notice shall be included in
20
+ # all copies or substantial portions of the Software.
21
+ #
22
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28
+ # THE SOFTWARE.
29
+
30
+ from shutil import which
31
+ import subprocess
32
+ from typing import Any, Iterable
33
+
34
+ DELIMITER = '\x00'
35
+
36
+
37
+ class Fzf:
38
+ def __init__(
39
+ self,
40
+ executable: str = 'fzf',
41
+ ):
42
+ self.executable: str | None = executable
43
+
44
+ if not which(executable):
45
+ self.executable = None
46
+
47
+ def is_available(self) -> bool:
48
+ return self.executable is not None
49
+
50
+ def prompt(
51
+ self,
52
+ items: Iterable[Any],
53
+ options: list[str] | None = None,
54
+ ) -> str | None:
55
+ options_final = ['--read0', '--print0'] + (options or [])
56
+
57
+ fzf_process = subprocess.Popen(
58
+ [self.executable or 'fzf', *options_final],
59
+ stdin=subprocess.PIPE,
60
+ stdout=subprocess.PIPE,
61
+ encoding='utf-8',
62
+ )
63
+
64
+ assert fzf_process.stdin is not None, 'Error creating fzf input stream'
65
+ try:
66
+ for item in items:
67
+ fzf_process.stdin.write(str(item) + DELIMITER)
68
+ except BrokenPipeError:
69
+ pass
70
+
71
+ stdout_str, _stderr_str = fzf_process.communicate()
72
+
73
+ if stdout_str:
74
+ # in mycli there is no meaning for multiple selection, so
75
+ # just return the first item
76
+ return stdout_str.split(DELIMITER)[0]
77
+
78
+ return None
@@ -4,22 +4,12 @@ from shutil import which
4
4
 
5
5
  from prompt_toolkit import search
6
6
  from prompt_toolkit.key_binding.key_processor import KeyPressEvent
7
- from pyfzf import FzfPrompt
8
7
 
8
+ from mycli.packages.fzf import Fzf
9
9
  from mycli.packages.ptoolkit.history import FileHistoryWithTimestamp
10
10
  from mycli.packages.ptoolkit.utils import safe_invalidate_display
11
11
 
12
12
 
13
- class Fzf(FzfPrompt):
14
- def __init__(self):
15
- self.executable = which("fzf")
16
- if self.executable:
17
- super().__init__()
18
-
19
- def is_available(self) -> bool:
20
- return self.executable is not None
21
-
22
-
23
13
  def search_history(
24
14
  event: KeyPressEvent,
25
15
  highlight_preview: bool = False,
@@ -60,17 +50,17 @@ def search_history(
60
50
  ]
61
51
 
62
52
  if highlight_preview and which('pygmentize'):
63
- options.append(f'--preview="printf \'%s\' {{}} | pygmentize -l mysql -P style={shlex.quote(highlight_style)}"')
53
+ options.append(f'--preview=printf \'%s\' {{}} | pygmentize -l mysql -P style={shlex.quote(highlight_style)}')
64
54
  else:
65
- options.append('--preview="printf \'%s\' {}"')
55
+ options.append('--preview=printf \'%s\' {}')
66
56
 
67
57
  result = fzf.prompt(
68
58
  formatted_history_items,
69
- fzf_options=' '.join(options),
59
+ options=options,
70
60
  )
71
61
  safe_invalidate_display(event.app)
72
62
 
73
63
  if result:
74
- selected_index = formatted_history_items.index(result[0])
64
+ selected_index = formatted_history_items.index(result)
75
65
  buffer.text = original_history_items[selected_index]
76
66
  buffer.cursor_position = len(buffer.text)
@@ -1,11 +1,3 @@
1
- __lazy_modules__ = [
2
- 'mycli.packages.special.dbcommands',
3
- 'mycli.packages.special.iocommands',
4
- 'mycli.packages.special.llm',
5
- 'mycli.packages.special.main',
6
- 'mycli.packages.special.source',
7
- ]
8
-
9
1
  import os
10
2
 
11
3
  from mycli.packages.special.dbcommands import (