mycli 2.11.0__tar.gz → 2.12.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.
- {mycli-2.11.0/mycli.egg-info → mycli-2.12.0}/PKG-INFO +5 -5
- {mycli-2.11.0 → mycli-2.12.0}/README.md +3 -3
- {mycli-2.11.0 → mycli-2.12.0}/changelog.md +31 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/TIPS +2 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/client.py +9 -2
- {mycli-2.11.0 → mycli-2.12.0}/mycli/client_commands.py +151 -14
- {mycli-2.11.0 → mycli-2.12.0}/mycli/client_query.py +1 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/main_modes/repl.py +3 -1
- {mycli-2.11.0 → mycli-2.12.0}/mycli/myclirc +6 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/completion_engine.py +15 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/special/__init__.py +2 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/special/dsn_aliases.py +42 -3
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/special/main.py +6 -1
- {mycli-2.11.0 → mycli-2.12.0}/mycli/sqlcompleter.py +15 -2
- {mycli-2.11.0 → mycli-2.12.0/mycli.egg-info}/PKG-INFO +5 -5
- {mycli-2.11.0 → mycli-2.12.0}/mycli.egg-info/requires.txt +1 -1
- mycli-2.12.0/mycli.egg-info/scm_version.json +8 -0
- {mycli-2.11.0 → mycli-2.12.0}/pyproject.toml +3 -1
- {mycli-2.11.0 → mycli-2.12.0}/test/features/basic_commands.feature +1 -1
- {mycli-2.11.0 → mycli-2.12.0}/test/features/connection.feature +6 -4
- mycli-2.12.0/test/features/fixture_data/help_commands.txt +41 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/steps/basic_commands.py +23 -1
- {mycli-2.11.0 → mycli-2.12.0}/test/features/steps/connection.py +9 -7
- {mycli-2.11.0 → mycli-2.12.0}/test/myclirc +6 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_client.py +58 -1
- mycli-2.12.0/test/pytests/test_client_commands.py +754 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_client_query.py +4 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_completion_engine.py +15 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_dsn_aliases.py +144 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_main.py +28 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_main_modes_repl.py +25 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_special_main.py +6 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_sqlcompleter.py +25 -0
- mycli-2.11.0/mycli.egg-info/scm_version.json +0 -8
- mycli-2.11.0/test/features/fixture_data/help_commands.txt +0 -40
- mycli-2.11.0/test/pytests/test_client_commands.py +0 -340
- {mycli-2.11.0 → mycli-2.12.0}/.git-blame-ignore-revs +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/.github/dependabot.yml +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/.github/workflows/ci.yml +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/.github/workflows/lint.yml +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/.github/workflows/publish.yml +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/.github/workflows/typecheck.yml +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/.gitignore +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/AGENTS.md +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/AUTHORS.rst +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/CONTRIBUTING.md +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/LICENSE.txt +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/MANIFEST.in +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/SPONSORS.rst +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/doc/key_bindings.rst +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/doc/llm.md +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/doc/screenshots/main.gif +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/doc/screenshots/tables.png +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/doc/screenshots/total_histogram.png +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/doc/transforms.md +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/AUTHORS +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/SPONSORS +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/__init__.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/app_state.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/cli_runner.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/clibuffer.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/client_connection.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/clistyle.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/clitoolbar.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/compat.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/completion_refresher.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/config.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/constants.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/key_bindings.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/lexer.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/main.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/main_modes/batch.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/main_modes/checkup.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/main_modes/completions.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/main_modes/execute.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/main_modes/list_dsn.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/output.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/__init__.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/batch_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/cli_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/filepaths.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/hybrid_redirection.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/interactive_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/key_binding_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/polars_transform.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/ptoolkit/__init__.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/ptoolkit/fzf.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/ptoolkit/history.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/ptoolkit/utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/special/dbcommands.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/special/delimitercommand.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/special/favoritequeries.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/special/iocommands.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/special/llm.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/special/utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/sql_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/sqlresult.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/string_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/tabular_output/__init__.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/packages/tabular_output/sql_format.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/password_sources.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/resources/completions/bash/mycli +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/resources/completions/fish/mycli.fish +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/resources/completions/zsh/_mycli +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/schema_prefetcher.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/sqlexecute.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/ssh_tunnel.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/types.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli/vault.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli.egg-info/SOURCES.txt +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli.egg-info/dependency_links.txt +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli.egg-info/entry_points.txt +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli.egg-info/scm_file_list.json +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/mycli.egg-info/top_level.txt +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/setup.cfg +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/__init__.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/__init__.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/auto_vertical.feature +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/crud_database.feature +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/crud_table.feature +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/db_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/environment.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/fixture_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/iocommands.feature +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/named_queries.feature +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/specials.feature +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/steps/__init__.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/steps/auto_vertical.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/steps/crud_database.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/steps/crud_table.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/steps/iocommands.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/steps/named_queries.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/steps/specials.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/steps/utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/steps/wrappers.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/features/wrappager.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/mylogin.cnf +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/conftest.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_app_state.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_bash_completion.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_batch_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_checkup.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_cli_runner.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_cli_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_clibuffer.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_client_connection.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_clistyle.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_clitoolbar.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_completer_use_switch.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_completion_refresher.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_config.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_delimitercommand.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_favoritequeries.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_filepaths.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_fish_completion.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_hybrid_redirection.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_interactive_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_key_binding_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_key_bindings.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_lexer.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_main_modes_batch.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_main_modes_execute.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_main_modes_list_dsn.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_naive_completion.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_output.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_password_sources.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_polars_transform.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_ptoolkit_fzf.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_ptoolkit_history.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_ptoolkit_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_schema_prefetcher.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_smart_completion_public_schema_only.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_special_dbcommands.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_special_init.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_special_iocommands.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_special_llm.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_special_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_sql_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_sqlexecute.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_sqlresult.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_ssh_tunnel.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_string_utils.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_tabular_output.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_vault.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/pytests/test_zsh_completion.py +0 -0
- {mycli-2.11.0 → mycli-2.12.0}/test/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.12.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
|
|
@@ -28,7 +28,7 @@ Requires-Python: >=3.10
|
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
License-File: LICENSE.txt
|
|
30
30
|
License-File: AUTHORS.rst
|
|
31
|
-
Requires-Dist: click~=8.
|
|
31
|
+
Requires-Dist: click~=8.3.3
|
|
32
32
|
Requires-Dist: clickdc~=0.1.1
|
|
33
33
|
Requires-Dist: cryptography~=50.0.0
|
|
34
34
|
Requires-Dist: Pygments~=2.20.0
|
|
@@ -147,14 +147,14 @@ Features
|
|
|
147
147
|
* Pretty print tabular data (with colors!).
|
|
148
148
|
* Support for SSL connections
|
|
149
149
|
* Shell-style trailing redirects with `$>`, `$>>` and `$|` operators.
|
|
150
|
-
* [Polars](https://pola.rs) dataframe [transforms and plots](doc/transforms.md) with `.|`, and Parquet saves with `.>`.
|
|
151
|
-
* Support for querying LLMs with context derived from your schema using `/llm`.
|
|
150
|
+
* [Polars](https://pola.rs) dataframe [transforms and plots](https://github.com/dbcli/mycli/blob/main/doc/transforms.md) with `.|`, and Parquet saves with `.>`.
|
|
151
|
+
* Support for [querying LLMs](https://www.mycli.net/llm) with context derived from your schema using `/llm`.
|
|
152
152
|
* Support for storing passwords in the system keyring.
|
|
153
153
|
|
|
154
154
|
Mycli creates a config file `~/.myclirc` on the first run; you can use the
|
|
155
155
|
options in that file to configure the above features, and more.
|
|
156
156
|
|
|
157
|
-
Some features are only exposed as [key bindings](doc/key_bindings.rst).
|
|
157
|
+
Some features are only exposed as [key bindings](https://github.com/dbcli/mycli/blob/main/doc/key_bindings.rst).
|
|
158
158
|
|
|
159
159
|
|
|
160
160
|
Implementation
|
|
@@ -75,14 +75,14 @@ Features
|
|
|
75
75
|
* Pretty print tabular data (with colors!).
|
|
76
76
|
* Support for SSL connections
|
|
77
77
|
* Shell-style trailing redirects with `$>`, `$>>` and `$|` operators.
|
|
78
|
-
* [Polars](https://pola.rs) dataframe [transforms and plots](doc/transforms.md) with `.|`, and Parquet saves with `.>`.
|
|
79
|
-
* Support for querying LLMs with context derived from your schema using `/llm`.
|
|
78
|
+
* [Polars](https://pola.rs) dataframe [transforms and plots](https://github.com/dbcli/mycli/blob/main/doc/transforms.md) with `.|`, and Parquet saves with `.>`.
|
|
79
|
+
* Support for [querying LLMs](https://www.mycli.net/llm) with context derived from your schema using `/llm`.
|
|
80
80
|
* Support for storing passwords in the system keyring.
|
|
81
81
|
|
|
82
82
|
Mycli creates a config file `~/.myclirc` on the first run; you can use the
|
|
83
83
|
options in that file to configure the above features, and more.
|
|
84
84
|
|
|
85
|
-
Some features are only exposed as [key bindings](doc/key_bindings.rst).
|
|
85
|
+
Some features are only exposed as [key bindings](https://github.com/dbcli/mycli/blob/main/doc/key_bindings.rst).
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
Implementation
|
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
2.12.0 (2026/08/08)
|
|
2
|
+
==============
|
|
3
|
+
|
|
4
|
+
Breaking Changes
|
|
5
|
+
---------
|
|
6
|
+
* `/source` scripts can no longer contain special /commands, but only SQL.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Features
|
|
10
|
+
---------
|
|
11
|
+
* Allow reading DSN aliases from a shared configuration file.
|
|
12
|
+
* Add a `/config` command to inspect configuration values from the REPL.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Bugfixes
|
|
16
|
+
---------
|
|
17
|
+
* Downgrade `click` to v8.3.3, since v8.4.2 broke the pager on Windows.
|
|
18
|
+
* Don't read `/source` targets entirely into memory.
|
|
19
|
+
* Continue with a `/source` script after declining a destructive query.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
Documentation
|
|
23
|
+
---------
|
|
24
|
+
* Make all links in `README.md` absolute, so that they don't break on PyPi.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Internal
|
|
28
|
+
---------
|
|
29
|
+
* Tiny change to quoting in `client_commands.py`.
|
|
30
|
+
|
|
31
|
+
|
|
1
32
|
2.11.0 (2026/08/07)
|
|
2
33
|
==============
|
|
3
34
|
|
|
@@ -20,7 +20,7 @@ from mycli.app_state import (
|
|
|
20
20
|
normalize_image_protocol,
|
|
21
21
|
normalize_ssl_mode,
|
|
22
22
|
)
|
|
23
|
-
from mycli.client_commands import ClientCommandsMixin
|
|
23
|
+
from mycli.client_commands import ClientCommandsMixin, get_config_property_names
|
|
24
24
|
from mycli.client_connection import ClientConnectionMixin
|
|
25
25
|
from mycli.client_query import ClientQueryMixin
|
|
26
26
|
from mycli.clistyle import style_factory_helpers, style_factory_ptoolkit
|
|
@@ -93,6 +93,7 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
|
|
|
93
93
|
myclirc = self.xdg_config_file
|
|
94
94
|
else:
|
|
95
95
|
myclirc = '~/.myclirc'
|
|
96
|
+
self.myclirc_path = os.path.abspath(os.path.expanduser(myclirc))
|
|
96
97
|
|
|
97
98
|
# Load config.
|
|
98
99
|
config_files: list[str | IO[str]] = self.system_config_files + [myclirc]
|
|
@@ -120,7 +121,12 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
|
|
|
120
121
|
myclirc,
|
|
121
122
|
c['main'].get('shared_favorites_file'),
|
|
122
123
|
)
|
|
123
|
-
DsnAliases.instance = DsnAliases.from_config(
|
|
124
|
+
DsnAliases.instance = DsnAliases.from_config(
|
|
125
|
+
self.config,
|
|
126
|
+
self,
|
|
127
|
+
config_file=myclirc,
|
|
128
|
+
shared_dsns_file=c['main'].get('shared_dsns_file'),
|
|
129
|
+
)
|
|
124
130
|
|
|
125
131
|
self.dsn_alias: str | None = None
|
|
126
132
|
self.main_formatter = TabularOutputFormatter(format_name=c["main"]["table_format"])
|
|
@@ -200,6 +206,7 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
|
|
|
200
206
|
supported_formats=self.main_formatter.supported_formats,
|
|
201
207
|
keyword_casing=keyword_casing,
|
|
202
208
|
indexed_column_suffix=indexed_column_suffix,
|
|
209
|
+
config_property_names=get_config_property_names(self.config),
|
|
203
210
|
)
|
|
204
211
|
self._completer_lock = threading.Lock()
|
|
205
212
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections.abc import Generator,
|
|
3
|
+
from collections.abc import Generator, Mapping
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
@@ -8,14 +8,82 @@ from typing import TYPE_CHECKING, Any, cast
|
|
|
8
8
|
|
|
9
9
|
import click
|
|
10
10
|
|
|
11
|
+
from mycli.config import write_default_config
|
|
11
12
|
from mycli.main_modes.repl import set_all_external_titles
|
|
12
13
|
from mycli.packages import special
|
|
14
|
+
from mycli.packages.batch_utils import statements_from_filehandle
|
|
13
15
|
from mycli.packages.filepaths import dir_path_exists
|
|
14
16
|
from mycli.packages.interactive_utils import confirm_destructive_query
|
|
15
17
|
from mycli.packages.special.main import ArgType, SpecialCommandAlias
|
|
16
18
|
from mycli.packages.sqlresult import SQLResult
|
|
17
19
|
from mycli.sqlexecute import SQLExecute
|
|
18
20
|
|
|
21
|
+
CONFIG_COMMAND_USAGE = '''Syntax:
|
|
22
|
+
/config get <key>
|
|
23
|
+
/config search <regex>
|
|
24
|
+
/config edit
|
|
25
|
+
Examples:
|
|
26
|
+
/config get main.show_warnings
|
|
27
|
+
/config search warning
|
|
28
|
+
/config edit'''
|
|
29
|
+
MISSING_CONFIG_VALUE = object()
|
|
30
|
+
DSN_CONFIG_VALUE = object()
|
|
31
|
+
FAVORITES_CONFIG_VALUE = object()
|
|
32
|
+
HIDDEN_CONFIG_SECTIONS = frozenset({'alias_dsn', 'favorite_queries'})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _render_config_value(value: Any) -> str:
|
|
36
|
+
if isinstance(value, list):
|
|
37
|
+
return ', '.join(str(item) for item in value)
|
|
38
|
+
return str(value)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _iter_config_values(
|
|
42
|
+
config: Mapping[str, Any],
|
|
43
|
+
prefix: str = '',
|
|
44
|
+
) -> Generator[tuple[str, str], None, None]:
|
|
45
|
+
"""Yield dotted paths and rendered values for searchable settings."""
|
|
46
|
+
for key, value in config.items():
|
|
47
|
+
if not prefix and key in HIDDEN_CONFIG_SECTIONS:
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
path = f'{prefix}.{key}' if prefix else key
|
|
51
|
+
if isinstance(value, Mapping):
|
|
52
|
+
yield from _iter_config_values(value, path)
|
|
53
|
+
else:
|
|
54
|
+
yield path, _render_config_value(value)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_config_property_names(config: Mapping[str, Any]) -> list[str]:
|
|
58
|
+
"""Return sorted leaf paths available to the config command."""
|
|
59
|
+
return sorted(path for path, _value in _iter_config_values(config))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _resolve_config_path(config: Mapping[str, Any], path: str) -> Any:
|
|
63
|
+
"""Resolve a dotted path while allowing dots within mapping keys."""
|
|
64
|
+
parts = path.split('.')
|
|
65
|
+
value: Any = config
|
|
66
|
+
top_level = True
|
|
67
|
+
while parts:
|
|
68
|
+
if not isinstance(value, Mapping):
|
|
69
|
+
return MISSING_CONFIG_VALUE
|
|
70
|
+
|
|
71
|
+
for part_count in range(len(parts), 0, -1):
|
|
72
|
+
key = '.'.join(parts[:part_count])
|
|
73
|
+
if key in value:
|
|
74
|
+
if top_level and key == 'alias_dsn':
|
|
75
|
+
return DSN_CONFIG_VALUE
|
|
76
|
+
if top_level and key == 'favorite_queries':
|
|
77
|
+
return FAVORITES_CONFIG_VALUE
|
|
78
|
+
value = value[key]
|
|
79
|
+
parts = parts[part_count:]
|
|
80
|
+
top_level = False
|
|
81
|
+
break
|
|
82
|
+
else:
|
|
83
|
+
return MISSING_CONFIG_VALUE
|
|
84
|
+
|
|
85
|
+
return value
|
|
86
|
+
|
|
19
87
|
|
|
20
88
|
class ClientCommandsMixin:
|
|
21
89
|
if TYPE_CHECKING:
|
|
@@ -25,6 +93,7 @@ class ClientCommandsMixin:
|
|
|
25
93
|
destructive_warning: bool
|
|
26
94
|
destructive_keywords: Any
|
|
27
95
|
config: Any
|
|
96
|
+
myclirc_path: str
|
|
28
97
|
prompt_format: str
|
|
29
98
|
|
|
30
99
|
def refresh_completions(self, reset: bool = False) -> list[SQLResult]: ...
|
|
@@ -86,6 +155,12 @@ class ClientCommandsMixin:
|
|
|
86
155
|
case_sensitive=True,
|
|
87
156
|
aliases=[SpecialCommandAlias("\\R", case_sensitive=True)],
|
|
88
157
|
)
|
|
158
|
+
special.register_special_command(
|
|
159
|
+
self.config_command,
|
|
160
|
+
r'\config',
|
|
161
|
+
'/config <help|get|search|edit> [key]',
|
|
162
|
+
'Inspect settings from config files.',
|
|
163
|
+
)
|
|
89
164
|
|
|
90
165
|
def manual_reconnect(self, arg: str = "", **_) -> Generator[SQLResult, None, None]:
|
|
91
166
|
"""
|
|
@@ -119,6 +194,54 @@ class ClientCommandsMixin:
|
|
|
119
194
|
msg += f"\n\t{table_type}"
|
|
120
195
|
yield SQLResult(status=msg)
|
|
121
196
|
|
|
197
|
+
def config_command(self, arg: str, **_) -> list[SQLResult]:
|
|
198
|
+
args = arg.strip().split(maxsplit=1)
|
|
199
|
+
if not args or args[0].lower() == 'help':
|
|
200
|
+
return [SQLResult(preamble=CONFIG_COMMAND_USAGE)]
|
|
201
|
+
|
|
202
|
+
subcommand = args[0].lower()
|
|
203
|
+
if subcommand == 'edit' and len(args) == 1:
|
|
204
|
+
return self.edit_config_file()
|
|
205
|
+
|
|
206
|
+
if subcommand == 'get' and len(args) == 2 and len(args[1].split()) == 1:
|
|
207
|
+
path = args[1]
|
|
208
|
+
value = _resolve_config_path(self.config, path)
|
|
209
|
+
if value is MISSING_CONFIG_VALUE:
|
|
210
|
+
return [SQLResult(status=f'Config key not found: {path}.')]
|
|
211
|
+
if value is DSN_CONFIG_VALUE:
|
|
212
|
+
return [SQLResult(status='See "/dsn list" for DSNs.')]
|
|
213
|
+
if value is FAVORITES_CONFIG_VALUE:
|
|
214
|
+
return [SQLResult(status='See "/f" for favorite queries.')]
|
|
215
|
+
if isinstance(value, Mapping):
|
|
216
|
+
return [SQLResult(status=f'Config path is not a value: {path}.')]
|
|
217
|
+
return [SQLResult(header=['Key', 'Value'], rows=[(path, _render_config_value(value))])]
|
|
218
|
+
|
|
219
|
+
if subcommand == 'search' and len(args) == 2:
|
|
220
|
+
pattern_text = args[1]
|
|
221
|
+
try:
|
|
222
|
+
pattern = re.compile(pattern_text, re.IGNORECASE)
|
|
223
|
+
except re.error as error:
|
|
224
|
+
return [SQLResult(status=f'Invalid regular expression: {error}.')]
|
|
225
|
+
|
|
226
|
+
rows = sorted(
|
|
227
|
+
(path, value) for path, value in _iter_config_values(self.config) if pattern.search(path) or pattern.search(value)
|
|
228
|
+
)
|
|
229
|
+
if not rows:
|
|
230
|
+
return [SQLResult(status=f'No configuration values match "{pattern_text}".')]
|
|
231
|
+
return [SQLResult(header=['Key', 'Value'], rows=rows)]
|
|
232
|
+
|
|
233
|
+
return [SQLResult(preamble=CONFIG_COMMAND_USAGE)]
|
|
234
|
+
|
|
235
|
+
def edit_config_file(self) -> list[SQLResult]:
|
|
236
|
+
try:
|
|
237
|
+
write_default_config(self.myclirc_path, overwrite=False)
|
|
238
|
+
click.edit(filename=self.myclirc_path)
|
|
239
|
+
except KeyboardInterrupt:
|
|
240
|
+
return [SQLResult(status='Config edit cancelled.')]
|
|
241
|
+
except (click.ClickException, OSError) as error:
|
|
242
|
+
return [SQLResult(status=f'Unable to edit config file "{self.myclirc_path}": {error}')]
|
|
243
|
+
return [SQLResult(status=f'Config file edited: {self.myclirc_path}. Restart mycli to apply changes.')]
|
|
244
|
+
|
|
122
245
|
def change_db(self, arg: str, **_) -> Generator[SQLResult, None, None]:
|
|
123
246
|
if arg.startswith("`") and arg.endswith("`"):
|
|
124
247
|
arg = re.sub(r"^`(.*)`$", r"\1", arg)
|
|
@@ -142,22 +265,36 @@ class ClientCommandsMixin:
|
|
|
142
265
|
|
|
143
266
|
yield SQLResult(status=msg)
|
|
144
267
|
|
|
145
|
-
def execute_from_file(self, arg: str, **_) ->
|
|
268
|
+
def execute_from_file(self, arg: str, **_) -> Generator[SQLResult, None, None]:
|
|
146
269
|
if not arg:
|
|
147
|
-
|
|
148
|
-
return
|
|
149
|
-
try:
|
|
150
|
-
with open(os.path.expanduser(arg)) as f:
|
|
151
|
-
query = f.read()
|
|
152
|
-
except IOError as e:
|
|
153
|
-
return [SQLResult(status=str(e))]
|
|
270
|
+
yield SQLResult(status="Missing required argument: filename.")
|
|
271
|
+
return
|
|
154
272
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
273
|
+
try:
|
|
274
|
+
file_h = open(os.path.expanduser(arg))
|
|
275
|
+
except OSError as error:
|
|
276
|
+
yield SQLResult(status=str(error))
|
|
277
|
+
return
|
|
158
278
|
|
|
159
279
|
assert isinstance(self.sqlexecute, SQLExecute)
|
|
160
|
-
|
|
280
|
+
with file_h:
|
|
281
|
+
statements = statements_from_filehandle(file_h)
|
|
282
|
+
while True:
|
|
283
|
+
try:
|
|
284
|
+
query, _counter = next(statements)
|
|
285
|
+
except StopIteration:
|
|
286
|
+
return
|
|
287
|
+
except (OSError, ValueError) as error:
|
|
288
|
+
yield SQLResult(status=str(error))
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
if special.is_special_command(query.rstrip(';')):
|
|
292
|
+
yield SQLResult(status='Special commands are not supported in source files.')
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
if self.destructive_warning and confirm_destructive_query(self.destructive_keywords, query) is False:
|
|
296
|
+
continue
|
|
297
|
+
yield from self.sqlexecute.run(query)
|
|
161
298
|
|
|
162
299
|
def change_prompt_format(self, arg: str, **_) -> list[SQLResult]:
|
|
163
300
|
"""
|
|
@@ -166,7 +303,7 @@ class ClientCommandsMixin:
|
|
|
166
303
|
if not arg:
|
|
167
304
|
return [SQLResult(status=f'Prompt format: "{self.prompt_format}"')]
|
|
168
305
|
|
|
169
|
-
if len(arg) >= 2 and arg[0] == arg[-1] and arg[0] in {'
|
|
306
|
+
if len(arg) >= 2 and arg[0] == arg[-1] and arg[0] in {"'", '"'}:
|
|
170
307
|
arg = arg[1:-1]
|
|
171
308
|
|
|
172
309
|
self.prompt_format = arg
|
|
@@ -53,6 +53,7 @@ class ClientQueryMixin:
|
|
|
53
53
|
"supported_formats": self.main_formatter.supported_formats,
|
|
54
54
|
"keyword_casing": self.completer.keyword_casing,
|
|
55
55
|
"indexed_column_suffix": self.completer.indexed_column_suffix,
|
|
56
|
+
"config_property_names": self.completer.config_property_names,
|
|
56
57
|
},
|
|
57
58
|
)
|
|
58
59
|
|
|
@@ -119,6 +119,7 @@ def complete_while_typing_filter() -> bool:
|
|
|
119
119
|
text = app.current_buffer.text.lstrip()
|
|
120
120
|
if text.startswith('/') and not text.startswith('/*') and ' ' not in text:
|
|
121
121
|
return True
|
|
122
|
+
completing_config_property = bool(re.match(r'^(?:/|\\)config\s+get\s+', text, re.IGNORECASE))
|
|
122
123
|
text_len = len(text)
|
|
123
124
|
if text_len < MIN_COMPLETION_TRIGGER:
|
|
124
125
|
return False
|
|
@@ -137,7 +138,8 @@ def complete_while_typing_filter() -> bool:
|
|
|
137
138
|
# limit. We would have to parse the statement, or at least go back more
|
|
138
139
|
# characters, costing performance. This still works within a backtick! So
|
|
139
140
|
# long as there are three trailing non-punctuation characters.
|
|
140
|
-
|
|
141
|
+
punctuation_check = last_word.replace('.', '') if completing_config_property else last_word
|
|
142
|
+
return not bool(re.search(r'[\s!-/:-@\[-^\{-~]', punctuation_check))
|
|
141
143
|
|
|
142
144
|
|
|
143
145
|
def _create_history(mycli: 'MyCli') -> FileHistoryWithTimestamp | None:
|
|
@@ -77,6 +77,12 @@ show_favorite_query = True
|
|
|
77
77
|
# Example: /usr/local/etc/mycli/shared-favorites.ini
|
|
78
78
|
shared_favorites_file =
|
|
79
79
|
|
|
80
|
+
# Load additional DSN aliases from the [alias_dsn] section of this file. DSN
|
|
81
|
+
# aliases in the user's configuration file take precedence over the shared
|
|
82
|
+
# file. The path must be absolute after expanding ~.
|
|
83
|
+
# Example: /usr/local/etc/mycli/shared-dsns.ini
|
|
84
|
+
shared_dsns_file =
|
|
85
|
+
|
|
80
86
|
# Beep after long-running queries are completed; 0 to disable.
|
|
81
87
|
beep_after_seconds = 0
|
|
82
88
|
|
|
@@ -831,6 +831,21 @@ def suggest_special(text: str) -> list[dict[str, Any]]:
|
|
|
831
831
|
if cmd in ["\\llm", "/llm", "\\ai", "/ai"]:
|
|
832
832
|
return [{"type": "llm"}]
|
|
833
833
|
|
|
834
|
+
if cmd.lower() in (r'\config', '/config'):
|
|
835
|
+
config_arguments = _arg.split(maxsplit=1)
|
|
836
|
+
config_subcommands = ['help', 'get', 'search', 'edit']
|
|
837
|
+
if config_arguments and config_arguments[0].lower() == 'get':
|
|
838
|
+
completing_property = (len(config_arguments) == 1 and text[-1].isspace()) or (
|
|
839
|
+
len(config_arguments) == 2 and not text[-1].isspace()
|
|
840
|
+
)
|
|
841
|
+
if completing_property:
|
|
842
|
+
prefix = config_arguments[1] if len(config_arguments) == 2 else ''
|
|
843
|
+
return [{'type': 'config_property', 'prefix': prefix}]
|
|
844
|
+
return []
|
|
845
|
+
if config_arguments and config_arguments[0].lower() in config_subcommands:
|
|
846
|
+
return []
|
|
847
|
+
return [{'type': 'special_subcommand', 'subcommands': config_subcommands}]
|
|
848
|
+
|
|
834
849
|
if cmd.lower() in (r'\dsn', '/dsn'):
|
|
835
850
|
dsn_arguments = _arg.split(maxsplit=1)
|
|
836
851
|
completing_delete_target = (len(dsn_arguments) == 1 and text[-1].isspace()) or (len(dsn_arguments) == 2 and not text[-1].isspace())
|
|
@@ -81,6 +81,7 @@ from mycli.packages.special.main import (
|
|
|
81
81
|
CommandNotFound,
|
|
82
82
|
SpecialCommandAlias,
|
|
83
83
|
execute,
|
|
84
|
+
is_special_command,
|
|
84
85
|
parse_special_command,
|
|
85
86
|
register_special_command,
|
|
86
87
|
special_command,
|
|
@@ -110,6 +111,7 @@ __all__: list[str] = [
|
|
|
110
111
|
'is_llm_command',
|
|
111
112
|
'is_pager_enabled',
|
|
112
113
|
'is_redirected',
|
|
114
|
+
'is_special_command',
|
|
113
115
|
'is_show_warnings_enabled',
|
|
114
116
|
'is_timing_enabled',
|
|
115
117
|
'list_databases',
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
from typing import TYPE_CHECKING, Any
|
|
5
6
|
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
|
6
7
|
|
|
7
|
-
from mycli.config import read_config_file, str_to_bool
|
|
8
|
+
from mycli.config import log, read_config_file, str_to_bool
|
|
8
9
|
from mycli.constants import DEFAULT_CHARSET, DEFAULT_PROMPT, KNOWN_DSN_QUERY_PARAMS
|
|
9
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
10
13
|
if TYPE_CHECKING:
|
|
11
14
|
from mycli.client import MyCli
|
|
12
15
|
|
|
@@ -94,8 +97,44 @@ Examples:
|
|
|
94
97
|
self.config_file = config_file
|
|
95
98
|
|
|
96
99
|
@classmethod
|
|
97
|
-
def from_config(
|
|
98
|
-
|
|
100
|
+
def from_config(
|
|
101
|
+
cls,
|
|
102
|
+
config: Any,
|
|
103
|
+
mycli: MyCli | None = None,
|
|
104
|
+
config_file: str | None = None,
|
|
105
|
+
shared_dsns_file: str | None = None,
|
|
106
|
+
) -> DsnAliases:
|
|
107
|
+
aliases = cls(config, mycli, config_file)
|
|
108
|
+
if not shared_dsns_file:
|
|
109
|
+
return aliases
|
|
110
|
+
|
|
111
|
+
shared_dsns_file = os.path.expanduser(shared_dsns_file)
|
|
112
|
+
if not os.path.isabs(shared_dsns_file):
|
|
113
|
+
log(
|
|
114
|
+
logger,
|
|
115
|
+
logging.WARNING,
|
|
116
|
+
f"Shared DSNs file path must be absolute: '{shared_dsns_file}'.",
|
|
117
|
+
)
|
|
118
|
+
return aliases
|
|
119
|
+
|
|
120
|
+
if not os.path.isfile(shared_dsns_file):
|
|
121
|
+
log(
|
|
122
|
+
logger,
|
|
123
|
+
logging.WARNING,
|
|
124
|
+
f"Unable to read shared DSNs file '{shared_dsns_file}'.",
|
|
125
|
+
)
|
|
126
|
+
return aliases
|
|
127
|
+
|
|
128
|
+
shared_config = read_config_file(shared_dsns_file)
|
|
129
|
+
if shared_config is None:
|
|
130
|
+
return aliases
|
|
131
|
+
|
|
132
|
+
configured_aliases = config.get(cls.section_name, {})
|
|
133
|
+
shared_aliases = shared_config.get(cls.section_name, {})
|
|
134
|
+
config[cls.section_name] = {}
|
|
135
|
+
config[cls.section_name].update(shared_aliases)
|
|
136
|
+
config[cls.section_name].update(configured_aliases)
|
|
137
|
+
return aliases
|
|
99
138
|
|
|
100
139
|
def _config_for_write(self) -> Any:
|
|
101
140
|
if self.config_file is None:
|
|
@@ -72,6 +72,11 @@ def parse_special_command(sql: str) -> tuple[str, CommandVerbosity, str]:
|
|
|
72
72
|
return (command, command_verbosity, arg.strip())
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
def is_special_command(sql: str) -> bool:
|
|
76
|
+
command, _, _ = parse_special_command(sql)
|
|
77
|
+
return command in CASE_SENSITIVE_COMMANDS or command.lower() in CASE_INSENSITIVE_COMMANDS
|
|
78
|
+
|
|
79
|
+
|
|
75
80
|
def special_command(
|
|
76
81
|
command: str,
|
|
77
82
|
usage: str,
|
|
@@ -190,7 +195,7 @@ def execute(cur: Cursor, sql: str) -> list[SQLResult]:
|
|
|
190
195
|
"""
|
|
191
196
|
command, command_verbosity, arg = parse_special_command(sql)
|
|
192
197
|
|
|
193
|
-
if
|
|
198
|
+
if not is_special_command(sql):
|
|
194
199
|
raise CommandNotFound(f'Command not found: {command}')
|
|
195
200
|
|
|
196
201
|
try:
|
|
@@ -945,10 +945,12 @@ class SQLCompleter(Completer):
|
|
|
945
945
|
supported_formats: tuple = (),
|
|
946
946
|
keyword_casing: str = "auto",
|
|
947
947
|
indexed_column_suffix: str = '*',
|
|
948
|
+
config_property_names: Collection[str] = (),
|
|
948
949
|
) -> None:
|
|
949
950
|
super(self.__class__, self).__init__()
|
|
950
951
|
self.smart_completion = smart_completion
|
|
951
952
|
self.indexed_column_suffix = indexed_column_suffix
|
|
953
|
+
self.config_property_names = tuple(sorted(config_property_names))
|
|
952
954
|
self.reserved_words = set()
|
|
953
955
|
for x in self.keywords:
|
|
954
956
|
self.reserved_words.update(x.split())
|
|
@@ -1452,6 +1454,8 @@ class SQLCompleter(Completer):
|
|
|
1452
1454
|
suggestions = suggest_type(document.text, document.text_before_cursor)
|
|
1453
1455
|
rigid_sort = False
|
|
1454
1456
|
length_based_on_path = False
|
|
1457
|
+
config_property_length: int | None = None
|
|
1458
|
+
completion_filter_text = text_for_len
|
|
1455
1459
|
|
|
1456
1460
|
rank = 0
|
|
1457
1461
|
for suggestion in suggestions:
|
|
@@ -1745,6 +1749,13 @@ class SQLCompleter(Completer):
|
|
|
1745
1749
|
)
|
|
1746
1750
|
completions.extend([(*x, rank) for x in subcommands_m])
|
|
1747
1751
|
|
|
1752
|
+
elif suggestion['type'] == 'config_property':
|
|
1753
|
+
prefix = suggestion['prefix']
|
|
1754
|
+
property_matches = self.find_fuzzy_matches(prefix, prefix.lower(), self.config_property_names)
|
|
1755
|
+
completions.extend([(*x, rank) for x in property_matches])
|
|
1756
|
+
config_property_length = len(prefix)
|
|
1757
|
+
completion_filter_text = prefix.lower()
|
|
1758
|
+
|
|
1748
1759
|
elif suggestion['type'] == 'dsn_alias':
|
|
1749
1760
|
if hasattr(DsnAliases, 'instance'):
|
|
1750
1761
|
aliases_m = self.find_matches(
|
|
@@ -1789,10 +1800,12 @@ class SQLCompleter(Completer):
|
|
|
1789
1800
|
if rigid_sort:
|
|
1790
1801
|
uniq_completions_str = dict.fromkeys(x[0] for x in completions)
|
|
1791
1802
|
else:
|
|
1792
|
-
sorted_completions = sorted(completions, key=lambda item: completion_sort_key(item,
|
|
1803
|
+
sorted_completions = sorted(completions, key=lambda item: completion_sort_key(item, completion_filter_text))
|
|
1793
1804
|
uniq_completions_str = dict.fromkeys(x[0] for x in sorted_completions)
|
|
1794
1805
|
|
|
1795
|
-
if
|
|
1806
|
+
if config_property_length is not None:
|
|
1807
|
+
return (Completion(x, -config_property_length) for x in uniq_completions_str)
|
|
1808
|
+
elif length_based_on_path:
|
|
1796
1809
|
return (
|
|
1797
1810
|
Completion(
|
|
1798
1811
|
x,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.12.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
|
|
@@ -28,7 +28,7 @@ Requires-Python: >=3.10
|
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
License-File: LICENSE.txt
|
|
30
30
|
License-File: AUTHORS.rst
|
|
31
|
-
Requires-Dist: click~=8.
|
|
31
|
+
Requires-Dist: click~=8.3.3
|
|
32
32
|
Requires-Dist: clickdc~=0.1.1
|
|
33
33
|
Requires-Dist: cryptography~=50.0.0
|
|
34
34
|
Requires-Dist: Pygments~=2.20.0
|
|
@@ -147,14 +147,14 @@ Features
|
|
|
147
147
|
* Pretty print tabular data (with colors!).
|
|
148
148
|
* Support for SSL connections
|
|
149
149
|
* Shell-style trailing redirects with `$>`, `$>>` and `$|` operators.
|
|
150
|
-
* [Polars](https://pola.rs) dataframe [transforms and plots](doc/transforms.md) with `.|`, and Parquet saves with `.>`.
|
|
151
|
-
* Support for querying LLMs with context derived from your schema using `/llm`.
|
|
150
|
+
* [Polars](https://pola.rs) dataframe [transforms and plots](https://github.com/dbcli/mycli/blob/main/doc/transforms.md) with `.|`, and Parquet saves with `.>`.
|
|
151
|
+
* Support for [querying LLMs](https://www.mycli.net/llm) with context derived from your schema using `/llm`.
|
|
152
152
|
* Support for storing passwords in the system keyring.
|
|
153
153
|
|
|
154
154
|
Mycli creates a config file `~/.myclirc` on the first run; you can use the
|
|
155
155
|
options in that file to configure the above features, and more.
|
|
156
156
|
|
|
157
|
-
Some features are only exposed as [key bindings](doc/key_bindings.rst).
|
|
157
|
+
Some features are only exposed as [key bindings](https://github.com/dbcli/mycli/blob/main/doc/key_bindings.rst).
|
|
158
158
|
|
|
159
159
|
|
|
160
160
|
Implementation
|
|
@@ -25,7 +25,9 @@ classifiers = [
|
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
dependencies = [
|
|
28
|
-
|
|
28
|
+
# click 8.4.2 breaks the pager on Windows; check the next
|
|
29
|
+
# version for https://github.com/pallets/click/pull/3739
|
|
30
|
+
"click ~= 8.3.3",
|
|
29
31
|
"clickdc ~= 0.1.1",
|
|
30
32
|
"cryptography ~= 50.0.0",
|
|
31
33
|
"Pygments ~= 2.20.0",
|