diff --git a/changelog.md b/changelog.md index 4190ffa5..015d489c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,11 @@ +Upcoming (TBD) +============== + +Bug Fixes +-------- +* Read and write the default `myclirc` as UTF-8, fixing a startup crash on non-UTF-8 locales such as Japanese Windows. + + v2.30.1 (2026/10/24) ============== diff --git a/mycli/AUTHORS.txt b/mycli/AUTHORS.txt index 61f20170..36d5ddd5 100644 --- a/mycli/AUTHORS.txt +++ b/mycli/AUTHORS.txt @@ -120,6 +120,7 @@ Contributors: * Puneet Dixit * Liu Zhenlong * Chris Burr + * Wei-Lun Li Created by: diff --git a/mycli/config.py b/mycli/config.py index a41c6d5c..e3abfd9d 100644 --- a/mycli/config.py +++ b/mycli/config.py @@ -197,20 +197,20 @@ def read_config_files( def create_default_config(list_values: bool = True) -> ConfigObj: import mycli - default_config_file = resources.files(mycli).joinpath("myclirc").open('r') + default_config_file = resources.files(mycli).joinpath("myclirc").open('r', encoding='utf8') return read_config_file(default_config_file, list_values=list_values) def write_default_config(destination: str, overwrite: bool = False) -> None: import mycli - with resources.files(mycli).joinpath("myclirc").open('r') as f: + with resources.files(mycli).joinpath("myclirc").open('r', encoding='utf8') as f: default_config = f.read() destination = os.path.expanduser(destination) if not overwrite and exists(destination): return - with open(destination, "w") as f: + with open(destination, "w", encoding='utf8') as f: f.write(default_config) diff --git a/mycli_test/pytests/test_config.py b/mycli_test/pytests/test_config.py index f7b66ab2..1d30b18f 100644 --- a/mycli_test/pytests/test_config.py +++ b/mycli_test/pytests/test_config.py @@ -6,6 +6,7 @@ import logging import os import struct +import subprocess import sys from tempfile import NamedTemporaryFile from types import SimpleNamespace @@ -435,6 +436,34 @@ def test_create_and_write_default_config(tmp_path) -> None: assert '[main]' in destination.read_text(encoding='utf8') +def test_default_config_does_not_use_locale_encoding(tmp_path) -> None: + # The packaged myclirc is UTF-8 with non-ASCII text, so opening it with the + # locale encoding fails on non-UTF-8 locales such as cp932 (Japanese Windows). + script = '\n'.join(( + 'import sys', + 'from mycli.config import create_default_config, write_default_config', + 'create_default_config()', + 'write_default_config(sys.argv[1])', + )) + result = subprocess.run( + [ + sys.executable, + '-X', + 'warn_default_encoding', + '-W', + 'error::EncodingWarning:mycli.config', + '-c', + script, + str(tmp_path / 'myclirc'), + ], + capture_output=True, + encoding='utf8', + errors='replace', + ) + assert result.returncode == 0, result.stderr + assert '[main]' in (tmp_path / 'myclirc').read_text(encoding='utf8') + + def test_get_mylogin_cnf_path_returns_none_for_missing_file(monkeypatch, tmp_path) -> None: monkeypatch.setenv('MYSQL_TEST_LOGIN_FILE', str(tmp_path / 'missing.mylogin.cnf'))