From cbf1b419b6ee371a06bbc97c050b43f5ff915ac9 Mon Sep 17 00:00:00 2001 From: Aleksandar Acic Date: Tue, 22 Apr 2025 17:56:22 -0500 Subject: [PATCH 1/7] Add healthcheck and sessions endpoints. --- tiatoolbox/visualization/tileserver.py | 26 +++++++++++++++++++++ tiatoolbox/visualization/tileserver_api.yml | 18 ++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/tiatoolbox/visualization/tileserver.py b/tiatoolbox/visualization/tileserver.py index acce7dff6..3609216fb 100644 --- a/tiatoolbox/visualization/tileserver.py +++ b/tiatoolbox/visualization/tileserver.py @@ -165,6 +165,8 @@ def __init__( # noqa: PLR0915 self.route("/tileserver/tap_query//")(self.tap_query) self.route("/tileserver/prop_range", methods=["PUT"])(self.prop_range) self.route("/tileserver/shutdown", methods=["POST"])(self.shutdown) + self.route("/tileserver/sessions", methods=["GET"])(self.sessions) + self.route("/tileserver/healthcheck", methods=["GET"])(self.healthcheck) def _get_session_id(self: TileServer) -> str: """Get the session_id from the request. @@ -718,6 +720,30 @@ def prop_range(self: TileServer) -> str: self.renderers[session_id].score_fn = lambda x: (x - minv) / (maxv - minv) return "done" + def sessions(self: TileServer) -> Response: + """Retrieve a mapping of session keys to their corresponding slide file paths. + + Returns: + Response: A JSON response containing a mapping of session keys + and their respective slide file paths. + """ + session_paths = {} + for key, layer in self.layers.items(): + session_paths[key] = str(layer.get("slide").info.as_dict()["file_path"]) + + return jsonify(session_paths) + + def healthcheck(self: TileServer) -> Response: + """Simple health check endpoint to verify the server is running. + + Useful for load balancers or uptime monitoring tools to check + if the service is operational. + + Returns: + Response: A JSON response with status "OK" and HTTP status code 200. + """ + return jsonify({"status": "OK"}) + @staticmethod def shutdown() -> None: """Shutdown the tileserver.""" diff --git a/tiatoolbox/visualization/tileserver_api.yml b/tiatoolbox/visualization/tileserver_api.yml index 3f1081c14..bea5826c1 100644 --- a/tiatoolbox/visualization/tileserver_api.yml +++ b/tiatoolbox/visualization/tileserver_api.yml @@ -395,6 +395,24 @@ paths: '200': description: Successful response + /tileserver/sessions: + get: + summary: Returns all session/path mappings + responses: + '200': + description: A jsonified session/path mappings + content: + application/json: + + /tileserver/healthcheck: + get: + summary: Returns Ok if server is online + responses: + '200': + description: A json with status field + content: + application/json: + /tileserver/tap_query/{x}/{y}: get: summary: Query annotation at a point From 660914f58195aa90f09ce771e46e9c3b1c5d9f5a Mon Sep 17 00:00:00 2001 From: Aleksandar Acic Date: Wed, 23 Apr 2025 11:04:18 -0500 Subject: [PATCH 2/7] Add tests for healthcheck and sessions endpoints. --- tests/test_tileserver.py | 41 ++++++++++++++++++++++++++ tiatoolbox/visualization/tileserver.py | 7 +++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/tests/test_tileserver.py b/tests/test_tileserver.py index 8a8d784af..c4632b403 100644 --- a/tests/test_tileserver.py +++ b/tests/test_tileserver.py @@ -734,3 +734,44 @@ def test_prop_range(app: TileServer) -> None: assert response.status_code == 200 # should be back to no scaling assert layer.renderer.score_fn(0.5) == 0.5 + + +def test_healthcheck(empty_app: TileServer) -> None: + """Test the /tileserver/healthcheck endpoint.""" + with empty_app.test_client() as client: + response = client.get("/tileserver/healthcheck") + assert response.status_code == 200 + assert response.content_type == "application/json" + assert response.get_json() == {"status": "OK"} + + +def test_sessions_no_slide_loaded(empty_app: TileServer) -> None: + """Test /tileserver/sessions when no slides are loaded.""" + with empty_app.test_client() as client: + setup_app(client) + response = client.get("/tileserver/sessions") + assert response.status_code == 200 + assert response.is_json + assert response.get_json() == {} + + +def test_sessions_one_slide_loaded( + empty_app: TileServer, remote_sample: Callable +) -> None: + """Test /tileserver/sessions after loading one slide.""" + with empty_app.test_client() as client: + setup_app(client) + slide_path = safe_str(remote_sample("svs-1-small")) + + response = client.put( + "/tileserver/slide", + data={"slide_path": slide_path}, + ) + assert response.status_code == 200 + + response = client.get("/tileserver/sessions") + assert response.status_code == 200 + sessions = response.get_json() + + assert isinstance(sessions, dict) + assert len(sessions) == 1 diff --git a/tiatoolbox/visualization/tileserver.py b/tiatoolbox/visualization/tileserver.py index 3609216fb..93bab43a4 100644 --- a/tiatoolbox/visualization/tileserver.py +++ b/tiatoolbox/visualization/tileserver.py @@ -725,12 +725,13 @@ def sessions(self: TileServer) -> Response: Returns: Response: A JSON response containing a mapping of session keys - and their respective slide file paths. + and their respective slide file paths. """ session_paths = {} for key, layer in self.layers.items(): - session_paths[key] = str(layer.get("slide").info.as_dict()["file_path"]) - + slide = layer.get("slide") + if slide is not None: + session_paths[key] = str(slide.info.as_dict().get("file_path", "")) return jsonify(session_paths) def healthcheck(self: TileServer) -> Response: From fa6e0b9d30892706d51ab1de17660a86b46864b0 Mon Sep 17 00:00:00 2001 From: Aleksandar Acic Date: Wed, 23 Apr 2025 11:22:04 -0500 Subject: [PATCH 3/7] Make healthcheck static. --- tiatoolbox/visualization/tileserver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tiatoolbox/visualization/tileserver.py b/tiatoolbox/visualization/tileserver.py index 93bab43a4..8aef1dad2 100644 --- a/tiatoolbox/visualization/tileserver.py +++ b/tiatoolbox/visualization/tileserver.py @@ -734,7 +734,8 @@ def sessions(self: TileServer) -> Response: session_paths[key] = str(slide.info.as_dict().get("file_path", "")) return jsonify(session_paths) - def healthcheck(self: TileServer) -> Response: + @staticmethod + def healthcheck() -> Response: """Simple health check endpoint to verify the server is running. Useful for load balancers or uptime monitoring tools to check From 6d1fdfb57eda09a41a3a11a16e36e0396e5c4f0e Mon Sep 17 00:00:00 2001 From: Aleksandar Acic <32873451+aacic@users.noreply.github.com> Date: Mon, 28 Apr 2025 09:22:24 -0500 Subject: [PATCH 4/7] Update tiatoolbox/visualization/tileserver.py Fix docs. Co-authored-by: Shan E Ahmed Raza <13048456+shaneahmed@users.noreply.github.com> --- tiatoolbox/visualization/tileserver.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tiatoolbox/visualization/tileserver.py b/tiatoolbox/visualization/tileserver.py index 8aef1dad2..6a5f85de4 100644 --- a/tiatoolbox/visualization/tileserver.py +++ b/tiatoolbox/visualization/tileserver.py @@ -724,8 +724,10 @@ def sessions(self: TileServer) -> Response: """Retrieve a mapping of session keys to their corresponding slide file paths. Returns: - Response: A JSON response containing a mapping of session keys - and their respective slide file paths. + Response: + A JSON response containing a mapping of session keys + and their respective slide file paths. + """ session_paths = {} for key, layer in self.layers.items(): From 367ea49802b2cef57acaebacc953b7967c85e5d6 Mon Sep 17 00:00:00 2001 From: Aleksandar Acic <32873451+aacic@users.noreply.github.com> Date: Mon, 28 Apr 2025 09:22:42 -0500 Subject: [PATCH 5/7] Update tiatoolbox/visualization/tileserver.py Fix docs. Co-authored-by: Shan E Ahmed Raza <13048456+shaneahmed@users.noreply.github.com> --- tiatoolbox/visualization/tileserver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tiatoolbox/visualization/tileserver.py b/tiatoolbox/visualization/tileserver.py index 6a5f85de4..e024f1213 100644 --- a/tiatoolbox/visualization/tileserver.py +++ b/tiatoolbox/visualization/tileserver.py @@ -744,7 +744,9 @@ def healthcheck() -> Response: if the service is operational. Returns: - Response: A JSON response with status "OK" and HTTP status code 200. + Response: + A JSON response with status "OK" and HTTP status code 200. + """ return jsonify({"status": "OK"}) From a92295005740a8240cebea750ec7c8c774d322c2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:22:51 +0000 Subject: [PATCH 6/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tiatoolbox/visualization/tileserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tiatoolbox/visualization/tileserver.py b/tiatoolbox/visualization/tileserver.py index e024f1213..387ae24ab 100644 --- a/tiatoolbox/visualization/tileserver.py +++ b/tiatoolbox/visualization/tileserver.py @@ -724,10 +724,10 @@ def sessions(self: TileServer) -> Response: """Retrieve a mapping of session keys to their corresponding slide file paths. Returns: - Response: + Response: A JSON response containing a mapping of session keys and their respective slide file paths. - + """ session_paths = {} for key, layer in self.layers.items(): From 1fbf80ed59cb782162a6f13acc8fbb1602bd9b07 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:23:36 +0000 Subject: [PATCH 7/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tiatoolbox/visualization/tileserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tiatoolbox/visualization/tileserver.py b/tiatoolbox/visualization/tileserver.py index 387ae24ab..1e87370c6 100644 --- a/tiatoolbox/visualization/tileserver.py +++ b/tiatoolbox/visualization/tileserver.py @@ -744,9 +744,9 @@ def healthcheck() -> Response: if the service is operational. Returns: - Response: + Response: A JSON response with status "OK" and HTTP status code 200. - + """ return jsonify({"status": "OK"})