diff --git a/changelog.md b/changelog.md index 8b072210..6313505a 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ Features --------- * Respond to `-h` alone with the helpdoc. * Allow `--hostname` as an alias for `--host`. +* Deprecate `$DSN` environment variable in favor of `$MYSQL_DSN`. Bug Fixes diff --git a/mycli/main.py b/mycli/main.py index 47502911..af0dc60d 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -2055,7 +2055,7 @@ class CliArgs: '-d', type=str, default='', - envvar='DSN', + envvar='MYSQL_DSN', help='DSN alias configured in the ~/.myclirc file, or a full DSN.', ) list_dsn: bool = clickdc.option( @@ -2344,6 +2344,16 @@ def get_password_from_file(password_file: str | None) -> str | None: if not cli_args.socket: cli_args.socket = os.environ['MYSQL_UNIX_PORT'] + if 'DSN' in os.environ: + # deprecated 2026-03 + click.secho( + "The DSN environment variable is deprecated in favor of MYSQL_DSN. Support for DSN will be removed in a future release.", + err=True, + fg="red", + ) + if not cli_args.dsn: + cli_args.dsn = os.environ['DSN'] + # Choose which ever one has a valid value. database = cli_args.dbname or cli_args.database diff --git a/test/pytests/test_main.py b/test/pytests/test_main.py index e1906552..d75cc001 100644 --- a/test/pytests/test_main.py +++ b/test/pytests/test_main.py @@ -1118,6 +1118,112 @@ def run_query(self, query, new_line=True): assert MockMyCli.connect_args['character_set'] == 'utf8mb3' +def test_mysql_dsn_envvar(monkeypatch): + class Formatter: + format_name = None + + class Logger: + def debug(self, *args, **args_dict): + pass + + def warning(self, *args, **args_dict): + pass + + class MockMyCli: + config = { + 'main': {}, + 'alias_dsn': {}, + 'connection': { + 'default_keepalive_ticks': 0, + }, + } + + def __init__(self, **_args): + self.logger = Logger() + self.destructive_warning = False + self.main_formatter = Formatter() + self.redirect_formatter = Formatter() + self.ssl_mode = 'auto' + self.my_cnf = {'client': {}, 'mysqld': {}} + self.default_keepalive_ticks = 0 + + def connect(self, **args): + MockMyCli.connect_args = args + + def run_query(self, query, new_line=True): + pass + + import mycli.main + + monkeypatch.setattr(mycli.main, 'MyCli', MockMyCli) + monkeypatch.setenv('MYSQL_DSN', 'mysql://dsn_user:dsn_passwd@dsn_host:7/dsn_database') + runner = CliRunner() + + result = runner.invoke(mycli.main.click_entrypoint) + assert result.exit_code == 0, result.output + ' ' + str(result.exception) + assert 'DSN environment variable is deprecated' not in result.output + assert ( + MockMyCli.connect_args['user'] == 'dsn_user' + and MockMyCli.connect_args['passwd'] == 'dsn_passwd' + and MockMyCli.connect_args['host'] == 'dsn_host' + and MockMyCli.connect_args['port'] == 7 + and MockMyCli.connect_args['database'] == 'dsn_database' + ) + + +def test_legacy_dsn_envvar_warns_and_falls_back(monkeypatch): + class Formatter: + format_name = None + + class Logger: + def debug(self, *args, **args_dict): + pass + + def warning(self, *args, **args_dict): + pass + + class MockMyCli: + config = { + 'main': {}, + 'alias_dsn': {}, + 'connection': { + 'default_keepalive_ticks': 0, + }, + } + + def __init__(self, **_args): + self.logger = Logger() + self.destructive_warning = False + self.main_formatter = Formatter() + self.redirect_formatter = Formatter() + self.ssl_mode = 'auto' + self.my_cnf = {'client': {}, 'mysqld': {}} + self.default_keepalive_ticks = 0 + + def connect(self, **args): + MockMyCli.connect_args = args + + def run_query(self, query, new_line=True): + pass + + import mycli.main + + monkeypatch.setattr(mycli.main, 'MyCli', MockMyCli) + monkeypatch.setenv('DSN', 'mysql://dsn_user:dsn_passwd@dsn_host:8/dsn_database') + runner = CliRunner() + + result = runner.invoke(mycli.main.click_entrypoint) + assert result.exit_code == 0, result.output + ' ' + str(result.exception) + assert 'The DSN environment variable is deprecated' in result.output + assert ( + MockMyCli.connect_args['user'] == 'dsn_user' + and MockMyCli.connect_args['passwd'] == 'dsn_passwd' + and MockMyCli.connect_args['host'] == 'dsn_host' + and MockMyCli.connect_args['port'] == 8 + and MockMyCli.connect_args['database'] == 'dsn_database' + ) + + def test_password_flag_uses_sentinel(monkeypatch): class Formatter: format_name = None