Skip to content

Commit eee659b

Browse files
committed
clean up needless imports in main.py
and remove test_main_regression.py. test_main_regression.py was a low-quality, fully agent-generated file, only meant to aid in refactoring. This was always intended to be deleted, and the main refactoring is finished. test_main_regression.py was also the main reason that main.py had a series of otherwise needless imports, which are removed here. Various test and non-test files are updated to find the imports more directly. This does formally reduce test coverage to 97%. That gap can be closed in followups, more carefully than was done in test_main_regression.py.
1 parent a166fcf commit eee659b

9 files changed

Lines changed: 94 additions & 1663 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Internal
1616
---------
1717
* Factor `main.py` into several files using mixins.
1818
* Move CLI argument handling back to `main.py`.
19+
* Clean up needless imports in `main.py`.
1920
* Update Python versions used in CI.
2021
* Add CI on macOS.
2122
* Add limited CI on Windows.

mycli/cli_runner.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010

1111
from mycli.config import str_to_bool
1212
from mycli.constants import EMPTY_PASSWORD_FLAG_SENTINEL, ISSUES_URL, REPO_URL
13+
from mycli.main_modes.batch import main_batch_from_stdin, main_batch_with_progress_bar, main_batch_without_progress_bar
14+
from mycli.main_modes.checkup import main_checkup
15+
from mycli.main_modes.execute import main_execute_from_cli
16+
from mycli.main_modes.list_dsn import main_list_dsn
17+
from mycli.main_modes.list_ssh_config import main_list_ssh_config
18+
from mycli.packages.cli_utils import is_valid_connection_scheme
1319
from mycli.packages.ssh_utils import read_ssh_config
1420

1521
if TYPE_CHECKING:
@@ -21,7 +27,7 @@
2127
def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> None:
2228
from mycli import main as main_module
2329

24-
cli_verbosity = main_module.preprocess_cli_args(cli_args, main_module.is_valid_connection_scheme)
30+
cli_verbosity = main_module.preprocess_cli_args(cli_args, is_valid_connection_scheme)
2531

2632
mycli = client_factory(
2733
prompt=cli_args.prompt,
@@ -38,7 +44,7 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
3844
)
3945

4046
if cli_args.checkup:
41-
main_module.main_checkup(mycli)
47+
main_checkup(mycli)
4248
sys.exit(0)
4349

4450
if cli_args.csv and cli_args.format not in [None, 'csv']:
@@ -86,10 +92,10 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
8692
)
8793

8894
if cli_args.list_dsn:
89-
sys.exit(main_module.main_list_dsn(mycli))
95+
sys.exit(main_list_dsn(mycli))
9096

9197
if cli_args.list_ssh_config:
92-
sys.exit(main_module.main_list_ssh_config(mycli, cli_args))
98+
sys.exit(main_list_ssh_config(mycli, cli_args))
9399

94100
if 'MYSQL_UNIX_PORT' in os.environ:
95101
# deprecated 2026-03
@@ -141,7 +147,7 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
141147
try:
142148
dsn_uri = mycli.config["alias_dsn"][cli_args.dsn]
143149
except KeyError:
144-
is_valid_scheme, scheme = main_module.is_valid_connection_scheme(cli_args.dsn)
150+
is_valid_scheme, scheme = is_valid_connection_scheme(cli_args.dsn)
145151
if is_valid_scheme:
146152
dsn_uri = cli_args.dsn
147153
else:
@@ -410,16 +416,16 @@ def run_from_cli_args(cli_args: 'CliArgs', client_factory: ClientFactory) -> Non
410416
)
411417

412418
if cli_args.execute is not None:
413-
sys.exit(main_module.main_execute_from_cli(mycli, cli_args))
419+
sys.exit(main_execute_from_cli(mycli, cli_args))
414420

