Skip to content

Commit ed67b4e

Browse files
committed
remove support for reading vendor my.cnf files
* remove reading of configuration from my.cnf files, in any location * remove reading of [client] configuration from mylogin.cnf files * retain definition of host endpoints via login-path names in mylogin.cnf file * define behave pager boundary in test/myclirc, since it depended on the [client] stanza of mylogin.cnf * remove deprecation warning when my.cnf was read * ssl_config no longer needs merge logic with my.cnf values * config file includes no longer need to be supported * remove --defaults-file CLI flag * remove --defaults-group-suffix CLI flag Preparation for release 2.0
1 parent 7063a3f commit ed67b4e

21 files changed

Lines changed: 122 additions & 586 deletions

changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Upcoming (TBD)
22
==============
33

4+
Breaking Changes
5+
--------
6+
* Remove support for `my.cnf` vendor MySQL client option files.
7+
8+
49
Internal
510
---------
611
* Improve test coverage for DSN variable expansion.

mycli/app_state.py

Lines changed: 17 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
from __future__ import annotations
22

3-
from collections import defaultdict
43
import re
54
from typing import TYPE_CHECKING, Any
65

76
from configobj import ConfigObj
87

9-
from mycli.config import str_to_bool, strip_matching_quotes
8+
from mycli.config import strip_matching_quotes
109

1110
if TYPE_CHECKING:
1211
from mycli.client import MyCli
@@ -19,21 +18,13 @@ def normalize_ssl_mode(config: ConfigObj) -> tuple[str | None, str | None]:
1918
return ssl_mode, None
2019

2120

22-
def ensure_my_cnf_sections(my_cnf: ConfigObj) -> None:
23-
if not my_cnf.get('client'):
24-
my_cnf['client'] = {}
25-
if not my_cnf.get('mysqld'):
26-
my_cnf['mysqld'] = {}
27-
28-
2921
def configure_prompt_state(
3022
mycli: MyCli,
3123
config: ConfigObj,
3224
prompt: str | None,
33-
prompt_cnf: str | None,
3425
toolbar_format: str | None,
3526
) -> None:
36-
mycli.prompt_format = prompt or prompt_cnf or config['main']['prompt'] or mycli.default_prompt
27+
mycli.prompt_format = prompt or config['main']['prompt'] or mycli.default_prompt
3728
mycli.prompt_lines = 0
3829
mycli.multiline_continuation_char = config['main']['prompt_continuation']
3930
mycli.toolbar_format = toolbar_format or config['main']['toolbar']
@@ -61,47 +52,22 @@ def llm_prompt_truncation(config: ConfigObj) -> tuple[int, int]:
6152

6253

6354
class AppStateMixin:
64-
defaults_suffix: str | None
6555
login_path: str | None
6656

67-
def read_my_cnf(self, cnf: ConfigObj, keys: list[str]) -> dict[str, Any]:
68-
sections = ['client', 'mysqld']
69-
key_transformations = {
70-
'mysqld': {
71-
'socket': 'default_socket',
72-
'port': 'default_port',
73-
'user': 'default_user',
74-
},
75-
}
76-
77-
if self.login_path and self.login_path != 'client':
78-
sections.append(self.login_path)
79-
80-
if self.defaults_suffix:
81-
sections.extend([sect + self.defaults_suffix for sect in sections])
82-
83-
configuration: dict[str, Any] = defaultdict(lambda: None)
84-
for key in keys:
85-
for section in cnf:
86-
if section not in sections or key not in cnf[section]:
87-
continue
88-
new_key = key_transformations.get(section, {}).get(key) or key
89-
configuration[new_key] = strip_matching_quotes(cnf[section][key])
90-
91-
return configuration
92-
93-
def merge_ssl_with_cnf(self, ssl: dict[str, Any], cnf: dict[str, Any]) -> dict[str, Any]:
94-
merged = {}
95-
merged.update(ssl)
96-
prefix = 'ssl-'
97-
for key, value in cnf.items():
98-
if not key.startswith(prefix):
99-
continue
100-
if value is None:
57+
def read_mylogin_cnf(self, cnf: ConfigObj) -> dict[str, Any]:
58+
allowed_keys = [
59+
'user',
60+
'password',
61+
'host',
62+
'port',
63+
'socket',
64+
]
65+
configuration: dict[str, Any] = dict.fromkeys(allowed_keys)
66+
for section in cnf:
67+
if section != self.login_path:
10168
continue
102-
if key == 'ssl-verify-server-cert':
103-
merged['check_hostname'] = str_to_bool(value)
104-
else:
105-
merged[key[len(prefix) :]] = value
69+
for key in allowed_keys:
70+
if key in cnf[section]:
71+
configuration[key] = strip_matching_quotes(cnf[section][key])
10672

