diff --git a/.gitignore b/.gitignore index f07d212..6918ff5 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,7 @@ venv.bak/ # Test/config quick.py -config.toml \ No newline at end of file +config.toml +obsws.log + +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 6fa8c6b..4ae670b 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ By default the clients connect with parameters: - `host`: "localhost" - `port`: 4455 - `password`: "" +- `timeout`: None You may override these parameters by storing them in a toml config file or passing them as keyword arguments. @@ -53,7 +54,7 @@ Example `__main__.py`: import obsws_python as obs # pass conn info if not in config.toml -cl = obs.ReqClient(host='localhost', port=4455, password='mystrongpass') +cl = obs.ReqClient(host='localhost', port=4455, password='mystrongpass', timeout=3) # Toggle the mute state of your Mic input cl.toggle_input_mute('Mic/Aux') @@ -131,6 +132,8 @@ If a request fails an `OBSSDKError` will be raised with a status code. For a full list of status codes refer to [Codes](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requeststatus) +If a timeout occurs during sending/receiving a request or receiving an event an `OBSSDKTimeoutError` will be raised. + ### Logging If you want to see the raw messages simply set log level to DEBUG diff --git a/obsws_python/baseclient.py b/obsws_python/baseclient.py index 10e9d12..4f47d4b 100644 --- a/obsws_python/baseclient.py +++ b/obsws_python/baseclient.py @@ -7,15 +7,22 @@ from random import randint from typing import Optional import websocket +from websocket import WebSocketTimeoutException -from .error import OBSSDKError +from .error import OBSSDKError, OBSSDKTimeoutError class ObsClient: logger = logging.getLogger("baseclient.obsclient") def __init__(self, **kwargs): - defaultkwargs = {"host": "localhost", "port": 4455, "password": "", "subs": 0} + defaultkwargs = { + "host": "localhost", + "port": 4455, + "password": "", + "subs": 0, + "timeout": None, + } if not any(key in kwargs for key in ("host", "port", "password")): kwargs |= self._conn_from_toml() kwargs = defaultkwargs | kwargs @@ -23,14 +30,21 @@ class ObsClient: setattr(self, attr, val) self.logger.info( - "Connecting with parameters: host='{host}' port={port} password='{password}' subs={subs}".format( + "Connecting with parameters: host='{host}' port={port} password='{password}' subs={subs} timeout={timeout}".format( **self.__dict__ ) ) - self.ws = websocket.WebSocket() - self.ws.connect(f"ws://{self.host}:{self.port}") - self.server_hello = json.loads(self.ws.recv()) + try: + self.ws = websocket.WebSocket() + self.ws.connect(f"ws://{self.host}:{self.port}", timeout=self.timeout) + self.server_hello = json.loads(self.ws.recv()) + except ValueError as e: + self.logger.error(f"{type(e).__name__}: {e}") + raise + except (ConnectionRefusedError, WebSocketTimeoutException) as e: + self.logger.exception(f"{type(e).__name__}: {e}") + raise def _conn_from_toml(self) -> dict: try: @@ -105,7 +119,11 @@ class ObsClient: if req_data: payload["d"]["requestData"] = req_data self.logger.debug(f"Sending request {payload}") - self.ws.send(json.dumps(payload)) - response = json.loads(self.ws.recv()) + try: + self.ws.send(json.dumps(payload)) + response = json.loads(self.ws.recv()) + except WebSocketTimeoutException as e: + self.logger.exception(f"{type(e).__name__}: {e}") + raise OBSSDKTimeoutError("Timeout while trying to send the request") from e self.logger.debug(f"Response received {response}") return response["d"] diff --git a/obsws_python/error.py b/obsws_python/error.py index 67885a4..42719b1 100644 --- a/obsws_python/error.py +++ b/obsws_python/error.py @@ -1,4 +1,6 @@ class OBSSDKError(Exception): - """general errors""" + """Exception raised when general errors occur""" - pass + +class OBSSDKTimeoutError(Exception): + """Exception raised when a connection times out""" diff --git a/obsws_python/events.py b/obsws_python/events.py index 9a942e6..a358fe7 100644 --- a/obsws_python/events.py +++ b/obsws_python/events.py @@ -3,8 +3,11 @@ import logging import time from threading import Thread +from websocket import WebSocketTimeoutException + from .baseclient import ObsClient from .callback import Callback +from .error import OBSSDKTimeoutError from .subs import Subs """ @@ -30,7 +33,7 @@ class EventClient: def __repr__(self): return type( self - ).__name__ + "(host='{host}', port={port}, password='{password}', subs={subs})".format( + ).__name__ + "(host='{host}', port={port}, password='{password}', subs={subs}, timeout={timeout})".format( **self.base_client.__dict__, ) @@ -49,7 +52,11 @@ class EventClient: """ self.running = True while self.running: - event = json.loads(self.base_client.ws.recv()) + try: + event = json.loads(self.base_client.ws.recv()) + except WebSocketTimeoutException as e: + self.logger.exception(f"{type(e).__name__}: {e}") + raise OBSSDKTimeoutError("Timeout while waiting for event") from e self.logger.debug(f"Event received {event}") type_, data = ( event["d"].get("eventType"), diff --git a/obsws_python/reqs.py b/obsws_python/reqs.py index 9650e95..fb7a809 100644 --- a/obsws_python/reqs.py +++ b/obsws_python/reqs.py @@ -28,7 +28,7 @@ class ReqClient: def __repr__(self): return type( self - ).__name__ + "(host='{host}', port={port}, password='{password}')".format( + ).__name__ + "(host='{host}', port={port}, password='{password}', timeout={timeout})".format( **self.base_client.__dict__, ) diff --git a/obsws_python/version.py b/obsws_python/version.py index 841aad2..e3a0f01 100644 --- a/obsws_python/version.py +++ b/obsws_python/version.py @@ -1 +1 @@ -version = "1.4.2" +version = "1.5.0"