415421
if cli_args.batch is not None and cli_args.batch != '-' and cli_args.progress and sys.stderr.isatty():
416-
sys.exit(main_module.main_batch_with_progress_bar(mycli, cli_args))
422+
sys.exit(main_batch_with_progress_bar(mycli, cli_args))
417423

418424
if cli_args.batch is not None:
419-
sys.exit(main_module.main_batch_without_progress_bar(mycli, cli_args))
425+
sys.exit(main_batch_without_progress_bar(mycli, cli_args))
420426

421427
if not sys.stdin.isatty():
422-
sys.exit(main_module.main_batch_from_stdin(mycli, cli_args))
428+
sys.exit(main_batch_from_stdin(mycli, cli_args))
423429

424430
mycli.run_cli()
425431
mycli.close()

mycli/client.py

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

9+
from cli_helpers.tabular_output import TabularOutputFormatter
910
from prompt_toolkit.formatted_text import to_formatted_text
1011
from prompt_toolkit.shortcuts import PromptSession
1112
import sqlparse
@@ -21,12 +22,17 @@
2122
from mycli.client_commands import ClientCommandsMixin
2223
from mycli.client_connection import ClientConnectionMixin
2324
from mycli.client_query import ClientQueryMixin
25+
from mycli.clistyle import style_factory_helpers, style_factory_ptoolkit
26+
from mycli.completion_refresher import CompletionRefresher
27+
from mycli.config import get_mylogin_cnf_path, open_mylogin_cnf, read_config_files, write_default_config
2428
from mycli.constants import DEFAULT_PROMPT
2529
from mycli.main_modes import repl as repl_package
2630
from mycli.output import OutputMixin
2731
from mycli.packages import special
2832
from mycli.packages.special.favoritequeries import FavoriteQueries
2933
from mycli.packages.tabular_output import sql_format
34+
from mycli.schema_prefetcher import SchemaPrefetcher
35+
from mycli.sqlcompleter import SQLCompleter
3036
from mycli.sqlexecute import SQLExecute
3137
from mycli.types import Query
3238

