Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Bug Fixes
--------
* Refactor completions for special commands, with minor casing fixes.
* Raise `--password-file` higher in the precedence of password specification.
* Fix regression: show username in password prompt.


Internal
Expand Down
79 changes: 39 additions & 40 deletions mycli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ def connect(
ssh_key_filename: str | None = "",
init_command: str | None = "",
unbuffered: bool | None = None,
password_file: str | None = None,
) -> None:
cnf = {
"database": None,
Expand Down Expand Up @@ -538,9 +539,43 @@ def connect(
# 4. DSN (mysql://user:password)
# 5. cnf (.my.cnf / etc)

def get_password_from_file(password_file: str | None) -> str | None:
if not password_file:
return None
try:
with open(password_file) as fp:
password = fp.readline().strip()
return password
except FileNotFoundError:
click.secho(f"Password file '{password_file}' not found", err=True, fg="red")
sys.exit(1)
except PermissionError:
click.secho(f"Permission denied reading password file '{password_file}'", err=True, fg="red")
sys.exit(1)
except IsADirectoryError:
click.secho(f"Path '{password_file}' is a directory, not a file", err=True, fg="red")
sys.exit(1)
except Exception as e:
click.secho(f"Error reading password file '{password_file}': {str(e)}", err=True, fg="red")
sys.exit(1)

if passwd == "MYCLI_ASK_PASSWORD":
passwd = click.prompt(f"Enter password for {user}", hide_input=True, show_default=False, default='', type=str, err=True)

# if the passwd is not specified try to set it using the password_file option
if passwd is None and password_file:
password_from_file = get_password_from_file(password_file)
if password_from_file is not None:
passwd = password_from_file

# getting the envvar ourselves because the envvar from a click
# option cannot be an empty string, but a password can be
if passwd is None and os.environ.get("MYSQL_PWD") is not None:
passwd = os.environ.get("MYSQL_PWD")

# if no password was found from all of the above sources, ask for a password
if passwd is None:
passwd = click.prompt("Enter password", hide_input=True, show_default=False, default='', type=str, err=True)
passwd = click.prompt(f"Enter password for {user}", hide_input=True, show_default=False, default='', type=str, err=True)

# Connect to the database.
def _connect() -> None:
Expand Down Expand Up @@ -1600,34 +1635,7 @@ def cli(
- mycli mysql://my_user@my_host.com:3306/my_database

"""

def get_password_from_file(password_file: str | None) -> str | None:
if not password_file:
return None
try:
with open(password_file) as fp:
password = fp.readline().strip()
return password
except FileNotFoundError:
click.secho(f"Password file '{password_file}' not found", err=True, fg="red")
sys.exit(1)
except PermissionError:
click.secho(f"Permission denied reading password file '{password_file}'", err=True, fg="red")
sys.exit(1)
except IsADirectoryError:
click.secho(f"Path '{password_file}' is a directory, not a file", err=True, fg="red")
sys.exit(1)
except Exception as e:
click.secho(f"Error reading password file '{password_file}': {str(e)}", err=True, fg="red")
sys.exit(1)

# if user passes the --p* flag, ask for the password right away
# to reduce lag as much as possible
if password == "MYCLI_ASK_PASSWORD":
password = click.prompt("Enter password", hide_input=True, show_default=False, default='', type=str, err=True)
# if the password value looks like a DSN, treat it as such and
# prompt for password
elif database is None and password is not None and "://" in password:
if database is None and password is not None and "://" in password:
# check if the scheme is valid. We do not actually have any logic for these, but
# it will most usefully catch the case where we erroneously catch someone's
# password, and give them an easy error message to follow / report
Expand All @@ -1636,17 +1644,7 @@ def get_password_from_file(password_file: str | None) -> str | None:
click.secho(f"Error: Unknown connection scheme provided for DSN URI ({scheme}://)", err=True, fg="red")
sys.exit(1)
database = password
password = click.prompt("Enter password", hide_input=True, show_default=False, default='', type=str, err=True)

# if the passwd is not specified try to set it using the password_file option
if password is None and password_file:
if password_from_file := get_password_from_file(password_file):
password = password_from_file

# getting the envvar ourselves because the envvar from a click
# option cannot be an empty string, but a password can be
if password is None and os.environ.get("MYSQL_PWD") is not None:
password = os.environ.get("MYSQL_PWD")
password = "MYCLI_ASK_PASSWORD"

mycli = MyCli(
prompt=prompt,
Expand Down Expand Up @@ -1880,6 +1878,7 @@ def get_password_from_file(password_file: str | None) -> str | None:
init_command=combined_init_cmd,
unbuffered=unbuffered,
charset=charset,
password_file=password_file,
)

if combined_init_cmd:
Expand Down
3 changes: 2 additions & 1 deletion test/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from click.testing import CliRunner
from pymysql.err import OperationalError

from mycli.main import MyCli, cli, is_valid_connection_scheme, thanks_picker
from mycli.main import MyCli, cli, thanks_picker
from mycli.packages.parseutils import is_valid_connection_scheme
import mycli.packages.special
from mycli.packages.special.main import COMMANDS as SPECIAL_COMMANDS
from mycli.sqlexecute import ServerInfo, SQLExecute
Expand Down