From 7143f0ea10f6cfcddd1a21d46667f4001d889f84 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Sat, 22 Jul 2023 16:06:26 -0700 Subject: [PATCH] Add text support to ReadWriteClient.write_message (#120) The Vestaboard Read / Write API now supports passing text in addition to the previously-supported rows of character codes. It also now returns HTTP 200 (instead of HTTP 201) upon a successful write, so update the response logic accordingly. I've noticed that this endpoint also now returns HTTP 405 when the written message matches the board's existing message, returning a JSON error like this: { 'status': 'error', 'message': 'Message fingerprint matches the message that is already displayed on the board' } This is surfaced to the caller as an HTTPStatusError (just like any other request failure), but I may look at ways to make this a little friendlier if this is the official server-side behavior. --- tests/test_clients.py | 19 ++++++++++++++++--- vesta/clients.py | 26 ++++++++++++++++++++------ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/tests/test_clients.py b/tests/test_clients.py index 0cb3cd6..091a76a 100644 --- a/tests/test_clients.py +++ b/tests/test_clients.py @@ -190,13 +190,26 @@ def test_read_message_empty( message = rw_client.read_message() assert message is None - def test_write_message(self, rw_client: ReadWriteClient, respx_mock: MockRouter): + def test_write_message_text( + self, rw_client: ReadWriteClient, respx_mock: MockRouter + ): + text = "abc" + respx_mock.post("https://rw.vestaboard.com/").respond(200) + rw_client.write_message(text) + + def test_write_message_list( + self, rw_client: ReadWriteClient, respx_mock: MockRouter + ): chars = [[0] * COLS] * ROWS - respx_mock.post("https://rw.vestaboard.com/").respond(201) + respx_mock.post("https://rw.vestaboard.com/").respond(200) assert rw_client.write_message(chars) assert respx_mock.calls.called assert respx_mock.calls.last.request.content == json.dumps(chars).encode() - def test_write_message_dimensions(self, rw_client: ReadWriteClient): + def test_write_message_list_dimensions(self, rw_client: ReadWriteClient): with pytest.raises(ValueError, match=rf"expected a \({COLS}, {ROWS}\) array"): rw_client.write_message([]) + + def test_write_message_type(self, rw_client: ReadWriteClient): + with pytest.raises(TypeError, match=r"unsupported message type"): + rw_client.write_message(True) # type: ignore diff --git a/vesta/clients.py b/vesta/clients.py index 081a848..b8cef7b 100644 --- a/vesta/clients.py +++ b/vesta/clients.py @@ -255,15 +255,29 @@ def read_message(self) -> Optional[Rows]: return json.loads(layout) return None - def write_message(self, message: Rows) -> bool: + def write_message(self, message: Union[str, Rows]) -> bool: """Write a message to the Vestaboard. - `message` must be a two-dimensional (6, 22) array of character codes - representing the exact positions of characters on the board. + `message` can be either a string of text or a two-dimensional (6, 22) + array of character codes representing the exact positions of characters + on the board. + + If text is specified, the lines will be centered horizontally and + vertically. Character codes will be inferred for alphanumeric and + punctuation characters, or they can be explicitly specified using curly + braces containing the character code (such as ``{5}`` or ``{65}``). :raises ValueError: if ``message`` is a list with unsupported dimensions """ - validate_rows(message) - r = self.http.post("", json=message) + data: Union[Dict[str, str], Rows] + if isinstance(message, str): + data = {"text": message} + elif isinstance(message, list): + validate_rows(message) + data = message + else: + raise TypeError(f"unsupported message type: {type(message)}") + + r = self.http.post("", json=data) r.raise_for_status() - return r.status_code == httpx.codes.CREATED + return r.is_success