diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1230149 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21b843b..c9c984e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,13 +15,13 @@ jobs: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 with: version: "latest" - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a765d14..bdee589 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,16 +15,15 @@ jobs: - 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 + - name: Run ruff check + uses: astral-sh/ruff-action@c6bea5606c33b5d04902374392d9233464b90660 # v3.3.0 + 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 + uses: astral-sh/ruff-action@c6bea5606c33b5d04902374392d9233464b90660 # v3.3.0 with: version: 0.11.5 args: 'format --check' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bdbe149..ab44378 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,13 +16,13 @@ jobs: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 with: version: "latest" - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.python-version }} @@ -46,23 +46,22 @@ jobs: run: | uv run tox -e py${{ matrix.python-version }} - # TODO enable style checks here and in CI for PRs - # - # - name: Run Style Checks - # run: uv run tox -e style + # arguably this should be made identical to CI for PRs + - name: Run Style Checks + run: uv run tox -e style build: runs-on: ubuntu-latest needs: [test] steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 with: version: "latest" - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.13' @@ -73,7 +72,7 @@ jobs: run: uv build - name: Store the distribution packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: python-packages path: dist/ @@ -88,9 +87,9 @@ jobs: id-token: write steps: - name: Download distribution packages - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: python-packages path: dist/ - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 diff --git a/changelog.md b/changelog.md index 513df6b..fe22f71 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,21 @@ +1.31.2 (2025/05/01) +=================== + +Bug Fixes +--------- + +* Let table-name extraction work on multi-statement inputs. + + +Internal +-------- + +* Work on passing `ruff check` linting. +* Remove backward-compatibility hacks. +* Pin more GitHub Actions and add Dependabot support. +* Enable xpassing test. + + 1.31.1 (2025/04/25) =================== diff --git a/mycli/clibuffer.py b/mycli/clibuffer.py index d9fbf83..151351e 100644 --- a/mycli/clibuffer.py +++ b/mycli/clibuffer.py @@ -35,11 +35,13 @@ def _multiline_exception(text): text.lower().startswith("delimiter") or # Ended with the current delimiter (usually a semi-column) - text.endswith(special.get_current_delimiter()) - or text.endswith("\\g") - or text.endswith("\\G") - or text.endswith(r"\e") - or text.endswith(r"\clip") + text.endswith(( + special.get_current_delimiter(), + "\\g", + "\\G", + r"\e", + r"\clip", + )) or # Exit doesn't need semi-column` (text == "exit") diff --git a/mycli/config.py b/mycli/config.py index cad6ebb..e6d7451 100644 --- a/mycli/config.py +++ b/mycli/config.py @@ -11,11 +11,6 @@ from typing import Union, IO from configobj import ConfigObj, ConfigObjError import pyaes -try: - basestring -except NameError: - basestring = str - logger = logging.getLogger(__name__) @@ -40,7 +35,7 @@ def read_config_file(f, list_values=True): """ - if isinstance(f, basestring): + if isinstance(f, str): f = os.path.expanduser(f) try: @@ -73,7 +68,7 @@ def get_included_configs(config_file: Union[str, TextIOWrapper]) -> list: try: with open(config_file) as f: include_directives = filter(lambda s: s.startswith("!includedir"), f) - dirs_split = map(lambda s: s.strip().split()[-1], include_directives) + dirs_split = (s.strip().split()[-1] for s in include_directives) dirs = filter(os.path.isdir, dirs_split) for dir_ in dirs: for filename in os.listdir(dir_): @@ -284,7 +279,7 @@ def str_to_bool(s): """Convert a string value to its corresponding boolean value.""" if isinstance(s, bool): return s - elif not isinstance(s, basestring): + elif not isinstance(s, str): raise TypeError("argument must be a string") true_values = ("true", "on", "1") @@ -305,7 +300,7 @@ def strip_matching_quotes(s): values. """ - if isinstance(s, basestring) and len(s) >= 2 and s[0] == s[-1] and s[0] in ('"', "'"): + if isinstance(s, str) and len(s) >= 2 and s[0] == s[-1] and s[0] in ('"', "'"): s = s[1:-1] return s diff --git a/mycli/main.py b/mycli/main.py index f8a933a..7c63b81 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -1,5 +1,4 @@ from collections import defaultdict -from io import open import os import sys import shutil @@ -7,11 +6,8 @@ import traceback import logging import threading import re -import stat from collections import namedtuple -from pygments.lexer import combined - try: from pwd import getpwuid except ImportError: @@ -62,18 +58,10 @@ import itertools click.disable_unicode_literals_warning = True -try: - from urlparse import urlparse - from urlparse import unquote -except ImportError: - from urllib.parse import urlparse - from urllib.parse import unquote +from urllib.parse import urlparse +from urllib.parse import unquote -try: - import importlib.resources as resources -except ImportError: - # Python < 3.7 - import importlib_resources as resources +from importlib import resources try: import paramiko @@ -89,8 +77,6 @@ SUPPORT_INFO = "Home: http://mycli.net\nBug tracker: https://github.com/dbcli/my class PasswordFileError(Exception): """Base exception for errors related to reading password files.""" - pass - class MyCli(object): default_prompt = "\\t \\u@\\h:\\d> " @@ -1458,7 +1444,7 @@ def is_mutating(status): if not status: return False - mutating = set(["insert", "update", "delete", "alter", "create", "drop", "replace", "truncate", "load", "rename"]) + mutating = {"insert", "update", "delete", "alter", "create", "drop", "replace", "truncate", "load", "rename"} return status.split(None, 1)[0].lower() in mutating diff --git a/mycli/packages/completion_engine.py b/mycli/packages/completion_engine.py index a2cd63a..1bae6dd 100644 --- a/mycli/packages/completion_engine.py +++ b/mycli/packages/completion_engine.py @@ -129,7 +129,7 @@ def suggest_based_on_last_token(token, text_before_cursor, full_text, identifier else: token_v = token.value.lower() - is_operand = lambda x: x and any([x.endswith(op) for op in ["+", "-", "*", "/"]]) # noqa: E731 + is_operand = lambda x: x and any(x.endswith(op) for op in ["+", "-", "*", "/"]) # noqa: E731 if not token: return [{"type": "keyword"}, {"type": "special"}] @@ -289,5 +289,5 @@ def suggest_based_on_last_token(token, text_before_cursor, full_text, identifier return [{"type": "keyword"}] -def identifies(id, schema, table, alias): - return id == alias or id == table or (schema and (id == schema + "." + table)) +def identifies(identifier, schema, table, alias): + return identifier == alias or identifier == table or (schema and (identifier == schema + "." + table)) diff --git a/mycli/packages/paramiko_stub/__init__.py b/mycli/packages/paramiko_stub/__init__.py index 154c72c..10b1d99 100644 --- a/mycli/packages/paramiko_stub/__init__.py +++ b/mycli/packages/paramiko_stub/__init__.py @@ -16,9 +16,9 @@ class Paramiko: print( dedent(""" To enable certain SSH features you need to install paramiko and sshtunnel: - + pip install paramiko sshtunnel - + It is required for the following configuration options: --list-ssh-config --ssh-config-host diff --git a/mycli/packages/parseutils.py b/mycli/packages/parseutils.py index 9acbcd5..5eac267 100644 --- a/mycli/packages/parseutils.py +++ b/mycli/packages/parseutils.py @@ -1,4 +1,5 @@ import re +import sqlglot import sqlparse from sqlparse.sql import IdentifierList, Identifier, Function from sqlparse.tokens import Keyword, DML, Punctuation @@ -166,6 +167,42 @@ def extract_tables(sql): return list(extract_table_identifiers(stream)) +def extract_tables_from_complete_statements(sql): + """Extract the table names from a complete and valid series of SQL + statements. + + Returns a list of (schema, table, alias) tuples + + """ + # sqlglot chokes entirely on things like "\T" that it doesn't know about, + # but is much better at extracting table names from complete statements. + # sqlparse can extract the series of statements, though it also doesn't + # understand "\T". + roughly_parsed = sqlparse.parse(sql) + if not roughly_parsed: + return [] + + finely_parsed = [] + for statement in roughly_parsed: + try: + finely_parsed.append(sqlglot.parse_one(str(statement), read='mysql')) + except sqlglot.errors.ParseError: + pass + + tables = [] + for statement in finely_parsed: + for identifier in statement.find_all(sqlglot.exp.Table): + if identifier.parent_select.sql().startswith('WITH'): + continue + tables.append(( + None if identifier.db == '' else identifier.db, + identifier.name, + None if identifier.alias == '' else identifier.alias, + )) + + return tables + + def find_prev_keyword(sql): """Find the last sql keyword in an SQL statement diff --git a/mycli/packages/special/delimitercommand.py b/mycli/packages/special/delimitercommand.py index 530bf1a..a0686c8 100644 --- a/mycli/packages/special/delimitercommand.py +++ b/mycli/packages/special/delimitercommand.py @@ -26,10 +26,10 @@ class DelimiterCommand(object): return [stmt.replace(";", self._delimiter).replace(placeholder, ";") for stmt in split] - def queries_iter(self, input): + def queries_iter(self, input_str): """Iterate over queries in the input string.""" - queries = self._split(input) + queries = self._split(input_str) while queries: for sql in queries: delimiter = self._delimiter diff --git a/mycli/packages/special/iocommands.py b/mycli/packages/special/iocommands.py index 8ff0e89..603bf5e 100644 --- a/mycli/packages/special/iocommands.py +++ b/mycli/packages/special/iocommands.py @@ -4,7 +4,6 @@ import locale import logging import subprocess import shlex -from io import open from time import sleep import click @@ -547,6 +546,6 @@ def get_current_delimiter(): @export -def split_queries(input): - for query in delimiter_command.queries_iter(input): +def split_queries(input_str): + for query in delimiter_command.queries_iter(input_str): yield query diff --git a/mycli/packages/special/main.py b/mycli/packages/special/main.py index 4d1c941..2b03544 100644 --- a/mycli/packages/special/main.py +++ b/mycli/packages/special/main.py @@ -108,7 +108,7 @@ def show_keyword_help(cur, arg): @special_command("exit", "\\q", "Exit.", arg_type=NO_QUERY, aliases=("\\q",)) @special_command("quit", "\\q", "Quit.", arg_type=NO_QUERY) -def quit(*_args): +def quit_(*_args): raise EOFError diff --git a/mycli/packages/tabular_output/sql_format.py b/mycli/packages/tabular_output/sql_format.py index 828a4b3..008e4d4 100644 --- a/mycli/packages/tabular_output/sql_format.py +++ b/mycli/packages/tabular_output/sql_format.py @@ -1,6 +1,6 @@ """Format adapter for sql.""" -from mycli.packages.parseutils import extract_tables +from mycli.packages.parseutils import extract_tables_from_complete_statements supported_formats = ( "sql-insert", @@ -20,7 +20,7 @@ def escape_for_sql_statement(value): def adapter(data, headers, table_format=None, **kwargs): - tables = extract_tables(formatter.query) + tables = extract_tables_from_complete_statements(formatter.query) if len(tables) > 0: table = tables[0] if table[0]: diff --git a/mycli/sqlcompleter.py b/mycli/sqlcompleter.py index 1636289..34ed9e4 100644 --- a/mycli/sqlcompleter.py +++ b/mycli/sqlcompleter.py @@ -1,5 +1,5 @@ import logging -from re import compile, escape +import re from collections import Counter from prompt_toolkit.completion import Completer, Completion @@ -900,7 +900,7 @@ class SQLCompleter(Completer): self.reserved_words = set() for x in self.keywords: self.reserved_words.update(x.split()) - self.name_pattern = compile(r"^[_a-z][_a-z0-9\$]*$") + self.name_pattern = re.compile(r"^[_a-z][_a-z0-9\$]*$") self.special_commands = [] self.table_formats = supported_formats @@ -1075,8 +1075,8 @@ class SQLCompleter(Completer): completions = [] if fuzzy: - regex = ".*?".join(map(escape, text)) - pat = compile("(%s)" % regex) + regex = ".*?".join(map(re.escape, text)) + pat = re.compile("(%s)" % regex) for item in collection: r = pat.search(item.lower()) if r: diff --git a/pyproject.toml b/pyproject.toml index e6691e8..ce9ad9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ line-length = 140 [tool.ruff.lint] select = [ 'A', - 'I', +# 'I', # todo enableme imports 'E', 'W', 'F', @@ -79,12 +79,16 @@ ignore = [ 'E114', # indentation-with-invalid-multiple-comment 'E117', # over-indented 'W191', # tab-indentation + # TODO + 'PIE796', # todo enableme Enum contains duplicate value ] [tool.ruff.lint.isort] force-sort-within-sections = true known-first-party = [ 'mycli', + 'test', + 'steps', ] [tool.ruff.format] diff --git a/test/test_naive_completion.py b/test/test_naive_completion.py index 31ac165..99c4fd0 100644 --- a/test/test_naive_completion.py +++ b/test/test_naive_completion.py @@ -28,7 +28,7 @@ def test_select_keyword_completion(completer, complete_event): text = "SEL" position = len("SEL") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - assert result == list([Completion(text="SELECT", start_position=-3)]) + assert result == [Completion(text="SELECT", start_position=-3)] def test_function_name_completion(completer, complete_event): diff --git a/test/test_parseutils.py b/test/test_parseutils.py index 189c31b..7f1aa4c 100644 --- a/test/test_parseutils.py +++ b/test/test_parseutils.py @@ -1,6 +1,7 @@ import pytest from mycli.packages.parseutils import ( extract_tables, + extract_tables_from_complete_statements, query_starts_with, queries_start_with, is_destructive, @@ -77,7 +78,6 @@ def test_simple_insert_single_table(): assert tables == [(None, "abc", "abc")] -@pytest.mark.xfail def test_simple_insert_single_table_schema_qualified(): tables = extract_tables('insert into abc.def (id, name) values (1, "def")') assert tables == [("abc", "def", None)] @@ -108,6 +108,22 @@ def test_join_as_table(): assert tables == [(None, "my_table", "m")] +def test_extract_tables_from_complete_statements(): + tables = extract_tables_from_complete_statements("SELECT * FROM my_table AS m WHERE m.a > 5") + assert tables == [(None, "my_table", "m")] + + +def test_extract_tables_from_complete_statements_cte(): + tables = extract_tables_from_complete_statements("WITH my_cte (id, num) AS ( SELECT id, COUNT(1) FROM my_table GROUP BY id ) SELECT *") + assert tables == [(None, "my_table", None)] + + +# this would confuse plain extract_tables() per #1122 +def test_extract_tables_from_multiple_complete_statements(): + tables = extract_tables_from_complete_statements(r'\T sql-insert; SELECT * FROM my_table AS m WHERE m.a > 5') + assert tables == [(None, "my_table", "m")] + + def test_query_starts_with(): query = "USE test;" assert query_starts_with(query, ("use",)) is True diff --git a/test/test_smart_completion_public_schema_only.py b/test/test_smart_completion_public_schema_only.py index f2c745f..f627e8e 100644 --- a/test/test_smart_completion_public_schema_only.py +++ b/test/test_smart_completion_public_schema_only.py @@ -58,7 +58,7 @@ def test_select_keyword_completion(completer, complete_event): text = "SEL" position = len("SEL") result = completer.get_completions(Document(text=text, cursor_position=position), complete_event) - assert list(result) == list([Completion(text="SELECT", start_position=-3)]) + assert list(result) == [Completion(text="SELECT", start_position=-3)] def test_select_star(completer, complete_event): @@ -72,19 +72,19 @@ def test_table_completion(completer, complete_event): text = "SELECT * FROM " position = len(text) result = completer.get_completions(Document(text=text, cursor_position=position), complete_event) - assert list(result) == list([ + assert list(result) == [ Completion(text="users", start_position=0), Completion(text="orders", start_position=0), Completion(text="`select`", start_position=0), Completion(text="`réveillé`", start_position=0), - ]) + ] def test_function_name_completion(completer, complete_event): text = "SELECT MA" position = len("SELECT MA") result = completer.get_completions(Document(text=text, cursor_position=position), complete_event) - assert list(result) == list([ + assert list(result) == [ Completion(text="MAX", start_position=-2), Completion(text="CHANGE MASTER TO", start_position=-2), Completion(text="CURRENT_TIMESTAMP", start_position=-2), @@ -94,7 +94,7 @@ def test_function_name_completion(completer, complete_event): Completion(text="PRIMARY", start_position=-2), Completion(text="ROW_FORMAT", start_position=-2), Completion(text="SMALLINT", start_position=-2), - ]) + ] def test_suggested_column_names(completer, complete_event): @@ -134,13 +134,13 @@ def test_suggested_column_names_in_function(completer, complete_event): text = "SELECT MAX( from users" position = len("SELECT MAX(") result = completer.get_completions(Document(text=text, cursor_position=position), complete_event) - assert list(result) == list([ + assert list(result) == [ Completion(text="*", start_position=0), Completion(text="id", start_position=0), Completion(text="email", start_position=0), Completion(text="first_name", start_position=0), Completion(text="last_name", start_position=0), - ]) + ] def test_suggested_column_names_with_table_dot(completer, complete_event): @@ -154,13 +154,13 @@ def test_suggested_column_names_with_table_dot(completer, complete_event): text = "SELECT users. from users" position = len("SELECT users.") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - assert result == list([ + assert result == [ Completion(text="*", start_position=0), Completion(text="id", start_position=0), Completion(text="email", start_position=0), Completion(text="first_name", start_position=0), Completion(text="last_name", start_position=0), - ]) + ] def test_suggested_column_names_with_alias(completer, complete_event): @@ -174,13 +174,13 @@ def test_suggested_column_names_with_alias(completer, complete_event): text = "SELECT u. from users u" position = len("SELECT u.") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - assert result == list([ + assert result == [ Completion(text="*", start_position=0), Completion(text="id", start_position=0), Completion(text="email", start_position=0), Completion(text="first_name", start_position=0), Completion(text="last_name", start_position=0), - ]) + ] def test_suggested_multiple_column_names(completer, complete_event): @@ -221,13 +221,13 @@ def test_suggested_multiple_column_names_with_alias(completer, complete_event): text = "SELECT u.id, u. from users u" position = len("SELECT u.id, u.") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - assert result == list([ + assert result == [ Completion(text="*", start_position=0), Completion(text="id", start_position=0), Completion(text="email", start_position=0), Completion(text="first_name", start_position=0), Completion(text="last_name", start_position=0), - ]) + ] def test_suggested_multiple_column_names_with_dot(completer, complete_event): @@ -242,65 +242,65 @@ def test_suggested_multiple_column_names_with_dot(completer, complete_event): text = "SELECT users.id, users. from users u" position = len("SELECT users.id, users.") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - assert result == list([ + assert result == [ Completion(text="*", start_position=0), Completion(text="id", start_position=0), Completion(text="email", start_position=0), Completion(text="first_name", start_position=0), Completion(text="last_name", start_position=0), - ]) + ] def test_suggested_aliases_after_on(completer, complete_event): text = "SELECT u.name, o.id FROM users u JOIN orders o ON " position = len("SELECT u.name, o.id FROM users u JOIN orders o ON ") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - assert result == list([ + assert result == [ Completion(text="u", start_position=0), Completion(text="o", start_position=0), - ]) + ] def test_suggested_aliases_after_on_right_side(completer, complete_event): text = "SELECT u.name, o.id FROM users u JOIN orders o ON o.user_id = " position = len("SELECT u.name, o.id FROM users u JOIN orders o ON o.user_id = ") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - assert result == list([ + assert result == [ Completion(text="u", start_position=0), Completion(text="o", start_position=0), - ]) + ] def test_suggested_tables_after_on(completer, complete_event): text = "SELECT users.name, orders.id FROM users JOIN orders ON " position = len("SELECT users.name, orders.id FROM users JOIN orders ON ") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - assert result == list([ + assert result == [ Completion(text="users", start_position=0), Completion(text="orders", start_position=0), - ]) + ] def test_suggested_tables_after_on_right_side(completer, complete_event): text = "SELECT users.name, orders.id FROM users JOIN orders ON orders.user_id = " position = len("SELECT users.name, orders.id FROM users JOIN orders ON orders.user_id = ") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - assert result == list([ + assert result == [ Completion(text="users", start_position=0), Completion(text="orders", start_position=0), - ]) + ] def test_table_names_after_from(completer, complete_event): text = "SELECT * FROM " position = len("SELECT * FROM ") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - assert result == list([ + assert result == [ Completion(text="users", start_position=0), Completion(text="orders", start_position=0), Completion(text="`select`", start_position=0), Completion(text="`réveillé`", start_position=0), - ]) + ] def test_auto_escaped_col_names(completer, complete_event): @@ -369,5 +369,5 @@ def dummy_list_path(dir_name): def test_file_name_completion(completer, complete_event, text, expected): position = len(text) result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - expected = list((Completion(txt, pos) for txt, pos in expected)) + expected = [Completion(txt, pos) for txt, pos in expected] assert result == expected diff --git a/test/test_special_iocommands.py b/test/test_special_iocommands.py index bea5620..4701f50 100644 --- a/test/test_special_iocommands.py +++ b/test/test_special_iocommands.py @@ -223,9 +223,7 @@ def test_watch_query_full(): expected_results = 4 ctrl_c_process = send_ctrl_c(wait_interval) with db_connection().cursor() as cur: - results = list( - result for result in mycli.packages.special.iocommands.watch_query(arg="{0!s} {1!s}".format(watch_seconds, query), cur=cur) - ) + results = list(mycli.packages.special.iocommands.watch_query(arg="{0!s} {1!s}".format(watch_seconds, query), cur=cur)) ctrl_c_process.join(1) assert len(results) == expected_results for result in results: diff --git a/test/test_sqlexecute.py b/test/test_sqlexecute.py index 88be7ff..a48a929 100644 --- a/test/test_sqlexecute.py +++ b/test/test_sqlexecute.py @@ -61,8 +61,8 @@ def test_table_and_columns_query(executor): run(executor, "create table a(x text, y text)") run(executor, "create table b(z text)") - assert set(executor.tables()) == set([("a",), ("b",)]) - assert set(executor.table_columns()) == set([("a", "x"), ("a", "y"), ("b", "z")]) + assert set(executor.tables()) == {("a",), ("b",)} + assert set(executor.table_columns()) == {("a", "x"), ("a", "y"), ("b", "z")} @dbtest diff --git a/tox.ini b/tox.ini index f4228f2..6f4ae81 100644 --- a/tox.ini +++ b/tox.ini @@ -17,5 +17,5 @@ commands = uv pip install -e .[dev,ssh] [testenv:style] skip_install = true deps = ruff -commands = ruff check --fix - ruff format +commands = ruff check + ruff format --diff