ruff: apply automatic fixes
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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],
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user