Skip to content

Commit 0d896ab

Browse files
authored
Merge pull request #1963 from dbcli/RW/deprecate-main-section-connection-settings
Migrate connection-related settings from myclirc `[main]`
2 parents 852b168 + 1948b26 commit 0d896ab

8 files changed

Lines changed: 202 additions & 25 deletions

File tree

changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Breaking Changes
1111
* Remove support for `my.cnf` vendor MySQL option files.
1212
* Remove support for `.myclirc` files in the current working directory.
1313
* Make `--batch` and `--execute` non-interactive by default.
14+
* Migrate `default_character_set` out of the `[main]` section of `~/.myclirc`.
15+
* Migrate `ssl_mode` out of the `[main]` section of `~/.myclirc`.
1416

1517

1618
Features

mycli/app_state.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,39 @@
1111
from mycli.client import MyCli
1212

1313

14-
def normalize_ssl_mode(config: ConfigObj) -> tuple[str | None, str | None]:
15-
ssl_mode = config['main'].get('ssl_mode', None) or config['connection'].get('default_ssl_mode', None)
14+
def normalize_ssl_mode(
15+
config: ConfigObj,
16+
config_without_package_defaults: ConfigObj,
17+
) -> tuple[str | None, str | None]:
18+
error_notice: str | None = None
19+
ssl_mode: str | None = None
20+
21+
if 'main' in config_without_package_defaults and 'ssl_mode' in config_without_package_defaults['main']:
22+
# migration with notice added with mycli 2.0.0 in 2026-07
23+
# todo: entirely remove support for ssl_mode in [main]
24+
error_notice = (
25+
'Mycli 2.0 migration: automatically moving ssl_mode under [main] to default_ssl_mode under [connection] in ~/.myclirc .'
26+
)
27+
28+
ssl_mode = config_without_package_defaults['main']['ssl_mode']
29+
30+
config_without_package_defaults.encoding = 'utf-8'
31+
if 'connection' not in config_without_package_defaults:
32+
config_without_package_defaults['connection'] = {}
33+
if config_without_package_defaults['connection'].get('default_ssl_mode', None) in (None, ''):
34+
config_without_package_defaults['connection']['default_ssl_mode'] = ssl_mode
35+
else:
36+
ssl_mode = config_without_package_defaults['connection'].get('default_ssl_mode')
37+
error_notice += f'\nBut connection.default_ssl_mode already existed, with the value: "{ssl_mode}".'
38+
del config_without_package_defaults['main']['ssl_mode']
39+
config_without_package_defaults.write()
40+
41+
if not ssl_mode and 'default_ssl_mode' in config['connection']:
42+
ssl_mode = config['connection']['default_ssl_mode']
1643
if ssl_mode not in ('auto', 'on', 'off', None):
17-
return None, f'Invalid config option provided for ssl_mode ({ssl_mode}); ignoring.'
18-
return ssl_mode, None
44+
error_notice = f'Invalid config option provided for ssl_mode ({ssl_mode}); ignoring.'
45+
return None, error_notice
46+
return ssl_mode, error_notice
1947

2048

