From 7b8b762db428159dac53d761d8ccfdd16de45111 Mon Sep 17 00:00:00 2001 From: Corvo <60719165+brothercorvo@users.noreply.github.com> Date: Wed, 18 Jun 2025 11:18:49 -0300 Subject: [PATCH] Fix lint errors across project --- .../Server/controllers_emergency.py | 18 ++++++---- .../Server/models_emergency.py | 26 ++++++++++++--- .../Server/server_emergency.py | 9 +++-- .../client/client_emergency.py | 6 +++- reticulum_openapi/__init__.py | 1 - reticulum_openapi/client.py | 9 +++-- reticulum_openapi/controller.py | 27 ++++++++++----- reticulum_openapi/model.py | 6 +++- reticulum_openapi/service.py | 33 ++++++++++++------- 9 files changed, 97 insertions(+), 38 deletions(-) diff --git a/examples/EmergencyManagement/Server/controllers_emergency.py b/examples/EmergencyManagement/Server/controllers_emergency.py index fafc4af..2a8c328 100644 --- a/examples/EmergencyManagement/Server/controllers_emergency.py +++ b/examples/EmergencyManagement/Server/controllers_emergency.py @@ -1,9 +1,14 @@ -mport asyncio -from reticulum_openapi.controller import Controller, handle_exceptions, APIException +import asyncio +from reticulum_openapi.controller import Controller, handle_exceptions from examples.EmergencyManagement.Server.models_emergency import ( - EmergencyActionMessage, Event, EAMStatus + EmergencyActionMessage, + Event, + EAMStatus, + Detail, + Point, ) + class EmergencyController(Controller): @handle_exceptions async def CreateEmergencyActionMessage(self, req: EmergencyActionMessage): @@ -15,7 +20,7 @@ async def CreateEmergencyActionMessage(self, req: EmergencyActionMessage): async def DeleteEmergencyActionMessage(self, callsign: str): self.logger.info(f"DeleteEAM callsign={callsign}") await asyncio.sleep(0.1) - return {"status":"deleted","callsign":callsign} + return {"status": "deleted", "callsign": callsign} @handle_exceptions async def ListEmergencyActionMessage(self): @@ -41,6 +46,7 @@ async def RetrieveEmergencyActionMessage(self, callsign: str): commsMethod="Radio" ) + class EventController(Controller): @handle_exceptions async def CreateEvent(self, req: Event): @@ -52,7 +58,7 @@ async def CreateEvent(self, req: Event): async def DeleteEvent(self, uid: str): self.logger.info(f"DeleteEvent uid={uid}") await asyncio.sleep(0.1) - return {"status":"deleted","uid":uid} + return {"status": "deleted", "uid": uid} @handle_exceptions async def ListEvent(self): @@ -75,5 +81,5 @@ async def RetrieveEvent(self, uid: str): stale="PT1H", start="PT0S", access="public", opex=0, qos=1, detail=Detail(emergencyActionMessage=None), - point=Point(0,0,0,0,0) + point=Point(0, 0, 0, 0, 0) ) diff --git a/examples/EmergencyManagement/Server/models_emergency.py b/examples/EmergencyManagement/Server/models_emergency.py index 774f1be..1d4c0ea 100644 --- a/examples/EmergencyManagement/Server/models_emergency.py +++ b/examples/EmergencyManagement/Server/models_emergency.py @@ -1,11 +1,13 @@ from dataclasses import dataclass from reticulum_openapi.model import BaseModel + class EAMStatus(str): Red = "Red" Yellow = "Yellow" Green = "Green" + @dataclass class EmergencyActionMessage(BaseModel): callsign: str @@ -18,16 +20,32 @@ class EmergencyActionMessage(BaseModel): commsStatus: EAMStatus commsMethod: str + @dataclass class Detail(BaseModel): emergencyActionMessage: EmergencyActionMessage + @dataclass class Point(BaseModel): - lat: float; lon: float; ce: float; le: float; hae: float + lat: float + lon: float + ce: float + le: float + hae: float + @dataclass class Event(BaseModel): - uid: int; how: str; version: int; time: int; type: str - stale: str; start: str; access: str; opex: int; qos: int - detail: Detail; point: Point + uid: int + how: str + version: int + time: int + type: str + stale: str + start: str + access: str + opex: int + qos: int + detail: Detail + point: Point diff --git a/examples/EmergencyManagement/Server/server_emergency.py b/examples/EmergencyManagement/Server/server_emergency.py index 61c7aa1..da82c1f 100644 --- a/examples/EmergencyManagement/Server/server_emergency.py +++ b/examples/EmergencyManagement/Server/server_emergency.py @@ -3,11 +3,16 @@ from examples.EmergencyManagement.Server.controllers_emergency import ( EmergencyController, EventController ) -from examples.EmergencyManagement.Server.models_emergency import EmergencyActionMessage, Event +from examples.EmergencyManagement.Server.models_emergency import ( + EmergencyActionMessage, + Event, +) + async def main(): svc = LXMFService() - eamc = EmergencyController(); evc = EventController() + eamc = EmergencyController() + evc = EventController() svc.add_route("CreateEmergencyActionMessage", eamc.CreateEmergencyActionMessage, EmergencyActionMessage) svc.add_route("DeleteEmergencyActionMessage", eamc.DeleteEmergencyActionMessage) svc.add_route("ListEmergencyActionMessage", eamc.ListEmergencyActionMessage) diff --git a/examples/EmergencyManagement/client/client_emergency.py b/examples/EmergencyManagement/client/client_emergency.py index c47dd0f..1cb665e 100644 --- a/examples/EmergencyManagement/client/client_emergency.py +++ b/examples/EmergencyManagement/client/client_emergency.py @@ -1,6 +1,10 @@ import asyncio from reticulum_openapi.client import LXMFClient -from examples.EmergencyManagement.Server.models_emergency import EmergencyActionMessage, EAMStatus +from examples.EmergencyManagement.Server.models_emergency import ( + EmergencyActionMessage, + EAMStatus, +) + async def main(): client = LXMFClient() diff --git a/reticulum_openapi/__init__.py b/reticulum_openapi/__init__.py index 8b13789..e69de29 100644 --- a/reticulum_openapi/__init__.py +++ b/reticulum_openapi/__init__.py @@ -1 +0,0 @@ - diff --git a/reticulum_openapi/client.py b/reticulum_openapi/client.py index 2e2f539..6a8b8ae 100644 --- a/reticulum_openapi/client.py +++ b/reticulum_openapi/client.py @@ -1,8 +1,10 @@ import asyncio -import RNS, LXMF -from typing import Optional, Dict, Callable, Type +import RNS +import LXMF +from typing import Optional, Dict from .model import dataclass_to_json, dataclass_from_json + class LXMFClient: """Simple client for sending commands and awaiting responses.""" @@ -47,7 +49,8 @@ async def send_command(self, dest_hex: str, command: str, payload_obj=None, else: data = dataclass_to_json(payload_obj) if self.auth_token: - import json, zlib + import json + import zlib obj = dataclass_from_json(type(payload_obj), data) obj_dict = obj.__dict__ obj_dict['auth_token'] = self.auth_token diff --git a/reticulum_openapi/controller.py b/reticulum_openapi/controller.py index be1bc8f..fc8451b 100644 --- a/reticulum_openapi/controller.py +++ b/reticulum_openapi/controller.py @@ -1,5 +1,4 @@ import logging -import asyncio from typing import Callable, Any, Coroutine, TypeVar # Setup module logger @@ -10,6 +9,7 @@ handler.setFormatter(formatter) logger.addHandler(handler) + class APIException(Exception): """Base exception for API errors, carrying a message and HTTP-like status code.""" def __init__(self, message: str, code: int = 500): @@ -17,24 +17,31 @@ def __init__(self, message: str, code: int = 500): self.code = code self.message = message + F = TypeVar('F', bound=Callable[..., Coroutine[Any, Any, Any]]) + def handle_exceptions(func: F) -> F: """Decorator to wrap controller methods with logging and exception handling.""" async def wrapper(*args, **kwargs): - logger.info(f"Executing {{func.__name__}} with args={{args[1:]}} kwargs={{kwargs}}") + logger.info( + f"Executing {func.__name__} with args={args[1:]} kwargs={kwargs}" + ) try: result = await func(*args, **kwargs) - logger.info(f"{{func.__name__}} completed successfully.") + logger.info(f"{func.__name__} completed successfully.") return result except APIException as e: - logger.error(f"APIException in {{func.__name__}}: {{e.message}} (code={{e.code}})") + logger.error( + f"APIException in {func.__name__}: {e.message} (code={e.code})" + ) return {"error": e.message, "code": e.code} except Exception as e: - logger.exception(f"Unhandled exception in {{func.__name__}}: {{e}}") + logger.exception(f"Unhandled exception in {func.__name__}: {e}") return {"error": "InternalServerError", "code": 500} return wrapper # type: ignore + class Controller: """ Base controller class with built-in logging, exception management, @@ -52,11 +59,15 @@ async def run_business_logic(self, logic: Coroutine[Any, Any, Any], *args, **kwa self.logger.info(f"Running business logic: {logic.__name__}") try: result = await logic(*args, **kwargs) - self.logger.info(f"Business logic {{logic.__name__}} succeeded.") + self.logger.info(f"Business logic {logic.__name__} succeeded.") return result except APIException as e: - self.logger.error(f"APIException in business logic {{logic.__name__}}: {{e.message}} (code={{e.code}})") + self.logger.error( + f"APIException in business logic {logic.__name__}: {e.message} (code={e.code})" + ) return {"error": e.message, "code": e.code} except Exception as e: - self.logger.exception(f"Unhandled exception in business logic {{logic.__name__}}: {{e}}") + self.logger.exception( + f"Unhandled exception in business logic {logic.__name__}: {e}" + ) return {"error": "InternalServerError", "code": 500} diff --git a/reticulum_openapi/model.py b/reticulum_openapi/model.py index 282e419..973b8cc 100644 --- a/reticulum_openapi/model.py +++ b/reticulum_openapi/model.py @@ -1,12 +1,14 @@ # reticulum_openapi/model.py from dataclasses import dataclass, asdict, is_dataclass -import json, zlib +import json +import zlib from typing import Type, TypeVar from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select T = TypeVar('T') + def dataclass_to_json(data_obj: T) -> bytes: """ Serialize a dataclass instance to a compressed JSON byte string. @@ -23,6 +25,7 @@ def dataclass_to_json(data_obj: T) -> bytes: compressed = zlib.compress(json_bytes) return compressed + def dataclass_from_json(cls: Type[T], data: bytes) -> T: """ Deserialize a dataclass instance from a compressed JSON byte string. @@ -37,6 +40,7 @@ def dataclass_from_json(cls: Type[T], data: bytes) -> T: # Instantiate dataclass by unpacking dict (assumes keys match field names) return cls(**obj_dict) # type: ignore + @dataclass class BaseModel: """ diff --git a/reticulum_openapi/service.py b/reticulum_openapi/service.py index a3558af..ba6b9e7 100644 --- a/reticulum_openapi/service.py +++ b/reticulum_openapi/service.py @@ -1,13 +1,14 @@ # reticulum_openapi/service.py import asyncio -import time import json import zlib -import RNS, LXMF +import RNS +import LXMF from typing import Callable, Dict, Optional, Type from jsonschema import validate, ValidationError from .model import dataclass_from_json, dataclass_to_json + class LXMFService: def __init__(self, config_path: str = None, storage_path: str = None, identity: RNS.Identity = None, display_name: str = "ReticulumOpenAPI", @@ -41,9 +42,14 @@ def __init__(self, config_path: str = None, storage_path: str = None, self.auth_token = auth_token self.max_payload_size = max_payload_size RNS.log(f"LXMFService initialized (Identity hash: {RNS.prettyhexrep(self.source_identity.hash)})") - - def add_route(self, command: str, handler: Callable, payload_type: Optional[Type] = None, - payload_schema: dict = None): + + def add_route( + self, + command: str, + handler: Callable, + payload_type: Optional[Type] = None, + payload_schema: dict = None, + ) -> None: """ Register a handler for a given command name. :param command: Command string (should match LXMF message title). @@ -52,7 +58,7 @@ def add_route(self, command: str, handler: Callable, payload_type: Optional[Type """ self._routes[command] = (handler, payload_type, payload_schema) RNS.log(f"Route registered: '{command}' -> {handler}") - + def _lxmf_delivery_callback(self, message: LXMF.LXMessage): """ Internal callback invoked by LXMRouter on message delivery. @@ -105,11 +111,13 @@ def _lxmf_delivery_callback(self, message: LXMF.LXMessage): return else: payload_obj = None # No payload content + # Dispatch to handler asynchronously async def handle_and_reply(): result = None try: - # Call the handler with the parsed payload. If payload is None, some handlers may not accept a parameter. + # Call the handler with the parsed payload. + # If payload is None, some handlers may not accept a parameter. if payload_obj is not None: result = await handler(payload_obj) else: @@ -128,7 +136,8 @@ async def handle_and_reply(): resp_bytes = dataclass_to_json(result) except Exception as e: # Fallback: just JSON dump the object (it might not be a dataclass) - resp_bytes = zlib.compress(json.dumps(result).encode('utf-8')) + RNS.log(f"Failed to serialize result dataclass: {e}") + resp_bytes = zlib.compress(json.dumps(result).encode("utf-8")) else: # If result is a simple value (str, number, etc.), wrap it in JSON resp_bytes = zlib.compress(json.dumps(result).encode('utf-8')) @@ -146,7 +155,7 @@ async def handle_and_reply(): RNS.log("No source identity to respond to for message.") # Schedule the handler execution on the asyncio event loop self._loop.call_soon_threadsafe(lambda: asyncio.create_task(handle_and_reply())) - + def _send_lxmf(self, dest_identity: RNS.Identity, title: str, content_bytes: bytes, propagate: bool = False): """ @@ -164,7 +173,7 @@ def _send_lxmf(self, dest_identity: RNS.Identity, title: str, content_bytes: byt # For now, let LXMF choose the default (which is typically DIRECT if reachable). # Dispatch the message via the router self.router.handle_outbound(lxmessage) - + async def send_message(self, dest_hex: str, command: str, payload_obj=None, await_path: bool = True): """ Public method to send a command to another LXMF node (by hex hash of its identity). @@ -199,7 +208,7 @@ async def send_message(self, dest_hex: str, command: str, payload_obj=None, awai content_bytes = dataclass_to_json(payload_obj) # Use internal send helper self._send_lxmf(dest_identity, command, content_bytes, propagate=False) - + def announce(self): """Announce this service's identity (make its address known on the network).""" try: @@ -207,7 +216,7 @@ def announce(self): RNS.log("Service identity announced: " + RNS.prettyhexrep(self.source_identity.hash)) except Exception as e: RNS.log(f"Announcement failed: {e}") - + async def start(self): """Run the service until cancelled.""" RNS.log("LXMFService started and listening for messages...")