ruff: apply automatic fixes

This commit is contained in:
Jörg Thalheim
2025-08-20 13:52:45 +02:00
parent 798d445f3e
commit ea2d6aab65
217 changed files with 2283 additions and 1739 deletions

View File

@@ -13,7 +13,9 @@ log = logging.getLogger(__name__)
def main(argv: list[str] = sys.argv) -> int:
parser = argparse.ArgumentParser(description="Clan App")
parser.add_argument(
"--content-uri", type=str, help="The URI of the content to display"
"--content-uri",
type=str,
help="The URI of the content to display",
)
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
parser.add_argument(

View File

@@ -56,18 +56,23 @@ class ApiBridge(ABC):
for middleware in self.middleware_chain:
try:
log.debug(
f"{middleware.__class__.__name__} => {request.method_name}"
f"{middleware.__class__.__name__} => {request.method_name}",
)
middleware.process(context)
except Exception as e:
# If middleware fails, handle error
self.send_api_error_response(
request.op_key or "unknown", str(e), ["middleware_error"]
request.op_key or "unknown",
str(e),
["middleware_error"],
)
return
def send_api_error_response(
self, op_key: str, error_message: str, location: list[str]
self,
op_key: str,
error_message: str,
location: list[str],
) -> None:
"""Send an error response."""
from clan_lib.api import ApiError, ErrorDataClass
@@ -80,7 +85,7 @@ class ApiBridge(ABC):
message="An internal error occured",
description=error_message,
location=location,
)
),
],
)
@@ -107,6 +112,7 @@ class ApiBridge(ABC):
thread_name: Name for the thread (for debugging)
wait_for_completion: Whether to wait for the thread to complete
timeout: Timeout in seconds when waiting for completion
"""
op_key = request.op_key or "unknown"
@@ -116,7 +122,7 @@ class ApiBridge(ABC):
try:
log.debug(
f"Processing {request.method_name} with args {request.args} "
f"and header {request.header} in thread {thread_name}"
f"and header {request.header} in thread {thread_name}",
)
self.process_request(request)
finally:
@@ -124,7 +130,9 @@ class ApiBridge(ABC):
stop_event = threading.Event()
thread = threading.Thread(
target=thread_task, args=(stop_event,), name=thread_name
target=thread_task,
args=(stop_event,),
name=thread_name,
)
thread.start()
@@ -138,5 +146,7 @@ class ApiBridge(ABC):
if thread.is_alive():
stop_event.set() # Cancel the thread
self.send_api_error_response(
op_key, "Request timeout", ["api_bridge", request.method_name]
op_key,
"Request timeout",
["api_bridge", request.method_name],
)

View File

@@ -26,8 +26,7 @@ RESULT: dict[str, SuccessDataClass[list[str] | None] | ErrorDataClass] = {}
def get_clan_folder() -> SuccessDataClass[Flake] | ErrorDataClass:
"""
Opens the clan folder using the GTK file dialog.
"""Opens the clan folder using the GTK file dialog.
Returns the path to the clan folder or an error if it fails.
"""
file_request = FileRequest(
@@ -52,7 +51,7 @@ def get_clan_folder() -> SuccessDataClass[Flake] | ErrorDataClass:
message="No folder selected",
description="You must select a folder to open.",
location=["get_clan_folder"],
)
),
],
)
@@ -66,7 +65,7 @@ def get_clan_folder() -> SuccessDataClass[Flake] | ErrorDataClass:
message="Invalid clan folder",
description=f"The selected folder '{clan_folder}' is not a valid clan folder.",
location=["get_clan_folder"],
)
),
],
)
@@ -102,8 +101,10 @@ def gtk_open_file(file_request: FileRequest, op_key: str) -> bool:
selected_path = remove_none([gfile.get_path()])
returns(
SuccessDataClass(
op_key=op_key, data=selected_path, status="success"
)
op_key=op_key,
data=selected_path,
status="success",
),
)
except Exception as e:
log.exception("Error opening file")
@@ -116,9 +117,9 @@ def gtk_open_file(file_request: FileRequest, op_key: str) -> bool:
message=e.__class__.__name__,
description=str(e),
location=["get_system_file"],
)
),
],
)
),
)
def on_file_select_multiple(file_dialog: Gtk.FileDialog, task: Gio.Task) -> None:
@@ -128,8 +129,10 @@ def gtk_open_file(file_request: FileRequest, op_key: str) -> bool:
selected_paths = remove_none([gfile.get_path() for gfile in gfiles])
returns(
SuccessDataClass(
op_key=op_key, data=selected_paths, status="success"
)
op_key=op_key,
data=selected_paths,
status="success",
),
)
else:
returns(SuccessDataClass(op_key=op_key, data=None, status="success"))
@@ -144,9 +147,9 @@ def gtk_open_file(file_request: FileRequest, op_key: str) -> bool:
message=e.__class__.__name__,
description=str(e),
location=["get_system_file"],
)
),
],
)
),
)
def on_folder_select(file_dialog: Gtk.FileDialog, task: Gio.Task) -> None:
@@ -156,8 +159,10 @@ def gtk_open_file(file_request: FileRequest, op_key: str) -> bool:
selected_path = remove_none([gfile.get_path()])
returns(
SuccessDataClass(
op_key=op_key, data=selected_path, status="success"
)
op_key=op_key,
data=selected_path,
status="success",
),
)
else:
returns(SuccessDataClass(op_key=op_key, data=None, status="success"))
@@ -172,9 +177,9 @@ def gtk_open_file(file_request: FileRequest, op_key: str) -> bool:
message=e.__class__.__name__,
description=str(e),
location=["get_system_file"],
)
),
],
)
),
)
def on_save_finish(file_dialog: Gtk.FileDialog, task: Gio.Task) -> None:
@@ -184,8 +189,10 @@ def gtk_open_file(file_request: FileRequest, op_key: str) -> bool:
selected_path = remove_none([gfile.get_path()])
returns(
SuccessDataClass(
op_key=op_key, data=selected_path, status="success"
)
op_key=op_key,
data=selected_path,
status="success",
),
)
else:
returns(SuccessDataClass(op_key=op_key, data=None, status="success"))
@@ -200,9 +207,9 @@ def gtk_open_file(file_request: FileRequest, op_key: str) -> bool:
message=e.__class__.__name__,
description=str(e),
location=["get_system_file"],
)
),
],
)
),
)
dialog = Gtk.FileDialog()