@@ -94,16 +100,15 @@ def __init__(
94100

95101
# Load config.
96102
config_files: list[str | IO[str]] = self.system_config_files + [myclirc] + [self.pwd_config_file]
97-
from mycli import main as main_module
98103

99-
c = self.config = main_module.read_config_files(config_files)
104+
c = self.config = read_config_files(config_files)
100105
# this parallel config exists to
101106
# * compare with my.cnf
102107
# * support the --checkup feature
103108
# todo: after removing my.cnf, create the parallel configs only when --checkup is set
104-
self.config_without_package_defaults = main_module.read_config_files(config_files, ignore_package_defaults=True)
109+
self.config_without_package_defaults = read_config_files(config_files, ignore_package_defaults=True)
105110
# this parallel config exists to compare with my.cnf support the --checkup feature
106-
self.config_without_user_options = main_module.read_config_files(config_files, ignore_user_options=True)
111+
self.config_without_user_options = read_config_files(config_files, ignore_user_options=True)
107112
self.multi_line = c["main"].as_bool("multi_line")
108113
self.key_bindings = c["main"]["key_bindings"]
109114
self.emacs_ttimeoutlen = c['keys'].as_float('emacs_ttimeoutlen')
@@ -120,8 +125,8 @@ def __init__(
120125
FavoriteQueries.instance = FavoriteQueries.from_config(self.config)
121126

122127
self.dsn_alias: str | None = None
123-
self.main_formatter = main_module.TabularOutputFormatter(format_name=c["main"]["table_format"])
124-
self.redirect_formatter = main_module.TabularOutputFormatter(format_name=c["main"].get("redirect_format", "csv"))
128+
self.main_formatter = TabularOutputFormatter(format_name=c["main"]["table_format"])
129+
self.redirect_formatter = TabularOutputFormatter(format_name=c["main"].get("redirect_format", "csv"))
125130
sql_format.register_new_formatter(self.main_formatter)
126131
sql_format.register_new_formatter(self.redirect_formatter)
127132
self.main_formatter.mycli = self
@@ -131,9 +136,9 @@ def __init__(
131136
if cli_verbosity:
132137
self.verbosity = cli_verbosity
133138
self.cli_style = c["colors"]
134-
self.ptoolkit_style = main_module.style_factory_ptoolkit(self.syntax_style, self.cli_style)
135-
self.helpers_style = main_module.style_factory_helpers(self.syntax_style, self.cli_style)
136-
self.helpers_warnings_style = main_module.style_factory_helpers(self.syntax_style, self.cli_style, warnings=True)
139+
self.ptoolkit_style = style_factory_ptoolkit(self.syntax_style, self.cli_style)
140+
self.helpers_style = style_factory_helpers(self.syntax_style, self.cli_style)
141+
self.helpers_warnings_style = style_factory_helpers(self.syntax_style, self.cli_style, warnings=True)
137142
self.wider_completion_menu = c["main"].as_bool("wider_completion_menu")
138143
c_dest_warning = c["main"].as_bool("destructive_warning")
139144
self.destructive_warning = c_dest_warning if warn is None else warn
@@ -153,7 +158,7 @@ def __init__(
153158

154159
# Write user config if system config wasn't the last config loaded.
155160
if c.filename not in self.system_config_files and not os.path.exists(myclirc):
156-
main_module.write_default_config(myclirc)
161+
write_default_config(myclirc)
157162

158163
# audit log
159164
if self.logfile is None and "audit_log" in c["main"]:
@@ -163,11 +168,11 @@ def __init__(
163168
self.echo("Error: Unable to open the audit log file. Your queries will not be logged.", err=True, fg="red")
164169
self.logfile = False
165170

166-
self.completion_refresher = main_module.CompletionRefresher()
171+
self.completion_refresher = CompletionRefresher()
167172
self.prefetch_schemas_mode = c["main"].get("prefetch_schemas_mode", "always") or "always"
168173
raw_prefetch_list = c["main"].as_list("prefetch_schemas_list") if "prefetch_schemas_list" in c["main"] else []
169174
self.prefetch_schemas_list = [s.strip() for s in raw_prefetch_list if s and s.strip()]
170-
self.schema_prefetcher = main_module.SchemaPrefetcher(self)
175+
self.schema_prefetcher = SchemaPrefetcher(self)
171176

172177
self.logger = logging.getLogger(__name__)
173178
self.initialize_logging()
@@ -180,7 +185,7 @@ def __init__(
180185

181186
# Initialize completer.
182187
self.smart_completion = c["main"].as_bool("smart_completion")
183-
self.completer = main_module.SQLCompleter(
188+
self.completer = SQLCompleter(
184189
self.smart_completion, supported_formats=self.main_formatter.supported_formats, keyword_casing=keyword_casing
185190
)
186191
self._completer_lock = threading.Lock()
@@ -195,17 +200,17 @@ def __init__(
195200
self.register_special_commands()
196201

197202
# Load .mylogin.cnf if it exists.
198-
mylogin_cnf_path = main_module.get_mylogin_cnf_path()
203+
mylogin_cnf_path = get_mylogin_cnf_path()
199204
if mylogin_cnf_path:
200-
mylogin_cnf = main_module.open_mylogin_cnf(mylogin_cnf_path)
205+
mylogin_cnf = open_mylogin_cnf(mylogin_cnf_path)
201206
if mylogin_cnf_path and mylogin_cnf:
202207
# .mylogin.cnf gets read last, even if defaults_file is specified.
203208
self.cnf_files.append(mylogin_cnf)
204209
elif mylogin_cnf_path and not mylogin_cnf:
205210
# There was an error reading the login path file.
206211
print("Error: Unable to read login path file.")
207212

208-
self.my_cnf = main_module.read_config_files(self.cnf_files, list_values=False)
213+
self.my_cnf = read_config_files(self.cnf_files, list_values=False)
209214
ensure_my_cnf_sections(self.my_cnf)
210215
prompt_cnf = self.read_my_cnf(self.my_cnf, ["prompt"])["prompt"]
211216
configure_prompt_state(self, c, prompt, prompt_cnf, toolbar_format)
@@ -220,6 +225,4 @@ def close(self) -> None:
220225
self.sqlexecute.close()
221226

222227
def run_cli(self) -> None:
223-
from mycli import main as main_module
224-
225-
main_module.main_repl(self)
228+
repl_package.main_repl(self)

mycli/client_commands.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88

99
import click
1010

11+
from mycli.main_modes.repl import set_all_external_titles
1112
from mycli.packages import special
13+
from mycli.packages.filepaths import dir_path_exists
14+
from mycli.packages.interactive_utils import confirm_destructive_query
1215
from mycli.packages.special.main import ArgType, SpecialCommandAlias
1316
from mycli.packages.sqlresult import SQLResult
17+
from mycli.sqlexecute import SQLExecute
1418

1519

1620
class ClientCommandsMixin:
@@ -124,19 +128,17 @@ def change_db(self, arg: str, **_) -> Generator[SQLResult, None, None]:
124128
click.secho("No database selected", err=True, fg="red")
125129
return
126130

127-
# todo: this jump back to repl.py is a sign that separation is incomplete.
128-
# also: it should not be needed. Don't titles update on every new prompt?
129-
from mycli import main as main_module
130-
131-
assert isinstance(self.sqlexecute, main_module.SQLExecute)
131+
assert isinstance(self.sqlexecute, SQLExecute)
132132

133133
if self.sqlexecute.dbname == arg:
134134
msg = f'You are already connected to database "{self.sqlexecute.dbname}" as user "{self.sqlexecute.user}"'
135135
else:
136136
self.sqlexecute.change_db(arg)
137137
msg = f'You are now connected to database "{self.sqlexecute.dbname}" as user "{self.sqlexecute.user}"'
138138

139-
main_module.set_all_external_titles(cast(Any, self))
139+
# todo: this jump back to repl.py is a sign that separation is incomplete.
140+
# also: it should not be needed. Don't titles update on every new prompt?
141+
set_all_external_titles(cast(Any, self))
140142

141143
yield SQLResult(status=msg)
142144

@@ -150,13 +152,11 @@ def execute_from_file(self, arg: str, **_) -> Iterable[SQLResult]:
150152
except IOError as e:
151153
return [SQLResult(status=str(e))]
152154

153-
from mycli import main as main_module
154-
155-
if self.destructive_warning and main_module.confirm_destructive_query(self.destructive_keywords, query) is False:
155+
if self.destructive_warning and confirm_destructive_query(self.destructive_keywords, query) is False:
156156
message = "Wise choice. Command execution stopped."
157157
return [SQLResult(status=message)]
158158

159-
assert isinstance(self.sqlexecute, main_module.SQLExecute)
159+
assert isinstance(self.sqlexecute, SQLExecute)
160160
return self.sqlexecute.run(query)
161161

162162
def change_prompt_format(self, arg: str, **_) -> list[SQLResult]:
@@ -182,14 +182,12 @@ def initialize_logging(self) -> None:
182182
"DEBUG": logging.DEBUG,
183183
}
184184

185-
from mycli import main as main_module
186-
187185
# Disable logging if value is NONE by switching to a no-op handler
188186
# Set log level to a high value so it doesn't even waste cycles getting called.
189187
if log_level.upper() == "NONE":
190188
handler: logging.Handler = logging.NullHandler()
191189
log_level = "CRITICAL"
192-
elif main_module.dir_path_exists(log_file):
190+
elif dir_path_exists(log_file):
193191
handler = logging.FileHandler(log_file)
194192
else:
195193
self.echo(f'Error: Unable to open the log file "{log_file}".', err=True, fg="red")

mycli/client_connection.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@
66
from typing import TYPE_CHECKING, Any
77

88
import click
9+
import keyring
910
import pymysql
1011
from pymysql.constants.CR import CR_SERVER_LOST
1112
from pymysql.constants.ER import ACCESS_DENIED_ERROR, HANDSHAKE_ERROR
1213

1314
from mycli.compat import WIN
15+
from mycli.config import str_to_bool
1416
from mycli.constants import (
1517
DEFAULT_CHARSET,
1618
DEFAULT_HOST,
1719
DEFAULT_PORT,
1820
EMPTY_PASSWORD_FLAG_SENTINEL,
1921
ER_MUST_CHANGE_PASSWORD_LOGIN,
2022
)
23+
from mycli.packages.filepaths import guess_socket_location
24+
from mycli.sqlexecute import SQLExecute
2125

2226
try:
2327
from pwd import getpwuid
@@ -61,8 +65,6 @@ def connect(
6165
reset_keyring: bool | None = None,
6266
keepalive_ticks: int | None = None,
6367
) -> None:
64-
from mycli import main as main_module
65-
6668
cnf = {
6769
"database": None,
6870
"user": None,
@@ -101,7 +103,7 @@ def connect(
101103
or user_connection_config.get("default_socket")
102104
or cnf["socket"]
103105
or cnf["default_socket"]
104-
or main_module.guess_socket_location()
106+
or guess_socket_location()
105107
)
106108

107109
passwd = passwd if isinstance(passwd, (str, int)) else cnf["password"]
@@ -133,7 +135,7 @@ def connect(
133135
False,
134136
):
135137
try:
136-
use_local_infile = main_module.str_to_bool(local_infile_option or '')
138+
use_local_infile = str_to_bool(local_infile_option or '')
137139
break
138140
except (TypeError, ValueError):
139141
pass
@@ -176,7 +178,7 @@ def connect(
176178
keyring_retrieved_cleanly = False
177179

178180
if passwd is None and use_keyring and not reset_keyring:
179-
passwd = main_module.keyring.get_password(keyring_domain, keyring_identifier)
181+
passwd = keyring.get_password(keyring_domain, keyring_identifier)
180182
if passwd is not None:
181183
keyring_retrieved_cleanly = True
182184

@@ -212,9 +214,9 @@ def _update_keyring(password: str | None, keyring_retrieved_cleanly: bool):
212214
return
213215
if reset_keyring or (use_keyring and not keyring_retrieved_cleanly):
214216
try:
215-
saved_pw = main_module.keyring.get_password(keyring_domain, keyring_identifier)
217+
saved_pw = keyring.get_password(keyring_domain, keyring_identifier)
216218
if password != saved_pw or reset_keyring:
217-
main_module.keyring.set_password(keyring_domain, keyring_identifier, password)
219+
keyring.set_password(keyring_domain, keyring_identifier, password)
218220
click.secho(f'Password saved to the system keyring at {keyring_domain}/{keyring_identifier}', err=True)
219221
except Exception as e:
220222
click.secho(f'Password not saved to the system keyring: {e}', err=True, fg='red')
@@ -228,7 +230,7 @@ def _connect(
228230
try:
229231
if keyring_save_eligible:
230232
_update_keyring(connection_info["password"], keyring_retrieved_cleanly=keyring_retrieved_cleanly)
231-
self.sqlexecute = main_module.SQLExecute(**connection_info)
233+
self.sqlexecute = SQLExecute(**connection_info)
232234
except pymysql.OperationalError as e1:
233235
if e1.args[0] == HANDSHAKE_ERROR and ssl is not None and ssl.get("mode", None) == "auto":
234236
# if we already tried and failed to connect without SSL, raise the error

0 commit comments

Comments
 (0)