From 09a44b2dea7bb566865c065bdd09352956ee9772 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Fri, 13 Jun 2025 14:10:54 +0100 Subject: [PATCH] add SlobsCliProtocolError for wrapping ProtocolError handle ProtocolError(s) and reraise as SlobsCliProtocolError. This has the following benefits: A user friendly error message A non-zero exit code --- src/slobs_cli/errors.py | 30 ++++++++++++++++++++++++++++++ src/slobs_cli/scene.py | 36 +++++++++++++++++++++++++----------- 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/slobs_cli/errors.py b/src/slobs_cli/errors.py index 42cadc7..4d8790f 100644 --- a/src/slobs_cli/errors.py +++ b/src/slobs_cli/errors.py @@ -1,5 +1,7 @@ """module for custom exceptions in Slobs CLI.""" +import json + import asyncclick as click @@ -14,3 +16,31 @@ class SlobsCliError(click.ClickException): def show(self): """Display the error message in red.""" click.secho(f'Error: {self.message}', fg='red', err=True) + + +class SlobsCliProtocolError(SlobsCliError): + """Converts pyslobs ProtocolError to a SlobsCliProtocolError.""" + + def __init__(self, message: str): + """Initialize the SlobsCliProtocolError with a message.""" + protocol_message_to_dict = json.loads( + str(message).replace('"', '\\"').replace("'", '"') + ) + super().__init__( + protocol_message_to_dict.get('message', 'Unable to parse error message') + ) + self.exit_code = 2 + self.protocol_code = protocol_message_to_dict.get('code', 'Unknown error code') + + def show(self): + """Display the protocol error message in red.""" + match self.protocol_code: + case -32600: + click.secho( + 'Oops! Looks like we hit a rate limit for this command. Please try again later.', + fg='red', + err=True, + ) + case _: + # Fall back to the base error display for unknown protocol codes + super().show() diff --git a/src/slobs_cli/scene.py b/src/slobs_cli/scene.py index 385be14..5cc49dd 100644 --- a/src/slobs_cli/scene.py +++ b/src/slobs_cli/scene.py @@ -2,11 +2,11 @@ import asyncclick as click from anyio import create_task_group -from pyslobs import ScenesService, TransitionsService +from pyslobs import ProtocolError, ScenesService, TransitionsService from terminaltables3 import AsciiTable from .cli import cli -from .errors import SlobsCliError +from .errors import SlobsCliError, SlobsCliProtocolError @cli.group() @@ -56,9 +56,14 @@ async def list(ctx: click.Context, id: bool = False): conn.close() - async with create_task_group() as tg: - tg.start_soon(conn.background_processing) - tg.start_soon(_run) + try: + async with create_task_group() as tg: + tg.start_soon(conn.background_processing) + tg.start_soon(_run) + except* ProtocolError as excgroup: + raisable = next(iter(excgroup.exceptions)) + e = SlobsCliProtocolError(str(raisable)) + raise e @scene.command() @@ -77,9 +82,14 @@ async def current(ctx: click.Context, id: bool = False): ) conn.close() - async with create_task_group() as tg: - tg.start_soon(conn.background_processing) - tg.start_soon(_run) + try: + async with create_task_group() as tg: + tg.start_soon(conn.background_processing) + tg.start_soon(_run) + except* ProtocolError as excgroup: + raisable = next(iter(excgroup.exceptions)) + e = SlobsCliProtocolError(str(raisable)) + raise e @scene.command() @@ -138,12 +148,16 @@ async def switch( break else: # If no scene by the given name was found conn.close() - raise SlobsCliError(f"Scene '{scene_name}' not found.") + raise SlobsCliError(f'Scene "{scene_name}" not found.') try: async with create_task_group() as tg: tg.start_soon(conn.background_processing) tg.start_soon(_run) except* SlobsCliError as excgroup: - for e in excgroup.exceptions: - raise e + raisable = next(iter(excgroup.exceptions)) + raise raisable + except* ProtocolError as excgroup: + p_error = next(iter(excgroup.exceptions)) + raisable = SlobsCliProtocolError(str(p_error)) + raise raisable