Skip to content
Merged
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
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,3 @@ jobs:
TERM: xterm
run: |
uv run tox -e py${{ matrix.python-version }}

- name: Run Style Checks
run: uv run tox -e style
30 changes: 30 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: lint

on:
pull_request:
paths-ignore:
- '**.md'
- 'AUTHORS'

jobs:
linters:
name: Linters
runs-on: ubuntu-latest

steps:
- name: Check out Git repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

# todo
# remember to sync the ruff-check version number with pyproject.toml
# - name: Run ruff check
# uses: astral-sh/ruff-action@9828f49eb4cadf267b40eaa330295c412c68c1f9 # v3.2.2
# with:
# version: 0.11.5

# remember to sync the ruff-check version number with pyproject.toml
- name: Run ruff format
uses: astral-sh/ruff-action@9828f49eb4cadf267b40eaa330295c412c68c1f9 # v3.2.2
with:
version: 0.11.5
args: 'format --check'
4 changes: 2 additions & 2 deletions mycli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ def read_config_file(f, list_values=True):
try:
config = ConfigObj(f, interpolation=False, encoding="utf8", list_values=list_values)
except ConfigObjError as e:
log(logger, logging.WARNING, "Unable to parse line {0} of config file " "'{1}'.".format(e.line_number, f))
log(logger, logging.WARNING, "Unable to parse line {0} of config file '{1}'.".format(e.line_number, f))
log(logger, logging.WARNING, "Using successfully parsed config values.")
return e.config
except (IOError, OSError) as e:
log(logger, logging.WARNING, "You don't have permission to read " "config file '{0}'.".format(e.filename))
log(logger, logging.WARNING, "You don't have permission to read config file '{0}'.".format(e.filename))
return None

return config
Expand Down
21 changes: 12 additions & 9 deletions mycli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,15 @@
# Query tuples are used for maintaining history
Query = namedtuple("Query", ["query", "successful", "mutating"])

SUPPORT_INFO = "Home: http://mycli.net\n" "Bug tracker: https://github.com/dbcli/mycli/issues"
SUPPORT_INFO = "Home: http://mycli.net\nBug tracker: https://github.com/dbcli/mycli/issues"


class PasswordFileError(Exception):
"""Base exception for errors related to reading password files."""

pass


class MyCli(object):
default_prompt = "\\t \\u@\\h:\\d> "
default_prompt_splitln = "\\u@\\h\\n(\\t):\\d>"
Expand Down Expand Up @@ -256,7 +259,7 @@ def change_db(self, arg, **_):
arg = re.sub(r"``", r"`", arg)
self.sqlexecute.change_db(arg)

yield (None, None, None, 'You are now connected to database "%s" as ' 'user "%s"' % (self.sqlexecute.dbname, self.sqlexecute.user))
yield (None, None, None, 'You are now connected to database "%s" as user "%s"' % (self.sqlexecute.dbname, self.sqlexecute.user))

def execute_from_file(self, arg, **_):
if not arg:
Expand Down Expand Up @@ -308,7 +311,7 @@ def initialize_logging(self):
self.echo('Error: Unable to open the log file "{}".'.format(log_file), err=True, fg="red")
return

formatter = logging.Formatter("%(asctime)s (%(process)d/%(threadName)s) " "%(name)s %(levelname)s - %(message)s")
formatter = logging.Formatter("%(asctime)s (%(process)d/%(threadName)s) %(name)s %(levelname)s - %(message)s")

handler.setFormatter(formatter)

Expand Down Expand Up @@ -643,7 +646,7 @@ def run_cli(self):
else:
history = None
self.echo(
'Error: Unable to open the history file "{}". ' "Your query history will not be saved.".format(history_file),
'Error: Unable to open the history file "{}". Your query history will not be saved.'.format(history_file),
err=True,
fg="red",
)
Expand Down Expand Up @@ -1113,7 +1116,7 @@ def get_last_query(self):