107-
return merged
73+
return configuration

mycli/cli_runner.py

Lines changed: 1 addition & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
import os
44
import re
55
import sys
6-
from textwrap import dedent
76
from typing import TYPE_CHECKING, Any, Callable
87
from urllib.parse import parse_qs, unquote, urlparse
98

109
import click
1110

1211
from mycli.config import str_to_bool
13-
from mycli.constants import EMPTY_PASSWORD_FLAG_SENTINEL, ISSUES_URL, REPO_URL
12+
from mycli.constants import EMPTY_PASSWORD_FLAG_SENTINEL, ISSUES_URL
1413
from mycli.main_modes.batch import main_batch_from_stdin, main_batch_with_progress_bar, main_batch_without_progress_bar
1514
from mycli.main_modes.checkup import main_checkup
1615
from mycli.main_modes.execute import main_execute_from_cli
@@ -103,8 +102,6 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
103102
prompt=cli_args.prompt,
104103
toolbar_format=cli_args.toolbar,
105104
logfile=cli_args.logfile,
106-
defaults_suffix=cli_args.defaults_group_suffix,
107-
defaults_file=cli_args.defaults_file,
108105
login_path=cli_args.login_path,
109106
auto_vertical_output=cli_args.auto_vertical_output,
110107
warn=cli_args.warn,
@@ -392,82 +389,6 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
392389
use_keyring = str_to_bool(cli_args.use_keyring)
393390
reset_keyring = False
394391