2149
def configure_prompt_state(

mycli/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def __init__(
132132
self.binary_display = c['main'].get('binary_display')
133133
self.llm_prompt_field_truncate, self.llm_prompt_section_truncate = llm_prompt_truncation(c)
134134

135-
self.ssl_mode, ssl_mode_error = normalize_ssl_mode(c)
135+
self.ssl_mode, ssl_mode_error = normalize_ssl_mode(c, self.config_without_package_defaults)
136136
if ssl_mode_error:
137137
self.echo(ssl_mode_error, err=True, fg="red")
138138

mycli/client_connection.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,34 @@ def connect(
7676

7777
passwd = passwd if isinstance(passwd, (str, int)) else mylogin_cnf["password"]
7878

79-
# default_character_set doesn't check in self.config_without_package_defaults, because the
80-
# option already existed before the my.cnf deprecation. For the same reason,
81-
# default_character_set can be in [connection] or [main].
8279
if not character_set:
83-
if 'default_character_set' in self.config['connection']:
80+
if 'main' in self.config_without_package_defaults and 'default_character_set' in self.config_without_package_defaults['main']:
81+
# migration with notice added with mycli 2.0.0 in 2026-07
82+
# todo: entirely remove support for default_character_set in [main]
83+
click.secho(
84+
'Mycli 2.0 migration: automatically moving default_character_set from [main] to [connection] in ~/.myclirc .',
85+
err=True,
86+
fg='red',
87+
)
88+
character_set = self.config_without_package_defaults['main']['default_character_set']
89+
90+
self.config_without_package_defaults.encoding = 'utf-8'
91+
if 'connection' not in self.config_without_package_defaults:
92+
self.config_without_package_defaults['connection'] = {}
93+
if self.config_without_package_defaults['connection'].get('default_character_set', None) in (None, ''):
94+
self.config_without_package_defaults['connection']['default_character_set'] = character_set
95+
else:
96+
character_set = self.config_without_package_defaults["connection"].get("default_character_set")
97+
click.secho(
98+
f'But connection.default_character_set already existed, with the value: "{character_set}".',
99+
err=True,
100+
fg='red',
101+
)
102+
del self.config_without_package_defaults['main']['default_character_set']
103+
self.config_without_package_defaults.write()
104+
105+
if not character_set and 'default_character_set' in self.config['connection']:
84106
character_set = self.config['connection']['default_character_set']
85-
elif 'default_character_set' in self.config['main']:
86-
character_set = self.config['main']['default_character_set']
87107
if not character_set:
88108
character_set = DEFAULT_CHARSET
89109

test/pytests/test_app_state.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,60 @@ def __init__(self, login_path: str | None = None) -> None:
1818

1919
@pytest.mark.parametrize('ssl_mode', ['auto', 'on', 'off'])
2020
def test_normalize_ssl_mode_accepts_known_values(ssl_mode: str) -> None:
21-
config = ConfigObj({'main': {'ssl_mode': ssl_mode}, 'connection': {'default_ssl_mode': 'off'}})
21+
config = ConfigObj({'main': {'ssl_mode': ssl_mode}, 'connection': {'default_ssl_mode': ssl_mode}})
22+
config_wo = ConfigObj({'main': {}, 'connection': {}})
2223

23-
assert normalize_ssl_mode(config) == (ssl_mode, None)
24+
assert normalize_ssl_mode(config, config_wo) == (ssl_mode, None)
2425

2526

2627
def test_normalize_ssl_mode_falls_back_to_connection_default() -> None:
2728
config = ConfigObj({'main': {'ssl_mode': ''}, 'connection': {'default_ssl_mode': 'on'}})
29+
config_wo = ConfigObj({'main': {}, 'connection': {}})
2830

29-
assert normalize_ssl_mode(config) == ('on', None)
31+
assert normalize_ssl_mode(config, config_wo) == ('on', None)
32+
33+
34+
def test_normalize_ssl_mode_returns_none_when_not_configured() -> None:
35+
config = ConfigObj({'main': {}, 'connection': {}})
36+
config_wo = ConfigObj({'main': {}, 'connection': {}})
37+
38+
assert normalize_ssl_mode(config, config_wo) == (None, None)
39+
40+
41+
def test_normalize_ssl_mode_migrates_deprecated_main_value() -> None:
42+
config = ConfigObj({'main': {}, 'connection': {'default_ssl_mode': 'off'}})
43+
config_wo = ConfigObj({'main': {'ssl_mode': 'on'}})
44+
45+
ssl_mode, warning = normalize_ssl_mode(config, config_wo)
46+
47+
assert ssl_mode == 'on'
48+
assert (
49+
warning == 'Mycli 2.0 migration: automatically moving ssl_mode under [main] to default_ssl_mode under [connection] in ~/.myclirc .'
50+
)
51+
assert config_wo['connection']['default_ssl_mode'] == 'on'
52+
assert 'ssl_mode' not in config_wo['main']
53+
54+
55+
def test_normalize_ssl_mode_uses_existing_connection_value_when_migrating() -> None:
56+
config = ConfigObj({'main': {}, 'connection': {'default_ssl_mode': 'off'}})
57+
config_wo = ConfigObj({'main': {'ssl_mode': 'on'}, 'connection': {'default_ssl_mode': 'off'}})
58+
59+
ssl_mode, warning = normalize_ssl_mode(config, config_wo)
60+
61+
assert ssl_mode == 'off'
62+
assert warning == (
63+
'Mycli 2.0 migration: automatically moving ssl_mode under [main] to default_ssl_mode under [connection] in ~/.myclirc .'
64+
'\nBut connection.default_ssl_mode already existed, with the value: "off".'
65+
)
66+
assert config_wo['connection']['default_ssl_mode'] == 'off'
67+
assert 'ssl_mode' not in config_wo['main']
3068

3169

3270
def test_normalize_ssl_mode_reports_invalid_values() -> None:
33-
config = ConfigObj({'main': {'ssl_mode': 'required'}, 'connection': {'default_ssl_mode': 'off'}})
71+
config = ConfigObj({'main': {'ssl_mode': 'required'}, 'connection': {'default_ssl_mode': 'required'}})
72+
config_wo = ConfigObj()
3473

35-
ssl_mode, warning = normalize_ssl_mode(config)
74+
ssl_mode, warning = normalize_ssl_mode(config, config_wo)
3675

3776
assert ssl_mode is None
3877
assert warning == 'Invalid config option provided for ssl_mode (required); ignoring.'

test/pytests/test_checkup.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ def test_configuration_checkup_reports_missing_unsupported_and_deprecated(capsys
133133
'main': {
134134
'present': '',
135135
'unsupported_item': '',
136-
'default_character_set': '',
137136
},
138137
'unsupported_section': {
139138
'anything': '',
@@ -162,9 +161,6 @@ def test_configuration_checkup_reports_missing_unsupported_and_deprecated(capsys
162161
assert '### Unsupported in user ~/.myclirc:' in output
163162
assert 'The entire section:\n\n [unsupported_section]\n' in output
164163
assert 'The item:\n\n [main]\n unsupported_item =' in output
165-
assert '### Deprecated in user ~/.myclirc:' in output
166-
assert ' [main]\n default_character_set' in output
167-
assert ' [connection]\n default_character_set' in output
168164
assert f'{checkup.REPO_URL}/blob/main/mycli/myclirc' in output
169165

170166

@@ -200,10 +196,33 @@ def test_configuration_checkup_skips_transitioned_and_free_entry_items(capsys) -
200196
assert 'The entire section:\n\n [extra_section]\n' in output
201197
assert 'Unsupported in user ~/.myclirc:' in output
202198
assert 'The entire section:\n\n [unsupported_section]\n' in output
203-
assert '[connection]\n default_character_set =' not in output
204199
assert '[favorite_queries]' not in output
205200

206201

202+
def test_configuration_checkup_reports_deprecated_transition(capsys) -> None:
203+
mycli = SimpleNamespace(
204+
config={
205+
'main': {},
206+
},
207+
config_without_package_defaults={
208+
'main': {
209+
'ssl_mode': '',
210+
},
211+
},
212+
config_without_user_options={
213+
'main': {},
214+
},
215+
)
216+
217+
checkup._configuration_checkup(mycli)
218+
output = capsys.readouterr().out
219+
220+
assert '### Deprecated in user ~/.myclirc:' in output
221+
assert 'It is recommended to transition:\n\n [main]\n ssl_mode\n\nto\n\n [connection]\n default_ssl_mode' in output
222+
assert '### Unsupported in user ~/.myclirc:' not in output
223+
assert f'{checkup.REPO_URL}/blob/main/mycli/myclirc' in output
224+
225+
207226
def test_configuration_checkup_up_to_date(capsys) -> None:
208227
mycli = SimpleNamespace(
209228
config={

test/pytests/test_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ def test_init_reports_invalid_ssl_mode(monkeypatch: pytest.MonkeyPatch, tmp_path
3131
myclirc = write_myclirc(
3232
tmp_path,
3333
"""
34-
[main]
35-
ssl_mode = invalid
34+
[connection]
35+
default_ssl_mode = invalid
3636
""",
3737
)
3838

test/pytests/test_client_connection.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ def echo(self, *args: Any, **kwargs: Any) -> None:
5252
self.echo_calls.append((args, kwargs))
5353

5454

55+
class WritableConfig(dict[str, Any]):
56+
encoding: str | None = None
57+
58+
def __init__(self, value: dict[str, Any]) -> None:
59+
super().__init__(value)
60+
self.write_calls = 0
61+
62+
def write(self) -> None:
63+
self.write_calls += 1
64+
65+
5566
class FakeSQLExecute:
5667
calls: list[dict[str, Any]] = []
5768
effects: list[Any] = []
@@ -148,12 +159,70 @@ def test_connect_uses_character_set_from_connection_config() -> None:
148159
assert FakeSQLExecute.calls[-1]['character_set'] == 'utf16'
149160

150161

151-
def test_connect_uses_character_set_from_main_config() -> None:
152-
client = DummyClient(config={'main': {'default_character_set': 'utf32'}, 'connection': {}})
162+
def test_connect_migrates_deprecated_character_set_from_main_config(
163+
monkeypatch: pytest.MonkeyPatch,
164+
) -> None:
165+
config_wo = WritableConfig({'main': {'default_character_set': 'utf32'}})
166+
client = DummyClient(
167+
config={'main': {}, 'connection': {'default_character_set': 'utf16'}},
168+
config_without_package_defaults=config_wo,
169+
)
170+
secho_calls: list[tuple[str, dict[str, Any]]] = []
171+
monkeypatch.setattr(
172+
client_connection.click,
173+
'secho',
174+
lambda message, **kwargs: secho_calls.append((message, kwargs)),
175+
)
153176

154177
client.connect(host='db', port=3307)
155178

156179
assert FakeSQLExecute.calls[-1]['character_set'] == 'utf32'
180+
assert config_wo.encoding == 'utf-8'
181+
assert config_wo['connection']['default_character_set'] == 'utf32'
182+
assert 'default_character_set' not in config_wo['main']
183+
assert config_wo.write_calls == 1
184+
assert secho_calls == [
185+
(
186+
'Mycli 2.0 migration: automatically moving default_character_set from [main] to [connection] in ~/.myclirc .',
187+
{'err': True, 'fg': 'red'},
188+
)
189+
]
190+
191+
192+
def test_connect_uses_existing_connection_character_set_when_migrating(
193+
monkeypatch: pytest.MonkeyPatch,
194+
) -> None:
195+
config_wo = WritableConfig({
196+
'main': {'default_character_set': 'utf32'},
197+
'connection': {'default_character_set': 'utf16'},
198+
})
199+
client = DummyClient(
200+
config={'main': {}, 'connection': {'default_character_set': 'latin1'}},
201+
config_without_package_defaults=config_wo,
202+
)
203+
secho_calls: list[tuple[str, dict[str, Any]]] = []
204+
monkeypatch.setattr(
205+
client_connection.click,
206+
'secho',
207+
lambda message, **kwargs: secho_calls.append((message, kwargs)),
208+
)
209+
210+
client.connect(host='db', port=3307)
211+
212+
assert FakeSQLExecute.calls[-1]['character_set'] == 'utf16'
213+
assert config_wo['connection']['default_character_set'] == 'utf16'
214+
assert 'default_character_set' not in config_wo['main']
215+
assert config_wo.write_calls == 1
216+
assert secho_calls == [
217+
(
218+
'Mycli 2.0 migration: automatically moving default_character_set from [main] to [connection] in ~/.myclirc .',
219+
{'err': True, 'fg': 'red'},
220+
),
221+
(
222+
'But connection.default_character_set already existed, with the value: "utf16".',
223+
{'err': True, 'fg': 'red'},
224+
),
225+
]
157226

158227

159228
def test_connect_uses_default_character_set_when_none_configured() -> None:

0 commit comments

Comments
 (0)