From 57653ba3a825930ee224ad34426430d05d2f4acc Mon Sep 17 00:00:00 2001 From: Tom Jakubowski Date: Fri, 1 Mar 2024 20:10:47 -0800 Subject: [PATCH 1/2] Fix get_hosted_table_names in Python client It was returning None. It now returns a future which resolves to the list of hosted table names. --- python/perspective/perspective/client/base.py | 4 +++- .../tests/handlers/test_starlette_handler.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/python/perspective/perspective/client/base.py b/python/perspective/perspective/client/base.py index 94e4fe64ac..4a738a2bef 100644 --- a/python/perspective/perspective/client/base.py +++ b/python/perspective/perspective/client/base.py @@ -97,7 +97,9 @@ def send(self, msg): def get_hosted_table_names(self): msg = {"cmd": "get_hosted_table_names"} - return self.post(msg) + future = asyncio.Future() + self.post(msg, future=future) + return future def post(self, msg, future=None, keep_alive=False): """Given a message and an associated `Future` object, store the future diff --git a/python/perspective/perspective/tests/handlers/test_starlette_handler.py b/python/perspective/perspective/tests/handlers/test_starlette_handler.py index 2a25bb5b1d..be4206c812 100644 --- a/python/perspective/perspective/tests/handlers/test_starlette_handler.py +++ b/python/perspective/perspective/tests/handlers/test_starlette_handler.py @@ -16,6 +16,7 @@ import pytest import random +from contextlib import asynccontextmanager from datetime import datetime from fastapi import FastAPI, WebSocket @@ -64,6 +65,14 @@ async def websocket_client(self): """ return await websocket(CLIENT, "/websocket") + @asynccontextmanager + async def managed_websocket_client(self): + client = await self.websocket_client() + try: + yield client + finally: + await client.terminate() + @pytest.mark.asyncio async def test_starlette_handler_init_terminate(self): """Using FastAPI's builtin test client, test the websocket provided by @@ -325,3 +334,13 @@ async def test_starlette_handler_create_view_to_arrow_update(self): assert size2 == 110 await client.terminate() + + @pytest.mark.asyncio + async def test_starlette_handler_get_hosted_table_names(self): + table_name = str(random.random()) + _table = Table(data) + MANAGER.host_table(table_name, _table) + + async with self.managed_websocket_client() as client: + names = await client.get_hosted_table_names() + assert names == [table_name] From 75e6e5b8a51e80edcfcecaaff112048bc51fb77e Mon Sep 17 00:00:00 2001 From: Tom Jakubowski Date: Mon, 4 Mar 2024 15:57:25 -0800 Subject: [PATCH 2/2] Port starlette handler tests to context manager --- .../tests/handlers/test_starlette_handler.py | 260 ++++++++---------- 1 file changed, 118 insertions(+), 142 deletions(-) diff --git a/python/perspective/perspective/tests/handlers/test_starlette_handler.py b/python/perspective/perspective/tests/handlers/test_starlette_handler.py index be4206c812..05123f54d3 100644 --- a/python/perspective/perspective/tests/handlers/test_starlette_handler.py +++ b/python/perspective/perspective/tests/handlers/test_starlette_handler.py @@ -59,15 +59,12 @@ def setup_method(self): MANAGER._tables = {} MANAGER._views = {} - async def websocket_client(self): - """Connect and initialize a websocket client connection to the - Perspective starlette server. - """ - return await websocket(CLIENT, "/websocket") - @asynccontextmanager async def managed_websocket_client(self): - client = await self.websocket_client() + """Connect and manage a websocket client connection to the + Perspective starlette server. + """ + client = await websocket(CLIENT, "/websocket") try: yield client finally: @@ -80,8 +77,9 @@ async def test_starlette_handler_init_terminate(self): All test methods must import `app`, `http_client`, and `http_port`, otherwise a mysterious timeout will occur.""" - client = await self.websocket_client() - await client.terminate() + async with self.managed_websocket_client() as client: + # terminate() is called by the context manager + assert client is not None @pytest.mark.asyncio async def test_starlette_handler_table_method(self): @@ -89,37 +87,33 @@ async def test_starlette_handler_table_method(self): _table = Table(data) MANAGER.host_table(table_name, _table) - client = await self.websocket_client() - table = client.open_table(table_name) - - schema = await table.schema() - size = await table.size() + async with self.managed_websocket_client() as client: + table = client.open_table(table_name) - assert schema == { - "a": "integer", - "b": "float", - "c": "string", - "d": "datetime", - } + schema = await table.schema() + size = await table.size() - assert size == 10 + assert schema == { + "a": "integer", + "b": "float", + "c": "string", + "d": "datetime", + } - await client.terminate() + assert size == 10 @pytest.mark.asyncio async def test_starlette_handler_make_table(self): - client = await self.websocket_client() - table = await client.table(data) - size = await table.size() - - assert size == 10 + async with self.managed_websocket_client() as client: + table = await client.table(data) + size = await table.size() - table.update(data) + assert size == 10 - size2 = await table.size() - assert size2 == 20 + table.update(data) - await client.terminate() + size2 = await table.size() + assert size2 == 20 @pytest.mark.asyncio async def test_starlette_handler_table_update(self): @@ -127,18 +121,16 @@ async def test_starlette_handler_table_update(self): _table = Table(data) MANAGER.host_table(table_name, _table) - client = await self.websocket_client() - table = client.open_table(table_name) - size = await table.size() - - assert size == 10 + async with self.managed_websocket_client() as client: + table = client.open_table(table_name) + size = await table.size() - table.update(data) + assert size == 10 - size2 = await table.size() - assert size2 == 20 + table.update(data) - await client.terminate() + size2 = await table.size() + assert size2 == 20 @pytest.mark.asyncio async def test_starlette_handler_table_update_port(self, sentinel): @@ -146,34 +138,32 @@ async def test_starlette_handler_table_update_port(self, sentinel): _table = Table(data) MANAGER.host_table(table_name, _table) - client = await self.websocket_client() - table = client.open_table(table_name) - view = await table.view() - - size = await table.size() + async with self.managed_websocket_client() as client: + table = client.open_table(table_name) + view = await table.view() - assert size == 10 + size = await table.size() - for i in range(5): - await table.make_port() + assert size == 10 - port = await table.make_port() + for i in range(5): + await table.make_port() - s = sentinel(False) + port = await table.make_port() - def updater(port_id): - s.set(True) - assert port_id == port + s = sentinel(False) - view.on_update(updater) + def updater(port_id): + s.set(True) + assert port_id == port - table.update(data, port_id=port) + view.on_update(updater) - size2 = await table.size() - assert size2 == 20 - assert s.get() is True + table.update(data, port_id=port) - await client.terminate() + size2 = await table.size() + assert size2 == 20 + assert s.get() is True @pytest.mark.asyncio async def test_starlette_handler_table_update_row_delta(self, sentinel): @@ -181,31 +171,29 @@ async def test_starlette_handler_table_update_row_delta(self, sentinel): _table = Table(data) MANAGER.host_table(table_name, _table) - client = await self.websocket_client() - table = client.open_table(table_name) - view = await table.view() - - size = await table.size() + async with self.managed_websocket_client() as client: + table = client.open_table(table_name) + view = await table.view() - assert size == 10 + size = await table.size() - s = sentinel(False) + assert size == 10 - def updater(port_id, delta): - s.set(True) - assert port_id == 0 - t2 = Table(delta) - assert t2.view().to_dict() == data + s = sentinel(False) - view.on_update(updater, mode="row") + def updater(port_id, delta): + s.set(True) + assert port_id == 0 + t2 = Table(delta) + assert t2.view().to_dict() == data - table.update(data) + view.on_update(updater, mode="row") - size2 = await table.size() - assert size2 == 20 - assert s.get() is True + table.update(data) - await client.terminate() + size2 = await table.size() + assert size2 == 20 + assert s.get() is True @pytest.mark.asyncio async def test_starlette_handler_table_update_row_delta_port(self, sentinel): @@ -213,37 +201,35 @@ async def test_starlette_handler_table_update_row_delta_port(self, sentinel): _table = Table(data) MANAGER.host_table(table_name, _table) - client = await self.websocket_client() - table = client.open_table(table_name) - view = await table.view() - - size = await table.size() + async with self.managed_websocket_client() as client: + table = client.open_table(table_name) + view = await table.view() - assert size == 10 + size = await table.size() - for i in range(5): - await table.make_port() + assert size == 10 - port = await table.make_port() + for i in range(5): + await table.make_port() - s = sentinel(False) + port = await table.make_port() - def updater(port_id, delta): - s.set(True) - assert port_id == port + s = sentinel(False) - t2 = Table(delta) - assert t2.view().to_dict() == data + def updater(port_id, delta): + s.set(True) + assert port_id == port - view.on_update(updater, mode="row") + t2 = Table(delta) + assert t2.view().to_dict() == data - table.update(data, port_id=port) + view.on_update(updater, mode="row") - size2 = await table.size() - assert size2 == 20 - assert s.get() is True + table.update(data, port_id=port) - await client.terminate() + size2 = await table.size() + assert size2 == 20 + assert s.get() is True @pytest.mark.asyncio async def test_starlette_handler_table_remove(self): @@ -251,20 +237,18 @@ async def test_starlette_handler_table_remove(self): _table = Table(data, index="a") MANAGER.host_table(table_name, _table) - client = await self.websocket_client() - table = client.open_table(table_name) - size = await table.size() - - assert size == 10 + async with self.managed_websocket_client() as client: + table = client.open_table(table_name) + size = await table.size() - table.remove([i for i in range(5)]) + assert size == 10 - view = await table.view(columns=["a"]) - output = await view.to_dict() + table.remove([i for i in range(5)]) - assert output == {"a": [i for i in range(5, 10)]} + view = await table.view(columns=["a"]) + output = await view.to_dict() - await client.terminate() + assert output == {"a": [i for i in range(5, 10)]} @pytest.mark.asyncio async def test_starlette_handler_create_view(self): @@ -272,16 +256,14 @@ async def test_starlette_handler_create_view(self): _table = Table(data) MANAGER.host_table(table_name, _table) - client = await self.websocket_client() - table = client.open_table(table_name) - view = await table.view(columns=["a"]) - output = await view.to_dict() - - assert output == { - "a": [i for i in range(10)], - } + async with self.managed_websocket_client() as client: + table = client.open_table(table_name) + view = await table.view(columns=["a"]) + output = await view.to_dict() - await client.terminate() + assert output == { + "a": [i for i in range(10)], + } @pytest.mark.asyncio async def test_starlette_handler_create_view_errors(self): @@ -289,15 +271,13 @@ async def test_starlette_handler_create_view_errors(self): _table = Table(data) MANAGER.host_table(table_name, _table) - client = await self.websocket_client() - table = client.open_table(table_name) - - with pytest.raises(PerspectiveError) as exc: - await table.view(columns=["abcde"]) + async with self.managed_websocket_client() as client: + table = client.open_table(table_name) - assert str(exc.value) == "Invalid column 'abcde' found in View columns.\n" + with pytest.raises(PerspectiveError) as exc: + await table.view(columns=["abcde"]) - await client.terminate() + assert str(exc.value) == "Invalid column 'abcde' found in View columns.\n" @pytest.mark.asyncio async def test_starlette_handler_create_view_to_arrow(self): @@ -305,15 +285,13 @@ async def test_starlette_handler_create_view_to_arrow(self): _table = Table(data) MANAGER.host_table(table_name, _table) - client = await self.websocket_client() - table = client.open_table(table_name) - view = await table.view() - output = await view.to_arrow() - expected = await table.schema() - - assert Table(output).schema(as_string=True) == expected + async with self.managed_websocket_client() as client: + table = client.open_table(table_name) + view = await table.view() + output = await view.to_arrow() + expected = await table.schema() - await client.terminate() + assert Table(output).schema(as_string=True) == expected @pytest.mark.asyncio async def test_starlette_handler_create_view_to_arrow_update(self): @@ -321,19 +299,17 @@ async def test_starlette_handler_create_view_to_arrow_update(self): _table = Table(data) MANAGER.host_table(table_name, _table) - client = await self.websocket_client() - table = client.open_table(table_name) - view = await table.view() - - output = await view.to_arrow() + async with self.managed_websocket_client() as client: + table = client.open_table(table_name) + view = await table.view() - for _ in range(10): - await table.update(output) + output = await view.to_arrow() - size2 = await table.size() - assert size2 == 110 + for _ in range(10): + await table.update(output) - await client.terminate() + size2 = await table.size() + assert size2 == 110 @pytest.mark.asyncio async def test_starlette_handler_get_hosted_table_names(self):