mycli 2.24.4__tar.gz → 2.25.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.24.4 → mycli-2.25.0}/.github/dependabot.yml +2 -0
- {mycli-2.24.4 → mycli-2.25.0}/.github/workflows/ci.yml +24 -5
- {mycli-2.24.4 → mycli-2.25.0}/.github/workflows/publish.yml +15 -8
- {mycli-2.24.4 → mycli-2.25.0}/.github/workflows/typecheck.yml +1 -1
- {mycli-2.24.4 → mycli-2.25.0}/AGENTS.md +2 -0
- {mycli-2.24.4/mycli.egg-info → mycli-2.25.0}/PKG-INFO +18 -12
- {mycli-2.24.4 → mycli-2.25.0}/README.md +17 -11
- {mycli-2.24.4 → mycli-2.25.0}/changelog.md +28 -0
- {mycli-2.24.4 → mycli-2.25.0}/docker/resources/test_db-1.0.7.tar.gz +1 -1
- {mycli-2.24.4 → mycli-2.25.0}/mycli/client_connection.py +2 -1
- mycli-2.25.0/mycli/keyring_utils.py +34 -0
- mycli-2.25.0/mycli/macos_keychain.py +80 -0
- {mycli-2.24.4 → mycli-2.25.0/mycli.egg-info}/PKG-INFO +18 -12
- {mycli-2.24.4 → mycli-2.25.0}/mycli.egg-info/SOURCES.txt +4 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli.egg-info/scm_file_list.json +4 -0
- mycli-2.25.0/mycli.egg-info/scm_version.json +8 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_client_connection.py +50 -7
- mycli-2.25.0/test/pytests/test_keyring_utils.py +105 -0
- mycli-2.25.0/test/pytests/test_macos_keychain.py +160 -0
- mycli-2.24.4/mycli.egg-info/scm_version.json +0 -8
- {mycli-2.24.4 → mycli-2.25.0}/.git-blame-ignore-revs +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/.github/workflows/lint.yml +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/.gitignore +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/AUTHORS.rst +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/CONTRIBUTING.md +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/LICENSE.txt +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/MANIFEST.in +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/SPONSORS.rst +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/doc/key_bindings.rst +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/doc/llm.md +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/doc/screenshots/main.gif +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/doc/screenshots/tables.png +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/doc/screenshots/total_histogram.png +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/doc/transforms.md +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/docker/Dockerfile +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/AUTHORS +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/SPONSORS +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/TIPS +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/__init__.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/app_state.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/boundary_tunnel.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/cli_runner.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/clibuffer.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/client.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/client_commands.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/client_query.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/clistyle.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/clitoolbar.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/compat.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/completion_refresher.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/config.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/constants.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/key_bindings.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/kubectl_tunnel.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/lexer.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/main.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/main_modes/batch.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/main_modes/checkup.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/main_modes/completions.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/main_modes/execute.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/main_modes/list_dsn.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/main_modes/repl.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/myclirc +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/output.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/__init__.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/batch_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/cli_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/completion_engine.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/filepaths.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/hybrid_redirection.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/interactive_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/key_binding_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/polars_completion.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/polars_transform.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/ptoolkit/__init__.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/ptoolkit/fzf.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/ptoolkit/history.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/ptoolkit/utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/special/__init__.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/special/dbcommands.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/special/delimitercommand.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/special/dsn_aliases.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/special/favoritequeries.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/special/iocommands.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/special/llm.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/special/main.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/special/source.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/special/utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/sql_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/sqlresult.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/string_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/tabular_output/__init__.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/packages/tabular_output/sql_format.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/password_sources.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/resources/completions/bash/mycli +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/resources/completions/fish/mycli.fish +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/resources/completions/zsh/_mycli +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/schema_prefetcher.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/sqlcompleter.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/sqlexecute.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/ssh_tunnel.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/types.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli/vault.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli.egg-info/dependency_links.txt +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli.egg-info/entry_points.txt +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli.egg-info/requires.txt +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/mycli.egg-info/top_level.txt +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/pyproject.toml +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/setup.cfg +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/__init__.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/__init__.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/auto_vertical.feature +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/basic_commands.feature +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/connection.feature +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/crud_database.feature +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/crud_table.feature +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/db_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/environment.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/fixture_data/help_commands.txt +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/fixture_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/iocommands.feature +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/named_queries.feature +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/specials.feature +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/steps/__init__.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/steps/auto_vertical.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/steps/basic_commands.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/steps/connection.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/steps/crud_database.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/steps/crud_table.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/steps/iocommands.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/steps/named_queries.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/steps/specials.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/steps/utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/steps/wrappers.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/features/wrappager.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/myclirc +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/mylogin.cnf +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/conftest.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_app_state.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_bash_completion.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_batch_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_boundary_tunnel.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_checkup.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_cli_runner.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_cli_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_clibuffer.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_client.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_client_commands.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_client_query.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_clistyle.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_clitoolbar.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_completer_use_switch.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_completion_engine.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_completion_refresher.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_config.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_delimitercommand.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_dsn_aliases.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_favoritequeries.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_filepaths.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_fish_completion.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_hybrid_redirection.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_interactive_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_key_binding_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_key_bindings.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_kubectl_tunnel.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_main.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_main_modes_batch.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_main_modes_execute.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_main_modes_list_dsn.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_main_modes_repl.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_naive_completion.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_output.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_password_sources.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_polars_completion.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_polars_transform.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_ptoolkit_fzf.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_ptoolkit_history.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_ptoolkit_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_schema_prefetcher.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_smart_completion_public_schema_only.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_special_dbcommands.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_special_init.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_special_iocommands.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_special_llm.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_special_main.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_special_source.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_special_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_sql_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_sqlcompleter.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_sqlexecute.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_sqlresult.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_ssh_tunnel.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_string_utils.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_tabular_output.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_vault.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/pytests/test_zsh_completion.py +0 -0
- {mycli-2.24.4 → mycli-2.25.0}/test/utils.py +0 -0
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
24
24
|
steps:
|
|
25
25
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
26
26
|
|
|
27
|
-
- uses: astral-sh/setup-uv@
|
|
27
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
28
28
|
with:
|
|
29
29
|
version: "latest"
|
|
30
30
|
|
|
@@ -66,7 +66,7 @@ jobs:
|
|
|
66
66
|
steps:
|
|
67
67
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
68
68
|
|
|
69
|
-
- uses: astral-sh/setup-uv@
|
|
69
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
70
70
|
with:
|
|
71
71
|
version: "latest"
|
|
72
72
|
|
|
@@ -116,7 +116,7 @@ jobs:
|
|
|
116
116
|
steps:
|
|
117
117
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
118
118
|
|
|
119
|
-
- uses: astral-sh/setup-uv@
|
|
119
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
120
120
|
with:
|
|
121
121
|
version: 'latest'
|
|
122
122
|
|
|
@@ -130,7 +130,7 @@ jobs:
|
|
|
130
130
|
run: uv sync --all-extras -p ${{ matrix.python-version }}
|
|
131
131
|
|
|
132
132
|
- name: Set up MySQL
|
|
133
|
-
uses: shogo82148/actions-setup-mysql@
|
|
133
|
+
uses: shogo82148/actions-setup-mysql@083ec148d28e62f2b24b8f4541693dfe1d50488d # v1.54.0
|
|
134
134
|
with:
|
|
135
135
|
mysql-version: '9.7'
|
|
136
136
|
root-password: root
|
|
@@ -220,7 +220,7 @@ jobs:
|
|
|
220
220
|
steps:
|
|
221
221
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
222
222
|
|
|
223
|
-
- uses: astral-sh/setup-uv@
|
|
223
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
224
224
|
with:
|
|
225
225
|
version: "latest"
|
|
226
226
|
|
|
@@ -249,3 +249,22 @@ jobs:
|
|
|
249
249
|
TERM: xterm
|
|
250
250
|
run: |
|
|
251
251
|
uv run tox -e py3.14
|
|
252
|
+
|
|
253
|
+
docker-build:
|
|
254
|
+
name: Build Docker Image
|
|
255
|
+
runs-on: ubuntu-latest
|
|
256
|
+
|
|
257
|
+
permissions:
|
|
258
|
+
contents: read
|
|
259
|
+
|
|
260
|
+
steps:
|
|
261
|
+
- name: Checkout repository
|
|
262
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
263
|
+
|
|
264
|
+
- name: Build Docker image
|
|
265
|
+
id: build
|
|
266
|
+
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
|
267
|
+
with:
|
|
268
|
+
context: .
|
|
269
|
+
file: docker/Dockerfile
|
|
270
|
+
push: false
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
name: Publish
|
|
1
|
+
name: Publish Packages
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
release:
|
|
@@ -32,7 +32,7 @@ jobs:
|
|
|
32
32
|
|
|
33
33
|
steps:
|
|
34
34
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
35
|
-
- uses: astral-sh/setup-uv@
|
|
35
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
36
36
|
with:
|
|
37
37
|
version: "latest"
|
|
38
38
|
|
|
@@ -77,7 +77,7 @@ jobs:
|
|
|
77
77
|
steps:
|
|
78
78
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
79
79
|
|
|
80
|
-
- uses: astral-sh/setup-uv@
|
|
80
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
81
81
|
with:
|
|
82
82
|
version: "latest"
|
|
83
83
|
|
|
@@ -127,7 +127,7 @@ jobs:
|
|
|
127
127
|
steps:
|
|
128
128
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
129
129
|
|
|
130
|
-
- uses: astral-sh/setup-uv@
|
|
130
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
131
131
|
with:
|
|
132
132
|
version: 'latest'
|
|
133
133
|
|
|
@@ -141,7 +141,7 @@ jobs:
|
|
|
141
141
|
run: uv sync --all-extras -p ${{ matrix.python-version }}
|
|
142
142
|
|
|
143
143
|
- name: Set up MySQL
|
|
144
|
-
uses: shogo82148/actions-setup-mysql@
|
|
144
|
+
uses: shogo82148/actions-setup-mysql@083ec148d28e62f2b24b8f4541693dfe1d50488d # v1.54.0
|
|
145
145
|
with:
|
|
146
146
|
mysql-version: '9.7'
|
|
147
147
|
root-password: root
|
|
@@ -230,7 +230,7 @@ jobs:
|
|
|
230
230
|
|
|
231
231
|
steps:
|
|
232
232
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
233
|
-
- uses: astral-sh/setup-uv@
|
|
233
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
234
234
|
with:
|
|
235
235
|
version: "latest"
|
|
236
236
|
|
|
@@ -252,7 +252,7 @@ jobs:
|
|
|
252
252
|
path: dist/
|
|
253
253
|
|
|
254
254
|
publish-pypi:
|
|
255
|
-
name: Publish to PyPI
|
|
255
|
+
name: Publish Wheel to PyPI
|
|
256
256
|
runs-on: ubuntu-latest
|
|
257
257
|
if: startsWith(github.ref, 'refs/tags/')
|
|
258
258
|
needs: [build]
|
|
@@ -269,9 +269,11 @@ jobs:
|
|
|
269
269
|
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
270
270
|
|
|
271
271
|
publish-image:
|
|
272
|
-
name: Publish to ghcr.io
|
|
272
|
+
name: Publish Image to ghcr.io
|
|
273
273
|
runs-on: ubuntu-latest
|
|
274
274
|
if: startsWith(github.ref, 'refs/tags/')
|
|
275
|
+
# doesn't actually need the results of build, but it makes sense to run this
|
|
276
|
+
# concurrently with publish-pypi
|
|
275
277
|
needs: [build]
|
|
276
278
|
environment: release
|
|
277
279
|
|
|
@@ -293,6 +295,10 @@ jobs:
|
|
|
293
295
|
username: ${{ github.actor }}
|
|
294
296
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
295
297
|
|
|
298
|
+
- name: Set up Docker Buildx
|
|
299
|
+
id: buildx
|
|
300
|
+
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
|
301
|
+
|
|
296
302
|
- name: Extract metadata (tags, labels) for Docker
|
|
297
303
|
id: meta
|
|
298
304
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
|
@@ -306,5 +312,6 @@ jobs:
|
|
|
306
312
|
context: .
|
|
307
313
|
file: docker/Dockerfile
|
|
308
314
|
push: true
|
|
315
|
+
platforms: linux/amd64,linux/arm64
|
|
309
316
|
tags: ${{ steps.meta.outputs.tags }}
|
|
310
317
|
labels: ${{ steps.meta.outputs.labels }}
|
|
@@ -25,8 +25,10 @@ A command line client for MySQL with auto-completion and syntax highlighting.
|
|
|
25
25
|
├── mycli/config.py # configuration file readers and utilities
|
|
26
26
|
├── mycli/constants.py # shared constants
|
|
27
27
|
├── mycli/key_bindings.py # prompt_toolkit key binding utilities
|
|
28
|
+
├── mycli/keyring_utils.py # saved credential utilities
|
|
28
29
|
├── mycli/kubectl_tunnel.py # connection over kubectl tunnel
|
|
29
30
|
├── mycli/lexer.py # extends `MySqlLexer` from Pygments
|
|
31
|
+
├── mycli/macos_keychain.py # macOS Keychain credentials preserving access controls
|
|
30
32
|
├── mycli/main.py # processes CLI arguments
|
|
31
33
|
├── mycli/main_modes/ # main execution paths
|
|
32
34
|
├── mycli/main_modes/batch.py # `--batch` mode
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.25.0
|
|
4
4
|
Summary: Rich MySQL terminal client with autocompletion, syntax highlighting, and dataframes
|
|
5
5
|
Author: Mycli Core Team
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -143,20 +143,26 @@ mycli --help
|
|
|
143
143
|
* Fuzzy history search using [fzf](https://github.com/junegunn/fzf).
|
|
144
144
|
* Output explorer using [fzf](https://github.com/junegunn/fzf) or other tools.
|
|
145
145
|
* Syntax highlighting using [Pygments](https://pygments.org/).
|
|
146
|
-
* Smart-completion
|
|
147
|
-
- `SELECT * FROM
|
|
148
|
-
- `SELECT * FROM users WHERE
|
|
149
|
-
*
|
|
146
|
+
* Smart-completion suggests context-sensitive completion:
|
|
147
|
+
- `SELECT * FROM `<kbd>tab</kbd> will only show table names.
|
|
148
|
+
- `SELECT * FROM users WHERE `<kbd>tab</kbd> will only show column names.
|
|
149
|
+
* Multiline queries.
|
|
150
150
|
* Favorite queries with positional or named parameters using [Jinja](https://jinja.palletsprojects.com/en/stable/). Save a query using
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
```
|
|
152
|
+
/fs alias <query>
|
|
153
|
+
```
|
|
154
|
+
and execute it with
|
|
155
|
+
```
|
|
156
|
+
/f alias --key=value
|
|
157
|
+
```
|
|
158
|
+
* Timing of SQL statements and table rendering.
|
|
159
|
+
* Logging of every query and its results to a file (off by default).
|
|
160
|
+
* Pretty printing of tabular data.
|
|
156
161
|
* Shell-style trailing redirects with `$>`, `$>>` and `$|` operators.
|
|
157
162
|
* [Polars](https://pola.rs) dataframe [transforms and plots](https://github.com/dbcli/mycli/blob/main/doc/transforms.md) with `.|`, and Parquet saves with `.>`.
|
|
158
|
-
*
|
|
159
|
-
*
|
|
163
|
+
* [Querying LLMs](https://www.mycli.net/llm) with context derived from your schema using `/llm`.
|
|
164
|
+
* Storing passwords in the system keyring.
|
|
165
|
+
* Modern defaults such as UTF-8 connection type and SSL.
|
|
160
166
|
* Integrations with: [SSH](https://www.openssh.org/), [Kubernetes](https://kubernetes.io/), [Vault](https://github.com/hashicorp/vault), and [Boundary](https://github.com/hashicorp/boundary).
|
|
161
167
|
|
|
162
168
|
Mycli creates a config file `~/.myclirc` on the first run; you can use the
|
|
@@ -71,20 +71,26 @@ mycli --help
|
|
|
71
71
|
* Fuzzy history search using [fzf](https://github.com/junegunn/fzf).
|
|
72
72
|
* Output explorer using [fzf](https://github.com/junegunn/fzf) or other tools.
|
|
73
73
|
* Syntax highlighting using [Pygments](https://pygments.org/).
|
|
74
|
-
* Smart-completion
|
|
75
|
-
- `SELECT * FROM
|
|
76
|
-
- `SELECT * FROM users WHERE
|
|
77
|
-
*
|
|
74
|
+
* Smart-completion suggests context-sensitive completion:
|
|
75
|
+
- `SELECT * FROM `<kbd>tab</kbd> will only show table names.
|
|
76
|
+
- `SELECT * FROM users WHERE `<kbd>tab</kbd> will only show column names.
|
|
77
|
+
* Multiline queries.
|
|
78
78
|
* Favorite queries with positional or named parameters using [Jinja](https://jinja.palletsprojects.com/en/stable/). Save a query using
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
```
|
|
80
|
+
/fs alias <query>
|
|
81
|
+
```
|
|
82
|
+
and execute it with
|
|
83
|
+
```
|
|
84
|
+
/f alias --key=value
|
|
85
|
+
```
|
|
86
|
+
* Timing of SQL statements and table rendering.
|
|
87
|
+
* Logging of every query and its results to a file (off by default).
|
|
88
|
+
* Pretty printing of tabular data.
|
|
84
89
|
* Shell-style trailing redirects with `$>`, `$>>` and `$|` operators.
|
|
85
90
|
* [Polars](https://pola.rs) dataframe [transforms and plots](https://github.com/dbcli/mycli/blob/main/doc/transforms.md) with `.|`, and Parquet saves with `.>`.
|
|
86
|
-
*
|
|
87
|
-
*
|
|
91
|
+
* [Querying LLMs](https://www.mycli.net/llm) with context derived from your schema using `/llm`.
|
|
92
|
+
* Storing passwords in the system keyring.
|
|
93
|
+
* Modern defaults such as UTF-8 connection type and SSL.
|
|
88
94
|
* Integrations with: [SSH](https://www.openssh.org/), [Kubernetes](https://kubernetes.io/), [Vault](https://github.com/hashicorp/vault), and [Boundary](https://github.com/hashicorp/boundary).
|
|
89
95
|
|
|
90
96
|
Mycli creates a config file `~/.myclirc` on the first run; you can use the
|
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
2.25.0 (2026/09/16)
|
|
2
|
+
==============
|
|
3
|
+
|
|
4
|
+
Features
|
|
5
|
+
--------
|
|
6
|
+
* Publish multi-architecture Docker images.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
2.24.5 (2026/09/16)
|
|
10
|
+
==============
|
|
11
|
+
|
|
12
|
+
Bug Fixes
|
|
13
|
+
--------
|
|
14
|
+
* New macOS keyring entries no longer automatically trust Python.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Documentation
|
|
18
|
+
--------
|
|
19
|
+
* Update features bullets in `README.md`.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
Internal
|
|
23
|
+
--------
|
|
24
|
+
* Naming and commentary in `publish.yml` workflow.
|
|
25
|
+
* Build Docker image as part of regular CI tests.
|
|
26
|
+
* One week cooldown for dependabot.
|
|
27
|
+
|
|
28
|
+
|
|
1
29
|
2.24.4 (2026/09/12)
|
|
2
30
|
==============
|
|
3
31
|
|
|
@@ -22,6 +22,7 @@ from mycli.constants import (
|
|
|
22
22
|
EMPTY_PASSWORD_FLAG_SENTINEL,
|
|
23
23
|
ER_MUST_CHANGE_PASSWORD_LOGIN,
|
|
24
24
|
)
|
|
25
|
+
from mycli.keyring_utils import set_keyring_password
|
|
25
26
|
from mycli.kubectl_tunnel import KubectlTunnel, KubectlTunnelError
|
|
26
27
|
from mycli.packages.filepaths import guess_socket_location
|
|
27
28
|
from mycli.packages.special.utils import format_connection_dsn
|
|
@@ -381,7 +382,7 @@ class ClientConnectionMixin:
|
|
|
381
382
|
try:
|
|
382
383
|
saved_pw = keyring.get_password(keyring_domain, keyring_identifier)
|
|
383
384
|
if password != saved_pw or reset_keyring:
|
|
384
|
-
|
|
385
|
+
set_keyring_password(keyring_domain, keyring_identifier, password)
|
|
385
386
|
click.secho(
|
|
386
387
|
f'Password from source: "{password_source}" '
|
|
387
388
|
f'saved to the system keyring at {keyring_domain}/{keyring_identifier}',
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import keyring
|
|
4
|
+
from keyring.backend import KeyringBackend
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _set_password_with_backend(backend: KeyringBackend, service: str, account: str, password: str) -> None:
|
|
8
|
+
from keyring.backends.macOS import Keyring
|
|
9
|
+
|
|
10
|
+
if type(backend) is Keyring:
|
|
11
|
+
from mycli import macos_keychain
|
|
12
|
+
|
|
13
|
+
macos_keychain.set_password(service, account, password)
|
|
14
|
+
else:
|
|
15
|
+
backend.set_password(service, account, password)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def set_keyring_password(service: str, account: str, password: str) -> None:
|
|
19
|
+
if sys.platform != 'darwin':
|
|
20
|
+
keyring.set_password(service, account, password)
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
from keyring.backends.chainer import ChainerBackend # type: ignore[unreachable]
|
|
24
|
+
|
|
25
|
+
backend = keyring.get_keyring()
|
|
26
|
+
if type(backend) is ChainerBackend:
|
|
27
|
+
for candidate in backend.backends:
|
|
28
|
+
try:
|
|
29
|
+
_set_password_with_backend(candidate, service, account, password)
|
|
30
|
+
return
|
|
31
|
+
except NotImplementedError:
|
|
32
|
+
continue
|
|
33
|
+
else:
|
|
34
|
+
_set_password_with_backend(backend, service, account, password)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from keyring.errors import KeyringLocked, PasswordSetError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _get_api() -> Any:
|
|
8
|
+
from keyring.backends.macOS import api
|
|
9
|
+
|
|
10
|
+
return api
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def set_password(service: str, account: str, password: str) -> None:
|
|
14
|
+
"""Preserve existing ACLs and create credentials without trusting Python."""
|
|
15
|
+
api = _get_api()
|
|
16
|
+
cf_array_create = api._found.CFArrayCreate
|
|
17
|
+
cf_array_create.restype = ctypes.c_void_p
|
|
18
|
+
cf_array_create.argtypes = (ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_void_p)
|
|
19
|
+
cf_data_create = api._found.CFDataCreate
|
|
20
|
+
cf_data_create.restype = ctypes.c_void_p
|
|
21
|
+
cf_data_create.argtypes = (ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long)
|
|
22
|
+
cf_release = api._found.CFRelease
|
|
23
|
+
cf_release.restype = None
|
|
24
|
+
cf_release.argtypes = (ctypes.c_void_p,)
|
|
25
|
+
sec_access_create = api._sec.SecAccessCreate
|
|
26
|
+
sec_access_create.restype = api.OS_status
|
|
27
|
+
sec_access_create.argtypes = (ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p))
|
|
28
|
+
sec_item_update = api._sec.SecItemUpdate
|
|
29
|
+
sec_item_update.restype = api.OS_status
|
|
30
|
+
sec_item_update.argtypes = (ctypes.c_void_p, ctypes.c_void_p)
|
|
31
|
+
|
|
32
|
+
retained: list[ctypes.c_void_p] = []
|
|
33
|
+
|
|
34
|
+
def retain(value: int | ctypes.c_void_p | None, name: str) -> ctypes.c_void_p:
|
|
35
|
+
if not value:
|
|
36
|
+
raise RuntimeError(f'Unable to allocate Keychain {name}')
|
|
37
|
+
pointer = value if isinstance(value, ctypes.c_void_p) else ctypes.c_void_p(value)
|
|
38
|
+
retained.append(pointer)
|
|
39
|
+
return pointer
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
service_value = retain(api.create_cf(service), 'service')
|
|
43
|
+
account_value = retain(api.create_cf(account), 'account')
|
|
44
|
+
encoded = password.encode('utf-8')
|
|
45
|
+
password_value = retain(cf_data_create(None, ctypes.create_string_buffer(encoded), len(encoded)), 'password')
|
|
46
|
+
search = retain(
|
|
47
|
+
api.create_query(
|
|
48
|
+
kSecClass=api.k_('kSecClassGenericPassword'),
|
|
49
|
+
kSecAttrService=service_value,
|
|
50
|
+
kSecAttrAccount=account_value,
|
|
51
|
+
),
|
|
52
|
+
'search',
|
|
53
|
+
)
|
|
54
|
+
attributes = retain(api.create_query(kSecValueData=password_value), 'attributes')
|
|
55
|
+
status = sec_item_update(search, attributes)
|
|
56
|
+
if status == api.error.item_not_found:
|
|
57
|
+
# An empty array trusts nobody; NULL would trust the calling executable.
|
|
58
|
+
trusted_apps = retain(cf_array_create(None, None, 0, None), 'access controls')
|
|
59
|
+
access = ctypes.c_void_p()
|
|
60
|
+
api.Error.raise_for_status(sec_access_create(service_value, trusted_apps, ctypes.byref(access)))
|
|
61
|
+
retain(access, 'access')
|
|
62
|
+
item = retain(
|
|
63
|
+
api.create_query(
|
|
64
|
+
kSecClass=api.k_('kSecClassGenericPassword'),
|
|
65
|
+
kSecAttrService=service_value,
|
|
66
|
+
kSecAttrAccount=account_value,
|
|
67
|
+
kSecValueData=password_value,
|
|
68
|
+
kSecAttrAccess=access,
|
|
69
|
+
),
|
|
70
|
+
'item',
|
|
71
|
+
)
|
|
72
|
+
status = api.SecItemAdd(item, None)
|
|
73
|
+
api.Error.raise_for_status(status)
|
|
74
|
+
except api.KeychainDenied as e:
|
|
75
|
+
raise KeyringLocked(f"Can't store password on keychain: {e}") from e
|
|
76
|
+
except api.Error as e:
|
|
77
|
+
raise PasswordSetError(f"Can't store password on keychain: {e}") from e
|
|
78
|
+
finally:
|
|
79
|
+
for value in reversed(retained):
|
|
80
|
+
cf_release(value)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.25.0
|
|
4
4
|
Summary: Rich MySQL terminal client with autocompletion, syntax highlighting, and dataframes
|
|
5
5
|
Author: Mycli Core Team
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -143,20 +143,26 @@ mycli --help
|
|
|
143
143
|
* Fuzzy history search using [fzf](https://github.com/junegunn/fzf).
|
|
144
144
|
* Output explorer using [fzf](https://github.com/junegunn/fzf) or other tools.
|
|
145
145
|
* Syntax highlighting using [Pygments](https://pygments.org/).
|
|
146
|
-
* Smart-completion
|
|
147
|
-
- `SELECT * FROM
|
|
148
|
-
- `SELECT * FROM users WHERE
|
|
149
|
-
*
|
|
146
|
+
* Smart-completion suggests context-sensitive completion:
|
|
147
|
+
- `SELECT * FROM `<kbd>tab</kbd> will only show table names.
|
|
148
|
+
- `SELECT * FROM users WHERE `<kbd>tab</kbd> will only show column names.
|
|
149
|
+
* Multiline queries.
|
|
150
150
|
* Favorite queries with positional or named parameters using [Jinja](https://jinja.palletsprojects.com/en/stable/). Save a query using
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
```
|
|
152
|
+
/fs alias <query>
|
|
153
|
+
```
|
|
154
|
+
and execute it with
|
|
155
|
+
```
|
|
156
|
+
/f alias --key=value
|
|
157
|
+
```
|
|
158
|
+
* Timing of SQL statements and table rendering.
|
|
159
|
+
* Logging of every query and its results to a file (off by default).
|
|
160
|
+
* Pretty printing of tabular data.
|
|
156
161
|
* Shell-style trailing redirects with `$>`, `$>>` and `$|` operators.
|
|
157
162
|
* [Polars](https://pola.rs) dataframe [transforms and plots](https://github.com/dbcli/mycli/blob/main/doc/transforms.md) with `.|`, and Parquet saves with `.>`.
|
|
158
|
-
*
|
|
159
|
-
*
|
|
163
|
+
* [Querying LLMs](https://www.mycli.net/llm) with context derived from your schema using `/llm`.
|
|
164
|
+
* Storing passwords in the system keyring.
|
|
165
|
+
* Modern defaults such as UTF-8 connection type and SSL.
|
|
160
166
|
* Integrations with: [SSH](https://www.openssh.org/), [Kubernetes](https://kubernetes.io/), [Vault](https://github.com/hashicorp/vault), and [Boundary](https://github.com/hashicorp/boundary).
|
|
161
167
|
|
|
162
168
|
Mycli creates a config file `~/.myclirc` on the first run; you can use the
|
|
@@ -44,8 +44,10 @@ mycli/completion_refresher.py
|
|
|
44
44
|
mycli/config.py
|
|
45
45
|
mycli/constants.py
|
|
46
46
|
mycli/key_bindings.py
|
|
47
|
+
mycli/keyring_utils.py
|
|
47
48
|
mycli/kubectl_tunnel.py
|
|
48
49
|
mycli/lexer.py
|
|
50
|
+
mycli/macos_keychain.py
|
|
49
51
|
mycli/main.py
|
|
50
52
|
mycli/myclirc
|
|
51
53
|
mycli/output.py
|
|
@@ -159,7 +161,9 @@ test/pytests/test_hybrid_redirection.py
|
|
|
159
161
|
test/pytests/test_interactive_utils.py
|
|
160
162
|
test/pytests/test_key_binding_utils.py
|
|
161
163
|
test/pytests/test_key_bindings.py
|
|
164
|
+
test/pytests/test_keyring_utils.py
|
|
162
165
|
test/pytests/test_kubectl_tunnel.py
|
|
166
|
+
test/pytests/test_macos_keychain.py
|
|
163
167
|
test/pytests/test_main.py
|
|
164
168
|
test/pytests/test_main_modes_batch.py
|
|
165
169
|
test/pytests/test_main_modes_execute.py
|
|
@@ -45,8 +45,10 @@
|
|
|
45
45
|
"mycli/config.py",
|
|
46
46
|
"mycli/constants.py",
|
|
47
47
|
"mycli/key_bindings.py",
|
|
48
|
+
"mycli/keyring_utils.py",
|
|
48
49
|
"mycli/kubectl_tunnel.py",
|
|
49
50
|
"mycli/lexer.py",
|
|
51
|
+
"mycli/macos_keychain.py",
|
|
50
52
|
"mycli/main.py",
|
|
51
53
|
"mycli/main_modes/batch.py",
|
|
52
54
|
"mycli/main_modes/checkup.py",
|
|
@@ -152,7 +154,9 @@
|
|
|
152
154
|
"test/pytests/test_interactive_utils.py",
|
|
153
155
|
"test/pytests/test_key_binding_utils.py",
|
|
154
156
|
"test/pytests/test_key_bindings.py",
|
|
157
|
+
"test/pytests/test_keyring_utils.py",
|
|
155
158
|
"test/pytests/test_kubectl_tunnel.py",
|
|
159
|
+
"test/pytests/test_macos_keychain.py",
|
|
156
160
|
"test/pytests/test_main.py",
|
|
157
161
|
"test/pytests/test_main_modes_batch.py",
|
|
158
162
|
"test/pytests/test_main_modes_execute.py",
|
|
@@ -260,11 +260,14 @@ def test_connect_retrieves_password_from_keyring(monkeypatch: pytest.MonkeyPatch
|
|
|
260
260
|
return 'from-keyring'
|
|
261
261
|
|
|
262
262
|
monkeypatch.setattr(client_connection.keyring, 'get_password', fake_get_password)
|
|
263
|
+
writes: list[tuple[str, str, str]] = []
|
|
264
|
+
monkeypatch.setattr(client_connection, 'set_keyring_password', lambda *args: writes.append(args))
|
|
263
265
|
|
|
264
266
|
client.connect(user='alice', host='db', port=3307, use_keyring=True)
|
|
265
267
|
|
|
266
268
|
assert FakeSQLExecute.calls[-1]['password'] == 'from-keyring'
|
|
267
269
|
assert get_password_calls == [('mycli.net', 'alice@db:3307:')]
|
|
270
|
+
assert writes == []
|
|
268
271
|
|
|
269
272
|
|
|
270
273
|
def test_connect_uses_mylogin_password_before_keyring(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
@@ -275,7 +278,7 @@ def test_connect_uses_mylogin_password_before_keyring(monkeypatch: pytest.Monkey
|
|
|
275
278
|
'get_password',
|
|
276
279
|
lambda domain, identifier: get_password_calls.append((domain, identifier)) or 'from-mylogin', # type: ignore[func-returns-value]
|
|
277
280
|
)
|
|
278
|
-
monkeypatch.setattr(client_connection
|
|
281
|
+
monkeypatch.setattr(client_connection, 'set_keyring_password', lambda *_args: None)
|
|
279
282
|
|
|
280
283
|
client.connect(user='alice', host='db', port=3307, use_keyring=True)
|
|
281
284
|
|
|
@@ -293,7 +296,7 @@ def test_connect_resolves_supplied_candidates_after_connection_details(monkeypat
|
|
|
293
296
|
'get_password',
|
|
294
297
|
lambda domain, identifier: keyring_calls.append((domain, identifier)) or 'from-dsn', # type: ignore[func-returns-value]
|
|
295
298
|
)
|
|
296
|
-
monkeypatch.setattr(client_connection
|
|
299
|
+
monkeypatch.setattr(client_connection, 'set_keyring_password', lambda *_args: None)
|
|
297
300
|
|
|
298
301
|
client.connect(user='alice', host='db', port=3307, password_candidates=candidates, use_keyring=True)
|
|
299
302
|
|
|
@@ -326,8 +329,8 @@ def test_connect_saves_selected_password_to_keyring(monkeypatch: pytest.MonkeyPa
|
|
|
326
329
|
secho_calls: list[tuple[str, dict[str, Any]]] = []
|
|
327
330
|
monkeypatch.setattr(client_connection.keyring, 'get_password', lambda *_args: 'old-secret')
|
|
328
331
|
monkeypatch.setattr(
|
|
329
|
-
client_connection
|
|
330
|
-
'
|
|
332
|
+
client_connection,
|
|
333
|
+
'set_keyring_password',
|
|
331
334
|
lambda domain, identifier, password: set_password_calls.append((domain, identifier, password)),
|
|
332
335
|
)
|
|
333
336
|
monkeypatch.setattr(client_connection.click, 'secho', lambda message, **kwargs: secho_calls.append((message, kwargs)))
|
|
@@ -348,7 +351,7 @@ def test_connect_reports_keyring_save_error(monkeypatch: pytest.MonkeyPatch) ->
|
|
|
348
351
|
def fail_set_password(*_args: Any) -> None:
|
|
349
352
|
raise RuntimeError('locked')
|
|
350
353
|
|
|
351
|
-
monkeypatch.setattr(client_connection
|
|
354
|
+
monkeypatch.setattr(client_connection, 'set_keyring_password', fail_set_password)
|
|
352
355
|
monkeypatch.setattr(client_connection.click, 'secho', lambda message, **kwargs: secho_calls.append((message, kwargs)))
|
|
353
356
|
|
|
354
357
|
client.connect(user='alice', host='db', port=3307, password_candidates=candidates, use_keyring=True)
|
|
@@ -356,6 +359,46 @@ def test_connect_reports_keyring_save_error(monkeypatch: pytest.MonkeyPatch) ->
|
|
|
356
359
|
assert secho_calls == [('Password not saved to the system keyring: locked', {'err': True, 'fg': 'red'})]
|
|
357
360
|
|
|
358
361
|
|
|
362
|
+
def test_connect_saves_fallback_password_when_keyring_is_empty(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
363
|
+
client = DummyClient()
|
|
364
|
+
FakeSQLExecute.effects = [op_error(1045), None]
|
|
365
|
+
writes: list[tuple[str, str, str]] = []
|
|
366
|
+
monkeypatch.setattr(client_connection.keyring, 'get_password', lambda *_args: None)
|
|
367
|
+
monkeypatch.setattr(client_connection.click, 'prompt', lambda *_args, **_kwargs: 'new-secret')
|
|
368
|
+
monkeypatch.setattr(client_connection, 'set_keyring_password', lambda *args: writes.append(args))
|
|
369
|
+
|
|
370
|
+
client.connect(user='alice', host='db', port=3307, use_keyring=True)
|
|
371
|
+
|
|
372
|
+
assert writes == [('mycli.net', 'alice@db:3307:', 'new-secret')]
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def test_connect_does_not_rewrite_rejected_keyring_password(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
376
|
+
client = DummyClient()
|
|
377
|
+
FakeSQLExecute.effects = [op_error(1045)]
|
|
378
|
+
writes: list[tuple[str, str, str]] = []
|
|
379
|
+
monkeypatch.setattr(client_connection.keyring, 'get_password', lambda *_args: 'old-secret')
|
|
380
|
+
monkeypatch.setattr(client_connection, 'set_keyring_password', lambda *args: writes.append(args))
|
|
381
|
+
|
|
382
|
+
with pytest.raises(SystemExit):
|
|
383
|
+
client.connect(user='alice', host='db', port=3307, use_keyring=True)
|
|
384
|
+
|
|
385
|
+
assert writes == []
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
@pytest.mark.parametrize('reset', [False, True])
|
|
389
|
+
def test_connect_only_rewrites_unchanged_password_for_reset(monkeypatch: pytest.MonkeyPatch, reset: bool) -> None:
|
|
390
|
+
client = DummyClient()
|
|
391
|
+
candidates = PasswordCandidates()
|
|
392
|
+
candidates.add_value('literal', 'secret')
|
|
393
|
+
writes: list[tuple[str, str, str]] = []
|
|
394
|
+
monkeypatch.setattr(client_connection.keyring, 'get_password', lambda *_args: 'secret')
|
|
395
|
+
monkeypatch.setattr(client_connection, 'set_keyring_password', lambda *args: writes.append(args))
|
|
396
|
+
|
|
397
|
+
client.connect(user='alice', host='db', port=3307, password_candidates=candidates, use_keyring=True, reset_keyring=reset)
|
|
398
|
+
|
|
399
|
+
assert writes == ([('mycli.net', 'alice@db:3307:', 'secret')] if reset else [])
|
|
400
|
+
|
|
401
|
+
|
|
359
402
|
def test_connect_uses_ssh_jump_with_remote_socket(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
360
403
|
tunnel_calls: list[dict[str, Any]] = []
|
|
361
404
|
|
|
@@ -485,8 +528,8 @@ def test_connect_uses_kubectl_tunnel_with_resolved_database_port(monkeypatch: py
|
|
|
485
528
|
lambda domain, identifier: keyring_calls.append(('get', domain, identifier)),
|
|
486
529
|
)
|
|
487
530
|
monkeypatch.setattr(
|
|
488
|
-
client_connection
|
|
489
|
-
'
|
|
531
|
+
client_connection,
|
|
532
|
+
'set_keyring_password',
|
|
490
533
|
lambda domain, identifier, password: keyring_calls.append(('set', domain, identifier, password)),
|
|
491
534
|
)
|
|
492
535
|
password_candidates = PasswordCandidates()
|