mycli 2.1.1__tar.gz → 2.2.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.1.1 → mycli-2.2.0}/.github/workflows/ci.yml +4 -4
- {mycli-2.1.1 → mycli-2.2.0}/.github/workflows/publish.yml +4 -4
- {mycli-2.1.1 → mycli-2.2.0}/.github/workflows/typecheck.yml +1 -1
- {mycli-2.1.1 → mycli-2.2.0}/AGENTS.md +8 -4
- {mycli-2.1.1/mycli.egg-info → mycli-2.2.0}/PKG-INFO +1 -1
- {mycli-2.1.1 → mycli-2.2.0}/changelog.md +35 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/TIPS +2 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/cli_runner.py +75 -11
- {mycli-2.1.1 → mycli-2.2.0}/mycli/client_connection.py +29 -6
- {mycli-2.1.1 → mycli-2.2.0}/mycli/main.py +24 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/myclirc +28 -7
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/special/dsn_aliases.py +4 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/special/utils.py +36 -12
- {mycli-2.1.1 → mycli-2.2.0}/mycli/sqlexecute.py +4 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/ssh_tunnel.py +74 -17
- mycli-2.2.0/mycli/vault.py +54 -0
- {mycli-2.1.1 → mycli-2.2.0/mycli.egg-info}/PKG-INFO +1 -1
- {mycli-2.1.1 → mycli-2.2.0}/mycli.egg-info/SOURCES.txt +3 -1
- {mycli-2.1.1 → mycli-2.2.0}/mycli.egg-info/scm_file_list.json +2 -0
- mycli-2.2.0/mycli.egg-info/scm_version.json +8 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/myclirc +28 -7
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_cli_runner.py +358 -2
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_client_connection.py +119 -14
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_main.py +28 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_special_iocommands.py +2 -2
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_special_utils.py +31 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_sqlexecute.py +3 -0
- mycli-2.2.0/test/pytests/test_ssh_tunnel.py +512 -0
- mycli-2.2.0/test/pytests/test_vault.py +96 -0
- mycli-2.1.1/mycli.egg-info/scm_version.json +0 -8
- mycli-2.1.1/test/pytests/test_ssh_tunnel.py +0 -307
- {mycli-2.1.1 → mycli-2.2.0}/.git-blame-ignore-revs +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/.github/dependabot.yml +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/.github/workflows/lint.yml +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/.gitignore +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/AUTHORS.rst +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/CONTRIBUTING.md +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/LICENSE.txt +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/MANIFEST.in +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/README.md +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/SPONSORS.rst +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/doc/key_bindings.rst +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/doc/llm.md +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/doc/screenshots/main.gif +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/doc/screenshots/tables.png +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/AUTHORS +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/SPONSORS +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/__init__.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/app_state.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/clibuffer.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/client.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/client_commands.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/client_query.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/clistyle.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/clitoolbar.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/compat.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/completion_refresher.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/config.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/constants.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/key_bindings.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/lexer.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/main_modes/batch.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/main_modes/checkup.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/main_modes/execute.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/main_modes/list_dsn.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/main_modes/repl.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/output.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/__init__.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/batch_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/cli_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/completion_engine.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/filepaths.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/hybrid_redirection.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/interactive_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/key_binding_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/ptoolkit/__init__.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/ptoolkit/fzf.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/ptoolkit/history.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/ptoolkit/utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/special/__init__.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/special/dbcommands.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/special/delimitercommand.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/special/favoritequeries.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/special/iocommands.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/special/llm.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/special/main.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/sql_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/sqlresult.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/string_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/tabular_output/__init__.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/packages/tabular_output/sql_format.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/schema_prefetcher.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/sqlcompleter.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli/types.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli.egg-info/dependency_links.txt +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli.egg-info/entry_points.txt +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli.egg-info/requires.txt +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/mycli.egg-info/top_level.txt +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/pyproject.toml +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/setup.cfg +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/__init__.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/__init__.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/auto_vertical.feature +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/basic_commands.feature +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/connection.feature +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/crud_database.feature +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/crud_table.feature +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/db_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/environment.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/fixture_data/help_commands.txt +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/fixture_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/iocommands.feature +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/named_queries.feature +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/specials.feature +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/steps/__init__.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/steps/auto_vertical.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/steps/basic_commands.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/steps/connection.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/steps/crud_database.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/steps/crud_table.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/steps/iocommands.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/steps/named_queries.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/steps/specials.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/steps/utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/steps/wrappers.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/features/wrappager.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/mylogin.cnf +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/conftest.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_app_state.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_batch_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_checkup.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_cli_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_clibuffer.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_client.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_client_commands.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_client_query.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_clistyle.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_clitoolbar.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_completer_use_switch.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_completion_engine.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_completion_refresher.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_config.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_delimitercommand.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_dsn_aliases.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_favoritequeries.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_filepaths.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_hybrid_redirection.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_interactive_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_key_binding_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_key_bindings.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_lexer.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_main_modes_batch.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_main_modes_execute.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_main_modes_list_dsn.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_main_modes_repl.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_naive_completion.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_output.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_ptoolkit_fzf.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_ptoolkit_history.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_ptoolkit_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_schema_prefetcher.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_smart_completion_public_schema_only.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_special_dbcommands.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_special_init.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_special_llm.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_special_main.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_sql_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_sqlcompleter.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_sqlresult.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_string_utils.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/pytests/test_tabular_output.py +0 -0
- {mycli-2.1.1 → mycli-2.2.0}/test/utils.py +0 -0
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
24
24
|
steps:
|
|
25
25
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
26
26
|
|
|
27
|
-
- uses: astral-sh/setup-uv@
|
|
27
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
28
28
|
with:
|
|
29
29
|
version: "latest"
|
|
30
30
|
|
|
@@ -66,7 +66,7 @@ jobs:
|
|
|
66
66
|
steps:
|
|
67
67
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
68
68
|
|
|
69
|
-
- uses: astral-sh/setup-uv@
|
|
69
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
70
70
|
with:
|
|
71
71
|
version: "latest"
|
|
72
72
|
|
|
@@ -116,7 +116,7 @@ jobs:
|
|
|
116
116
|
steps:
|
|
117
117
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
118
118
|
|
|
119
|
-
- uses: astral-sh/setup-uv@
|
|
119
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
120
120
|
with:
|
|
121
121
|
version: 'latest'
|
|
122
122
|
|
|
@@ -219,7 +219,7 @@ jobs:
|
|
|
219
219
|
steps:
|
|
220
220
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
221
221
|
|
|
222
|
-
- uses: astral-sh/setup-uv@
|
|
222
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
223
223
|
with:
|
|
224
224
|
version: "latest"
|
|
225
225
|
|
|
@@ -29,7 +29,7 @@ jobs:
|
|
|
29
29
|
|
|
30
30
|
steps:
|
|
31
31
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
32
|
-
- uses: astral-sh/setup-uv@
|
|
32
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
33
33
|
with:
|
|
34
34
|
version: "latest"
|
|
35
35
|
|
|
@@ -74,7 +74,7 @@ jobs:
|
|
|
74
74
|
steps:
|
|
75
75
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
76
76
|
|
|
77
|
-
- uses: astral-sh/setup-uv@
|
|
77
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
78
78
|
with:
|
|
79
79
|
version: "latest"
|
|
80
80
|
|
|
@@ -124,7 +124,7 @@ jobs:
|
|
|
124
124
|
steps:
|
|
125
125
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
126
126
|
|
|
127
|
-
- uses: astral-sh/setup-uv@
|
|
127
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
128
128
|
with:
|
|
129
129
|
version: 'latest'
|
|
130
130
|
|
|
@@ -226,7 +226,7 @@ jobs:
|
|
|
226
226
|
|
|
227
227
|
steps:
|
|
228
228
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
229
|
-
- uses: astral-sh/setup-uv@
|
|
229
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
230
230
|
with:
|
|
231
231
|
version: "latest"
|
|
232
232
|
|
|
@@ -27,8 +27,11 @@ A command line client for MySQL with auto-completion and syntax highlighting.
|
|
|
27
27
|
├── mycli/lexer.py # extends `MySqlLexer` from Pygments
|
|
28
28
|
├── mycli/main.py # processes CLI arguments
|
|
29
29
|
├── mycli/main_modes/ # main execution paths
|
|
30
|
-
├── mycli/main_modes/batch.py # batch mode
|
|
31
|
-
├── mycli/main_modes/checkup.py #
|
|
30
|
+
├── mycli/main_modes/batch.py # `--batch` mode
|
|
31
|
+
├── mycli/main_modes/checkup.py # `--checkup` mode
|
|
32
|
+
├── mycli/main_modes/execute.py # `--execute` mode
|
|
33
|
+
├── mycli/main_modes/list_dsn.py # `--list-dsn` mode
|
|
34
|
+
├── mycli/main_modes/repl.py # interactive REPL made
|
|
32
35
|
├── mycli/myclirc # project-level configuration file
|
|
33
36
|
├── mycli/output.py # `OutputMixin` mixin for feedback and output of query results
|
|
34
37
|
├── mycli/packages/ # application packages
|
|
@@ -48,13 +51,14 @@ A command line client for MySQL with auto-completion and syntax highlighting.
|
|
|
48
51
|
├── mycli/schema_prefetcher.py # background prefetcher for multi-schema auto-completion
|
|
49
52
|
├── mycli/sqlcompleter.py # offers SQL completions
|
|
50
53
|
├── mycli/sqlexecute.py # runs SQL queries
|
|
54
|
+
├── mycli/types.py # shared types
|
|
55
|
+
├── mycli/vault.py # Vault integration
|
|
51
56
|
├── test/conftest.py # pytest configuration
|
|
52
57
|
├── test/features/ # behave tests
|
|
53
58
|
├── test/myclirc # mycli configuration used for tests
|
|
54
59
|
├── test/mylogin.cnf # `mylogin.cnf` example used for tests
|
|
55
60
|
├── test/pytests/ # pytest tests
|
|
56
|
-
|
|
57
|
-
└── types.py # shared types
|
|
61
|
+
└── test/utils.py # shared utilities for tests
|
|
58
62
|
|
|
59
63
|
## Development
|
|
60
64
|
|
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
2.2.0 (2026/07/11)
|
|
2
|
+
==============
|
|
3
|
+
|
|
4
|
+
Breaking Changes
|
|
5
|
+
---------
|
|
6
|
+
* `ssl` is no longer supported as a DSN parameter. Use `ssl_mode` instead.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Features
|
|
10
|
+
---------
|
|
11
|
+
* Configurable tunnel method for `--ssh-jump`, making UDS usually default.
|
|
12
|
+
* Add `--ssh-options` CLI argument to pass extra options with `--ssh-jump`.
|
|
13
|
+
* Make `ssh_jump` a DSN query parameter.
|
|
14
|
+
* Warn on unknown DSN query parameters.
|
|
15
|
+
* Experimental: reading credentials from Vault using the `vault kv get` CLI.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
Bug Fixes
|
|
19
|
+
---------
|
|
20
|
+
* Allow `/dsn` command to show correct values with `--ssh-jump`.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
Documentation
|
|
24
|
+
---------
|
|
25
|
+
* Add `--ssh-jump` to TIPS.
|
|
26
|
+
* Better document `/dsn show`.
|
|
27
|
+
* Improve `ssh_options` commentary.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
Internal
|
|
31
|
+
---------
|
|
32
|
+
* Make flaky `/watch` test more lenient for CI.
|
|
33
|
+
* Tweak `AGENTS.md` project structure.
|
|
34
|
+
|
|
35
|
+
|
|
1
36
|
2.1.1 (2026/07/08)
|
|
2
37
|
==============
|
|
3
38
|
|
|
@@ -9,18 +9,39 @@ from urllib.parse import parse_qs, unquote, urlparse
|
|
|
9
9
|
import click
|
|
10
10
|
|
|
11
11
|
from mycli.config import str_to_bool
|
|
12
|
-
from mycli.constants import EMPTY_PASSWORD_FLAG_SENTINEL
|
|
12
|
+
from mycli.constants import EMPTY_PASSWORD_FLAG_SENTINEL
|
|
13
13
|
from mycli.main_modes.batch import main_batch_from_stdin, main_batch_with_progress_bar, main_batch_without_progress_bar
|
|
14
14
|
from mycli.main_modes.checkup import main_checkup
|
|
15
15
|
from mycli.main_modes.execute import main_execute_from_cli
|
|
16
16
|
from mycli.main_modes.list_dsn import main_list_dsn
|
|
17
17
|
from mycli.packages.cli_utils import is_valid_connection_scheme
|
|
18
|
+
from mycli.vault import (
|
|
19
|
+
DEFAULT_VAULT_EXECUTABLE,
|
|
20
|
+
DEFAULT_VAULT_PASSWORD_FIELD,
|
|
21
|
+
DEFAULT_VAULT_USERNAME_FIELD,
|
|
22
|
+
VaultError,
|
|
23
|
+
get_field_from_vault,
|
|
24
|
+
)
|
|
18
25
|
|
|
19
26
|
if TYPE_CHECKING:
|
|
20
27
|
from mycli.main import CliArgs
|
|
21
28
|
|
|
22
29
|
ClientFactory = Callable[..., Any]
|
|
23
30
|
ENV_VAR_PATTERN = re.compile(r'^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$')
|
|
31
|
+
KNOWN_DSN_QUERY_PARAMS = {
|
|
32
|
+
'character_set',
|
|
33
|
+
'keepalive_ticks',
|
|
34
|
+
'socket',
|
|
35
|
+
'ssh_jump',
|
|
36
|
+
'ssl_ca',
|
|
37
|
+
'ssl_capath',
|
|
38
|
+
'ssl_cert',
|
|
39
|
+
'ssl_cipher',
|
|
40
|
+
'ssl_key',
|
|
41
|
+
'ssl_mode',
|
|
42
|
+
'ssl_verify_server_cert',
|
|
43
|
+
'tls_version',
|
|
44
|
+
}
|
|
24
45
|
|
|
25
46
|
|
|
26
47
|
class DsnAliasEnvVarError(ValueError):
|
|
@@ -79,7 +100,10 @@ def expand_dsn_alias_env_vars(
|
|
|
79
100
|
except ValueError as exc:
|
|
80
101
|
raise DsnAliasEnvVarError(f'Port in DSN alias {alias_name} must be an integer.') from exc
|
|
81
102
|
|
|
82
|
-
params = {
|
|
103
|
+
params = {
|
|
104
|
+
key: [expand_dsn_alias_env_var(value, alias_name) or '' for value in values]
|
|
105
|
+
for key, values in parse_qs(uri.query, keep_blank_values=True).items()
|
|
106
|
+
}
|
|
83
107
|
|
|
84
108
|
return (
|
|
85
109
|
expand_dsn_alias_env_var(unquote(username) if username is not None else None, alias_name),
|
|
@@ -200,7 +224,7 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
|
|
|
200
224
|
dsn_host = uri.hostname
|
|
201
225
|
dsn_port = uri.port
|
|
202
226
|
dsn_database = uri.path[1:]
|
|
203
|
-
dsn_params = parse_qs(uri.query) if uri.query else {}
|
|
227
|
+
dsn_params = parse_qs(uri.query, keep_blank_values=True) if uri.query else {}
|
|
204
228
|
|
|
205
229
|
if not database:
|
|
206
230
|
database = dsn_database
|
|
@@ -214,16 +238,13 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
|
|
|
214
238
|
if not cli_args.port:
|
|
215
239
|
cli_args.port = dsn_port
|
|
216
240
|
|
|
217
|
-
if
|
|
241
|
+
if unknown_dsn_params := sorted(set(dsn_params) - KNOWN_DSN_QUERY_PARAMS):
|
|
218
242
|
click.secho(
|
|
219
|
-
'Warning:
|
|
220
|
-
'Please use the "ssl_mode" parameter instead. '
|
|
221
|
-
f'See issue {ISSUES_URL}/1507',
|
|
243
|
+
f'Warning: Ignored unknown DSN URI query parameters: {", ".join(unknown_dsn_params)}.',
|
|
222
244
|
err=True,
|
|
223
245
|
fg='yellow',
|
|
224
246
|
)
|
|
225
|
-
|
|
226
|
-
cli_args.ssl_mode = 'on'
|
|
247
|
+
|
|
227
248
|
if params := dsn_params.get('ssl_mode'):
|
|
228
249
|
cli_args.ssl_mode = cli_args.ssl_mode or params[0]
|
|
229
250
|
if params := dsn_params.get('ssl_ca'):
|
|
@@ -254,6 +275,8 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
|
|
|
254
275
|
cli_args.keepalive_ticks = int(params[0])
|
|
255
276
|
if params := dsn_params.get('character_set'):
|
|
256
277
|
cli_args.character_set = cli_args.character_set or params[0]
|
|
278
|
+
if params := dsn_params.get('ssh_jump'):
|
|
279
|
+
cli_args.ssh_jump = cli_args.ssh_jump or params[0]
|
|
257
280
|
|
|
258
281
|
keepalive_ticks = cli_args.keepalive_ticks if cli_args.keepalive_ticks is not None else mycli.default_keepalive_ticks
|
|
259
282
|
ssl_mode = cli_args.ssl_mode or mycli.ssl_mode
|
|
@@ -326,11 +349,51 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
|
|
|
326
349
|
use_keyring = str_to_bool(cli_args.use_keyring)
|
|
327
350
|
reset_keyring = False
|
|
328
351
|
|
|
352
|
+
if cli_args.password is None and cli_args.vault_secret:
|
|
353
|
+
vault_config = mycli.config.get('vault_beta', {})
|
|
354
|
+
vault_address = cli_args.vault_address or os.environ.get('VAULT_ADDR') or vault_config.get('address') or None
|
|
355
|
+
vault_mount = cli_args.vault_mount or vault_config.get('default_mount') or None
|
|
356
|
+
vault_field = cli_args.vault_password_field or vault_config.get('default_password_field') or DEFAULT_VAULT_PASSWORD_FIELD
|
|
357
|
+
vault_executable = vault_config.get('vault_executable') or DEFAULT_VAULT_EXECUTABLE
|
|
358
|
+
try:
|
|
359
|
+
vault_password: str | None = get_field_from_vault(
|
|
360
|
+
vault_field,
|
|
361
|
+
cli_args.vault_secret,
|
|
362
|
+
executable=vault_executable,
|
|
363
|
+
mount=vault_mount,
|
|
364
|
+
address=vault_address,
|
|
365
|
+
)
|
|
366
|
+
except VaultError as exc:
|
|
367
|
+
click.secho(f'Error reading password from Vault: {exc}', err=True, fg='red')
|
|
368
|
+
sys.exit(1)
|
|
369
|
+
else:
|
|
370
|
+
vault_password = None
|
|
371
|
+
|
|
372
|
+
if cli_args.user is None and cli_args.vault_secret:
|
|
373
|
+
vault_config = mycli.config.get('vault_beta', {})
|
|
374
|
+
vault_address = cli_args.vault_address or os.environ.get('VAULT_ADDR') or vault_config.get('address') or None
|
|
375
|
+
vault_mount = cli_args.vault_mount or vault_config.get('default_mount') or None
|
|
376
|
+
vault_field = cli_args.vault_username_field or vault_config.get('default_username_field') or DEFAULT_VAULT_USERNAME_FIELD
|
|
377
|
+
vault_executable = vault_config.get('vault_executable') or DEFAULT_VAULT_EXECUTABLE
|
|
378
|
+
try:
|
|
379
|
+
vault_username = get_field_from_vault(
|
|
380
|
+
vault_field,
|
|
381
|
+
cli_args.vault_secret,
|
|
382
|
+
executable=vault_executable,
|
|
383
|
+
mount=vault_mount,
|
|
384
|
+
address=vault_address,
|
|
385
|
+
)
|
|
386
|
+
except VaultError as exc:
|
|
387
|
+
click.secho(f'Error reading username from Vault: {exc}', err=True, fg='red')
|
|
388
|
+
sys.exit(1)
|
|
389
|
+
else:
|
|
390
|
+
vault_username = None
|
|
391
|
+
|
|
329
392
|
try:
|
|
330
393
|
mycli.connect(
|
|
331
394
|
database=database,
|
|
332
|
-
user=cli_args.user,
|
|
333
|
-
passwd=cli_args.password,
|
|
395
|
+
user=vault_username if cli_args.user is None else cli_args.user,
|
|
396
|
+
passwd=vault_password if cli_args.password is None else cli_args.password,
|
|
334
397
|
host=cli_args.host,
|
|
335
398
|
port=cli_args.port,
|
|
336
399
|
socket=cli_args.socket,
|
|
@@ -343,6 +406,7 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
|
|
|
343
406
|
reset_keyring=reset_keyring,
|
|
344
407
|
keepalive_ticks=keepalive_ticks,
|
|
345
408
|
ssh_jump=cli_args.ssh_jump,
|
|
409
|
+
ssh_cli_options=cli_args.ssh_options,
|
|
346
410
|
)
|
|
347
411
|
|
|
348
412
|
if combined_init_cmd:
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
5
|
import traceback
|
|
6
|
-
from typing import TYPE_CHECKING, Any
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
7
7
|
from urllib.parse import quote as urlquote
|
|
8
8
|
|
|
9
9
|
import click
|
|
@@ -22,6 +22,7 @@ from mycli.constants import (
|
|
|
22
22
|
ER_MUST_CHANGE_PASSWORD_LOGIN,
|
|
23
23
|
)
|
|
24
24
|
from mycli.packages.filepaths import guess_socket_location
|
|
25
|
+
from mycli.packages.special.utils import format_connection_dsn
|
|
25
26
|
from mycli.sqlexecute import SQLExecute
|
|
26
27
|
from mycli.ssh_tunnel import SshTunnel, SshTunnelError
|
|
27
28
|
|
|
@@ -61,6 +62,7 @@ class ClientConnectionMixin:
|
|
|
61
62
|
reset_keyring: bool | None = None,
|
|
62
63
|
keepalive_ticks: int | None = None,
|
|
63
64
|
ssh_jump: str | None = None,
|
|
65
|
+
ssh_cli_options: str | None = None,
|
|
64
66
|
) -> None:
|
|
65
67
|
mylogin_cnf: dict[str, Any] = self.read_mylogin_cnf(self.mylogin_cnf)
|
|
66
68
|
# Fall back to .mylogin.cnf values only if user did not specify a value.
|
|
@@ -83,7 +85,8 @@ class ClientConnectionMixin:
|
|
|
83
85
|
remote_port = int_port or DEFAULT_PORT
|
|
84
86
|
remote_socket = socket or None
|
|
85
87
|
ssh_executable = self.config.get('ssh', {}).get('ssh_executable', 'ssh') or 'ssh'
|
|
86
|
-
|
|
88
|
+
ssh_config_options = self.config.get('ssh', {}).get('ssh_options')
|
|
89
|
+
tunnel_method: Literal['auto', 'socket', 'port'] = self.config.get('ssh', {}).get('tunnel_method', 'auto').lower() or 'auto'
|
|
87
90
|
try:
|
|
88
91
|
self.ssh_tunnel = SshTunnel.from_target(
|
|
89
92
|
ssh_jump,
|
|
@@ -91,7 +94,9 @@ class ClientConnectionMixin:
|
|
|
91
94
|
remote_port=int(remote_port),
|
|
92
95
|
remote_socket=remote_socket,
|
|
93
96
|
ssh_executable=ssh_executable,
|
|
94
|
-
|
|
97
|
+
ssh_config_options=ssh_config_options,
|
|
98
|
+
ssh_cli_options=ssh_cli_options,
|
|
99
|
+
tunnel_method=tunnel_method,
|
|
95
100
|
)
|
|
96
101
|
self.ssh_tunnel.start()
|
|
97
102
|
except (OSError, ValueError, SshTunnelError) as exc:
|
|
@@ -161,8 +166,9 @@ class ClientConnectionMixin:
|
|
|
161
166
|
# 2. --password-file CLI option
|
|
162
167
|
# 3. envvar (MYSQL_PWD)
|
|
163
168
|
# 4. DSN (mysql://user:password)
|
|
164
|
-
# 5.
|
|
165
|
-
# 6.
|
|
169
|
+
# 5. Vault
|
|
170
|
+
# 6. .mylogin.cnf
|
|
171
|
+
# 7. keyring
|
|
166
172
|
|
|
167
173
|
ssh_tunnel_field = urlquote(ssh_jump or '')
|
|
168
174
|
if ssh_tunnel_field:
|
|
@@ -184,6 +190,18 @@ class ClientConnectionMixin:
|
|
|
184
190
|
# should not fail, but will help the typechecker
|
|
185
191
|
assert not isinstance(passwd, int)
|
|
186
192
|
|
|
193
|
+
display_dsn = None
|
|
194
|
+
if self.ssh_tunnel:
|
|
195
|
+
display_dsn = format_connection_dsn(
|
|
196
|
+
user=user,
|
|
197
|
+
host=remote_host,
|
|
198
|
+
port=remote_port,
|
|
199
|
+
socket=remote_socket,
|
|
200
|
+
database=database,
|
|
201
|
+
character_set=character_set,
|
|
202
|
+
ssh_jump=ssh_jump,
|
|
203
|
+
)
|
|
204
|
+
|
|
187
205
|
connection_info: dict[str, Any] = {
|
|
188
206
|
'database': database,
|
|
189
207
|
'user': user,
|
|
@@ -193,8 +211,13 @@ class ClientConnectionMixin:
|
|
|
193
211
|
'ssl': ssl_config,
|
|
194
212
|
'init_command': init_command,
|
|
195
213
|
'unbuffered': unbuffered,
|
|
214
|
+
'display_dsn': display_dsn,
|
|
196
215
|
}
|
|
197
|
-
if self.ssh_tunnel:
|
|
216
|
+
if self.ssh_tunnel and self.ssh_tunnel.local_socket:
|
|
217
|
+
connection_info['host'] = None
|
|
218
|
+
connection_info['port'] = None
|
|
219
|
+
connection_info['socket'] = self.ssh_tunnel.local_socket
|
|
220
|
+
elif self.ssh_tunnel:
|
|
198
221
|
connection_info['host'] = self.ssh_tunnel.local_host
|
|
199
222
|
connection_info['port'] = self.ssh_tunnel.local_port
|
|
200
223
|
connection_info['socket'] = None
|
|
@@ -89,6 +89,26 @@ class CliArgs:
|
|
|
89
89
|
type=click.Path(),
|
|
90
90
|
help='File or FIFO path containing the password to connect to the db if not specified otherwise.',
|
|
91
91
|
)
|
|
92
|
+
vault_address: str | None = clickdc.option(
|
|
93
|
+
type=str,
|
|
94
|
+
help='EXPERIMENTAL "vault kv get" integration: value for $VAULT_ADDR if unset in the environment or ~/.myclirc.',
|
|
95
|
+
)
|
|
96
|
+
vault_mount: str | None = clickdc.option(
|
|
97
|
+
type=str,
|
|
98
|
+
help='EXPERIMENTAL "vault kv get" integration: value for -mount if unset in ~/.myclirc.',
|
|
99
|
+
)
|
|
100
|
+
vault_secret: str | None = clickdc.option(
|
|
101
|
+
type=str,
|
|
102
|
+
help='EXPERIMENTAL "vault kv get" integration: secret name.',
|
|
103
|
+
)
|
|
104
|
+
vault_password_field: str | None = clickdc.option(
|
|
105
|
+
type=str,
|
|
106
|
+
help='EXPERIMENTAL "vault kv get" integration: field containing the password.',
|
|
107
|
+
)
|
|
108
|
+
vault_username_field: str | None = clickdc.option(
|
|
109
|
+
type=str,
|
|
110
|
+
help='EXPERIMENTAL "vault kv get" integration: field containing the username.',
|
|
111
|
+
)
|
|
92
112
|
ssl_mode: str = clickdc.option(
|
|
93
113
|
type=click.Choice(['auto', 'on', 'off']),
|
|
94
114
|
help='Set desired SSL behavior. auto=preferred if TCP/IP, on=required, off=off.',
|
|
@@ -275,6 +295,10 @@ class CliArgs:
|
|
|
275
295
|
type=str,
|
|
276
296
|
help='Open an SSH tunnel via [user@]host[:port] and connect to MySQL through it.',
|
|
277
297
|
)
|
|
298
|
+
ssh_options: str | None = clickdc.option(
|
|
299
|
+
type=str,
|
|
300
|
+
help='Extra CLI arguments for SSH with --ssh-jump, placed after options from myclirc.',
|
|
301
|
+
)
|
|
278
302
|
checkup: bool = clickdc.option(
|
|
279
303
|
is_flag=True,
|
|
280
304
|
help='Run a checkup on your configuration.',
|
|
@@ -305,6 +305,34 @@ vi_ttimeoutlen = 0.1
|
|
|
305
305
|
# How long to wait for an Escape key sequence in Emacs mode.
|
|
306
306
|
emacs_ttimeoutlen = 0.5
|
|
307
307
|
|
|
308
|
+
[ssh]
|
|
309
|
+
# Path to the ssh executable used by --ssh-jump.
|
|
310
|
+
ssh_executable = ssh
|
|
311
|
+
|
|
312
|
+
# Options to pass to the ssh executable when making a tunnel.
|
|
313
|
+
# The defaults may need to be changed if not using OpenSSH.
|
|
314
|
+
ssh_options = -a -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes -o IPQoS=af13 -o LogLevel=FATAL
|
|
315
|
+
|
|
316
|
+
# Method for establishing the local side of the SSH tunnel: auto, port, socket.
|
|
317
|
+
# auto means: port if on Windows, socket otherwise.
|
|
318
|
+
tunnel_method = auto
|
|
319
|
+
|
|
320
|
+
[vault_beta]
|
|
321
|
+
# Path to the vault executable used for Vault integration.
|
|
322
|
+
vault_executable = vault
|
|
323
|
+
|
|
324
|
+
# URL for VAULT_ADDR, if the environment variable is unset.
|
|
325
|
+
address =
|
|
326
|
+
|
|
327
|
+
# Default mount, which can be overridden by --vault-mount at the CLI.
|
|
328
|
+
default_mount =
|
|
329
|
+
|
|
330
|
+
# Field/property containing the password, if --vault-password-field is not provided.
|
|
331
|
+
default_password_field = password
|
|
332
|
+
|
|
333
|
+
# Field/property containing the username, if --vault-username-field is not provided.
|
|
334
|
+
default_username_field = username
|
|
335
|
+
|
|
308
336
|
# Custom colors for the completion menu, toolbar, etc, with actual support
|
|
309
337
|
# depending on the terminal, and the property being set.
|
|
310
338
|
# Colors: #ffffff, bg:#ffffff, border:#ffffff.
|
|
@@ -407,10 +435,3 @@ matching-bracket.other = '#000000 bg:#aacccc'
|
|
|
407
435
|
[alias_dsn.init-commands]
|
|
408
436
|
# Define one or more SQL statements per alias (semicolon-separated).
|
|
409
437
|
# example_dsn = "SET sql_select_limit=1000; SET time_zone='+00:00'"
|
|
410
|
-
|
|
411
|
-
[ssh]
|
|
412
|
-
# Path to the ssh executable used by --ssh-jump.
|
|
413
|
-
ssh_executable = ssh
|
|
414
|
-
|
|
415
|
-
# options to pass to the ssh executable when making a tunnel
|
|
416
|
-
ssh_options = -a -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes -o IPQoS=af13 -o LogLevel=FATAL
|
|
@@ -147,21 +147,45 @@ def get_local_timezone() -> str:
|
|
|
147
147
|
return datetime.datetime.now().astimezone().tzname() or ''
|
|
148
148
|
|
|
149
149
|
|
|
150
|
-
def
|
|
151
|
-
|
|
152
|
-
user
|
|
153
|
-
host
|
|
154
|
-
port
|
|
155
|
-
|
|
150
|
+
def format_connection_dsn(
|
|
151
|
+
*,
|
|
152
|
+
user: str | None,
|
|
153
|
+
host: str | None,
|
|
154
|
+
port: int | None,
|
|
155
|
+
database: str | None,
|
|
156
|
+
socket: str | None,
|
|
157
|
+
character_set: str | None,
|
|
158
|
+
ssh_jump: str | None = None,
|
|
159
|
+
) -> str:
|
|
160
|
+
user = urlquote(user or '')
|
|
161
|
+
host = host or 'localhost'
|
|
162
|
+
port_part = f':{port}' if port is not None else ''
|
|
163
|
+
db = urlquote(database or '')
|
|
156
164
|
if db:
|
|
157
165
|
db = f'/{db}'
|
|
158
166
|
query_part = {}
|
|
159
|
-
if
|
|
160
|
-
query_part['socket'] =
|
|
161
|
-
|
|
162
|
-
if
|
|
163
|
-
query_part['character_set'] =
|
|
164
|
-
|
|
167
|
+
if socket:
|
|
168
|
+
query_part['socket'] = socket
|
|
169
|
+
port_part = ''
|
|
170
|
+
if character_set and character_set != 'utf8mb4':
|
|
171
|
+
query_part['character_set'] = character_set
|
|
172
|
+
if ssh_jump:
|
|
173
|
+
query_part['ssh_jump'] = ssh_jump
|
|
174
|
+
dsn = f'mysql://{user}@{host}{port_part}{db}'
|
|
165
175
|
if query_part:
|
|
166
176
|
dsn += '?' + urlencode(query_part)
|
|
167
177
|
return dsn
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def compute_current_dsn(cur: Cursor) -> str:
|
|
181
|
+
conn = cur.connection
|
|
182
|
+
if display_dsn := getattr(conn, '_mycli_display_dsn', None):
|
|
183
|
+
return display_dsn
|
|
184
|
+
return format_connection_dsn(
|
|
185
|
+
user=conn.user.decode() if isinstance(conn.user, bytes) else conn.user,
|
|
186
|
+
host=conn.host,
|
|
187
|
+
port=conn.port,
|
|
188
|
+
database=conn.db.decode() if isinstance(conn.db, bytes) else conn.db,
|
|
189
|
+
socket=getattr(conn, 'unix_socket', None),
|
|
190
|
+
character_set=getattr(conn, 'charset', None),
|
|
191
|
+
)
|
|
@@ -167,6 +167,7 @@ class SQLExecute:
|
|
|
167
167
|
ssl: dict[str, Any] | None,
|
|
168
168
|
init_command: str | None = None,
|
|
169
169
|
unbuffered: bool | None = None,
|
|
170
|
+
display_dsn: str | None = None,
|
|
170
171
|
) -> None:
|
|
171
172
|
self.dbname = database
|
|
172
173
|
self.user = user
|
|
@@ -177,6 +178,7 @@ class SQLExecute:
|
|
|
177
178
|
self.character_set = character_set
|
|
178
179
|
self.local_infile = local_infile
|
|
179
180
|
self.ssl = ssl
|
|
181
|
+
self.display_dsn = display_dsn
|
|
180
182
|
self.server_info: ServerInfo | None = None
|
|
181
183
|
self.connection_id: int | None = None
|
|
182
184
|
self.init_command = init_command
|
|
@@ -295,6 +297,8 @@ class SQLExecute:
|
|
|
295
297
|
except pymysql.err.Error:
|
|
296
298
|
pass
|
|
297
299
|
self.conn = conn
|
|
300
|
+
if self.display_dsn is not None:
|
|
301
|
+
self.conn._mycli_display_dsn = self.display_dsn # type: ignore[attr-defined]
|
|
298
302
|
# Update them after the connection is made to ensure that it was a
|
|
299
303
|
# successful connection.
|
|
300
304
|
self.dbname = db
|