mycli 2.0.0__tar.gz → 2.1.1__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.0.0 → mycli-2.1.1}/.github/workflows/ci.yml +4 -4
- {mycli-2.0.0 → mycli-2.1.1}/.github/workflows/lint.yml +2 -2
- {mycli-2.0.0 → mycli-2.1.1}/.github/workflows/publish.yml +4 -4
- {mycli-2.0.0 → mycli-2.1.1}/.github/workflows/typecheck.yml +1 -1
- {mycli-2.0.0/mycli.egg-info → mycli-2.1.1}/PKG-INFO +1 -1
- {mycli-2.0.0 → mycli-2.1.1}/changelog.md +21 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/TIPS +3 -1
- {mycli-2.0.0 → mycli-2.1.1}/mycli/cli_runner.py +57 -39
- {mycli-2.0.0 → mycli-2.1.1}/mycli/client.py +16 -2
- {mycli-2.0.0 → mycli-2.1.1}/mycli/client_connection.py +52 -13
- {mycli-2.0.0 → mycli-2.1.1}/mycli/main.py +15 -11
- {mycli-2.0.0 → mycli-2.1.1}/mycli/myclirc +13 -2
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/cli_utils.py +2 -6
- mycli-2.1.1/mycli/packages/special/dsn_aliases.py +60 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/special/favoritequeries.py +4 -4
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/special/iocommands.py +47 -1
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/special/llm.py +6 -6
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/special/utils.py +22 -0
- mycli-2.1.1/mycli/ssh_tunnel.py +155 -0
- {mycli-2.0.0 → mycli-2.1.1/mycli.egg-info}/PKG-INFO +1 -1
- {mycli-2.0.0 → mycli-2.1.1}/mycli.egg-info/SOURCES.txt +4 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli.egg-info/scm_file_list.json +4 -0
- mycli-2.1.1/mycli.egg-info/scm_version.json +8 -0
- mycli-2.1.1/test/features/fixture_data/help_commands.txt +39 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/myclirc +13 -2
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_cli_runner.py +56 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_cli_utils.py +2 -2
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_client.py +17 -1
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_client_connection.py +136 -0
- mycli-2.1.1/test/pytests/test_dsn_aliases.py +102 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_main.py +33 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_smart_completion_public_schema_only.py +4 -1
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_special_iocommands.py +104 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_special_utils.py +44 -0
- mycli-2.1.1/test/pytests/test_ssh_tunnel.py +307 -0
- mycli-2.0.0/mycli.egg-info/scm_version.json +0 -8
- mycli-2.0.0/test/features/fixture_data/help_commands.txt +0 -38
- {mycli-2.0.0 → mycli-2.1.1}/.git-blame-ignore-revs +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/.github/dependabot.yml +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/.gitignore +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/AGENTS.md +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/AUTHORS.rst +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/CONTRIBUTING.md +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/LICENSE.txt +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/MANIFEST.in +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/README.md +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/SPONSORS.rst +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/doc/key_bindings.rst +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/doc/llm.md +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/doc/screenshots/main.gif +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/doc/screenshots/tables.png +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/AUTHORS +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/SPONSORS +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/__init__.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/app_state.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/clibuffer.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/client_commands.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/client_query.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/clistyle.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/clitoolbar.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/compat.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/completion_refresher.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/config.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/constants.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/key_bindings.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/lexer.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/main_modes/batch.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/main_modes/checkup.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/main_modes/execute.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/main_modes/list_dsn.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/main_modes/repl.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/output.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/__init__.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/batch_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/completion_engine.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/filepaths.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/hybrid_redirection.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/interactive_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/key_binding_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/ptoolkit/__init__.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/ptoolkit/fzf.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/ptoolkit/history.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/ptoolkit/utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/special/__init__.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/special/dbcommands.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/special/delimitercommand.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/special/main.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/sql_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/sqlresult.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/string_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/tabular_output/__init__.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/packages/tabular_output/sql_format.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/schema_prefetcher.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/sqlcompleter.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/sqlexecute.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli/types.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli.egg-info/dependency_links.txt +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli.egg-info/entry_points.txt +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli.egg-info/requires.txt +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/mycli.egg-info/top_level.txt +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/pyproject.toml +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/setup.cfg +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/__init__.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/__init__.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/auto_vertical.feature +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/basic_commands.feature +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/connection.feature +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/crud_database.feature +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/crud_table.feature +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/db_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/environment.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/fixture_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/iocommands.feature +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/named_queries.feature +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/specials.feature +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/steps/__init__.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/steps/auto_vertical.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/steps/basic_commands.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/steps/connection.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/steps/crud_database.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/steps/crud_table.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/steps/iocommands.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/steps/named_queries.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/steps/specials.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/steps/utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/steps/wrappers.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/features/wrappager.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/mylogin.cnf +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/conftest.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_app_state.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_batch_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_checkup.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_clibuffer.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_client_commands.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_client_query.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_clistyle.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_clitoolbar.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_completer_use_switch.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_completion_engine.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_completion_refresher.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_config.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_delimitercommand.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_favoritequeries.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_filepaths.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_hybrid_redirection.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_interactive_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_key_binding_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_key_bindings.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_lexer.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_main_modes_batch.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_main_modes_execute.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_main_modes_list_dsn.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_main_modes_repl.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_naive_completion.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_output.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_ptoolkit_fzf.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_ptoolkit_history.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_ptoolkit_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_schema_prefetcher.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_special_dbcommands.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_special_init.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_special_llm.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_special_main.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_sql_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_sqlcompleter.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_sqlexecute.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_sqlresult.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_string_utils.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/test/pytests/test_tabular_output.py +0 -0
- {mycli-2.0.0 → mycli-2.1.1}/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@f98e06938123ccabd21905ea5d0069192241f9f1 # v8.3.1
|
|
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@f98e06938123ccabd21905ea5d0069192241f9f1 # v8.3.1
|
|
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@f98e06938123ccabd21905ea5d0069192241f9f1 # v8.3.1
|
|
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@f98e06938123ccabd21905ea5d0069192241f9f1 # v8.3.1
|
|
223
223
|
with:
|
|
224
224
|
version: "latest"
|
|
225
225
|
|
|
@@ -21,9 +21,9 @@ jobs:
|
|
|
21
21
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
22
22
|
|
|
23
23
|
- name: Run ruff check
|
|
24
|
-
uses: astral-sh/ruff-action@
|
|
24
|
+
uses: astral-sh/ruff-action@278981a28ce3188b1e39527901f38254bf3aac89 # v4.1.0
|
|
25
25
|
|
|
26
26
|
- name: Run ruff format
|
|
27
|
-
uses: astral-sh/ruff-action@
|
|
27
|
+
uses: astral-sh/ruff-action@278981a28ce3188b1e39527901f38254bf3aac89 # v4.1.0
|
|
28
28
|
with:
|
|
29
29
|
args: 'format --check'
|
|
@@ -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@f98e06938123ccabd21905ea5d0069192241f9f1 # v8.3.1
|
|
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@f98e06938123ccabd21905ea5d0069192241f9f1 # v8.3.1
|
|
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@f98e06938123ccabd21905ea5d0069192241f9f1 # v8.3.1
|
|
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@f98e06938123ccabd21905ea5d0069192241f9f1 # v8.3.1
|
|
230
230
|
with:
|
|
231
231
|
version: "latest"
|
|
232
232
|
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
2.1.1 (2026/07/08)
|
|
2
|
+
==============
|
|
3
|
+
|
|
4
|
+
Breaking Changes
|
|
5
|
+
---------
|
|
6
|
+
* Remove undocumented support for `tcp://` and `socket://` DSNs.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Features
|
|
10
|
+
---------
|
|
11
|
+
* Add `--ssh-jump` CLI argument, restoring SSH jump functionality.
|
|
12
|
+
* Add a `/dsn` command to manage persisted DSN aliases.
|
|
13
|
+
* Add `auto` option to ignore keyring when inside an SSH session.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Documentation
|
|
17
|
+
---------
|
|
18
|
+
* Fix a typo in myclirc commentary.
|
|
19
|
+
* Advertise forward-slash command forms in more places.
|
|
20
|
+
|
|
21
|
+
|
|
1
22
|
2.0.0 (2026/07/03)
|
|
2
23
|
==============
|
|
3
24
|
|
|
@@ -20,7 +20,7 @@ the --character-set option sets the character set for a single session!
|
|
|
20
20
|
|
|
21
21
|
the --unbuffered flag can save memory when in batch mode!
|
|
22
22
|
|
|
23
|
-
--use-keyring=
|
|
23
|
+
--use-keyring=auto lets you access the system keyring for passwords!
|
|
24
24
|
|
|
25
25
|
--use-keyring=reset resets a password saved to the system keyring!
|
|
26
26
|
|
|
@@ -108,6 +108,8 @@ the /watch command executes a query every N seconds!
|
|
|
108
108
|
|
|
109
109
|
use /bug to file a bug on GitHub!
|
|
110
110
|
|
|
111
|
+
use /dsn to manage saved DSNs!
|
|
112
|
+
|
|
111
113
|
###
|
|
112
114
|
### environment variables
|
|
113
115
|
###
|
|
@@ -304,52 +304,70 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
|
|
|
304
304
|
if cli_args.use_keyring is not None and cli_args.use_keyring.lower() == 'reset':
|
|
305
305
|
use_keyring = True
|
|
306
306
|
reset_keyring = True
|
|
307
|
+
elif cli_args.use_keyring is not None and cli_args.use_keyring.lower() == 'auto':
|
|
308
|
+
if os.environ.get('SSH_CONNECTION'):
|
|
309
|
+
use_keyring = False
|
|
310
|
+
reset_keyring = False
|
|
311
|
+
else:
|
|
312
|
+
use_keyring = True
|
|
313
|
+
reset_keyring = False
|
|
307
314
|
elif cli_args.use_keyring is None:
|
|
308
|
-
|
|
309
|
-
|
|
315
|
+
if mycli.config['main'].get('use_keyring', 'False').lower() == 'auto':
|
|
316
|
+
if os.environ.get('SSH_CONNECTION'):
|
|
317
|
+
use_keyring = False
|
|
318
|
+
reset_keyring = False
|
|
319
|
+
else:
|
|
320
|
+
use_keyring = True
|
|
321
|
+
reset_keyring = False
|
|
322
|
+
else:
|
|
323
|
+
use_keyring = str_to_bool(mycli.config['main'].get('use_keyring', 'False'))
|
|
324
|
+
reset_keyring = False
|
|
310
325
|
else:
|
|
311
326
|
use_keyring = str_to_bool(cli_args.use_keyring)
|
|
312
327
|
reset_keyring = False
|
|
313
328
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
329
|
+
try:
|
|
330
|
+
mycli.connect(
|
|
331
|
+
database=database,
|
|
332
|
+
user=cli_args.user,
|
|
333
|
+
passwd=cli_args.password,
|
|
334
|
+
host=cli_args.host,
|
|
335
|
+
port=cli_args.port,
|
|
336
|
+
socket=cli_args.socket,
|
|
337
|
+
local_infile=cli_args.local_infile,
|
|
338
|
+
ssl=ssl,
|
|
339
|
+
init_command=combined_init_cmd,
|
|
340
|
+
unbuffered=cli_args.unbuffered,
|
|
341
|
+
character_set=cli_args.character_set,
|
|
342
|
+
use_keyring=use_keyring,
|
|
343
|
+
reset_keyring=reset_keyring,
|
|
344
|
+
keepalive_ticks=keepalive_ticks,
|
|
345
|
+
ssh_jump=cli_args.ssh_jump,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
if combined_init_cmd:
|
|
349
|
+
click.echo(f"Executing init-command: {combined_init_cmd}", err=True)
|
|
350
|
+
|
|
351
|
+
mycli.logger.debug(
|
|
352
|
+
"Launch Params: \n\tdatabase: %r\tuser: %r\thost: %r\tport: %r",
|
|
353
|
+
database,
|
|
354
|
+
cli_args.user,
|
|
355
|
+
cli_args.host,
|
|
356
|
+
cli_args.port,
|
|
357
|
+
)
|
|
341
358
|
|
|
342
|
-
|
|
343
|
-
|
|
359
|
+
if cli_args.execute is not None:
|
|
360
|
+
sys.exit(main_execute_from_cli(mycli, cli_args))
|
|
344
361
|
|
|
345
|
-
|
|
346
|
-
|
|
362
|
+
if cli_args.batch is not None and cli_args.batch != '-' and cli_args.progress and sys.stderr.isatty():
|
|
363
|
+
sys.exit(main_batch_with_progress_bar(mycli, cli_args))
|
|
347
364
|
|
|
348
|
-
|
|
349
|
-
|
|
365
|
+
if cli_args.batch is not None:
|
|
366
|
+
sys.exit(main_batch_without_progress_bar(mycli, cli_args))
|
|
350
367
|
|
|
351
|
-
|
|
352
|
-
|
|
368
|
+
if not sys.stdin.isatty():
|
|
369
|
+
sys.exit(main_batch_from_stdin(mycli, cli_args))
|
|
353
370
|
|
|
354
|
-
|
|
355
|
-
|
|
371
|
+
mycli.run_cli()
|
|
372
|
+
finally:
|
|
373
|
+
mycli.close()
|
|
@@ -35,11 +35,13 @@ from mycli.constants import DEFAULT_PROMPT
|
|
|
35
35
|
from mycli.main_modes import repl as repl_package
|
|
36
36
|
from mycli.output import OutputMixin
|
|
37
37
|
from mycli.packages import special
|
|
38
|
+
from mycli.packages.special.dsn_aliases import DsnAliases
|
|
38
39
|
from mycli.packages.special.favoritequeries import FavoriteQueries
|
|
39
40
|
from mycli.packages.tabular_output import sql_format
|
|
40
41
|
from mycli.schema_prefetcher import SchemaPrefetcher
|
|
41
42
|
from mycli.sqlcompleter import SQLCompleter
|
|
42
43
|
from mycli.sqlexecute import SQLExecute
|
|
44
|
+
from mycli.ssh_tunnel import SshTunnel
|
|
43
45
|
from mycli.types import Query
|
|
44
46
|
|
|
45
47
|
sqlparse.engine.grouping.MAX_GROUPING_DEPTH = None # type: ignore[assignment]
|
|
@@ -75,6 +77,7 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
|
|
|
75
77
|
cli_verbosity: int = 0,
|
|
76
78
|
) -> None:
|
|
77
79
|
self.sqlexecute = sqlexecute
|
|
80
|
+
self.ssh_tunnel: SshTunnel | None = None
|
|
78
81
|
self.logfile = logfile
|
|
79
82
|
self.login_path = login_path
|
|
80
83
|
self.toolbar_error_message: str | None = None
|
|
@@ -106,6 +109,7 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
|
|
|
106
109
|
self.default_keepalive_ticks = c['connection'].as_int('default_keepalive_ticks')
|
|
107
110
|
|
|
108
111
|
FavoriteQueries.instance = FavoriteQueries.from_config(self.config)
|
|
112
|
+
DsnAliases.instance = DsnAliases.from_config(self.config)
|
|
109
113
|
|
|
110
114
|
self.dsn_alias: str | None = None
|
|
111
115
|
self.main_formatter = TabularOutputFormatter(format_name=c["main"]["table_format"])
|
|
@@ -198,10 +202,20 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
|
|
|
198
202
|
special.set_destructive_keywords(self.destructive_keywords)
|
|
199
203
|
|
|
200
204
|
def close(self) -> None:
|
|
201
|
-
|
|
205
|
+
try:
|
|
202
206
|
self.schema_prefetcher.stop()
|
|
207
|
+
except Exception:
|
|
208
|
+
pass
|
|
203
209
|
if self.sqlexecute is not None:
|
|
204
|
-
|
|
210
|
+
try:
|
|
211
|
+
self.sqlexecute.close()
|
|
212
|
+
except Exception:
|
|
213
|
+
pass
|
|
214
|
+
if self.ssh_tunnel is not None:
|
|
215
|
+
try:
|
|
216
|
+
self.ssh_tunnel.close()
|
|
217
|
+
except Exception:
|
|
218
|
+
pass
|
|
205
219
|
|
|
206
220
|
def run_cli(self) -> None:
|
|
207
221
|
repl_package.main_repl(self)
|
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import sys
|
|
5
5
|
import traceback
|
|
6
6
|
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from urllib.parse import quote as urlquote
|
|
7
8
|
|
|
8
9
|
import click
|
|
9
10
|
import keyring
|
|
@@ -22,6 +23,7 @@ from mycli.constants import (
|
|
|
22
23
|
)
|
|
23
24
|
from mycli.packages.filepaths import guess_socket_location
|
|
24
25
|
from mycli.sqlexecute import SQLExecute
|
|
26
|
+
from mycli.ssh_tunnel import SshTunnel, SshTunnelError
|
|
25
27
|
|
|
26
28
|
try:
|
|
27
29
|
from pwd import getpwuid
|
|
@@ -58,6 +60,7 @@ class ClientConnectionMixin:
|
|
|
58
60
|
use_keyring: bool | None = None,
|
|
59
61
|
reset_keyring: bool | None = None,
|
|
60
62
|
keepalive_ticks: int | None = None,
|
|
63
|
+
ssh_jump: str | None = None,
|
|
61
64
|
) -> None:
|
|
62
65
|
mylogin_cnf: dict[str, Any] = self.read_mylogin_cnf(self.mylogin_cnf)
|
|
63
66
|
# Fall back to .mylogin.cnf values only if user did not specify a value.
|
|
@@ -67,6 +70,7 @@ class ClientConnectionMixin:
|
|
|
67
70
|
ssl_config: dict[str, Any] = ssl or {}
|
|
68
71
|
user_connection_config = self.config_without_package_defaults.get('connection', {})
|
|
69
72
|
self.keepalive_ticks = keepalive_ticks
|
|
73
|
+
self.ssh_tunnel = None
|
|
70
74
|
|
|
71
75
|
int_port = port and int(port)
|
|
72
76
|
if not int_port:
|
|
@@ -74,6 +78,31 @@ class ClientConnectionMixin:
|
|
|
74
78
|
if not host or host == DEFAULT_HOST:
|
|
75
79
|
socket = socket or user_connection_config.get("default_socket") or mylogin_cnf["socket"] or guess_socket_location()
|
|
76
80
|
|
|
81
|
+
if ssh_jump:
|
|
82
|
+
remote_host = host or DEFAULT_HOST
|
|
83
|
+
remote_port = int_port or DEFAULT_PORT
|
|
84
|
+
remote_socket = socket or None
|
|
85
|
+
ssh_executable = self.config.get('ssh', {}).get('ssh_executable', 'ssh') or 'ssh'
|
|
86
|
+
ssh_options = self.config.get('ssh', {}).get('ssh_options')
|
|
87
|
+
try:
|
|
88
|
+
self.ssh_tunnel = SshTunnel.from_target(
|
|
89
|
+
ssh_jump,
|
|
90
|
+
remote_host=remote_host,
|
|
91
|
+
remote_port=int(remote_port),
|
|
92
|
+
remote_socket=remote_socket,
|
|
93
|
+
ssh_executable=ssh_executable,
|
|
94
|
+
ssh_options=ssh_options,
|
|
95
|
+
)
|
|
96
|
+
self.ssh_tunnel.start()
|
|
97
|
+
except (OSError, ValueError, SshTunnelError) as exc:
|
|
98
|
+
click.secho(f'Error: Unable to start SSH tunnel: {exc}', err=True, fg='red')
|
|
99
|
+
try:
|
|
100
|
+
if self.ssh_tunnel:
|
|
101
|
+
self.ssh_tunnel.close()
|
|
102
|
+
except Exception:
|
|
103
|
+
pass
|
|
104
|
+
sys.exit(1)
|
|
105
|
+
|
|
77
106
|
passwd = passwd if isinstance(passwd, (str, int)) else mylogin_cnf["password"]
|
|
78
107
|
|
|
79
108
|
if not character_set:
|
|
@@ -135,7 +164,10 @@ class ClientConnectionMixin:
|
|
|
135
164
|
# 5. .mylogin.cnf
|
|
136
165
|
# 6. keyring
|
|
137
166
|
|
|
138
|
-
|
|
167
|
+
ssh_tunnel_field = urlquote(ssh_jump or '')
|
|
168
|
+
if ssh_tunnel_field:
|
|
169
|
+
ssh_tunnel_field = ':' + ssh_tunnel_field
|
|
170
|
+
keyring_identifier = f'{user}@{host}:{"" if socket else int_port}:{socket or ""}{ssh_tunnel_field}'
|
|
139
171
|
keyring_domain = 'mycli.net'
|
|
140
172
|
keyring_retrieved_cleanly = False
|
|
141
173
|
|
|
@@ -152,19 +184,24 @@ class ClientConnectionMixin:
|
|
|
152
184
|
# should not fail, but will help the typechecker
|
|
153
185
|
assert not isinstance(passwd, int)
|
|
154
186
|
|
|
155
|
-
connection_info: dict[
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
"ssl": ssl_config,
|
|
165
|
-
"init_command": init_command,
|
|
166
|
-
"unbuffered": unbuffered,
|
|
187
|
+
connection_info: dict[str, Any] = {
|
|
188
|
+
'database': database,
|
|
189
|
+
'user': user,
|
|
190
|
+
'password': passwd,
|
|
191
|
+
'character_set': character_set,
|
|
192
|
+
'local_infile': use_local_infile,
|
|
193
|
+
'ssl': ssl_config,
|
|
194
|
+
'init_command': init_command,
|
|
195
|
+
'unbuffered': unbuffered,
|
|
167
196
|
}
|
|
197
|
+
if self.ssh_tunnel:
|
|
198
|
+
connection_info['host'] = self.ssh_tunnel.local_host
|
|
199
|
+
connection_info['port'] = self.ssh_tunnel.local_port
|
|
200
|
+
connection_info['socket'] = None
|
|
201
|
+
else:
|
|
202
|
+
connection_info['host'] = host
|
|
203
|
+
connection_info['port'] = int_port
|
|
204
|
+
connection_info['socket'] = socket
|
|
168
205
|
|
|
169
206
|
def _update_keyring(password: str | None, keyring_retrieved_cleanly: bool):
|
|
170
207
|
if not password:
|
|
@@ -239,6 +276,8 @@ class ClientConnectionMixin:
|
|
|
239
276
|
socket_owner = getpwuid(os.stat(socket).st_uid).pw_name
|
|
240
277
|
except KeyError:
|
|
241
278
|
socket_owner = '<unknown>'
|
|
279
|
+
except FileNotFoundError:
|
|
280
|
+
socket_owner = '<unknown>'
|
|
242
281
|
self.echo(f"Connecting to socket {socket}, owned by user {socket_owner}", err=True)
|
|
243
282
|
try:
|
|
244
283
|
_connect(keyring_retrieved_cleanly=keyring_retrieved_cleanly)
|
|
@@ -263,14 +263,18 @@ class CliArgs:
|
|
|
263
263
|
help='Show progress on the standard error with --batch.',
|
|
264
264
|
)
|
|
265
265
|
use_keyring: str | None = clickdc.option(
|
|
266
|
-
type=click.Choice(['true', 'false', 'reset']),
|
|
266
|
+
type=click.Choice(['auto', 'true', 'false', 'reset']),
|
|
267
267
|
default=None,
|
|
268
|
-
help='Store and retrieve passwords from the system keyring
|
|
268
|
+
help='Store and retrieve passwords from the system keyring. auto means true, unless within an SSH connection.',
|
|
269
269
|
)
|
|
270
270
|
keepalive_ticks: int | None = clickdc.option(
|
|
271
271
|
type=int,
|
|
272
272
|
help='Send regular keepalive pings to the connection, roughly every <int> seconds.',
|
|
273
273
|
)
|
|
274
|
+
ssh_jump: str | None = clickdc.option(
|
|
275
|
+
type=str,
|
|
276
|
+
help='Open an SSH tunnel via [user@]host[:port] and connect to MySQL through it.',
|
|
277
|
+
)
|
|
274
278
|
checkup: bool = clickdc.option(
|
|
275
279
|
is_flag=True,
|
|
276
280
|
help='Run a checkup on your configuration.',
|
|
@@ -287,47 +291,47 @@ class CliArgs:
|
|
|
287
291
|
ssh_user: str | None = clickdc.option(
|
|
288
292
|
type=str,
|
|
289
293
|
hidden=True,
|
|
290
|
-
deprecated='No effect. See
|
|
294
|
+
deprecated='No effect. See --ssh-jump.',
|
|
291
295
|
)
|
|
292
296
|
ssh_host: str | None = clickdc.option(
|
|
293
297
|
type=str,
|
|
294
298
|
hidden=True,
|
|
295
|
-
deprecated='No effect. See
|
|
299
|
+
deprecated='No effect. See --ssh-jump.',
|
|
296
300
|
)
|
|
297
301
|
ssh_port: int = clickdc.option(
|
|
298
302
|
type=int,
|
|
299
303
|
hidden=True,
|
|
300
|
-
deprecated='No effect. See
|
|
304
|
+
deprecated='No effect. See --ssh-jump.',
|
|
301
305
|
)
|
|
302
306
|
ssh_password: str | None = clickdc.option(
|
|
303
307
|
type=str,
|
|
304
308
|
hidden=True,
|
|
305
|
-
deprecated='No effect. See
|
|
309
|
+
deprecated='No effect. See --ssh-jump.',
|
|
306
310
|
)
|
|
307
311
|
ssh_key_filename: str | None = clickdc.option(
|
|
308
312
|
type=str,
|
|
309
313
|
hidden=True,
|
|
310
|
-
deprecated='No effect. See
|
|
314
|
+
deprecated='No effect. See --ssh-jump.',
|
|
311
315
|
)
|
|
312
316
|
ssh_config_path: str = clickdc.option(
|
|
313
317
|
type=str,
|
|
314
318
|
hidden=True,
|
|
315
|
-
deprecated='No effect. See
|
|
319
|
+
deprecated='No effect. See --ssh-jump.',
|
|
316
320
|
)
|
|
317
321
|
ssh_config_host: str | None = clickdc.option(
|
|
318
322
|
type=str,
|
|
319
323
|
hidden=True,
|
|
320
|
-
deprecated='No effect. See
|
|
324
|
+
deprecated='No effect. See --ssh-jump.',
|
|
321
325
|
)
|
|
322
326
|
list_ssh_config: bool = clickdc.option(
|
|
323
327
|
is_flag=True,
|
|
324
328
|
hidden=True,
|
|
325
|
-
deprecated='No effect. See
|
|
329
|
+
deprecated='No effect. See --ssh-jump.',
|
|
326
330
|
)
|
|
327
331
|
ssh_warning_off: bool = clickdc.option(
|
|
328
332
|
is_flag=True,
|
|
329
333
|
hidden=True,
|
|
330
|
-
deprecated='No effect. See
|
|
334
|
+
deprecated='No effect. See --ssh-jump.',
|
|
331
335
|
)
|
|
332
336
|
|
|
333
337
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# after executing a SQL statement when applicable.
|
|
6
6
|
show_warnings = False
|
|
7
7
|
|
|
8
|
-
# Enables context sensitive auto-completion. If this is disabled
|
|
8
|
+
# Enables context sensitive auto-completion. If this is disabled then all
|
|
9
9
|
# possible completions will be listed.
|
|
10
10
|
smart_completion = True
|
|
11
11
|
|
|
@@ -206,10 +206,14 @@ enable_pager = True
|
|
|
206
206
|
pager = 'less'
|
|
207
207
|
|
|
208
208
|
# Whether to store and retrieve passwords from the system keyring.
|
|
209
|
+
# * False - never use keyring
|
|
210
|
+
# * True - always use keyring
|
|
211
|
+
# * auto - use keyring unless currently in an SSH connection
|
|
209
212
|
# See the documentation for https://pypi.org/project/keyring/ for your OS.
|
|
210
213
|
# Note that the hostname is considered to be different if short or qualified.
|
|
211
214
|
# This can be overridden with --use-keyring= at the CLI.
|
|
212
215
|
# A password can be reset with --use-keyring=reset at the CLI.
|
|
216
|
+
# Recommanded: auto
|
|
213
217
|
use_keyring = False
|
|
214
218
|
|
|
215
219
|
[search]
|
|
@@ -385,7 +389,7 @@ matching-bracket.other = '#000000 bg:#aacccc'
|
|
|
385
389
|
|
|
386
390
|
# Favorite queries.
|
|
387
391
|
# You can add your favorite queries here. They will be available in the
|
|
388
|
-
# REPL when you type
|
|
392
|
+
# REPL when you type `/f` or `/f <query_name>`.
|
|
389
393
|
[favorite_queries]
|
|
390
394
|
# example = "SELECT * FROM example_table WHERE id = 1"
|
|
391
395
|
|
|
@@ -403,3 +407,10 @@ matching-bracket.other = '#000000 bg:#aacccc'
|
|
|
403
407
|
[alias_dsn.init-commands]
|
|
404
408
|
# Define one or more SQL statements per alias (semicolon-separated).
|
|
405
409
|
# 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
|
|
@@ -23,11 +23,7 @@ def is_valid_connection_scheme(text: str) -> tuple[bool, str | None]:
|
|
|
23
23
|
# exit early if the text does not resemble a DSN URI
|
|
24
24
|
if "://" not in text:
|
|
25
25
|
return False, None
|
|
26
|
-
scheme = text.split("://")[0]
|
|
27
|
-
if scheme
|
|
28
|
-
return True, None
|
|
29
|
-
if scheme.startswith('mysqlx+'):
|
|
30
|
-
return True, None
|
|
31
|
-
if scheme in ("mysql", "mysqlx", "tcp", "socket"):
|
|
26
|
+
scheme = text.split("://")[0].split("+")[0]
|
|
27
|
+
if scheme in ("mysql", "mysqlx"):
|
|
32
28
|
return True, None
|
|
33
29
|
return False, scheme
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DsnAliases:
|
|
5
|
+
section_name: str = 'alias_dsn'
|
|
6
|
+
|
|
7
|
+
usage = """
|
|
8
|
+
DSN aliases are a way to save frequently used connections
|
|
9
|
+
with a short alias. You can manage them by editing ~/.myclirc
|
|
10
|
+
directly or by using this command.
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
|
|
14
|
+
# List all DSN aliases
|
|
15
|
+
mysql> /dsn list
|
|
16
|
+
┌───────┬───────────────────────────────┐
|
|
17
|
+
│ Alias │ DSN │
|
|
18
|
+
├───────┼───────────────────────────────┤
|
|
19
|
+
│ rocks │ mysql://mycli@localhost/mysql │
|
|
20
|
+
└───────┴───────────────────────────────┘
|
|
21
|
+
|
|
22
|
+
# Save a new DSN alias based on the current connection.
|
|
23
|
+
# The password will not be included!
|
|
24
|
+
mysql> /dsn save connection_1
|
|
25
|
+
|
|
26
|
+
# Delete a DSN alias.
|
|
27
|
+
mysql> /dsn delete connection_1
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# Class-level variable, for convenience to use as a singleton.
|
|
31
|
+
instance: DsnAliases
|
|
32
|
+
|
|
33
|
+
def __init__(self, config) -> None:
|
|
34
|
+
self.config = config
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_config(cls, config):
|
|
38
|
+
return DsnAliases(config)
|
|
39
|
+
|
|
40
|
+
def list(self) -> list[str]:
|
|
41
|
+
return list(self.config.get(self.section_name, {}))
|
|
42
|
+
|
|
43
|
+
def get(self, alias: str) -> str | None:
|
|
44
|
+
return self.config.get(self.section_name, {}).get(alias, None)
|
|
45
|
+
|
|
46
|
+
def save(self, alias: str, dsn: str) -> str:
|
|
47
|
+
self.config.encoding = 'utf-8'
|
|
48
|
+
if self.section_name not in self.config:
|
|
49
|
+
self.config[self.section_name] = {}
|
|
50
|
+
self.config[self.section_name][alias] = dsn
|
|
51
|
+
self.config.write()
|
|
52
|
+
return f'Saved: {alias}'
|
|
53
|
+
|
|
54
|
+
def delete(self, alias: str) -> str:
|
|
55
|
+
try:
|
|
56
|
+
del self.config[self.section_name][alias]
|
|
57
|
+
except KeyError:
|
|
58
|
+
return f'Not Found: {alias}'
|
|
59
|
+
self.config.write()
|
|
60
|
+
return f'Deleted: {alias}'
|
|
@@ -10,10 +10,10 @@ with a short name.
|
|
|
10
10
|
Examples:
|
|
11
11
|
|
|
12
12
|
# Save a new favorite query.
|
|
13
|
-
>
|
|
13
|
+
> /fs simple select * from abc where a is not Null;
|
|
14
14
|
|
|
15
15
|
# List all favorite queries.
|
|
16
|
-
>
|
|
16
|
+
> /f
|
|
17
17
|
╒════════╤═══════════════════════════════════════╕
|
|
18
18
|
│ Name │ Query │
|
|
19
19
|
╞════════╪═══════════════════════════════════════╡
|
|
@@ -21,7 +21,7 @@ Examples:
|
|
|
21
21
|
╘════════╧═══════════════════════════════════════╛
|
|
22
22
|
|
|
23
23
|
# Run a favorite query.
|
|
24
|
-
>
|
|
24
|
+
> /f simple
|
|
25
25
|
╒════════╤════════╕
|
|
26
26
|
│ a │ b │
|
|
27
27
|
╞════════╪════════╡
|
|
@@ -29,7 +29,7 @@ Examples:
|
|
|
29
29
|
╘════════╧════════╛
|
|
30
30
|
|
|
31
31
|
# Delete a favorite query.
|
|
32
|
-
>
|
|
32
|
+
> /fd simple
|
|
33
33
|
simple: Deleted.
|
|
34
34
|
"""
|
|
35
35
|
|