View File

@@ -39,7 +39,7 @@ class ArgumentParsingMiddleware(Middleware):
except Exception as e:
log.exception(
f"Error while parsing arguments for {context.request.method_name}"
f"Error while parsing arguments for {context.request.method_name}",
)
context.bridge.send_api_error_response(
context.request.op_key or "unknown",

View File

@@ -23,7 +23,9 @@ class Middleware(ABC):
"""Process the request through this middleware."""
def register_context_manager(
self, context: MiddlewareContext, cm: AbstractContextManager[Any]
self,
context: MiddlewareContext,
cm: AbstractContextManager[Any],
) -> Any:
"""Register a context manager with the exit stack."""
return context.exit_stack.enter_context(cm)

View File

@@ -25,23 +25,26 @@ class LoggingMiddleware(Middleware):
try:
# Handle log group configuration
log_group: list[str] | None = context.request.header.get("logging", {}).get(
"group_path", None
"group_path",
None,
)
if log_group is not None:
if not isinstance(log_group, list):
msg = f"Expected log_group to be a list, got {type(log_group)}"
raise TypeError(msg) # noqa: TRY301
log.warning(
f"Using log group {log_group} for {context.request.method_name} with op_key {context.request.op_key}"
f"Using log group {log_group} for {context.request.method_name} with op_key {context.request.op_key}",
)
# Create log file
log_file = self.log_manager.create_log_file(
method, op_key=context.request.op_key or "unknown", group_path=log_group
method,
op_key=context.request.op_key or "unknown",
group_path=log_group,
).get_file_path()
except Exception as e:
log.exception(
f"Error while handling request header of {context.request.method_name}"
f"Error while handling request header of {context.request.method_name}",
)
context.bridge.send_api_error_response(
context.request.op_key or "unknown",
@@ -76,7 +79,8 @@ class LoggingMiddleware(Middleware):
line_buffering=True,
)
self.handler = setup_logging(
log.getEffectiveLevel(), log_file=handler_stream
log.getEffectiveLevel(),
log_file=handler_stream,
)
return self

View File

@@ -32,7 +32,7 @@ class MethodExecutionMiddleware(Middleware):
except Exception as e:
log.exception(
f"Error while handling result of {context.request.method_name}"
f"Error while handling result of {context.request.method_name}",
)
context.bridge.send_api_error_response(
context.request.op_key or "unknown",

View File

@@ -48,7 +48,7 @@ def app_run(app_opts: ClanAppOptions) -> int:
# Add a log group ["clans", <dynamic_name>, "machines", <dynamic_name>]
log_manager = LogManager(base_dir=user_data_dir() / "clan-app" / "logs")
clan_log_group = LogGroupConfig("clans", "Clans").add_child(
LogGroupConfig("machines", "Machines")
LogGroupConfig("machines", "Machines"),
)
log_manager = log_manager.add_root_group_config(clan_log_group)
# Init LogManager global in log_manager_api module
@@ -89,7 +89,7 @@ def app_run(app_opts: ClanAppOptions) -> int:
# HTTP-only mode - keep the server running
log.info("HTTP API server running...")
log.info(
f"Swagger: http://{app_opts.http_host}:{app_opts.http_port}/api/swagger"
f"Swagger: http://{app_opts.http_host}:{app_opts.http_port}/api/swagger",
)
log.info("Press Ctrl+C to stop the server")

View File

@@ -63,7 +63,9 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler):
self.send_header("Access-Control-Allow-Headers", "Content-Type")
def _send_json_response_with_status(
self, data: dict[str, Any], status_code: int = 200
self,
data: dict[str, Any],
status_code: int = 200,
) -> None:
"""Send a JSON response with the given status code."""
try:
@@ -82,11 +84,13 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler):
response_dict = dataclass_to_dict(response)
self._send_json_response_with_status(response_dict, 200)
log.debug(
f"HTTP response for {response._op_key}: {json.dumps(response_dict, indent=2)}" # noqa: SLF001
f"HTTP response for {response._op_key}: {json.dumps(response_dict, indent=2)}", # noqa: SLF001
)
def _create_success_response(
self, op_key: str, data: dict[str, Any]
self,
op_key: str,
data: dict[str, Any],
) -> BackendResponse:
"""Create a successful API response."""
return BackendResponse(
@@ -98,14 +102,16 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler):
def _send_info_response(self) -> None:
"""Send server information response."""
response = self._create_success_response(
"info", {"message": "Clan API Server", "version": "1.0.0"}
"info",
{"message": "Clan API Server", "version": "1.0.0"},
)
self.send_api_response(response)
def _send_methods_response(self) -> None:
"""Send available API methods response."""
response = self._create_success_response(
"methods", {"methods": list(self.api.functions.keys())}
"methods",
{"methods": list(self.api.functions.keys())},
)
self.send_api_response(response)
@@ -179,7 +185,7 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler):
json_data = json.loads(file_data.decode("utf-8"))
server_address = getattr(self.server, "server_address", ("localhost", 80))
json_data["servers"] = [
{"url": f"http://{server_address[0]}:{server_address[1]}/api/v1/"}
{"url": f"http://{server_address[0]}:{server_address[1]}/api/v1/"},
]
file_data = json.dumps(json_data, indent=2).encode("utf-8")
@@ -213,7 +219,9 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler):
# Validate API path
if not path.startswith("/api/v1/"):
self.send_api_error_response(
"post", f"Path not found: {path}", ["http_bridge", "POST"]
"post",
f"Path not found: {path}",
["http_bridge", "POST"],
)
return
@@ -221,7 +229,9 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler):
method_name = path[len("/api/v1/") :]
if not method_name:
self.send_api_error_response(
"post", "Method name required", ["http_bridge", "POST"]
"post",
"Method name required",
["http_bridge", "POST"],
)
return
@@ -289,19 +299,26 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler):
# Create API request
api_request = BackendRequest(
method_name=method_name, args=body, header=header, op_key=op_key
method_name=method_name,
args=body,
header=header,
op_key=op_key,
)
except Exception as e:
self.send_api_error_response(
gen_op_key, str(e), ["http_bridge", method_name]
gen_op_key,
str(e),
["http_bridge", method_name],
)
return
self._process_api_request_in_thread(api_request, method_name)
def _parse_request_data(
self, request_data: dict[str, Any], gen_op_key: str
self,
request_data: dict[str, Any],
gen_op_key: str,
) -> tuple[dict[str, Any], dict[str, Any], str]:
"""Parse and validate request data components."""
header = request_data.get("header", {})
@@ -344,7 +361,9 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler):
pass
def _process_api_request_in_thread(
self, api_request: BackendRequest, method_name: str
self,
api_request: BackendRequest,
method_name: str,
) -> None:
"""Process the API request in a separate thread."""
stop_event = threading.Event()
@@ -358,7 +377,7 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler):
log.debug(
f"Processing {request.method_name} with args {request.args} "
f"and header {request.header}"
f"and header {request.header}",
)
self.process_request(request)

