mycli 2.7.0__tar.gz → 2.9.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 (189) hide show
  1. {mycli-2.7.0 → mycli-2.9.0}/.github/workflows/publish.yml +1 -1
  2. {mycli-2.7.0 → mycli-2.9.0}/AGENTS.md +1 -0
  3. {mycli-2.7.0 → mycli-2.9.0}/MANIFEST.in +1 -0
  4. {mycli-2.7.0/mycli.egg-info → mycli-2.9.0}/PKG-INFO +21 -3
  5. {mycli-2.7.0 → mycli-2.9.0}/README.md +5 -2
  6. {mycli-2.7.0 → mycli-2.9.0}/changelog.md +45 -0
  7. {mycli-2.7.0 → mycli-2.9.0}/mycli/TIPS +1 -1
  8. {mycli-2.7.0 → mycli-2.9.0}/mycli/cli_runner.py +114 -40
  9. {mycli-2.7.0 → mycli-2.9.0}/mycli/client.py +1 -1
  10. {mycli-2.7.0 → mycli-2.9.0}/mycli/client_commands.py +8 -6
  11. {mycli-2.7.0 → mycli-2.9.0}/mycli/constants.py +20 -0
  12. {mycli-2.7.0 → mycli-2.9.0}/mycli/main.py +24 -5
  13. mycli-2.9.0/mycli/main_modes/completions.py +16 -0
  14. {mycli-2.7.0 → mycli-2.9.0}/mycli/main_modes/list_dsn.py +4 -0
  15. {mycli-2.7.0 → mycli-2.9.0}/mycli/main_modes/repl.py +10 -2
  16. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/completion_engine.py +19 -0
  17. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/__init__.py +7 -0
  18. mycli-2.9.0/mycli/packages/special/dsn_aliases.py +173 -0
  19. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/iocommands.py +15 -6
  20. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/llm.py +4 -0
  21. mycli-2.9.0/mycli/resources/completions/bash/mycli +169 -0
  22. mycli-2.9.0/mycli/resources/completions/fish/mycli.fish +132 -0
  23. mycli-2.9.0/mycli/resources/completions/zsh/_mycli +161 -0
  24. {mycli-2.7.0 → mycli-2.9.0/mycli.egg-info}/PKG-INFO +21 -3
  25. {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/SOURCES.txt +8 -1
  26. {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/scm_file_list.json +8 -1
  27. mycli-2.9.0/mycli.egg-info/scm_version.json +8 -0
  28. {mycli-2.7.0 → mycli-2.9.0}/pyproject.toml +17 -0
  29. {mycli-2.7.0 → mycli-2.9.0}/test/features/environment.py +4 -18
  30. {mycli-2.7.0 → mycli-2.9.0}/test/features/fixture_data/help_commands.txt +1 -1
  31. mycli-2.9.0/test/pytests/test_bash_completion.py +151 -0
  32. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_cli_runner.py +367 -18
  33. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_client_commands.py +53 -3
  34. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_completion_engine.py +16 -0
  35. mycli-2.9.0/test/pytests/test_dsn_aliases.py +283 -0
  36. mycli-2.9.0/test/pytests/test_fish_completion.py +123 -0
  37. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_main.py +41 -4
  38. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_main_modes_list_dsn.py +8 -2
  39. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_smart_completion_public_schema_only.py +37 -2
  40. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_iocommands.py +62 -2
  41. mycli-2.9.0/test/pytests/test_zsh_completion.py +156 -0
  42. mycli-2.7.0/mycli/packages/special/dsn_aliases.py +0 -66
  43. mycli-2.7.0/mycli.egg-info/scm_version.json +0 -8
  44. mycli-2.7.0/test/pytests/test_dsn_aliases.py +0 -102
  45. {mycli-2.7.0 → mycli-2.9.0}/.git-blame-ignore-revs +0 -0
  46. {mycli-2.7.0 → mycli-2.9.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  47. {mycli-2.7.0 → mycli-2.9.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  48. {mycli-2.7.0 → mycli-2.9.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  49. {mycli-2.7.0 → mycli-2.9.0}/.github/dependabot.yml +0 -0
  50. {mycli-2.7.0 → mycli-2.9.0}/.github/workflows/ci.yml +0 -0
  51. {mycli-2.7.0 → mycli-2.9.0}/.github/workflows/lint.yml +0 -0
  52. {mycli-2.7.0 → mycli-2.9.0}/.github/workflows/typecheck.yml +0 -0
  53. {mycli-2.7.0 → mycli-2.9.0}/.gitignore +0 -0
  54. {mycli-2.7.0 → mycli-2.9.0}/AUTHORS.rst +0 -0
  55. {mycli-2.7.0 → mycli-2.9.0}/CONTRIBUTING.md +0 -0
  56. {mycli-2.7.0 → mycli-2.9.0}/LICENSE.txt +0 -0
  57. {mycli-2.7.0 → mycli-2.9.0}/SPONSORS.rst +0 -0
  58. {mycli-2.7.0 → mycli-2.9.0}/doc/key_bindings.rst +0 -0
  59. {mycli-2.7.0 → mycli-2.9.0}/doc/llm.md +0 -0
  60. {mycli-2.7.0 → mycli-2.9.0}/doc/screenshots/main.gif +0 -0
  61. {mycli-2.7.0 → mycli-2.9.0}/doc/screenshots/tables.png +0 -0
  62. {mycli-2.7.0 → mycli-2.9.0}/doc/screenshots/total_histogram.png +0 -0
  63. {mycli-2.7.0 → mycli-2.9.0}/doc/transforms.md +0 -0
  64. {mycli-2.7.0 → mycli-2.9.0}/mycli/AUTHORS +0 -0
  65. {mycli-2.7.0 → mycli-2.9.0}/mycli/SPONSORS +0 -0
  66. {mycli-2.7.0 → mycli-2.9.0}/mycli/__init__.py +0 -0
  67. {mycli-2.7.0 → mycli-2.9.0}/mycli/app_state.py +0 -0
  68. {mycli-2.7.0 → mycli-2.9.0}/mycli/clibuffer.py +0 -0
  69. {mycli-2.7.0 → mycli-2.9.0}/mycli/client_connection.py +0 -0
  70. {mycli-2.7.0 → mycli-2.9.0}/mycli/client_query.py +0 -0
  71. {mycli-2.7.0 → mycli-2.9.0}/mycli/clistyle.py +0 -0
  72. {mycli-2.7.0 → mycli-2.9.0}/mycli/clitoolbar.py +0 -0
  73. {mycli-2.7.0 → mycli-2.9.0}/mycli/compat.py +0 -0
  74. {mycli-2.7.0 → mycli-2.9.0}/mycli/completion_refresher.py +0 -0
  75. {mycli-2.7.0 → mycli-2.9.0}/mycli/config.py +0 -0
  76. {mycli-2.7.0 → mycli-2.9.0}/mycli/key_bindings.py +0 -0
  77. {mycli-2.7.0 → mycli-2.9.0}/mycli/lexer.py +0 -0
  78. {mycli-2.7.0 → mycli-2.9.0}/mycli/main_modes/batch.py +0 -0
  79. {mycli-2.7.0 → mycli-2.9.0}/mycli/main_modes/checkup.py +0 -0
  80. {mycli-2.7.0 → mycli-2.9.0}/mycli/main_modes/execute.py +0 -0
  81. {mycli-2.7.0 → mycli-2.9.0}/mycli/myclirc +0 -0
  82. {mycli-2.7.0 → mycli-2.9.0}/mycli/output.py +0 -0
  83. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/__init__.py +0 -0
  84. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/batch_utils.py +0 -0
  85. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/cli_utils.py +0 -0
  86. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/filepaths.py +0 -0
  87. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/hybrid_redirection.py +0 -0
  88. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/interactive_utils.py +0 -0
  89. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/key_binding_utils.py +0 -0
  90. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/polars_transform.py +0 -0
  91. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/ptoolkit/__init__.py +0 -0
  92. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/ptoolkit/fzf.py +0 -0
  93. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/ptoolkit/history.py +0 -0
  94. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/ptoolkit/utils.py +0 -0
  95. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/dbcommands.py +0 -0
  96. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/delimitercommand.py +0 -0
  97. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/favoritequeries.py +0 -0
  98. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/main.py +0 -0
  99. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/special/utils.py +0 -0
  100. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/sql_utils.py +0 -0
  101. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/sqlresult.py +0 -0
  102. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/string_utils.py +0 -0
  103. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/tabular_output/__init__.py +0 -0
  104. {mycli-2.7.0 → mycli-2.9.0}/mycli/packages/tabular_output/sql_format.py +0 -0
  105. {mycli-2.7.0 → mycli-2.9.0}/mycli/password_sources.py +0 -0
  106. {mycli-2.7.0 → mycli-2.9.0}/mycli/schema_prefetcher.py +0 -0
  107. {mycli-2.7.0 → mycli-2.9.0}/mycli/sqlcompleter.py +0 -0
  108. {mycli-2.7.0 → mycli-2.9.0}/mycli/sqlexecute.py +0 -0
  109. {mycli-2.7.0 → mycli-2.9.0}/mycli/ssh_tunnel.py +0 -0
  110. {mycli-2.7.0 → mycli-2.9.0}/mycli/types.py +0 -0
  111. {mycli-2.7.0 → mycli-2.9.0}/mycli/vault.py +0 -0
  112. {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/dependency_links.txt +0 -0
  113. {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/entry_points.txt +0 -0
  114. {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/requires.txt +0 -0
  115. {mycli-2.7.0 → mycli-2.9.0}/mycli.egg-info/top_level.txt +0 -0
  116. {mycli-2.7.0 → mycli-2.9.0}/setup.cfg +0 -0
  117. {mycli-2.7.0 → mycli-2.9.0}/test/__init__.py +0 -0
  118. {mycli-2.7.0 → mycli-2.9.0}/test/features/__init__.py +0 -0
  119. {mycli-2.7.0 → mycli-2.9.0}/test/features/auto_vertical.feature +0 -0
  120. {mycli-2.7.0 → mycli-2.9.0}/test/features/basic_commands.feature +0 -0
  121. {mycli-2.7.0 → mycli-2.9.0}/test/features/connection.feature +0 -0
  122. {mycli-2.7.0 → mycli-2.9.0}/test/features/crud_database.feature +0 -0
  123. {mycli-2.7.0 → mycli-2.9.0}/test/features/crud_table.feature +0 -0
  124. {mycli-2.7.0 → mycli-2.9.0}/test/features/db_utils.py +0 -0
  125. {mycli-2.7.0 → mycli-2.9.0}/test/features/fixture_utils.py +0 -0
  126. {mycli-2.7.0 → mycli-2.9.0}/test/features/iocommands.feature +0 -0
  127. {mycli-2.7.0 → mycli-2.9.0}/test/features/named_queries.feature +0 -0
  128. {mycli-2.7.0 → mycli-2.9.0}/test/features/specials.feature +0 -0
  129. {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/__init__.py +0 -0
  130. {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/auto_vertical.py +0 -0
  131. {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/basic_commands.py +0 -0
  132. {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/connection.py +0 -0
  133. {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/crud_database.py +0 -0
  134. {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/crud_table.py +0 -0
  135. {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/iocommands.py +0 -0
  136. {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/named_queries.py +0 -0
  137. {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/specials.py +0 -0
  138. {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/utils.py +0 -0
  139. {mycli-2.7.0 → mycli-2.9.0}/test/features/steps/wrappers.py +0 -0
  140. {mycli-2.7.0 → mycli-2.9.0}/test/features/wrappager.py +0 -0
  141. {mycli-2.7.0 → mycli-2.9.0}/test/myclirc +0 -0
  142. {mycli-2.7.0 → mycli-2.9.0}/test/mylogin.cnf +0 -0
  143. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/conftest.py +0 -0
  144. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_app_state.py +0 -0
  145. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_batch_utils.py +0 -0
  146. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_checkup.py +0 -0
  147. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_cli_utils.py +0 -0
  148. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_clibuffer.py +0 -0
  149. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_client.py +0 -0
  150. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_client_connection.py +0 -0
  151. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_client_query.py +0 -0
  152. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_clistyle.py +0 -0
  153. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_clitoolbar.py +0 -0
  154. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_completer_use_switch.py +0 -0
  155. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_completion_refresher.py +0 -0
  156. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_config.py +0 -0
  157. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_delimitercommand.py +0 -0
  158. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_favoritequeries.py +0 -0
  159. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_filepaths.py +0 -0
  160. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_hybrid_redirection.py +0 -0
  161. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_interactive_utils.py +0 -0
  162. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_key_binding_utils.py +0 -0
  163. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_key_bindings.py +0 -0
  164. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_lexer.py +0 -0
  165. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_main_modes_batch.py +0 -0
  166. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_main_modes_execute.py +0 -0
  167. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_main_modes_repl.py +0 -0
  168. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_naive_completion.py +0 -0
  169. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_output.py +0 -0
  170. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_password_sources.py +0 -0
  171. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_polars_transform.py +0 -0
  172. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_ptoolkit_fzf.py +0 -0
  173. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_ptoolkit_history.py +0 -0
  174. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_ptoolkit_utils.py +0 -0
  175. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_schema_prefetcher.py +0 -0
  176. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_dbcommands.py +0 -0
  177. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_init.py +0 -0
  178. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_llm.py +0 -0
  179. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_main.py +0 -0
  180. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_special_utils.py +0 -0
  181. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_sql_utils.py +0 -0
  182. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_sqlcompleter.py +0 -0
  183. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_sqlexecute.py +0 -0
  184. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_sqlresult.py +0 -0
  185. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_ssh_tunnel.py +0 -0
  186. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_string_utils.py +0 -0
  187. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_tabular_output.py +0 -0
  188. {mycli-2.7.0 → mycli-2.9.0}/test/pytests/test_vault.py +0 -0
  189. {mycli-2.7.0 → mycli-2.9.0}/test/utils.py +0 -0
@@ -262,4 +262,4 @@ jobs:
262
262
  name: python-packages
263
263
  path: dist/
264
264
  - name: Publish to PyPI
265
- uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
265
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -49,6 +49,7 @@ A command line client for MySQL with auto-completion and syntax highlighting.
49
49
  ├── mycli/packages/string_utils.py # generic string utilities
50
50
  ├── mycli/packages/tabular_output/ # extends cli_helper with additional output formats
51
51
  ├── mycli/password_sources.py # password sources and precedence
52
+ ├── mycli/resources/completions/ # shell completions for CLI interface
52
53
  ├── mycli/ssh_tunnel.py # connecting over a tunnel with `--ssh-jump`
53
54
  ├── mycli/schema_prefetcher.py # background prefetcher for multi-schema auto-completion
54
55
  ├── mycli/sqlcompleter.py # offers SQL completions
@@ -1,4 +1,5 @@
1
1
  include LICENSE.txt *.md *.rst doc/screenshots/*
2
+ recursive-include mycli/resources/completions *
2
3
  recursive-include test *.cnf
3
4
  recursive-include test *.feature
4
5
  recursive-include test *.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycli
3
- Version: 2.7.0
3
+ Version: 2.9.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
@@ -9,6 +9,21 @@ Project-URL: Documentation, https://mycli.net/docs
9
9
  Project-URL: Source, https://github.com/dbcli/mycli
10
10
  Project-URL: Issues, https://github.com/dbcli/mycli/issues
11
11
  Project-URL: Changelog, https://github.com/dbcli/mycli/blob/main/changelog.md
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: SQL
24
+ Classifier: Topic :: Database
25
+ Classifier: Topic :: Database :: Front-Ends
26
+ Classifier: Topic :: Software Development
12
27
  Requires-Python: >=3.10
13
28
  Description-Content-Type: text/markdown
14
29
  License-File: LICENSE.txt
@@ -57,6 +72,7 @@ Dynamic: license-file
57
72
  # mycli
58
73
 
59
74
  [![Build Status](https://github.com/dbcli/mycli/workflows/mycli/badge.svg)](https://github.com/dbcli/mycli/actions?query=workflow%3Amycli)
75
+ [![License](https://img.shields.io/github/license/dbcli/mycli.svg)](https://github.com/dbcli/mycli/blob/main/LICENSE.txt)
60
76
 
61
77
  A command line client for MySQL with auto-completion and syntax highlighting.
62
78
 
@@ -64,8 +80,10 @@ Homepage: [https://mycli.net](https://mycli.net)
64
80
 
65
81
  Documentation: [https://mycli.net/docs](https://mycli.net/docs)
66
82
 
67
- ![Completion](https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/tables.png)
68
- ![CompletionGif](https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/main.gif)
83
+ <div align="center">
84
+ <img alt="Completion" src="https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/tables.png">
85
+ <img alt="CompletionGif" src="https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/main.gif">
86
+ </div>
69
87
 
70
88
  Mycli is compatible with MySQL, MariaDB, Percona, TiDB, and Apache Doris.
71
89
 
@@ -1,6 +1,7 @@
1
1
  # mycli
2
2
 
3
3
  [![Build Status](https://github.com/dbcli/mycli/workflows/mycli/badge.svg)](https://github.com/dbcli/mycli/actions?query=workflow%3Amycli)
4
+ [![License](https://img.shields.io/github/license/dbcli/mycli.svg)](https://github.com/dbcli/mycli/blob/main/LICENSE.txt)
4
5
 
5
6
  A command line client for MySQL with auto-completion and syntax highlighting.
6
7
 
@@ -8,8 +9,10 @@ Homepage: [https://mycli.net](https://mycli.net)
8
9
 
9
10
  Documentation: [https://mycli.net/docs](https://mycli.net/docs)
10
11
 
11
- ![Completion](https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/tables.png)
12
- ![CompletionGif](https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/main.gif)
12
+ <div align="center">
13
+ <img alt="Completion" src="https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/tables.png">
14
+ <img alt="CompletionGif" src="https://raw.githubusercontent.com/dbcli/mycli/main/doc/screenshots/main.gif">
15
+ </div>
13
16
 
14
17
  Mycli is compatible with MySQL, MariaDB, Percona, TiDB, and Apache Doris.
15
18
 
@@ -1,3 +1,48 @@
1
+ 2.9.0 (2026/08/01)
2
+ ==============
3
+
4
+ Features
5
+ ---------
6
+ * Improve filename completions for zsh.
7
+ * Improve acceptance of a literal DSN or alias as a positional argument.
8
+ * Improve acceptance of a literal DSN or alias as a `--database` argument.
9
+ * Add bash completions which can complete DSN aliases.
10
+ * Add fish completions which can complete DSN aliases.
11
+ * Support Python 3.15 lazy imports for startup performance.
12
+ * Add a `--completions` argument to emit a shell completion script.
13
+ * Ability to set the `prompt` value as a DSN query parameter.
14
+ * Add `--more` option to some `/dsn` subcommands, showing more DSN query parameters.
15
+ * Let `/prompt` with no arguments display the current prompt format string.
16
+ * Allow the `/prompt` argument to be quoted.
17
+
18
+
19
+ Bugfixes
20
+ ---------
21
+ * Disallow DSN aliases starting with `-`, because of ambiguity.
22
+
23
+
24
+ Documentation
25
+ ---------
26
+ * Add license badge to `README.md`.
27
+ * Center images in `README.md`.
28
+
29
+
30
+ 2.8.0 (2026/07/31)
31
+ ==============
32
+
33
+ Features
34
+ ---------
35
+ * Render tab literals as four spaces in the REPL.
36
+ * Let cursor shape respond to vi modes: _eg_ bar for insert mode.
37
+ * Add zsh completions which can complete DSN aliases.
38
+
39
+
40
+ Internal
41
+ ---------
42
+ * Add Trove classifiers for PyPi.
43
+ * Don't let behave tests refer to real home-directory resources.
44
+
45
+
1
46
  2.7.0 (2026/07/25)
2
47
  ==============
3
48
 
@@ -90,7 +90,7 @@ use /tee or /notee to write/stop-writing results to a output file!
90
90
 
91
91
  /P or /pager sets the pager. Try "/pager less"!
92
92
 
93
- /R or /prompt changes the prompt format!
93
+ /R or /prompt shows or changes the prompt format!
94
94
 
95
95
  /Tr or /redirectformat changes the table format for redirects!
96
96
 
@@ -1,5 +1,25 @@
1
1
  from __future__ import annotations
2
2
 
3
+ __lazy_modules__ = [
4
+ 'io',
5
+ 're',
6
+ 'sys',
7
+ 'typing',
8
+ 'urllib.parse',
9
+ 'click',
10
+ 'mycli.config',
11
+ 'mycli.constants',
12
+ 'mycli.main_modes.batch',
13
+ 'mycli.main_modes.checkup',
14
+ 'mycli.main_modes.completions',
15
+ 'mycli.main_modes.execute',
16
+ 'mycli.main_modes.list_dsn',
17
+ 'mycli.packages.cli_utils',
18
+ 'mycli.packages.special.dsn_aliases',
19
+ 'mycli.password_sources',
20
+ 'mycli.vault',
21
+ ]
22
+
3
23
  import os
4
24
  import re
5
25
  import sys
@@ -9,12 +29,14 @@ from urllib.parse import parse_qs, unquote, urlparse
9
29
  import click
10
30
 
11
31
  from mycli.config import str_to_bool
12
- from mycli.constants import EMPTY_PASSWORD_FLAG_SENTINEL
32
+ from mycli.constants import EMPTY_PASSWORD_FLAG_SENTINEL, KNOWN_DSN_QUERY_PARAMS
13
33
  from mycli.main_modes.batch import main_batch_from_stdin, main_batch_with_progress_bar, main_batch_without_progress_bar
14
34
  from mycli.main_modes.checkup import main_checkup
35
+ from mycli.main_modes.completions import main_completions
15
36
  from mycli.main_modes.execute import main_execute_from_cli
16
37
  from mycli.main_modes.list_dsn import main_list_dsn
17
38
  from mycli.packages.cli_utils import is_valid_connection_scheme
39
+ from mycli.packages.special.dsn_aliases import INVALID_DSN_ALIAS_ERROR, is_valid_dsn_alias
18
40
  from mycli.password_sources import PasswordCandidates
19
41
  from mycli.vault import (
20
42
  DEFAULT_VAULT_EXECUTABLE,
@@ -29,25 +51,6 @@ if TYPE_CHECKING:
29
51
 
30
52
  ClientFactory = Callable[..., Any]
31
53
  ENV_VAR_PATTERN = re.compile(r'^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$')
32
- KNOWN_DSN_QUERY_PARAMS = {
33
- 'character_set',
34
- 'keepalive_ticks',
35
- 'socket',
36
- 'ssh_jump',
37
- 'ssl_ca',
38
- 'ssl_capath',
39
- 'ssl_cert',
40
- 'ssl_cipher',
41
- 'ssl_key',
42
- 'ssl_mode',
43
- 'ssl_verify_server_cert',
44
- 'tls_version',
45
- 'vault_address',
46
- 'vault_mount',
47
- 'vault_secret',
48
- 'vault_password_field',
49
- 'vault_username_field',
50
- }
51
54
 
52
55
 
53
56
  class DsnAliasEnvVarError(ValueError):
@@ -124,6 +127,10 @@ def expand_dsn_alias_env_vars(
124
127
  def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> None:
125
128
  from mycli import main as main_module
126
129
 
130
+ if cli_args.completions:
131
+ main_completions(cli_args.completions)
132
+ return
133
+
127
134
  cli_verbosity = main_module.preprocess_cli_args(cli_args, is_valid_connection_scheme)
128
135
 
129
136
  mycli = client_factory(
@@ -162,43 +169,108 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
162
169
  if cli_args.list_dsn:
163
170
  sys.exit(main_list_dsn(mycli))
164
171
 
165
- # Choose which ever one has a valid value.
166
- database = cli_args.dbname or cli_args.database
167
-
168
172
  dsn_uri = None
169
173
  dsn_password: str | None = None
174
+ database: str | None = ''
175
+ explicit_dsn = bool(cli_args.dsn)
170
176
 
171
- # Treat the database argument as a DSN alias only if it matches a configured alias
172
- # todo why is port tested but not socket?
173
- truthy_password = (
174
- cli_args.password not in (None, EMPTY_PASSWORD_FLAG_SENTINEL)
175
- or cli_args.password_file is not None
176
- or os.environ.get('MYSQL_PWD') is not None
177
- )
177
+ # This could be better written, but it is all made harder by the fact that a DSN is recognized after --database
178
178
  if (
179
- database
180
- and "://" not in database
181
- and not any([
179
+ cli_args.positional_database
180
+ and "://" not in cli_args.positional_database
181
+ and cli_args.positional_database in mycli.config.get("alias_dsn", {})
182
+ ):
183
+ if any([
182
184
  cli_args.user,
183
- truthy_password,
184
185
  cli_args.host,
185
186
  cli_args.port,
187
+ cli_args.socket,
186
188
  cli_args.login_path,
187
- ])
188
- and database in mycli.config.get("alias_dsn", {})
189
- ):
190
- cli_args.dsn, database = database, ""
189
+ cli_args.dsn,
190
+ ]):
191
+ if cli_verbosity:
192
+ click.secho(
193
+ f'Interpreting ambiguous positional database/DSN-alias argument "{cli_args.positional_database}" as a database name.',
194
+ err=True,
195
+ fg='yellow',
196
+ )
197
+ database = cli_args.database or cli_args.positional_database
198
+ else:
199
+ if not is_valid_dsn_alias(cli_args.positional_database):
200
+ click.secho(INVALID_DSN_ALIAS_ERROR, err=True, fg='red')
201
+ sys.exit(1)
202
+ cli_args.dsn, database = cli_args.positional_database, cli_args.database or ''
203
+ elif cli_args.positional_database and '://' in cli_args.positional_database:
204
+ if cli_args.dsn:
205
+ click.secho(
206
+ f'Ignoring duplicate positional DSN argument "{cli_args.positional_database}".',
207
+ err=True,
208
+ fg='yellow',
209
+ )
210
+ database = cli_args.database or ''
211
+ else:
212
+ cli_args.dsn, database = cli_args.positional_database, cli_args.database or ''
213
+ elif cli_args.positional_database:
214
+ if cli_args.database:
215
+ click.secho(
216
+ f'Ignoring ambiguous positional database argument "{cli_args.positional_database}" since --database was given.',
217
+ err=True,
218
+ fg='yellow',
219
+ )
220
+ database = cli_args.database
221
+ else:
222
+ database = cli_args.positional_database
223
+ elif cli_args.database:
224
+ database = cli_args.database
191
225
 
192
- if database and "://" in database:
193
- dsn_uri, database = database, ""
226
+ database_from_option = bool(cli_args.database and database == cli_args.database)
227
+
228
+ if database and '://' not in database and database in mycli.config.get('alias_dsn', {}):
229
+ if any([
230
+ cli_args.user,
231
+ cli_args.host,
232
+ cli_args.port,
233
+ cli_args.socket,
234
+ cli_args.login_path,
235
+ cli_args.dsn,
236
+ ]):
237
+ if database_from_option and (cli_args.verbose or 0) >= 2:
238
+ click.secho(
239
+ f'Interpreting ambiguous --database argument "{cli_args.database}" as a database name, not a DSN alias, '
240
+ ' since other connection coordinates were given.',
241
+ err=True,
242
+ fg='yellow',
243
+ )
244
+ else:
245
+ if not is_valid_dsn_alias(database):
246
+ click.secho(INVALID_DSN_ALIAS_ERROR, err=True, fg='red')
247
+ sys.exit(1)
248
+ cli_args.dsn, database = database, ''
249
+ elif database and '://' in database:
250
+ if explicit_dsn:
251
+ click.secho(
252
+ f'Ignoring duplicate DSN argument in --database "{database}".',
253
+ err=True,
254
+ fg='yellow',
255
+ )
256
+ database = ''
257
+ else:
258
+ cli_args.dsn = ''
259
+ dsn_uri, database = database, ''
194
260
 
195
261
  if cli_args.dsn:
262
+ if not is_valid_dsn_alias(cli_args.dsn):
263
+ click.secho(INVALID_DSN_ALIAS_ERROR, err=True, fg='red')
264
+ sys.exit(1)
196
265
  try:
197
266
  dsn_uri = mycli.config["alias_dsn"][cli_args.dsn]
198
267
  except KeyError:
199
268
  is_valid_scheme, scheme = is_valid_connection_scheme(cli_args.dsn)
200
269
  if is_valid_scheme:
201
270
  dsn_uri = cli_args.dsn
271
+ elif '://' in cli_args.dsn:
272
+ click.secho(f'Error: Unknown connection scheme provided for DSN URI ({scheme}://)', err=True, fg='red')
273
+ sys.exit(1)
202
274
  else:
203
275
  click.secho(
204
276
  "Could not find the specified DSN in the config file. Please check the \"[alias_dsn]\" section in your myclirc.",
@@ -283,6 +355,8 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
283
355
  cli_args.keepalive_ticks = int(params[0])
284
356
  if params := dsn_params.get('character_set'):
285
357
  cli_args.character_set = cli_args.character_set or params[0]
358
+ if params := dsn_params.get('prompt'):
359
+ mycli.prompt_format = cli_args.prompt or params[0] or mycli.prompt_format
286
360
  if params := dsn_params.get('ssh_jump'):
287
361
  cli_args.ssh_jump = cli_args.ssh_jump or params[0]
288
362
  if params := dsn_params.get('vault_address'):
@@ -116,7 +116,7 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
116
116
  self.default_keepalive_ticks = c['connection'].as_int('default_keepalive_ticks')
117
117
 
118
118
  FavoriteQueries.instance = FavoriteQueries.from_config(self.config)
119
- DsnAliases.instance = DsnAliases.from_config(self.config)
119
+ DsnAliases.instance = DsnAliases.from_config(self.config, self)
120
120
 
121
121
  self.dsn_alias: str | None = None
122
122
  self.main_formatter = TabularOutputFormatter(format_name=c["main"]["table_format"])
@@ -81,8 +81,8 @@ class ClientCommandsMixin:
81
81
  special.register_special_command(
82
82
  self.change_prompt_format,
83
83
  "prompt",
84
- "/prompt <string>",
85
- "Change prompt format.",
84
+ "/prompt [string]",
85
+ "Show or change prompt format.",
86
86
  case_sensitive=True,
87
87
  aliases=[SpecialCommandAlias("\\R", case_sensitive=True)],
88
88
  )
@@ -161,14 +161,16 @@ class ClientCommandsMixin:
161
161
 
162
162
  def change_prompt_format(self, arg: str, **_) -> list[SQLResult]:
163
163
  """
164
- Change the prompt format.
164
+ Show or change the prompt format.
165
165
  """
166
166
  if not arg:
167
- message = "Missing required argument, format."
168
- return [SQLResult(status=message)]
167
+ return [SQLResult(status=f'Prompt format: "{self.prompt_format}"')]
168
+
169
+ if len(arg) >= 2 and arg[0] == arg[-1] and arg[0] in {'\'', '"'}:
170
+ arg = arg[1:-1]
169
171
 
170
172
  self.prompt_format = arg
171
- return [SQLResult(status=f"Changed prompt format to {arg}")]
173
+ return [SQLResult(status=f'Changed prompt format to: "{arg}"')]
172
174
 
173
175
  def initialize_logging(self) -> None:
174
176
  log_file = os.path.expanduser(self.config["main"]["log_file"])
@@ -4,6 +4,26 @@ DOCS_URL = f'{HOME_URL}/docs'
4
4
  ISSUES_URL = f'{REPO_URL}/issues'
5
5
 
6
6
  DEFAULT_CHARSET = 'utf8mb4'
7
+ KNOWN_DSN_QUERY_PARAMS = {
8
+ 'character_set',
9
+ 'keepalive_ticks',
10
+ 'prompt',
11
+ 'socket',
12
+ 'ssh_jump',
13
+ 'ssl_ca',
14
+ 'ssl_capath',
15
+ 'ssl_cert',
16
+ 'ssl_cipher',
17
+ 'ssl_key',
18
+ 'ssl_mode',
19
+ 'ssl_verify_server_cert',
20
+ 'tls_version',
21
+ 'vault_address',
22
+ 'vault_mount',
23
+ 'vault_password_field',
24
+ 'vault_secret',
25
+ 'vault_username_field',
26
+ }
7
27
  DEFAULT_DATABASE = 'mysql'
8
28
  DEFAULT_HOST = 'localhost'
9
29
  DEFAULT_PORT = 3306
@@ -1,5 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
+ __lazy_modules__ = [
4
+ 'dataclasses',
5
+ 'io',
6
+ 'sys',
7
+ 'textwrap',
8
+ 'typing',
9
+ 'click',
10
+ 'clickdc',
11
+ 'mycli',
12
+ 'mycli.cli_runner',
13
+ 'mycli.client',
14
+ 'mycli.constants',
15
+ 'mycli.packages.cli_utils',
16
+ ]
17
+
3
18
  from dataclasses import dataclass
4
19
  from io import TextIOWrapper
5
20
  import os
@@ -39,8 +54,9 @@ INT_OR_STRING_CLICK_TYPE = IntOrStringClickParamType()
39
54
 
40
55
  @dataclass(slots=True)
41
56
  class CliArgs:
42
- database: str | None = clickdc.argument(
57
+ positional_database: str | None = clickdc.argument(
43
58
  type=str,
59
+ metavar='DATABASE',
44
60
  default=None,
45
61
  nargs=1,
46
62
  )
@@ -151,10 +167,9 @@ class CliArgs:
151
167
  is_flag=True,
152
168
  help='Less verbose output and feedback.',
153
169
  )
154
- dbname: str | None = clickdc.option(
170
+ database: str | None = clickdc.option(
155
171
  '-D',
156
172
  '--database',
157
- 'dbname',
158
173
  type=str,
159
174
  clickdc=None,
160
175
  help='Database or DSN to use for the connection.',
@@ -170,6 +185,10 @@ class CliArgs:
170
185
  is_flag=True,
171
186
  help='Show list of DSN aliases configured in the [alias_dsn] section of ~/.myclirc.',
172
187
  )
188
+ completions: str | None = clickdc.option(
189
+ type=click.Choice(['bash', 'zsh', 'fish']),
190
+ help='Print a completion script for the selected shell.',
191
+ )
173
192
  prompt: str | None = clickdc.option(
174
193
  '-R',
175
194
  type=str,
@@ -382,12 +401,12 @@ def preprocess_cli_args(
382
401
  cli_args: CliArgs,
383
402
  is_valid_connection_scheme: Callable[[str], tuple[bool, str | None]],
384
403
  ) -> int:
385
- if cli_args.database is None and isinstance(cli_args.password, str) and '://' in cli_args.password:
404
+ if cli_args.positional_database is None and isinstance(cli_args.password, str) and '://' in cli_args.password:
386
405
  is_valid_scheme, scheme = is_valid_connection_scheme(cli_args.password)
387
406
  if not is_valid_scheme:
388
407
  click.secho(f'Error: Unknown connection scheme provided for DSN URI ({scheme}://)', err=True, fg='red')
389
408
  sys.exit(1)
390
- cli_args.database = cli_args.password
409
+ cli_args.positional_database = cli_args.password
391
410
  cli_args.password = EMPTY_PASSWORD_FLAG_SENTINEL
392
411
 
393
412
  if cli_args.resume and not cli_args.checkpoint:
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import resources
4
+
5
+ import mycli as mycli_package
6
+
7
+ COMPLETION_PATHS = {
8
+ 'bash': ('resources', 'completions', 'bash', 'mycli'),
9
+ 'zsh': ('resources', 'completions', 'zsh', '_mycli'),
10
+ 'fish': ('resources', 'completions', 'fish', 'mycli.fish'),
11
+ }
12
+
13
+
14
+ def main_completions(shell: str) -> None:
15
+ completion_path = resources.files(mycli_package).joinpath(*COMPLETION_PATHS[shell])
16
+ print(completion_path.read_text(), end='')
@@ -4,6 +4,8 @@ from typing import TYPE_CHECKING
4
4
 
5
5
  import click
6
6
 
7
+ from mycli.packages.special.dsn_aliases import is_valid_dsn_alias
8
+
7
9
  if TYPE_CHECKING:
8
10
  from mycli.client import MyCli
9
11
 
@@ -18,6 +20,8 @@ def main_list_dsn(mycli: 'MyCli') -> int:
18
20
  click.secho(str(e), err=True, fg='red')
19
21
  return 1
20
22
  for alias, value in alias_dsn.items():
23
+ if not is_valid_dsn_alias(alias):
24
+ continue
21
25
  if mycli.verbosity >= 1:
22
26
  click.secho(f'{alias} : {value}')
23
27
  else:
@@ -23,6 +23,7 @@ import prompt_toolkit
23
23
  from prompt_toolkit.application.current import get_app
24
24
  from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, ThreadedAutoSuggest
25
25
  from prompt_toolkit.completion import DynamicCompleter
26
+ from prompt_toolkit.cursor_shapes import CursorShape, ModalCursorShapeConfig
26
27
  from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
27
28
  from prompt_toolkit.filters import Condition, has_focus, is_done
28
29
  from prompt_toolkit.formatted_text import (
@@ -33,7 +34,11 @@ from prompt_toolkit.formatted_text import (
33
34
  to_plain_text,
34
35
  )
35
36
  from prompt_toolkit.key_binding import KeyBindings
36
- from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
37
+ from prompt_toolkit.layout.processors import (
38
+ ConditionalProcessor,
39
+ HighlightMatchingBracketProcessor,
40
+ TabsProcessor,
41
+ )
37
42
  from prompt_toolkit.lexers import PygmentsLexer
38
43
  from prompt_toolkit.output import ColorDepth
39
44
  from prompt_toolkit.shortcuts import CompleteStyle, PromptSession
@@ -583,7 +588,9 @@ def _build_prompt_session(
583
588
  ConditionalProcessor(
584
589
  processor=HighlightMatchingBracketProcessor(chars='[](){}'),
585
590
  filter=has_focus(DEFAULT_BUFFER) & ~is_done,
586
- )
591
+ ),
592
+ # renders tab as four spaces rather than ^I control character
593
+ TabsProcessor(char1=' ', char2=' '),
587
594
  ],
588
595
  tempfile_suffix='.sql',
589
596
  completer=DynamicCompleter(lambda: mycli.completer),
@@ -599,6 +606,7 @@ def _build_prompt_session(
599
606
  enable_system_prompt=True,
600
607
  enable_suspend=True,
601
608
  editing_mode=editing_mode,
609
+ cursor=ModalCursorShapeConfig() if editing_mode == EditingMode.VI else CursorShape.BLOCK,
602
610
  search_ignore_case=True,
603
611
  )
604
612
 
@@ -834,6 +834,25 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
834
834
  return [{'type': 'dsn_alias'}]
835
835
  if dsn_arguments and dsn_arguments[0].lower() == 'delete' and len(dsn_arguments) == 2:
836
836
  return []
837
+ if dsn_arguments and dsn_arguments[0].lower() == 'save':
838
+ completing_option = (len(dsn_arguments) == 1 and text[-1].isspace()) or (
839
+ len(dsn_arguments) == 2
840
+ and dsn_arguments[1].startswith('-')
841
+ and dsn_arguments[1] != '--more'
842
+ and ' ' not in dsn_arguments[1]
843
+ and not text[-1].isspace()
844
+ )
845
+ if completing_option:
846
+ return [{'type': 'special_subcommand', 'subcommands': ['--more']}]
847
+ return []
848
+ if dsn_arguments and dsn_arguments[0].lower() == 'show':
849
+ option = '--more'
850
+ completing_option = (len(dsn_arguments) == 1 and text[-1].isspace()) or (
851
+ len(dsn_arguments) == 2 and dsn_arguments[1] != option and not text[-1].isspace()
852
+ )
853
+ if completing_option:
854
+ return [{'type': 'special_subcommand', 'subcommands': [option]}]
855
+ return []
837
856
  if dsn_arguments and dsn_arguments[0].lower() in DSN_SUBCOMMANDS - {'delete'}:
838
857
  return []
839
858
  return [{'type': 'special_subcommand', 'subcommands': list(DSN_SUBCOMMANDS)}]
@@ -1,3 +1,10 @@
1
+ __lazy_modules__ = [
2
+ 'mycli.packages.special.dbcommands',
3
+ 'mycli.packages.special.iocommands',
4
+ 'mycli.packages.special.llm',
5
+ 'mycli.packages.special.main',
6
+ ]
7
+
1
8
  import os
2
9
 
3
10
  from mycli.packages.special.dbcommands import (