395-
# todo: removeme after a period of transition
396-
for tup in [
397-
('client', 'prompt', 'prompt', 'main', 'prompt'),
398-
('client', 'pager', 'pager', 'main', 'pager'),
399-
('client', 'skip-pager', 'skip-pager', 'main', 'enable_pager'),
400-
# this is a white lie, because default_character_set can actually be read from the package config
401-
('client', 'default-character-set', 'default-character-set', 'connection', 'default_character_set'),
402-
# local-infile can be read from both sections
403-
('mysqld', 'local-infile', 'local-infile', 'connection', 'default_local_infile'),
404-
('client', 'local-infile', 'local-infile', 'connection', 'default_local_infile'),
405-
('mysqld', 'loose-local-infile', 'loose-local-infile', 'connection', 'default_local_infile'),
406-
('client', 'loose-local-infile', 'loose-local-infile', 'connection', 'default_local_infile'),
407-
# todo: in the future we should add default_port, etc, but only in .myclirc
408-
# they are currently ignored in my.cnf
409-
('mysqld', 'default_socket', 'socket', 'connection', 'default_socket'),
410-
('client', 'ssl-ca', 'ssl-ca', 'connection', 'default_ssl_ca'),
411-
('client', 'ssl-cert', 'ssl-cert', 'connection', 'default_ssl_cert'),
412-
('client', 'ssl-key', 'ssl-key', 'connection', 'default_ssl_key'),
413-
('client', 'ssl-cipher', 'ssl-cipher', 'connection', 'default_ssl_cipher'),
414-
('client', 'ssl-verify-server-cert', 'ssl-verify-server-cert', 'connection', 'default_ssl_verify_server_cert'),
415-
]:
416-
(
417-
mycnf_section_name,
418-
mycnf_item_name,
419-
printable_mycnf_item_name,
420-
myclirc_section_name,
421-
myclirc_item_name,
422-
) = tup
423-
if str_to_bool(mycli.config['main'].get('my_cnf_transition_done', 'False')):
424-
break
425-
if (
426-
mycli.my_cnf[mycnf_section_name].get(mycnf_item_name) is None
427-
and mycli.my_cnf[mycnf_section_name].get(mycnf_item_name.replace('-', '_')) is None
428-
):
429-
continue
430-
user_section = mycli.config_without_package_defaults.get(myclirc_section_name, {})
431-
if user_section.get(myclirc_item_name) is None:
432-
cnf_value = mycli.my_cnf[mycnf_section_name].get(mycnf_item_name)
433-
if cnf_value is None:
434-
cnf_value = mycli.my_cnf[mycnf_section_name].get(mycnf_item_name.replace('-', '_'))
435-
click.secho(
436-
dedent(
437-
f"""
438-
Reading configuration from my.cnf files is deprecated.
439-
See {ISSUES_URL}/1490 .
440-
The cause of this message is the following in a my.cnf file without a corresponding
441-
~/.myclirc entry:
442-
443-
[{mycnf_section_name}]
444-
{printable_mycnf_item_name} = {cnf_value}
445-
446-
To suppress this message, remove the my.cnf item add or the following to ~/.myclirc:
447-
448-
[{myclirc_section_name}]
449-
{myclirc_item_name} = <value>
450-
451-
The ~/.myclirc setting will take precedence. In the future, the my.cnf will be ignored.
452-
453-
Values are documented at {REPO_URL}/blob/main/mycli/myclirc . An
454-
empty <value> is generally accepted.
455-
456-
To ignore all of this, set
457-
458-
[main]
459-
my_cnf_transition_done = True
460-
461-
in ~/.myclirc.
462-
463-
--------
464-
465-
"""
466-
),
467-
err=True,
468-
fg='yellow',
469-
)
470-
471392
mycli.connect(
472393
database=database,
473394
user=cli_args.user,

mycli/client.py

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import IO, Literal
88

99
from cli_helpers.tabular_output import TabularOutputFormatter
10+
from configobj import ConfigObj
1011
from prompt_toolkit.formatted_text import to_formatted_text
1112
from prompt_toolkit.shortcuts import PromptSession
1213
import sqlparse
@@ -15,7 +16,6 @@
1516
AppStateMixin,
1617
configure_prompt_state,
1718
destructive_keywords_from_config,
18-
ensure_my_cnf_sections,
1919
llm_prompt_truncation,
2020
normalize_ssl_mode,
2121
)
@@ -24,7 +24,13 @@
2424
from mycli.client_query import ClientQueryMixin
2525
from mycli.clistyle import style_factory_helpers, style_factory_ptoolkit
2626
from mycli.completion_refresher import CompletionRefresher
27-
from mycli.config import get_mylogin_cnf_path, open_mylogin_cnf, read_config_files, write_default_config
27+
from mycli.config import (
28+
get_mylogin_cnf_path,
29+
open_mylogin_cnf,
30+
read_config_file,
31+
read_config_files,
32+
write_default_config,
33+
)
2834
from mycli.constants import DEFAULT_PROMPT
2935
from mycli.main_modes import repl as repl_package
3036
from mycli.output import OutputMixin
@@ -44,19 +50,10 @@ class MyCli(AppStateMixin, OutputMixin, ClientCommandsMixin, ClientConnectionMix
4450
default_prompt = DEFAULT_PROMPT
4551
default_prompt_splitln = "\\u@\\h\\n(\\t):\\d>"
4652
max_len_prompt = 45
47-
defaults_suffix = None
4853
prompt_lines: int
4954
sqlexecute: SQLExecute | None
5055
numeric_alignment: str
5156

52-
# In order of being loaded. Files lower in list override earlier ones.
53-
cnf_files: list[str | IO[str]] = [
54-
"/etc/my.cnf",
55-
"/etc/mysql/my.cnf",
56-
"/usr/local/etc/my.cnf",
57-
os.path.expanduser("~/.my.cnf"),
58-
]
59-
6057
# check XDG_CONFIG_HOME exists and not an empty string
6158
xdg_config_home = os.environ.get("XDG_CONFIG_HOME", "~/.config")
6259
system_config_files: list[str | IO[str]] = [
@@ -72,8 +69,6 @@ def __init__(
7269
prompt: str | None = None,
7370
toolbar_format: str | None = None,
7471
logfile: TextIOWrapper | Literal[False] | None = None,
75-
defaults_suffix: str | None = None,
76-
defaults_file: str | None = None,
7772
login_path: str | None = None,
7873
auto_vertical_output: bool = False,
7974
warn: bool | None = None,
@@ -83,7 +78,6 @@ def __init__(
8378
) -> None:
8479
self.sqlexecute = sqlexecute
8580
self.logfile = logfile
86-
self.defaults_suffix = defaults_suffix
8781
self.login_path = login_path
8882
self.toolbar_error_message: str | None = None
8983
self.prompt_session: PromptSession | None = None
@@ -92,23 +86,13 @@ def __init__(
9286
self.sandbox_mode: bool = False
9387
self.checkpoint: IO | None = None
9488

95-
# self.cnf_files is a class variable that stores the list of mysql
96-
# config files to read in at launch.
97-
# If defaults_file is specified then override the class variable with
98-
# defaults_file.
99-
if defaults_file:
100-
self.cnf_files = [defaults_file]
101-
10289
# Load config.
10390
config_files: list[str | IO[str]] = self.system_config_files + [myclirc] + [self.pwd_config_file]
10491

10592
c = self.config = read_config_files(config_files)
106-
# this parallel config exists to
107-
# * compare with my.cnf
108-
# * support the --checkup feature
109-
# todo: after removing my.cnf, create the parallel configs only when --checkup is set
93+
# only needed in --checkup mode. todo: only load when needed
11094
self.config_without_package_defaults = read_config_files(config_files, ignore_package_defaults=True)
111-
# this parallel config exists to compare with my.cnf support the --checkup feature
95+
# only needed in --checkup mode. todo: only load when needed
11296
self.config_without_user_options = read_config_files(config_files, ignore_user_options=True)
11397
self.multi_line = c["main"].as_bool("multi_line")
11498
self.key_bindings = c["main"]["key_bindings"]
@@ -201,20 +185,16 @@ def __init__(
201185
self.register_special_commands()
202186

203187
# Load .mylogin.cnf if it exists.
204-
mylogin_cnf_path = get_mylogin_cnf_path()
205-
if mylogin_cnf_path:
206-
mylogin_cnf = open_mylogin_cnf(mylogin_cnf_path)
207-
if mylogin_cnf_path and mylogin_cnf:
208-
# .mylogin.cnf gets read last, even if defaults_file is specified.
209-
self.cnf_files.append(mylogin_cnf)
210-
elif mylogin_cnf_path and not mylogin_cnf:
211-
# There was an error reading the login path file.
188+
self.mylogin_cnf = ConfigObj()
189+
mylogin_cnf_h = None
190+
if mylogin_cnf_path := get_mylogin_cnf_path():
191+
mylogin_cnf_h = open_mylogin_cnf(mylogin_cnf_path)
192+
if mylogin_cnf_h:
193+
self.mylogin_cnf = read_config_file(mylogin_cnf_h, list_values=False) or ConfigObj()
194+
else:
212195
print("Error: Unable to read login path file.")
213196

214-
self.my_cnf = read_config_files(self.cnf_files, list_values=False)
215-
ensure_my_cnf_sections(self.my_cnf)
216-
prompt_cnf = self.read_my_cnf(self.my_cnf, ["prompt"])["prompt"]
217-
configure_prompt_state(self, c, prompt, prompt_cnf, toolbar_format)
197+
configure_prompt_state(self, c, prompt, toolbar_format)
218198
self.prompt_session = None
219199
self.destructive_keywords = destructive_keywords_from_config(c)
220200
special.set_destructive_keywords(self.destructive_keywords)

0 commit comments

Comments
 (0)