@click.command()
@click.option("-h", "--host", envvar="MYSQL_HOST", help="Host address of the database.")
@click.option("-P", "--port", envvar="MYSQL_TCP_PORT", type=int, help="Port number to use for connection. Honors " "$MYSQL_TCP_PORT.")
@click.option("-P", "--port", envvar="MYSQL_TCP_PORT", type=int, help="Port number to use for connection. Honors $MYSQL_TCP_PORT.")
@click.option("-u", "--user", help="User name to connect to the database.")
@click.option("-S", "--socket", envvar="MYSQL_UNIX_PORT", help="The socket file to use for connection.")
@click.option("-p", "--password", "password", envvar="MYSQL_PWD", type=str, help="Password to connect to the database.")
Expand All @@ -1139,7 +1142,7 @@ def get_last_query(self):
@click.option(
"--ssl-verify-server-cert",
is_flag=True,
help=('Verify server\'s "Common Name" in its cert against ' "hostname used when connecting. This option is disabled " "by default."),
help=('Verify server\'s "Common Name" in its cert against hostname used when connecting. This option is disabled by default.'),
)
# as of 2016-02-15 revocation list is not supported by underling PyMySQL
# library (--ssl-crl and --ssl-crlpath options in vanilla mysql client)
Expand Down Expand Up @@ -1237,7 +1240,7 @@ def cli(
try:
alias_dsn = mycli.config["alias_dsn"]
except KeyError:
click.secho("Invalid DSNs found in the config file. " 'Please check the "[alias_dsn]" section in myclirc.', err=True, fg="red")
click.secho("Invalid DSNs found in the config file. Please check the \"[alias_dsn]\" section in myclirc.", err=True, fg="red")
sys.exit(1)
except Exception as e:
click.secho(str(e), err=True, fg="red")
Expand Down Expand Up @@ -1293,7 +1296,7 @@ def cli(
dsn_uri = mycli.config["alias_dsn"][dsn]
except KeyError:
click.secho(
"Could not find the specified DSN in the config file. " 'Please check the "[alias_dsn]" section in your ' "myclirc.",
"Could not find the specified DSN in the config file. Please check the \"[alias_dsn]\" section in your myclirc.",
err=True,
fg="red",
)
Expand Down Expand Up @@ -1370,7 +1373,7 @@ def cli(
if combined_init_cmd:
click.echo("Executing init-command: %s" % combined_init_cmd, err=True)

mycli.logger.debug("Launch Params: \n" "\tdatabase: %r" "\tuser: %r" "\thost: %r" "\tport: %r", database, user, host, port)
mycli.logger.debug("Launch Params: \n\tdatabase: %r\tuser: %r\thost: %r\tport: %r", database, user, host, port)

# --execute argument
if execute:
Expand Down
2 changes: 1 addition & 1 deletion mycli/packages/prompt_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def confirm_destructive_query(queries):
* False if the query is destructive and the user doesn't want to proceed.

"""
prompt_text = "You're about to run a destructive command.\n" "Do you want to proceed? (y/n)"
prompt_text = "You're about to run a destructive command.\nDo you want to proceed? (y/n)"
if is_destructive(queries) and sys.stdin.isatty():
return prompt(prompt_text, type=BOOLEAN_TYPE)

Expand Down
2 changes: 1 addition & 1 deletion mycli/packages/special/dbcommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def status(cur, **_):

output.append(("Connection:", host_info))

query = "SELECT @@character_set_server, @@character_set_database, " "@@character_set_client, @@character_set_connection LIMIT 1;"
query = "SELECT @@character_set_server, @@character_set_database, @@character_set_client, @@character_set_connection LIMIT 1;"
log.debug(query)
cur.execute(query)
charset = cur.fetchone()
Expand Down
3 changes: 3 additions & 0 deletions mycli/packages/special/iocommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,18 @@ def set_expanded_output(val):
def is_expanded_output():
return use_expanded_output


@export
def set_forced_horizontal_output(val):
global force_horizontal_output
force_horizontal_output = val


@export
def forced_horizontal():
return force_horizontal_output


_logger = logging.getLogger(__name__)


Expand Down
26 changes: 26 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,29 @@ include = ["mycli*"]

[tool.ruff]
line-length = 140

[tool.ruff.lint]
select = [
'A',
'I',
'E',
'W',
'F',
'C4',
'PIE',
'TID',
]
ignore = [
'E401', # Multiple imports on one line
'E402', # Module level import not at top of file
'E501', # Line too long
'F541', # f-string without placeholders
'PIE808', # range() starting with 0
]

[tool.ruff.format]
quote-style = 'preserve'
exclude = [
'build',
'mycli_dev',
]
2 changes: 1 addition & 1 deletion test/features/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def before_all(context):
_, my_cnf = mkstemp()
with open(my_cnf, "w") as f:
f.write(
"[client]\n" "pager={0} {1} {2}\n".format(
"[client]\npager={0} {1} {2}\n".format(
sys.executable, os.path.join(context.package_root, "test/features/wrappager.py"), context.conf["pager_boundary"]
)
)
Expand Down
2 changes: 1 addition & 1 deletion test/features/steps/auto_vertical.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def step_see_small_results(context):
@then("we see large results in vertical format")
def step_see_large_results(context):
rows = ["{n:3}| {n}".format(n=str(n)) for n in range(1, 50)]
expected = "***************************[ 1. row ]" "***************************\r\n" + "{}\r\n".format("\r\n".join(rows) + "\r\n")
expected = "***************************[ 1. row ]***************************\r\n" + "{}\r\n".format("\r\n".join(rows) + "\r\n")

wrappers.expect_pager(context, expected, timeout=10)
wrappers.expect_exact(context, "1 row in set", timeout=2)
4 changes: 2 additions & 2 deletions test/features/steps/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ def status_contains(context, expression):

@when("we create my.cnf file")
def step_create_my_cnf_file(context):
my_cnf = "[client]\n" f"host = {HOST}\n" f"port = {PORT}\n" f"user = {USER}\n" f"password = {PASSWORD}\n"
my_cnf = f"[client]\nhost = {HOST}\nport = {PORT}\nuser = {USER}\npassword = {PASSWORD}\n"
with open(MY_CNF_PATH, "w") as f:
f.write(my_cnf)


@when("we create mylogin.cnf file")
def step_create_mylogin_cnf_file(context):
os.environ.pop("MYSQL_TEST_LOGIN_FILE", None)
mylogin_cnf = f"[{TEST_LOGIN_PATH}]\n" f"host = {HOST}\n" f"port = {PORT}\n" f"user = {USER}\n" f"password = {PASSWORD}\n"
mylogin_cnf = f"[{TEST_LOGIN_PATH}]\nhost = {HOST}\nport = {PORT}\nuser = {USER}\npassword = {PASSWORD}\n"
with open(MYLOGIN_CNF_PATH, "wb") as f:
input_file = io.StringIO(mylogin_cnf)
f.write(encrypt_mylogin_cnf(input_file).read())
Expand Down
4 changes: 1 addition & 3 deletions test/features/steps/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ def add_arg(name, key, value):
try:
cli_cmd = context.conf["cli_command"]
except KeyError:
cli_cmd = ('{0!s} -c "' "import coverage ; " "coverage.process_startup(); " "import mycli.main; " "mycli.main.cli()" '"').format(
sys.executable
)
cli_cmd = ('{0!s} -c "import coverage ; coverage.process_startup(); import mycli.main; mycli.main.cli()"').format(sys.executable)

cmd_parts = [cli_cmd] + rendered_args
cmd = " ".join(cmd_parts)
Expand Down
7 changes: 4 additions & 3 deletions test/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def test_batch_mode(executor):
run(executor, """create table test(a text)""")
run(executor, """insert into test values('abc'), ('def'), ('ghi')""")

sql = "select count(*) from test;\n" "select * from test limit 1;"
sql = "select count(*) from test;\nselect * from test limit 1;"

runner = CliRunner()
result = runner.invoke(cli, args=CLI_ARGS, input=sql)
Expand All @@ -107,7 +107,7 @@ def test_batch_mode_table(executor):
run(executor, """create table test(a text)""")
run(executor, """insert into test values('abc'), ('def'), ('ghi')""")

sql = "select count(*) from test;\n" "select * from test limit 1;"
sql = "select count(*) from test;\nselect * from test limit 1;"

runner = CliRunner()
result = runner.invoke(cli, args=CLI_ARGS + ["-t"], input=sql)
Expand Down Expand Up @@ -543,7 +543,7 @@ def test_init_command_arg(executor):
@dbtest
def test_init_command_multiple_arg(executor):
init_command = "set sql_select_limit=2000; set max_join_size=20000"
sql = 'show variables like "sql_select_limit";\n' 'show variables like "max_join_size"'
sql = 'show variables like "sql_select_limit";\nshow variables like "max_join_size"'
runner = CliRunner()
result = runner.invoke(cli, args=CLI_ARGS + ["--init-command", init_command], input=sql)

Expand All @@ -554,6 +554,7 @@ def test_init_command_multiple_arg(executor):
assert expected_sql_select_limit in result.output
assert expected_max_join_size in result.output


@dbtest
def test_global_init_commands(executor):
"""Tests that global init-commands from config are executed by default."""
Expand Down
10 changes: 5 additions & 5 deletions test/test_parseutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,24 +122,24 @@ def test_query_starts_with_comment():


def test_queries_start_with():
sql = "# comment\n" "show databases;" "use foo;"
sql = "# comment\nshow databases;use foo;"
assert queries_start_with(sql, ("show", "select")) is True
assert queries_start_with(sql, ("use", "drop")) is True
assert queries_start_with(sql, ("delete", "update")) is False


def test_is_destructive():
sql = "use test;\n" "show databases;\n" "drop database foo;"
sql = "use test;\nshow databases;\ndrop database foo;"
assert is_destructive(sql) is True


def test_is_destructive_update_with_where_clause():
sql = "use test;\n" "show databases;\n" "UPDATE test SET x = 1 WHERE id = 1;"
sql = "use test;\nshow databases;\nUPDATE test SET x = 1 WHERE id = 1;"
assert is_destructive(sql) is False


def test_is_destructive_update_without_where_clause():
sql = "use test;\n" "show databases;\n" "UPDATE test SET x = 1;"
sql = "use test;\nshow databases;\nUPDATE test SET x = 1;"
assert is_destructive(sql) is True


Expand Down Expand Up @@ -167,7 +167,7 @@ def test_query_has_where_clause(sql, has_where_clause):
("drop database foo; create database bar", "foo", True),
("select bar from foo; drop database bazz", "foo", False),
("select bar from foo; drop database bazz", "bazz", True),
("-- dropping database \n " "drop -- really dropping \n " "schema abc -- now it is dropped", "abc", True),
("-- dropping database \n drop -- really dropping \n schema abc -- now it is dropped", "abc", True),
],
)
def test_is_dropping_database(sql, dbname, is_dropping):
Expand Down
4 changes: 2 additions & 2 deletions test/test_sqlexecute.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_bools(executor):
@dbtest
def test_binary(executor):
run(executor, """create table bt(geom linestring NOT NULL)""")
run(executor, "INSERT INTO bt VALUES " "(ST_GeomFromText('LINESTRING(116.37604 39.73979,116.375 39.73965)'));")
run(executor, "INSERT INTO bt VALUES (ST_GeomFromText('LINESTRING(116.37604 39.73979,116.375 39.73965)'));")
results = run(executor, """select * from bt""")

geom = (
Expand Down Expand Up @@ -139,7 +139,7 @@ def test_favorite_query_multiple_statement(executor):
run(executor, "insert into test values('abc')")
run(executor, "insert into test values('def')")

results = run(executor, "\\fs test-ad select * from test where a like 'a%'; " "select * from test where a like 'd%'")
results = run(executor, "\\fs test-ad select * from test where a like 'a%'; select * from test where a like 'd%'")
assert_result_equal(results, status="Saved.")

results = run(executor, "\\f test-ad")
Expand Down