From a0a1ab735dfa1f701c88200444da100470a5984c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Wed, 5 Feb 2025 14:54:21 +0100 Subject: [PATCH] chore: use more of ruff --- pyproject.toml | 41 ++------ wlc/__init__.py | 7 +- wlc/config.py | 5 +- wlc/main.py | 3 +- wlc/test_base.py | 5 +- wlc/test_main.py | 241 ++++++++++++++++++++++++----------------------- wlc/test_wlc.py | 1 + 7 files changed, 145 insertions(+), 158 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 60303737..961dca50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -132,42 +132,15 @@ ignore = [ "TRY003", # WONTFIX: Avoid specifying long messages outside the exception class "PLR0913", # WONTFIX: Too many arguments to function call "PLR2004", # TODO: Magic value used in comparison, consider replacing 201 with a constant variable + "COM", # CONFIG: No trailing commas + "PT", # CONFIG: Not using pytest + "PTH", # TODO: Not using pathlib + "EM", # TODO: Exception strings + "FBT", # TODO: Boolean in function definition + "ANN", # TODO: type annotations "N818" # TODO: exception naming ] -select = [ - "E", - "F", - "B", - "T10", - "A", - "C4", - "C90", - "YTT", - "DJ", - "UP", - "D", - "PD", - "PGH", - "PL", - "TRY", - "RUF", - "ERA", - "ICN", - "ISC", - "EXE", - "INP", - "PIE", - "G", - "PYI", - "Q", - "SIM", - "TID", - "RSE", - "T20", - "RET", - "SLF", - "N" -] +select = ["ALL"] [tool.ruff.lint.mccabe] max-complexity = 16 diff --git a/wlc/__init__.py b/wlc/__init__.py index 776f2763..1b111437 100644 --- a/wlc/__init__.py +++ b/wlc/__init__.py @@ -5,6 +5,7 @@ """Weblate API client library.""" from __future__ import annotations + import json import logging from copy import copy @@ -153,7 +154,7 @@ def process_error(self, error): reason = error.response.reason try: error_string = str(error.response.json()) - except Exception: + except Exception: # noqa: BLE001 error_string = "" raise WeblateException( f"HTTP error {status_code}: {reason} {error_string}" @@ -221,7 +222,7 @@ def post(self, path, files=None, params=None, **kwargs): def _post_factory(self, prefix, path, kwargs): """Wrapper for posting objects.""" - return self.post("/".join((prefix, path, "")), **kwargs) + return self.post(f"{prefix}/{path}/", **kwargs) def get(self, path, params=None): """Perform GET request on the API.""" @@ -238,7 +239,7 @@ def list_factory(self, path, parser, params=None): def _get_factory(self, prefix, path, parser): """Wrapper for getting objects.""" - data = self.get("/".join((prefix, path, ""))) + data = self.get(f"{prefix}/{path}/") return parser(weblate=self, **data) def get_object(self, path): diff --git a/wlc/config.py b/wlc/config.py index 89578bec..ef031a33 100644 --- a/wlc/config.py +++ b/wlc/config.py @@ -8,12 +8,15 @@ import os.path from configparser import NoOptionError, RawConfigParser -from collections.abc import Generator +from typing import TYPE_CHECKING from xdg.BaseDirectory import load_config_paths # type: ignore[import-untyped] import wlc +if TYPE_CHECKING: + from collections.abc import Generator + __all__ = ["NoOptionError", "WeblateConfig"] diff --git a/wlc/main.py b/wlc/main.py index a311ad29..ab25f65a 100644 --- a/wlc/main.py +++ b/wlc/main.py @@ -5,6 +5,7 @@ """Command-line interface for Weblate.""" from __future__ import annotations + import csv import http.client import json @@ -81,7 +82,7 @@ class CommandError(Exception): def __init__(self, message, detail=None): """Create CommandError exception.""" if detail is not None: - message = "\n".join((message, detail)) + message = f"{message}\n{detail}" super().__init__(message) diff --git a/wlc/test_base.py b/wlc/test_base.py index b5c5dc52..3ca89f9b 100644 --- a/wlc/test_base.py +++ b/wlc/test_base.py @@ -5,6 +5,7 @@ """Test helpers.""" from __future__ import annotations + import os from email import message_from_string from hashlib import blake2b @@ -112,7 +113,7 @@ def format_multipart_body(body, content_type): def register_uri(path, domain="http://127.0.0.1:8000/api", auth=False): """Simplified URL registration.""" filename = os.path.join(DATA_TEST_BASE, path.replace("/", "-")) - url = "/".join((domain, path, "")) + url = f"{domain}/{path}/" with open(filename, "rb") as handle: responses.add_callback( responses.GET, @@ -157,7 +158,7 @@ def register_error( path, code, domain="http://127.0.0.1:8000/api", method=responses.GET, **kwargs ): """Simplified URL error registration.""" - url = "/".join((domain, path, "")) + url = f"{domain}/{path}/" if "callback" in kwargs: responses.add_callback(method, url, **kwargs) else: diff --git a/wlc/test_main.py b/wlc/test_main.py index a7da43a6..fa23f6e3 100644 --- a/wlc/test_main.py +++ b/wlc/test_main.py @@ -23,65 +23,68 @@ TEST_SECTION = os.path.join(TEST_DATA, "section") -def execute(args, settings=None, stdout=None, stdin=None, expected=0): - """Execute command and return output.""" - if settings is None: - settings = () - elif not settings: - settings = None - output = StringIO() - output.buffer = BytesIO() - backup = sys.stdout - backup_err = sys.stderr - try: - sys.stdout = output - sys.stderr = output - if stdout: - stdout = output - result = main(args=args, settings=settings, stdout=stdout, stdin=stdin) - assert result == expected - finally: - sys.stdout = backup - sys.stderr = backup_err - result = output.buffer.getvalue() - if result: - return result - return output.getvalue() - - -class TestSettings(APITest): +class CLITestBase(APITest): + def execute(self, args, settings=None, stdout=None, stdin=None, expected=0): + """Execute command and return output.""" + if settings is None: + settings = () + elif not settings: + settings = None + output = StringIO() + output.buffer = BytesIO() + backup = sys.stdout + backup_err = sys.stderr + try: + sys.stdout = output + sys.stderr = output + if stdout: + stdout = output + result = main(args=args, settings=settings, stdout=stdout, stdin=stdin) + self.assertEqual(result, expected) + finally: + sys.stdout = backup + sys.stderr = backup_err + result = output.buffer.getvalue() + if result: + return result + return output.getvalue() + + +class TestSettings(CLITestBase): """Test settings handling.""" def test_commandline(self): """Configuration using command-line.""" - output = execute(["--url", "https://example.net/", "list-projects"]) + output = self.execute(["--url", "https://example.net/", "list-projects"]) self.assertIn("Hello", output) def test_stdout(self): """Configuration using params.""" - output = execute(["list-projects"], stdout=True) + output = self.execute(["list-projects"], stdout=True) self.assertIn("Hello", output) def test_debug(self): """Debug mode.""" - output = execute(["--debug", "list-projects"], stdout=True) + output = self.execute(["--debug", "list-projects"], stdout=True) self.assertIn("api/projects", output) def test_settings(self): """Configuration using settings param.""" - output = execute( + output = self.execute( ["list-projects"], settings=(("weblate", "url", "https://example.net/"),) ) self.assertIn("Hello", output) def test_config(self): """Configuration using custom config file.""" - output = execute(["--config", TEST_CONFIG, "list-projects"], settings=False) + output = self.execute( + ["--config", TEST_CONFIG, "list-projects"], settings=False + ) self.assertIn("Hello", output) def test_config_section(self): """Configuration using custom config file section.""" - output = execute( + output = self.execute( ["--config", TEST_SECTION, "--config-section", "custom", "list-projects"], settings=False, ) @@ -89,7 +92,7 @@ def test_config_section(self): def test_config_key(self): """Configuration using custom config file section and key set.""" - output = execute( + output = self.execute( ["--config", TEST_CONFIG, "--config-section", "withkey", "show", "acl"], settings=False, ) @@ -97,10 +100,10 @@ def test_config_key(self): def test_config_appdata(self): """Configuration using custom config file section and key set.""" - output = execute(["show", "acl"], settings=False, expected=1) + output = self.execute(["show", "acl"], settings=False, expected=1) try: os.environ["APPDATA"] = TEST_DATA - output = execute(["show", "acl"], settings=False) + output = self.execute(["show", "acl"], settings=False) self.assertIn("ACL", output) finally: del os.environ["APPDATA"] @@ -110,7 +113,7 @@ def test_config_cwd(self): current = os.path.abspath(".") try: os.chdir(os.path.join(os.path.dirname(__file__), "test_data")) - output = execute(["show"], settings=False) + output = self.execute(["show"], settings=False) self.assertIn("Weblate", output) finally: os.chdir(current) @@ -164,55 +167,55 @@ def test_argv(self): backup = sys.argv try: sys.argv = ["wlc", "version"] - output = execute(None) + output = self.execute(None) self.assertIn(f"version: {wlc.__version__}", output) finally: sys.argv = backup -class TestOutput(APITest): +class TestOutput(CLITestBase): """Test output formatting.""" def test_version_text(self): """Test version printing.""" - output = execute(["--format", "text", "version"]) + output = self.execute(["--format", "text", "version"]) self.assertIn(f"version: {wlc.__version__}", output) def test_version_json(self): """Test version printing.""" - output = execute(["--format", "json", "version"]) + output = self.execute(["--format", "json", "version"]) values = json.loads(output) self.assertEqual({"version": wlc.__version__}, values) def test_version_csv(self): """Test version printing.""" - output = execute(["--format", "csv", "version"]) + output = self.execute(["--format", "csv", "version"]) self.assertIn(f"version,{wlc.__version__}", output) def test_version_html(self): """Test version printing.""" - output = execute(["--format", "html", "version"]) + output = self.execute(["--format", "html", "version"]) self.assertIn(wlc.__version__, output) def test_projects_text(self): """Test projects printing.""" - output = execute(["--format", "text", "list-projects"]) + output = self.execute(["--format", "text", "list-projects"]) self.assertIn("name: {}".format("Hello"), output) def test_projects_json(self): """Test projects printing.""" - output = execute(["--format", "json", "list-projects"]) + output = self.execute(["--format", "json", "list-projects"]) values = json.loads(output) self.assertEqual(2, len(values)) def test_projects_csv(self): """Test projects printing.""" - output = execute(["--format", "csv", "list-projects"]) + output = self.execute(["--format", "csv", "list-projects"]) self.assertIn("Hello", output) def test_projects_html(self): """Test projects printing.""" - output = execute(["--format", "html", "list-projects"]) + output = self.execute(["--format", "html", "list-projects"]) self.assertIn("Hello", output) def test_json_encoder(self): @@ -223,226 +226,228 @@ def test_json_encoder(self): cmd.print_json(self) -class TestCommands(APITest): +class TestCommands(CLITestBase): """Individual command tests.""" def test_version_bare(self): """Test version printing.""" - output = execute(["version", "--bare"]) + output = self.execute(["version", "--bare"]) self.assertEqual(f"{wlc.__version__}\n", output) def test_ls(self): """Project listing.""" - output = execute(["ls"]) + output = self.execute(["ls"]) self.assertIn("Hello", output) - output = execute(["ls", "hello"]) + output = self.execute(["ls", "hello"]) self.assertIn("Weblate", output) - output = execute(["ls", "empty"]) + output = self.execute(["ls", "empty"]) self.assertEqual("", output) def test_list_languages(self): """Language listing.""" - output = execute(["list-languages"]) + output = self.execute(["list-languages"]) self.assertIn("Turkish", output) def test_list_projects(self): """Project listing.""" - output = execute(["list-projects"]) + output = self.execute(["list-projects"]) self.assertIn("Hello", output) def test_list_components(self): """Components listing.""" - output = execute(["list-components"]) + output = self.execute(["list-components"]) self.assertIn("/hello/weblate", output) - output = execute(["list-components", "hello"]) + output = self.execute(["list-components", "hello"]) self.assertIn("/hello/weblate", output) - output = execute(["list-components", "hello/weblate"], expected=1) + output = self.execute(["list-components", "hello/weblate"], expected=1) self.assertIn("Not supported", output) def test_list_translations(self): """Translations listing.""" - output = execute(["list-translations"]) + output = self.execute(["list-translations"]) self.assertIn("/hello/weblate/cs/", output) - output = execute(["list-translations", "hello/weblate"]) + output = self.execute(["list-translations", "hello/weblate"]) self.assertIn("/hello/weblate/cs/", output) - output = execute(["list-translations", "hello/weblate"]) + output = self.execute(["list-translations", "hello/weblate"]) self.assertIn("/hello/weblate/cs/", output) - output = execute(["--format", "json", "list-translations", "hello/weblate"]) + output = self.execute( + ["--format", "json", "list-translations", "hello/weblate"] + ) self.assertIn("/hello/weblate/cs/", output) def test_show(self): """Project show.""" - output = execute(["show", "hello"]) + output = self.execute(["show", "hello"]) self.assertIn("Hello", output) - output = execute(["show", "hello/weblate"]) + output = self.execute(["show", "hello/weblate"]) self.assertIn("Weblate", output) - output = execute(["show", "hello/weblate/cs"]) + output = self.execute(["show", "hello/weblate/cs"]) self.assertIn("/hello/weblate/cs/", output) def test_show_error(self): - execute(["show", "io"], expected=10) + self.execute(["show", "io"], expected=10) with self.assertRaises(FileNotFoundError): - execute(["show", "bug"]) + self.execute(["show", "bug"]) def test_delete(self): """Project delete.""" - output = execute(["delete", "hello"]) + output = self.execute(["delete", "hello"]) self.assertEqual("", output) - output = execute(["delete", "hello/weblate"]) + output = self.execute(["delete", "hello/weblate"]) self.assertEqual("", output) - output = execute(["delete", "hello/weblate/cs"]) + output = self.execute(["delete", "hello/weblate/cs"]) self.assertEqual("", output) def test_commit(self): """Project commit.""" - output = execute(["commit", "hello"]) + output = self.execute(["commit", "hello"]) self.assertEqual("", output) - output = execute(["commit", "hello/weblate"]) + output = self.execute(["commit", "hello/weblate"]) self.assertEqual("", output) - output = execute(["commit", "hello/weblate/cs"]) + output = self.execute(["commit", "hello/weblate/cs"]) self.assertEqual("", output) def test_push(self): """Project push.""" msg = "Error: Failed to push changes!\nPush is disabled for Hello/Weblate.\n" - output = execute(["push", "hello"], expected=1) + output = self.execute(["push", "hello"], expected=1) self.assertEqual(msg, output) - output = execute(["push", "hello/weblate"], expected=1) + output = self.execute(["push", "hello/weblate"], expected=1) self.assertEqual(msg, output) - output = execute(["push", "hello/weblate/cs"], expected=1) + output = self.execute(["push", "hello/weblate/cs"], expected=1) self.assertEqual(msg, output) def test_pull(self): """Project pull.""" - output = execute(["pull", "hello"]) + output = self.execute(["pull", "hello"]) self.assertEqual("", output) - output = execute(["pull", "hello/weblate"]) + output = self.execute(["pull", "hello/weblate"]) self.assertEqual("", output) - output = execute(["pull", "hello/weblate/cs"]) + output = self.execute(["pull", "hello/weblate/cs"]) self.assertEqual("", output) def test_reset(self): """Project reset.""" - output = execute(["reset", "hello"]) + output = self.execute(["reset", "hello"]) self.assertEqual("", output) - output = execute(["reset", "hello/weblate"]) + output = self.execute(["reset", "hello/weblate"]) self.assertEqual("", output) - output = execute(["reset", "hello/weblate/cs"]) + output = self.execute(["reset", "hello/weblate/cs"]) self.assertEqual("", output) def test_cleanup(self): """Project cleanup.""" - output = execute(["cleanup", "hello"]) + output = self.execute(["cleanup", "hello"]) self.assertEqual("", output) - output = execute(["cleanup", "hello/weblate"]) + output = self.execute(["cleanup", "hello/weblate"]) self.assertEqual("", output) - output = execute(["cleanup", "hello/weblate/cs"]) + output = self.execute(["cleanup", "hello/weblate/cs"]) self.assertEqual("", output) def test_repo(self): """Project repo.""" - output = execute(["repo", "hello"]) + output = self.execute(["repo", "hello"]) self.assertIn("needs_commit", output) - output = execute(["repo", "hello/weblate"]) + output = self.execute(["repo", "hello/weblate"]) self.assertIn("needs_commit", output) - output = execute(["repo", "hello/weblate/cs"]) + output = self.execute(["repo", "hello/weblate/cs"]) self.assertIn("needs_commit", output) def test_stats(self): """Project stats.""" - output = execute(["stats", "hello"]) + output = self.execute(["stats", "hello"]) self.assertIn("translated_percent", output) - output = execute(["stats", "hello/weblate"]) + output = self.execute(["stats", "hello/weblate"]) self.assertIn("failing_percent", output) - output = execute(["stats", "hello/weblate/cs"]) + output = self.execute(["stats", "hello/weblate/cs"]) self.assertIn("failing_percent", output) def test_locks(self): """Project locks.""" - output = execute(["lock-status", "hello"], expected=1) + output = self.execute(["lock-status", "hello"], expected=1) self.assertIn("This command is supported only at component level", output) - output = execute(["lock-status", "hello/weblate"]) + output = self.execute(["lock-status", "hello/weblate"]) self.assertIn("locked", output) - output = execute(["lock", "hello/weblate"]) + output = self.execute(["lock", "hello/weblate"]) self.assertEqual("", output) - output = execute(["unlock", "hello/weblate"]) + output = self.execute(["unlock", "hello/weblate"]) self.assertEqual("", output) - output = execute(["lock-status", "hello/weblate/cs"], expected=1) + output = self.execute(["lock-status", "hello/weblate/cs"], expected=1) self.assertIn("This command is supported only at component level", output) def test_changes(self): """Project changes.""" - output = execute(["changes", "hello"]) + output = self.execute(["changes", "hello"]) self.assertIn("action_name", output) - output = execute(["changes", "hello/weblate"]) + output = self.execute(["changes", "hello/weblate"]) self.assertIn("action_name", output) - output = execute(["changes", "hello/weblate/cs"]) + output = self.execute(["changes", "hello/weblate/cs"]) self.assertIn("action_name", output) def test_download(self): """Translation file downloads.""" - output = execute(["download"], expected=1) + output = self.execute(["download"], expected=1) self.assertIn("Output is needed", output) with TemporaryDirectory() as tmpdirname: - execute(["download", "--output", tmpdirname]) + self.execute(["download", "--output", tmpdirname]) - output = execute(["download", "hello/weblate/cs"]) + output = self.execute(["download", "hello/weblate/cs"]) self.assertIn(b"Plural-Forms:", output) - output = execute(["download", "hello/weblate/cs", "--convert", "csv"]) + output = self.execute(["download", "hello/weblate/cs", "--convert", "csv"]) self.assertIn(b'"location"', output) with NamedTemporaryFile() as handle: handle.close() - execute(["download", "hello/weblate/cs", "-o", handle.name]) + self.execute(["download", "hello/weblate/cs", "-o", handle.name]) with open(handle.name, "rb") as tmp: output = tmp.read() self.assertIn(b"Plural-Forms:", output) - output = execute(["download", "hello/weblate"], expected=1) + output = self.execute(["download", "hello/weblate"], expected=1) self.assertIn("Output is needed", output) with TemporaryDirectory() as tmpdirname: - execute(["download", "hello/weblate", "--output", tmpdirname]) + self.execute(["download", "hello/weblate", "--output", tmpdirname]) - output = execute(["download", "hello"], expected=1) + output = self.execute(["download", "hello"], expected=1) self.assertIn("Output is needed", output) with TemporaryDirectory() as tmpdirname: - execute(["download", "hello", "--no-glossary", "--output", tmpdirname]) + self.execute(["download", "hello", "--no-glossary", "--output", tmpdirname]) # The hello-android should not be present as it is flagged as a glossary self.assertEqual(os.listdir(tmpdirname), ["hello-weblate.zip"]) with TemporaryDirectory() as tmpdirname: - execute( + self.execute( [ "download", "hello", @@ -456,7 +461,7 @@ def test_download(self): def test_download_config(self): with TemporaryDirectory() as tmpdirname: - execute( + self.execute( [ "--config", TEST_CONFIG, @@ -470,7 +475,7 @@ def test_download_config(self): ) self.assertEqual(os.listdir(tmpdirname), ["hello-weblate.zip"]) with TemporaryDirectory() as tmpdirname: - execute( + self.execute( [ "--config", TEST_CONFIG, @@ -490,23 +495,25 @@ def test_upload(self): """Translation file uploads.""" msg = "Error: Failed to upload translations!\nNot found.\n" - output = execute(["upload", "hello/weblate"], expected=1) + output = self.execute(["upload", "hello/weblate"], expected=1) self.assertEqual( "Error: This command is supported only at translation level\n", output ) with self.get_text_io_wrapper("test upload data") as stdin: - output = execute(["upload", "hello/weblate/cs"], stdin=stdin) + output = self.execute(["upload", "hello/weblate/cs"], stdin=stdin) self.assertEqual("", output) with self.get_text_io_wrapper("wrong upload data") as stdin: - output = execute(["upload", "hello/weblate/cs"], stdin=stdin, expected=1) + output = self.execute( + ["upload", "hello/weblate/cs"], stdin=stdin, expected=1 + ) self.assertEqual(msg, output) with NamedTemporaryFile(delete=False) as handle: handle.write(b"test upload overwrite") handle.close() - output = execute( + output = self.execute( ["upload", "hello/weblate/cs", "-i", handle.name, "--overwrite"] ) self.assertEqual("", output) @@ -517,19 +524,19 @@ def get_text_io_wrapper(string): return TextIOWrapper(BytesIO(string.encode()), "utf8") -class TestErrors(APITest): +class TestErrors(CLITestBase): """Error handling tests.""" def test_commandline_missing_key(self): """Configuration using command-line.""" - output = execute( + output = self.execute( ["--url", "http://denied.example.com", "list-projects"], expected=1 ) self.assertIn("Missing API key", output) def test_commandline_wrong_key(self): """Configuration using command-line.""" - output = execute( + output = self.execute( ["--key", "x", "--url", "http://denied.example.com", "list-projects"], expected=1, ) diff --git a/wlc/test_wlc.py b/wlc/test_wlc.py index 58966c79..c760cd78 100644 --- a/wlc/test_wlc.py +++ b/wlc/test_wlc.py @@ -5,6 +5,7 @@ """Test the module.""" from __future__ import annotations + import io import os from typing import Any, ClassVar