View File

@@ -64,7 +64,8 @@ def mock_log_manager() -> Mock:
@pytest.fixture
def http_bridge(
mock_api: MethodRegistry, mock_log_manager: Mock
mock_api: MethodRegistry,
mock_log_manager: Mock,
) -> tuple[MethodRegistry, tuple]:
"""Create HTTP bridge dependencies for testing."""
middleware_chain = (
@@ -256,7 +257,9 @@ class TestIntegration:
"""Integration tests for HTTP API components."""
def test_full_request_flow(
self, mock_api: MethodRegistry, mock_log_manager: Mock
self,
mock_api: MethodRegistry,
mock_log_manager: Mock,
) -> None:
"""Test complete request flow from server to bridge to middleware."""
server: HttpApiServer = HttpApiServer(
@@ -301,7 +304,9 @@ class TestIntegration:
server.stop()
def test_blocking_task(
self, mock_api: MethodRegistry, mock_log_manager: Mock
self,
mock_api: MethodRegistry,
mock_log_manager: Mock,
) -> None:
shared_threads: dict[str, tasks.WebThread] = {}
tasks.BAKEND_THREADS = shared_threads

View File

@@ -36,7 +36,6 @@ def _get_lib_names() -> list[str]:
def _be_sure_libraries() -> list[Path] | None:
"""Ensure libraries exist and return paths."""
lib_dir = os.environ.get("WEBVIEW_LIB_DIR")
if not lib_dir:
msg = "WEBVIEW_LIB_DIR environment variable is not set"

View File

@@ -144,7 +144,9 @@ class Webview:
)
else:
bridge = WebviewBridge(
webview=self, middleware_chain=tuple(self._middleware), threads={}
webview=self,
middleware_chain=tuple(self._middleware),
threads={},
)
self._bridge = bridge
@@ -154,7 +156,10 @@ class Webview:
def set_size(self, value: Size) -> None:
"""Set the webview size (legacy compatibility)."""
_webview_lib.webview_set_size(
self.handle, value.width, value.height, value.hint
self.handle,
value.width,
value.height,
value.hint,
)
def set_title(self, value: str) -> None:
@@ -194,7 +199,10 @@ class Webview:
self._callbacks[name] = c_callback
_webview_lib.webview_bind(
self.handle, _encode_c_string(name), c_callback, None
self.handle,
_encode_c_string(name),
c_callback,
None,
)
def bind(self, name: str, callback: Callable[..., Any]) -> None:
@@ -219,7 +227,10 @@ class Webview:
def return_(self, seq: str, status: int, result: str) -> None:
_webview_lib.webview_return(
self.handle, _encode_c_string(seq), status, _encode_c_string(result)
self.handle,
_encode_c_string(seq),
status,
_encode_c_string(result),
)
def eval(self, source: str) -> None:

View File

@@ -26,7 +26,9 @@ class WebviewBridge(ApiBridge):
def send_api_response(self, response: BackendResponse) -> None:
"""Send response back to the webview client."""
serialized = json.dumps(
dataclass_to_dict(response), indent=4, ensure_ascii=False
dataclass_to_dict(response),
indent=4,
ensure_ascii=False,
)
log.debug(f"Sending response: {serialized}")
@@ -40,7 +42,6 @@ class WebviewBridge(ApiBridge):
arg: int,
) -> None:
"""Handle a call from webview's JavaScript bridge."""
try:
op_key = op_key_bytes.decode()
raw_args = json.loads(request_data.decode())
@@ -68,7 +69,10 @@ class WebviewBridge(ApiBridge):
# Create API request
api_request = BackendRequest(
method_name=method_name, args=args, header=header, op_key=op_key
method_name=method_name,
args=args,
header=header,
op_key=op_key,
)
except Exception as e:
@@ -77,7 +81,9 @@ class WebviewBridge(ApiBridge):
)
log.exception(msg)
self.send_api_error_response(
op_key, str(e), ["webview_bridge", method_name]
op_key,
str(e),
["webview_bridge", method_name],
)
return