mycli 2.30.3__tar.gz → 2.31.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.30.3 → mycli-2.31.0}/.github/workflows/ci.yml +2 -2
- {mycli-2.30.3 → mycli-2.31.0}/.github/workflows/publish.yml +3 -3
- {mycli-2.30.3/mycli.egg-info → mycli-2.31.0}/PKG-INFO +7 -7
- {mycli-2.30.3 → mycli-2.31.0}/changelog.md +40 -0
- {mycli-2.30.3 → mycli-2.31.0}/docker/resources/test_db-1.0.7.tar.gz +1 -1
- {mycli-2.30.3 → mycli-2.31.0}/mycli/AUTHORS.txt +4 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/cli_runner.py +2 -2
- {mycli-2.30.3 → mycli-2.31.0}/mycli/client.py +1 -1
- {mycli-2.30.3 → mycli-2.31.0}/mycli/client_commands.py +1 -1
- {mycli-2.30.3 → mycli-2.31.0}/mycli/client_connection.py +2 -3
- {mycli-2.30.3 → mycli-2.31.0}/mycli/client_query.py +1 -1
- {mycli-2.30.3 → mycli-2.31.0}/mycli/constants.py +0 -4
- {mycli-2.30.3 → mycli-2.31.0}/mycli/main_modes/batch.py +12 -4
- {mycli-2.30.3 → mycli-2.31.0}/mycli/main_modes/repl.py +5 -3
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/execution/sql_execute.py +4 -4
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/special_commands/io_commands.py +4 -4
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/special_commands/special_command_utils.py +3 -1
- mycli-2.31.0/mycli/packages/utils/batch_utils.py +71 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/utils/string_utils.py +11 -0
- {mycli-2.30.3 → mycli-2.31.0/mycli.egg-info}/PKG-INFO +7 -7
- {mycli-2.30.3 → mycli-2.31.0}/mycli.egg-info/SOURCES.txt +1 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli.egg-info/requires.txt +6 -6
- {mycli-2.30.3 → mycli-2.31.0}/mycli.egg-info/scm_file_list.json +1 -0
- mycli-2.31.0/mycli.egg-info/scm_version.json +8 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_batch_utils.py +29 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_cli_runner.py +40 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_client.py +1 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_client_commands.py +33 -6
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_client_connection.py +3 -2
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_client_query.py +8 -2
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_main.py +2 -2
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_main_modes_batch.py +47 -7
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_main_modes_repl.py +23 -1
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_special_io_commands.py +128 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_special_main.py +39 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_special_utils.py +18 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_sql_execute.py +71 -1
- mycli-2.31.0/mycli_test/pytests/test_sql_format.py +97 -0
- mycli-2.31.0/mycli_test/pytests/test_string_utils.py +44 -0
- {mycli-2.30.3 → mycli-2.31.0}/pyproject.toml +7 -6
- mycli-2.30.3/mycli/packages/utils/batch_utils.py +0 -36
- mycli-2.30.3/mycli.egg-info/scm_version.json +0 -8
- mycli-2.30.3/mycli_test/pytests/test_string_utils.py +0 -27
- {mycli-2.30.3 → mycli-2.31.0}/.git-blame-ignore-revs +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/.github/actions/lint/action.yml +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/.github/actions/typecheck/action.yml +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/.github/dependabot.yml +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/.github/workflows/lint.yml +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/.github/workflows/typecheck.yml +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/.gitignore +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/AGENTS.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/AUTHORS.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/CONTRIBUTING.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/LICENSE.txt +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/MANIFEST.in +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/README.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/SPONSORS.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/doc/cookbook.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/doc/key_bindings.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/doc/known_bugs.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/doc/llm.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/doc/screenshots/main.gif +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/doc/screenshots/polars_describe.png +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/doc/screenshots/polars_group_by.png +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/doc/screenshots/polars_histogram.png +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/doc/screenshots/polars_pivot.png +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/doc/screenshots/tables.png +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/doc/transforms.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/docker/Dockerfile +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/docker/resources/dot_mycli_history.txt +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/docker/resources/test_db_license.md +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/SPONSORS.txt +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/TIPS.txt +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/app_state.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/compat.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/config.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/main.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/main_modes/checkup.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/main_modes/completions.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/main_modes/execute.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/main_modes/list_dsn.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/myclirc +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/output.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/completion/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/completion/completion_engine.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/completion/completion_refresher.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/completion/schema_prefetcher.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/completion/sql_completer.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/credentials/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/credentials/password_sources.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/dataframes/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/dataframes/completion.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/dataframes/transform.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/execution/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/execution/background_runner.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/boundary/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/boundary/boundary_tunnel.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/fzf/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/fzf/fzf.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/kubernetes/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/kubernetes/kubectl_tunnel.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/macos/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/macos/macos_keychain.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/openssh/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/openssh/ssh_tunnel.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/vault/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/integrations/vault/vault_credentials.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/prompt_toolkit/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/prompt_toolkit/fzf_history.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/prompt_toolkit/history.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/prompt_toolkit/key_bindings.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/prompt_toolkit/multiline.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/prompt_toolkit/style.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/prompt_toolkit/toolbar.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/prompt_toolkit/utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/pygments/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/pygments/mycli_lexer.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/redirection/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/redirection/hybrid_redirection.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/special_commands/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/special_commands/db_commands.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/special_commands/delimiter_command.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/special_commands/dsn_aliases.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/special_commands/favorite_queries.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/special_commands/llm.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/special_commands/main.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/special_commands/source.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/sql_result/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/sql_result/sql_result.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/tabular_output/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/tabular_output/sql_format.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/utils/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/utils/cli_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/utils/dsn_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/utils/interactive_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/utils/key_binding_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/utils/keyring_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/utils/path_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/packages/utils/sql_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/resources/completions/bash/mycli +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/resources/completions/fish/mycli.fish +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/resources/completions/zsh/_mycli +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli/types.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli.egg-info/dependency_links.txt +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli.egg-info/entry_points.txt +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli.egg-info/top_level.txt +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/auto_vertical.feature +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/basic_commands.feature +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/connection.feature +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/crud_database.feature +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/crud_table.feature +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/db_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/environment.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/fixture_data/help_commands.txt +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/fixture_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/iocommands.feature +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/named_queries.feature +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/specials.feature +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/steps/__init__.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/steps/auto_vertical.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/steps/basic_commands.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/steps/connection.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/steps/crud_database.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/steps/crud_table.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/steps/iocommands.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/steps/named_queries.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/steps/specials.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/steps/utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/steps/wrappers.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/features/wrappager.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/myclirc +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/mylogin.cnf +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/conftest.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_app_state.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_background_runner.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_bash_completion.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_boundary_tunnel.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_checkup.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_cli_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_clibuffer.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_completer_use_switch.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_completion_engine.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_completion_refresher.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_config.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_delimitercommand.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_dsn_aliases.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_favoritequeries.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_fish_completion.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_fzf.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_hybrid_redirection.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_interactive_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_key_binding_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_key_bindings.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_keyring_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_kubectl_tunnel.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_macos_keychain.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_main_modes_execute.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_main_modes_list_dsn.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_naive_completion.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_output.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_password_sources.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_path_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_polars_completion.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_polars_transform.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_prompt_toolkit_fzf_history.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_prompt_toolkit_history.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_prompt_toolkit_style.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_prompt_toolkit_toolbar.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_prompt_toolkit_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_schema_prefetcher.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_smart_completion_public_schema_only.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_special_commands_init.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_special_db_commands.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_special_llm.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_special_source.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_sql_utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_sqlcompleter.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_sqlresult.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_ssh_tunnel.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_tabular_output.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_vault.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/pytests/test_zsh_completion.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/mycli_test/utils.py +0 -0
- {mycli-2.30.3 → mycli-2.31.0}/setup.cfg +0 -0
|
@@ -130,7 +130,7 @@ jobs:
|
|
|
130
130
|
run: uv sync --extra dev,llm,dataframe -p ${{ matrix.python-version }}
|
|
131
131
|
|
|
132
132
|
- name: Set up MySQL
|
|
133
|
-
uses: shogo82148/actions-setup-mysql@
|
|
133
|
+
uses: shogo82148/actions-setup-mysql@13a38f2af408e58a0c80168fc620a63a15357339 # v1.56.2
|
|
134
134
|
with:
|
|
135
135
|
mysql-version: '9.7'
|
|
136
136
|
root-password: root
|
|
@@ -166,7 +166,7 @@ jobs:
|
|
|
166
166
|
run: |
|
|
167
167
|
mkdir -p /root/mycli
|
|
168
168
|
|
|
169
|
-
- uses: ubuntu/wsl/.github/actions/wsl-checkout@
|
|
169
|
+
- uses: ubuntu/wsl/.github/actions/wsl-checkout@c1984d32aea99733825250f86df5f9f6d7e284c3
|
|
170
170
|
with:
|
|
171
171
|
distro: Ubuntu-26.04
|
|
172
172
|
working-dir: /root/mycli
|
|
@@ -158,7 +158,7 @@ jobs:
|
|
|
158
158
|
run: uv sync --extra dev,llm,dataframe --python ${{ matrix.python-version }}
|
|
159
159
|
|
|
160
160
|
- name: Set up MySQL
|
|
161
|
-
uses: shogo82148/actions-setup-mysql@
|
|
161
|
+
uses: shogo82148/actions-setup-mysql@13a38f2af408e58a0c80168fc620a63a15357339 # v1.56.2
|
|
162
162
|
with:
|
|
163
163
|
mysql-version: '9.7'
|
|
164
164
|
root-password: root
|
|
@@ -195,7 +195,7 @@ jobs:
|
|
|
195
195
|
run: |
|
|
196
196
|
mkdir -p /root/mycli
|
|
197
197
|
|
|
198
|
-
- uses: ubuntu/wsl/.github/actions/wsl-checkout@
|
|
198
|
+
- uses: ubuntu/wsl/.github/actions/wsl-checkout@c1984d32aea99733825250f86df5f9f6d7e284c3
|
|
199
199
|
with:
|
|
200
200
|
distro: Ubuntu-26.04
|
|
201
201
|
working-dir: /root/mycli
|
|
@@ -309,7 +309,7 @@ jobs:
|
|
|
309
309
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
310
310
|
|
|
311
311
|
- name: Log in to the Container registry
|
|
312
|
-
uses: docker/login-action@
|
|
312
|
+
uses: docker/login-action@8aa5831fb8d48d8f76594c7ddde61ae58019f550 # v4.8.0
|
|
313
313
|
with:
|
|
314
314
|
registry: ${{ env.REGISTRY }}
|
|
315
315
|
username: ${{ github.actor }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.31.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
|
|
@@ -33,8 +33,8 @@ Requires-Dist: clickdc~=0.1.1
|
|
|
33
33
|
Requires-Dist: cryptography~=50.0.2
|
|
34
34
|
Requires-Dist: Pygments~=2.21.0
|
|
35
35
|
Requires-Dist: prompt_toolkit~=3.0.53
|
|
36
|
-
Requires-Dist: PyMySQL~=1.2.
|
|
37
|
-
Requires-Dist: sqlparse~=0.6.
|
|
36
|
+
Requires-Dist: PyMySQL~=1.2.4
|
|
37
|
+
Requires-Dist: sqlparse~=0.6.1
|
|
38
38
|
Requires-Dist: sqlglot~=30.25.0
|
|
39
39
|
Requires-Dist: sqlglotc~=30.25.0
|
|
40
40
|
Requires-Dist: configobj~=5.0.9
|
|
@@ -48,9 +48,9 @@ Requires-Dist: jinja2~=3.1.6
|
|
|
48
48
|
Requires-Dist: markupsafe~=3.0.4
|
|
49
49
|
Requires-Dist: yaspin~=3.5.1
|
|
50
50
|
Provides-Extra: llm
|
|
51
|
-
Requires-Dist: llm~=0.
|
|
51
|
+
Requires-Dist: llm~=0.37.1; extra == "llm"
|
|
52
52
|
Requires-Dist: setuptools==84.*; extra == "llm"
|
|
53
|
-
Requires-Dist: pip~=26.
|
|
53
|
+
Requires-Dist: pip~=26.3; extra == "llm"
|
|
54
54
|
Provides-Extra: dataframe
|
|
55
55
|
Requires-Dist: polars~=2.1.0; extra == "dataframe"
|
|
56
56
|
Requires-Dist: altair~=6.3.0; extra == "dataframe"
|
|
@@ -59,8 +59,8 @@ Provides-Extra: all
|
|
|
59
59
|
Requires-Dist: mycli[dataframe,llm]; extra == "all"
|
|
60
60
|
Provides-Extra: dev
|
|
61
61
|
Requires-Dist: behave~=1.3.3; extra == "dev"
|
|
62
|
-
Requires-Dist: coverage~=7.17.
|
|
63
|
-
Requires-Dist: mypy~=2.4.
|
|
62
|
+
Requires-Dist: coverage~=7.17.2; extra == "dev"
|
|
63
|
+
Requires-Dist: mypy~=2.4.1; extra == "dev"
|
|
64
64
|
Requires-Dist: pexpect~=4.9.0; extra == "dev"
|
|
65
65
|
Requires-Dist: pytest~=9.2.0; extra == "dev"
|
|
66
66
|
Requires-Dist: pytest-cov~=7.1.0; extra == "dev"
|
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
v2.31.0 (2026/11/14)
|
|
2
|
+
==============
|
|
3
|
+
|
|
4
|
+
Features
|
|
5
|
+
--------
|
|
6
|
+
* Bracket IPv6 literals in prompt strings.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Bug Fixes
|
|
10
|
+
--------
|
|
11
|
+
* Read `/source` files and the `\e` editor file as UTF-8 instead of the locale encoding, so non-ASCII SQL no longer fails or is mis-decoded under non-UTF-8 locales.
|
|
12
|
+
* Open the audit log and the `\T`/`\o` output files as UTF-8, so non-ASCII output no longer fails under non-UTF-8 locales.
|
|
13
|
+
* Bracket IPv6 hosts in the DSN shown and saved by `\dsn`, so a saved alias for `::1` can be used to connect again.
|
|
14
|
+
* Support `DELIMITER` in batch input (`mycli < file.sql`, `--batch`), so dumps with triggers or routines restore.
|
|
15
|
+
* Read `--batch` files and read and write `--checkpoint` files as UTF-8, fixing `--resume` and non-ASCII batch files on Windows.
|
|
16
|
+
* Terminate `use`, `source`, `\u` and `\.` at newline in batch input, as the vendor client does.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
v2.30.4 (2026/11/12)
|
|
20
|
+
==============
|
|
21
|
+
|
|
22
|
+
Bug Fixes
|
|
23
|
+
--------
|
|
24
|
+
* Decode percent-encoded database names in DSN URIs, including aliases with environment variable expansion enabled.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Internal
|
|
28
|
+
--------
|
|
29
|
+
* Improve test coverage for `packages/special_commands/main.py`.
|
|
30
|
+
* Improve test coverage for `packages/special_commands/io_commands.py`.
|
|
31
|
+
* Improve test coverage for `packages/execution/sql_execute.py`.
|
|
32
|
+
* Improve test coverage for `packages/tabular_output/sql_format.py`.
|
|
33
|
+
* Update `pymysql` dependency to v1.2.4.
|
|
34
|
+
* Update `sqlparse` dependency to v0.6.1.
|
|
35
|
+
* Update `llm` dependency to v0.37.1.
|
|
36
|
+
* Update `pip` dependency to v26.3.
|
|
37
|
+
* Update `coverage` dev dependency to v7.17.2.
|
|
38
|
+
* Update `mypy` dev dependency to v2.4.1.
|
|
39
|
+
|
|
40
|
+
|
|
1
41
|
v2.30.3 (2026/11/05)
|
|
2
42
|
==============
|
|
3
43
|
|
|
@@ -117,7 +117,7 @@ def expand_dsn_alias_env_vars(
|
|
|
117
117
|
expand_dsn_alias_env_var(unquote(password) if password is not None else None, alias_name),
|
|
118
118
|
expand_dsn_alias_env_var(host, alias_name),
|
|
119
119
|
port_number,
|
|
120
|
-
expand_dsn_alias_env_var(uri.path[1:], alias_name) or '',
|
|
120
|
+
expand_dsn_alias_env_var(unquote(uri.path[1:]), alias_name) or '',
|
|
121
121
|
params,
|
|
122
122
|
)
|
|
123
123
|
|
|
@@ -310,7 +310,7 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory | None
|
|
|
310
310
|
dsn_password = unquote(uri.password) if uri.password is not None else None
|
|
311
311
|
dsn_host = uri.hostname
|
|
312
312
|
dsn_port = uri.port
|
|
313
|
-
dsn_database = uri.path[1:]
|
|
313
|
+
dsn_database = unquote(uri.path[1:])
|
|
314
314
|
dsn_params = parse_qs(uri.query, keep_blank_values=True) if uri.query else {}
|
|
315
315
|
|
|
316
316
|
if not database:
|
|
@@ -180,7 +180,7 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
|
|
|
180
180
|
# audit log
|
|
181
181
|
if self.logfile is None and "audit_log" in c["main"]:
|
|
182
182
|
try:
|
|
183
|
-
self.logfile = open(os.path.expanduser(c["main"]["audit_log"]), "a")
|
|
183
|
+
self.logfile = open(os.path.expanduser(c["main"]["audit_log"]), "a", encoding="utf-8")
|
|
184
184
|
except (IOError, OSError):
|
|
185
185
|
self.echo("Error: Unable to open the audit log file. Your queries will not be logged.", err=True, fg="red")
|
|
186
186
|
self.logfile = False
|
|
@@ -10,7 +10,7 @@ import click
|
|
|
10
10
|
import keyring
|
|
11
11
|
import pymysql
|
|
12
12
|
from pymysql.constants.CR import CR_SERVER_LOST
|
|
13
|
-
from pymysql.constants.ER import ACCESS_DENIED_ERROR, HANDSHAKE_ERROR
|
|
13
|
+
from pymysql.constants.ER import ACCESS_DENIED_ERROR, HANDSHAKE_ERROR, MUST_CHANGE_PASSWORD_LOGIN
|
|
14
14
|
|
|
15
15
|
from mycli.compat import WIN
|
|
16
16
|
from mycli.config import str_to_bool
|
|
@@ -19,7 +19,6 @@ from mycli.constants import (
|
|
|
19
19
|
DEFAULT_HOST,
|
|
20
20
|
DEFAULT_PORT,
|
|
21
21
|
EMPTY_PASSWORD_FLAG_SENTINEL,
|
|
22
|
-
ER_MUST_CHANGE_PASSWORD_LOGIN,
|
|
23
22
|
)
|
|
24
23
|
from mycli.packages.credentials.password_sources import PasswordCandidates
|
|
25
24
|
from mycli.packages.execution.sql_execute import SQLExecute
|
|
@@ -433,7 +432,7 @@ class ClientConnectionMixin:
|
|
|
433
432
|
keyring_retrieved_cleanly=keyring_retrieved_cleanly,
|
|
434
433
|
keyring_save_eligible=keyring_save_eligible,
|
|
435
434
|
)
|
|
436
|
-
elif e1.args[0] ==
|
|
435
|
+
elif e1.args[0] == MUST_CHANGE_PASSWORD_LOGIN:
|
|
437
436
|
self.echo(
|
|
438
437
|
"Your password has expired and the server rejected the connection.",
|
|
439
438
|
err=True,
|
|
@@ -97,7 +97,7 @@ class ClientQueryMixin:
|
|
|
97
97
|
assert self.sql_execute is not None
|
|
98
98
|
self.log_query(query)
|
|
99
99
|
if checkpoint and not self.checkpoint:
|
|
100
|
-
self.checkpoint = click.open_file(checkpoint, mode='a')
|
|
100
|
+
self.checkpoint = click.open_file(checkpoint, mode='a', encoding='utf-8')
|
|
101
101
|
results = self.sql_execute.run(query)
|
|
102
102
|
for result in results:
|
|
103
103
|
self.main_formatter.query = query
|
|
@@ -38,10 +38,6 @@ TEST_DATABASE = 'mycli_test_db'
|
|
|
38
38
|
DEFAULT_WIDTH = 80
|
|
39
39
|
DEFAULT_HEIGHT = 25
|
|
40
40
|
|
|
41
|
-
# MySQL error codes not available in pymysql.constants.ER
|
|
42
|
-
ER_MUST_CHANGE_PASSWORD_LOGIN = 1862
|
|
43
|
-
ER_MUST_CHANGE_PASSWORD = 1820
|
|
44
|
-
|
|
45
41
|
EMPTY_PASSWORD_FLAG_SENTINEL = -1
|
|
46
42
|
DEFAULT_PROMPT = "\\t \\u@\\h:\\d> "
|
|
47
43
|
|
|
@@ -25,6 +25,11 @@ class CheckpointReplayError(Exception):
|
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def batch_file_encoding(batch_path: str) -> str | None:
|
|
29
|
+
# Leave standard input to click, which picks the console encoding.
|
|
30
|
+
return None if batch_path == '-' else 'utf-8'
|
|
31
|
+
|
|
32
|
+
|
|
28
33
|
def replay_checkpoint_file(
|
|
29
34
|
batch_path: str,
|
|
30
35
|
checkpoint_path: str | None,
|
|
@@ -48,7 +53,10 @@ def replay_checkpoint_file(
|
|
|
48
53
|
spinner = yaspin(text='replaying checkpoint', side='right', stream=sys.stderr)
|
|
49
54
|
spinner.start()
|
|
50
55
|
try:
|
|
51
|
-
with
|
|
56
|
+
with (
|
|
57
|
+
click.open_file(batch_path, encoding=batch_file_encoding(batch_path)) as batch_h,
|
|
58
|
+
click.open_file(checkpoint_path, mode='r', encoding='utf-8') as checkpoint_h,
|
|
59
|
+
):
|
|
52
60
|
try:
|
|
53
61
|
batch_gen = statements_from_filehandle(batch_h)
|
|
54
62
|
except ValueError as e:
|
|
@@ -140,13 +148,13 @@ def main_batch_with_progress_bar(mycli: 'MyCli', cli_args: 'CliArgs') -> int:
|
|
|
140
148
|
return 1
|
|
141
149
|
try:
|
|
142
150
|
completed_statement_count = replay_checkpoint_file(cli_args.batch, cli_args.checkpoint, cli_args.resume, progress=True)
|
|
143
|
-
batch_count_h = click.open_file(cli_args.batch)
|
|
151
|
+
batch_count_h = click.open_file(cli_args.batch, encoding=batch_file_encoding(cli_args.batch))
|
|
144
152
|
with yaspin(text='validating batch ', side='right', stream=sys.stderr) as spinner:
|
|
145
153
|
for _statement, _counter in statements_from_filehandle(batch_count_h):
|
|
146
154
|
goal_statements += 1
|
|
147
155
|
spinner.ok('✔')
|
|
148
156
|
batch_count_h.close()
|
|
149
|
-
batch_h = click.open_file(cli_args.batch)
|
|
157
|
+
batch_h = click.open_file(cli_args.batch, encoding=batch_file_encoding(cli_args.batch))
|
|
150
158
|
batch_gen = statements_from_filehandle(batch_h)
|
|
151
159
|
except (OSError, FileNotFoundError):
|
|
152
160
|
click.secho(f'Failed to open --batch file: {cli_args.batch}', err=True, fg='red')
|
|
@@ -192,7 +200,7 @@ def main_batch_without_progress_bar(mycli: 'MyCli', cli_args: 'CliArgs') -> int:
|
|
|
192
200
|
click.secho('Ignoring STDIN since --batch was also given.', err=True, fg='red')
|
|
193
201
|
try:
|
|
194
202
|
completed_statement_count = replay_checkpoint_file(cli_args.batch, cli_args.checkpoint, cli_args.resume)
|
|
195
|
-
batch_h = click.open_file(cli_args.batch)
|
|
203
|
+
batch_h = click.open_file(cli_args.batch, encoding=batch_file_encoding(cli_args.batch))
|
|
196
204
|
except (OSError, FileNotFoundError):
|
|
197
205
|
click.secho(f'Failed to open --batch file: {cli_args.batch}', err=True, fg='red')
|
|
198
206
|
return 1
|
|
@@ -49,6 +49,7 @@ from pymysql.constants.CR import (
|
|
|
49
49
|
CR_SERVER_GONE_ERROR,
|
|
50
50
|
CR_SERVER_LOST,
|
|
51
51
|
)
|
|
52
|
+
from pymysql.constants.ER import MUST_CHANGE_PASSWORD
|
|
52
53
|
from pymysql.constants.SERVER_STATUS import SERVER_STATUS_IN_TRANS
|
|
53
54
|
from pymysql.cursors import Cursor
|
|
54
55
|
|
|
@@ -57,7 +58,6 @@ from mycli.compat import WIN
|
|
|
57
58
|
from mycli.constants import (
|
|
58
59
|
DEFAULT_HOST,
|
|
59
60
|
DEFAULT_WIDTH,
|
|
60
|
-
ER_MUST_CHANGE_PASSWORD,
|
|
61
61
|
HOME_URL,
|
|
62
62
|
ISSUES_URL,
|
|
63
63
|
QueryState,
|
|
@@ -98,7 +98,7 @@ from mycli.packages.utils.sql_utils import (
|
|
|
98
98
|
need_completion_refresh,
|
|
99
99
|
need_completion_reset,
|
|
100
100
|
)
|
|
101
|
-
from mycli.packages.utils.string_utils import sanitize_terminal_title
|
|
101
|
+
from mycli.packages.utils.string_utils import bracket_ipv6_literal, sanitize_terminal_title
|
|
102
102
|
from mycli.types import Query
|
|
103
103
|
|
|
104
104
|
if TYPE_CHECKING:
|
|
@@ -321,6 +321,8 @@ def render_prompt_string(
|
|
|
321
321
|
short_prompt_host, _, _ = prompt_host.partition('.')
|
|
322
322
|
if re.match(r'^[\d\.]+$', short_prompt_host):
|
|
323
323
|
short_prompt_host = prompt_host
|
|
324
|
+
prompt_host = bracket_ipv6_literal(prompt_host)
|
|
325
|
+
short_prompt_host = bracket_ipv6_literal(short_prompt_host)
|
|
324
326
|
now = datetime.now()
|
|
325
327
|
species_name = sql_execute.server_info.species.name if sql_execute.server_info and sql_execute.server_info.species else 'MySQL'
|
|
326
328
|
strings = string.split('\\\\')
|
|
@@ -1110,7 +1112,7 @@ def _one_iteration(
|
|
|
1110
1112
|
mycli.echo(str(exc), err=True, fg='red')
|
|
1111
1113
|
except pymysql.OperationalError as e1:
|
|
1112
1114
|
mycli.logger.debug('Exception: %r', e1)
|
|
1113
|
-
if e1.args[0] ==
|
|
1115
|
+
if e1.args[0] == MUST_CHANGE_PASSWORD:
|
|
1114
1116
|
mycli.sandbox_mode = True
|
|
1115
1117
|
mycli.echo(
|
|
1116
1118
|
"ERROR 1820: You must reset your password using ALTER USER or SET PASSWORD before executing this statement.",
|
|
@@ -12,10 +12,10 @@ import pymysql
|
|
|
12
12
|
from pymysql.connections import Connection
|
|
13
13
|
from pymysql.constants import FIELD_TYPE
|
|
14
14
|
from pymysql.constants.CR import CR_SSL_CONNECTION_ERROR
|
|
15
|
+
from pymysql.constants.ER import MUST_CHANGE_PASSWORD
|
|
15
16
|
from pymysql.converters import conversions, convert_date, convert_datetime, convert_time, decoders
|
|
16
17
|
from pymysql.cursors import Cursor, SSCursor
|
|
17
18
|
|
|
18
|
-
from mycli.constants import ER_MUST_CHANGE_PASSWORD
|
|
19
19
|
from mycli.packages.execution.background_runner import BackgroundRunner
|
|
20
20
|
from mycli.packages.special_commands import io_commands
|
|
21
21
|
from mycli.packages.special_commands.main import CommandNotFound, execute
|
|
@@ -296,9 +296,9 @@ class SQLExecute:
|
|
|
296
296
|
connect_kwargs['ssl_disabled'] = True
|
|
297
297
|
conn = pymysql.connect(**connect_kwargs) # type: ignore[misc]
|
|
298
298
|
except pymysql.OperationalError as e:
|
|
299
|
-
if e.args[0] ==
|
|
299
|
+
if e.args[0] == MUST_CHANGE_PASSWORD:
|
|
300
300
|
# Post-handshake queries (SET NAMES, SET AUTOCOMMIT, init_command)
|
|
301
|
-
# fail with
|
|
301
|
+
# fail with MUST_CHANGE_PASSWORD in sandbox mode.
|
|
302
302
|
# Reconnect with only the raw handshake.
|
|
303
303
|
connect_kwargs['defer_connect'] = True
|
|
304
304
|
connect_kwargs['autocommit'] = None
|
|
@@ -659,7 +659,7 @@ class SQLExecute:
|
|
|
659
659
|
"""Connect in sandbox mode, performing only the handshake.
|
|
660
660
|
|
|
661
661
|
pymysql's normal connect() runs post-handshake queries (SET NAMES,
|
|
662
|
-
SET AUTOCOMMIT, init_command) that all fail with
|
|
662
|
+
SET AUTOCOMMIT, init_command) that all fail with MUST_CHANGE_PASSWORD
|
|
663
663
|
in sandbox mode. This method performs the raw socket connection and
|
|
664
664
|
authentication handshake only.
|
|
665
665
|
"""
|
|
@@ -289,7 +289,7 @@ def open_external_editor(filename: str | None = None, sql: str | None = None) ->
|
|
|
289
289
|
message = None
|
|
290
290
|
click.edit(filename=filename)
|
|
291
291
|
try:
|
|
292
|
-
with open(filename, 'r') as f:
|
|
292
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
|
293
293
|
query = f.read()
|
|
294
294
|
except IOError:
|
|
295
295
|
message = f'Error reading file: {filename}'
|
|
@@ -899,7 +899,7 @@ def set_tee(arg: str, **_) -> list[SQLResult]:
|
|
|
899
899
|
global tee_file
|
|
900
900
|
|
|
901
901
|
try:
|
|
902
|
-
tee_file = open(*parseargfile(arg))
|
|
902
|
+
tee_file = open(*parseargfile(arg), encoding='utf-8')
|
|
903
903
|
except (IOError, OSError) as e:
|
|
904
904
|
raise OSError(f"Cannot write to file '{e.filename}': {e.strerror}") from e
|
|
905
905
|
|
|
@@ -949,7 +949,7 @@ def _set_once_file(filename: str, mode: str) -> list[SQLResult]:
|
|
|
949
949
|
global once_file, written_to_once_file
|
|
950
950
|
|
|
951
951
|
try:
|
|
952
|
-
once_file = open(os.path.expanduser(filename), mode)
|
|
952
|
+
once_file = open(os.path.expanduser(filename), mode, encoding='utf-8')
|
|
953
953
|
except (IOError, OSError) as e:
|
|
954
954
|
raise OSError(f"Cannot write to file '{e.filename}': {e.strerror}") from e
|
|
955
955
|
written_to_once_file = False
|
|
@@ -1096,7 +1096,7 @@ def flush_pipe_once_if_written(
|
|
|
1096
1096
|
returncode = PIPE_ONCE['process'].returncode
|
|
1097
1097
|
if PIPE_ONCE['stdout_file']:
|
|
1098
1098
|
if not timed_out and not returncode and (stdout_data or force):
|
|
1099
|
-
with open(PIPE_ONCE['stdout_file'], PIPE_ONCE['stdout_mode']) as f:
|
|
1099
|
+
with open(PIPE_ONCE['stdout_file'], PIPE_ONCE['stdout_mode'], encoding='utf-8') as f:
|
|
1100
1100
|
if stdout_data:
|
|
1101
1101
|
print(stdout_data, file=f)
|
|
1102
1102
|
_run_post_redirect_hook(post_redirect_command, PIPE_ONCE['stdout_file'])
|
|
@@ -9,6 +9,8 @@ import click
|
|
|
9
9
|
import pymysql
|
|
10
10
|
from pymysql.cursors import Cursor
|
|
11
11
|
|
|
12
|
+
from mycli.packages.utils.string_utils import bracket_ipv6_literal
|
|
13
|
+
|
|
12
14
|
logger = logging.getLogger(__name__)
|
|
13
15
|
|
|
14
16
|
CACHED_SSL_VERSION: dict[tuple, str | None] = {}
|
|
@@ -166,7 +168,7 @@ def format_connection_dsn(
|
|
|
166
168
|
) -> str:
|
|
167
169
|
if user_part := urlquote(user or ''):
|
|
168
170
|
user_part = f'{user_part}@'
|
|
169
|
-
host_part = host or 'localhost'
|
|
171
|
+
host_part = bracket_ipv6_literal(host or 'localhost')
|
|
170
172
|
port_part = f':{port}' if port is not None else ''
|
|
171
173
|
if db_part := urlquote(database or ''):
|
|
172
174
|
db_part = f'/{db_part}'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import IO, Generator
|
|
3
|
+
|
|
4
|
+
import sqlglot
|
|
5
|
+
import sqlparse
|
|
6
|
+
|
|
7
|
+
MAX_MULTILINE_BATCH_STATEMENT = 5000
|
|
8
|
+
|
|
9
|
+
DELIMITER_RE = re.compile(r'^\s*delimiter\s+(\S+)\s*$', re.IGNORECASE)
|
|
10
|
+
# Client commands the vendor client ends at the newline, without a delimiter
|
|
11
|
+
LINE_COMMAND_RE = re.compile(r'^\s*(/?use|/?source|[/\\]u|[/\\]\.)\s+\S', re.IGNORECASE)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _has_tokens(text: str) -> bool:
|
|
15
|
+
try:
|
|
16
|
+
return bool(sqlglot.tokenize(text, read='mysql'))
|
|
17
|
+
except sqlglot.errors.TokenError:
|
|
18
|
+
return True
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def statements_from_filehandle(file_h: IO) -> Generator[tuple[str, int], None, None]:
|
|
22
|
+
statements = ''
|
|
23
|
+
line_counter = 0
|
|
24
|
+
batch_counter = 0
|
|
25
|
+
delimiter = ';'
|
|
26
|
+
for batch_text in file_h:
|
|
27
|
+
line_counter += 1
|
|
28
|
+
if line_counter > MAX_MULTILINE_BATCH_STATEMENT:
|
|
29
|
+
raise ValueError(f'Saw single input statement greater than {MAX_MULTILINE_BATCH_STATEMENT} lines; assuming a parsing error.')
|
|
30
|
+
# DELIMITER is a client command, as in the mysql client: switch delimiters, don't send it
|
|
31
|
+
delimiter_match = DELIMITER_RE.match(batch_text)
|
|
32
|
+
if delimiter_match and not _has_tokens(statements):
|
|
33
|
+
delimiter = delimiter_match.group(1)
|
|
34
|
+
statements = ''
|
|
35
|
+
line_counter = 0
|
|
36
|
+
continue
|
|
37
|
+
if LINE_COMMAND_RE.match(batch_text) and not _has_tokens(statements) and not batch_text.rstrip().endswith(delimiter):
|
|
38
|
+
yield (batch_text.strip(), batch_counter)
|
|
39
|
+
batch_counter += 1
|
|
40
|
+
statements = ''
|
|
41
|
+
line_counter = 0
|
|
42
|
+
continue
|
|
43
|
+
statements += batch_text
|
|
44
|
+
if delimiter != ';':
|
|
45
|
+
statement = statements.rstrip()
|
|
46
|
+
# A custom delimiter is only recognized at the end of a line, so
|
|
47
|
+
# two statements on one line are sent together as one
|
|
48
|
+
if statement.endswith(delimiter):
|
|
49
|
+
yield (statement[: -len(delimiter)].strip(), batch_counter)
|
|
50
|
+
batch_counter += 1
|
|
51
|
+
statements = ''
|
|
52
|
+
line_counter = 0
|
|
53
|
+
continue
|
|
54
|
+
try:
|
|
55
|
+
tokens = sqlglot.tokenize(statements, read='mysql')
|
|
56
|
+
if not tokens:
|
|
57
|
+
continue
|
|
58
|
+
if tokens[-1].text == ';':
|
|
59
|
+
# The advantage of sqlparse for splitting is that it preserves the input.
|
|
60
|
+
# https://github.com/tobymao/sqlglot/issues/2587#issuecomment-1823109501
|
|
61
|
+
for statement in sqlparse.split(statements):
|
|
62
|
+
yield (statement, batch_counter)
|
|
63
|
+
batch_counter += 1
|
|
64
|
+
statements = ''
|
|
65
|
+
line_counter = 0
|
|
66
|
+
except sqlglot.errors.TokenError:
|
|
67
|
+
continue
|
|
68
|
+
if statements:
|
|
69
|
+
for statement in sqlparse.split(statements):
|
|
70
|
+
yield (statement, batch_counter)
|
|
71
|
+
batch_counter += 1
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ipaddress
|
|
1
2
|
import re
|
|
2
3
|
|
|
3
4
|
from cli_helpers.utils import strip_ansi
|
|
@@ -13,3 +14,13 @@ def sanitize_terminal_title(title: FormattedText) -> str:
|
|
|
13
14
|
sanitized = sanitized.replace('\n', ' ')
|
|
14
15
|
sanitized = re.sub('[\x00-\x1f\x7f]', '', sanitized)
|
|
15
16
|
return sanitized
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def bracket_ipv6_literal(host: str) -> str:
|
|
20
|
+
try:
|
|
21
|
+
ip_obj = ipaddress.ip_address(host)
|
|
22
|
+
except ValueError:
|
|
23
|
+
return host
|
|
24
|
+
if isinstance(ip_obj, ipaddress.IPv6Address) and not host.startswith('['):
|
|
25
|
+
return f'[{host}]'
|
|
26
|
+
return host
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.31.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
|
|
@@ -33,8 +33,8 @@ Requires-Dist: clickdc~=0.1.1
|
|
|
33
33
|
Requires-Dist: cryptography~=50.0.2
|
|
34
34
|
Requires-Dist: Pygments~=2.21.0
|
|
35
35
|
Requires-Dist: prompt_toolkit~=3.0.53
|
|
36
|
-
Requires-Dist: PyMySQL~=1.2.
|
|
37
|
-
Requires-Dist: sqlparse~=0.6.
|
|
36
|
+
Requires-Dist: PyMySQL~=1.2.4
|
|
37
|
+
Requires-Dist: sqlparse~=0.6.1
|
|
38
38
|
Requires-Dist: sqlglot~=30.25.0
|
|
39
39
|
Requires-Dist: sqlglotc~=30.25.0
|
|
40
40
|
Requires-Dist: configobj~=5.0.9
|
|
@@ -48,9 +48,9 @@ Requires-Dist: jinja2~=3.1.6
|
|
|
48
48
|
Requires-Dist: markupsafe~=3.0.4
|
|
49
49
|
Requires-Dist: yaspin~=3.5.1
|
|
50
50
|
Provides-Extra: llm
|
|
51
|
-
Requires-Dist: llm~=0.
|
|
51
|
+
Requires-Dist: llm~=0.37.1; extra == "llm"
|
|
52
52
|
Requires-Dist: setuptools==84.*; extra == "llm"
|
|
53
|
-
Requires-Dist: pip~=26.
|
|
53
|
+
Requires-Dist: pip~=26.3; extra == "llm"
|
|
54
54
|
Provides-Extra: dataframe
|
|
55
55
|
Requires-Dist: polars~=2.1.0; extra == "dataframe"
|
|
56
56
|
Requires-Dist: altair~=6.3.0; extra == "dataframe"
|
|
@@ -59,8 +59,8 @@ Provides-Extra: all
|
|
|
59
59
|
Requires-Dist: mycli[dataframe,llm]; extra == "all"
|
|
60
60
|
Provides-Extra: dev
|
|
61
61
|
Requires-Dist: behave~=1.3.3; extra == "dev"
|
|
62
|
-
Requires-Dist: coverage~=7.17.
|
|
63
|
-
Requires-Dist: mypy~=2.4.
|
|
62
|
+
Requires-Dist: coverage~=7.17.2; extra == "dev"
|
|
63
|
+
Requires-Dist: mypy~=2.4.1; extra == "dev"
|
|
64
64
|
Requires-Dist: pexpect~=4.9.0; extra == "dev"
|
|
65
65
|
Requires-Dist: pytest~=9.2.0; extra == "dev"
|
|
66
66
|
Requires-Dist: pytest-cov~=7.1.0; extra == "dev"
|
|
@@ -216,6 +216,7 @@ mycli_test/pytests/test_special_main.py
|
|
|
216
216
|
mycli_test/pytests/test_special_source.py
|
|
217
217
|
mycli_test/pytests/test_special_utils.py
|
|
218
218
|
mycli_test/pytests/test_sql_execute.py
|
|
219
|
+
mycli_test/pytests/test_sql_format.py
|
|
219
220
|
mycli_test/pytests/test_sql_utils.py
|
|
220
221
|
mycli_test/pytests/test_sqlcompleter.py
|
|
221
222
|
mycli_test/pytests/test_sqlresult.py
|
|
@@ -3,8 +3,8 @@ clickdc~=0.1.1
|
|
|
3
3
|
cryptography~=50.0.2
|
|
4
4
|
Pygments~=2.21.0
|
|
5
5
|
prompt_toolkit~=3.0.53
|
|
6
|
-
PyMySQL~=1.2.
|
|
7
|
-
sqlparse~=0.6.
|
|
6
|
+
PyMySQL~=1.2.4
|
|
7
|
+
sqlparse~=0.6.1
|
|
8
8
|
sqlglot~=30.25.0
|
|
9
9
|
sqlglotc~=30.25.0
|
|
10
10
|
configobj~=5.0.9
|
|
@@ -28,8 +28,8 @@ vl-convert-python~=1.9.0
|
|
|
28
28
|
|
|
29
29
|
[dev]
|
|
30
30
|
behave~=1.3.3
|
|
31
|
-
coverage~=7.17.
|
|
32
|
-
mypy~=2.4.
|
|
31
|
+
coverage~=7.17.2
|
|
32
|
+
mypy~=2.4.1
|
|
33
33
|
pexpect~=4.9.0
|
|
34
34
|
pytest~=9.2.0
|
|
35
35
|
pytest-cov~=7.1.0
|
|
@@ -39,6 +39,6 @@ pdbpp~=0.12.1
|
|
|
39
39
|
ruff~=0.17.3
|
|
40
40
|
|
|
41
41
|
[llm]
|
|
42
|
-
llm~=0.
|
|
42
|
+
llm~=0.37.1
|
|
43
43
|
setuptools==84.*
|
|
44
|
-
pip~=26.
|
|
44
|
+
pip~=26.3
|
|
@@ -208,6 +208,7 @@
|
|
|
208
208
|
"mycli_test/pytests/test_special_source.py",
|
|
209
209
|
"mycli_test/pytests/test_special_utils.py",
|
|
210
210
|
"mycli_test/pytests/test_sql_execute.py",
|
|
211
|
+
"mycli_test/pytests/test_sql_format.py",
|
|
211
212
|
"mycli_test/pytests/test_sql_utils.py",
|
|
212
213
|
"mycli_test/pytests/test_sqlcompleter.py",
|
|
213
214
|
"mycli_test/pytests/test_sqlresult.py",
|
|
@@ -99,3 +99,32 @@ def test_statements_from_filehandle_continues_when_tokenizer_returns_no_tokens(m
|
|
|
99
99
|
('select 1;', 0),
|
|
100
100
|
('select 2;', 1),
|
|
101
101
|
]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_statements_from_filehandle_handles_delimiter_blocks() -> None:
|
|
105
|
+
statements = collect_statements('DELIMITER //\nCREATE PROCEDURE p1()\nBEGIN\n SELECT 1;\nEND //\nDELIMITER ;\nCALL p1();\n')
|
|
106
|
+
|
|
107
|
+
assert statements == [
|
|
108
|
+
('CREATE PROCEDURE p1()\nBEGIN\n SELECT 1;\nEND', 0),
|
|
109
|
+
('CALL p1();', 1),
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_statements_from_filehandle_ends_use_at_newline() -> None:
|
|
114
|
+
statements = collect_statements('select 1;\nuse mysql\nselect database();\n')
|
|
115
|
+
|
|
116
|
+
assert statements == [
|
|
117
|
+
('select 1;', 0),
|
|
118
|
+
('use mysql', 1),
|
|
119
|
+
('select database();', 2),
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_statements_from_filehandle_ends_slash_use_at_newline() -> None:
|
|
124
|
+
statements = collect_statements('select 1;\n/use mysql\nselect database();\n')
|
|
125
|
+
|
|
126
|
+
assert statements == [
|
|
127
|
+
('select 1;', 0),
|
|
128
|
+
('/use mysql', 1),
|
|
129
|
+
('select database();', 2),
|
|
130
|
+
]
|