From a0b20f88131ae0a9941077891fe00bff493eb703 Mon Sep 17 00:00:00 2001 From: WinPlay02 Date: Thu, 18 Jan 2024 02:36:05 +0100 Subject: [PATCH 01/12] feat: support placeholder queries that only request a subset of data --- src/safeds_runner/server/messages.py | 96 +++++++++++++++++-- src/safeds_runner/server/server.py | 2 +- .../server/test_websocket_mock.py | 52 +++++++--- 3 files changed, 128 insertions(+), 22 deletions(-) diff --git a/src/safeds_runner/server/messages.py b/src/safeds_runner/server/messages.py index 0d037de..aa7a596 100644 --- a/src/safeds_runner/server/messages.py +++ b/src/safeds_runner/server/messages.py @@ -7,6 +7,8 @@ from dataclasses import dataclass from typing import Any +import safeds.data.tabular.containers + message_type_program = "program" message_type_placeholder_query = "placeholder_query" message_type_placeholder_type = "placeholder_type" @@ -169,6 +171,57 @@ def to_dict(self) -> dict[str, Any]: return dataclasses.asdict(self) # pragma: no cover +@dataclass(frozen=True) +class MessageQueryInformation: + """ + Information used to query a placeholder with optional window bounds. Only complex types like tables are affected by window bounds. + + Parameters + ---------- + name : str + Placeholder name that is queried + window_begin : int | None + Index of the first entry that should be sent. Should be present if a windowed query is required. + window_size : int | None + Max. amount of entries that should be sent. Should be present if a windowed query is required. + """ + name: str + window_begin: int | None + window_size: int | None + + @staticmethod + def from_dict(d: dict[str, Any]) -> MessageQueryInformation: + """ + Create a new MessageQueryInformation object from a dictionary. + + Parameters + ---------- + d : dict[str, Any] + Dictionary which should contain all needed fields. + + Returns + ------- + MessageQueryInformation + Dataclass which contains information copied from the provided dictionary. + """ + if "window_begin" not in d: + d["window_begin"] = None + if "window_size" not in d: + d["window_size"] = None + return MessageQueryInformation(**d) + + def to_dict(self) -> dict[str, Any]: + """ + Convert this dataclass to a dictionary. + + Returns + ------- + dict[str, Any] + Dictionary containing all the fields which are part of this dataclass. + """ + return dataclasses.asdict(self) # pragma: no cover + + def create_placeholder_description(name: str, type_: str) -> dict[str, str]: """ Create the message data of a placeholder description message containing only name and type. @@ -188,14 +241,16 @@ def create_placeholder_description(name: str, type_: str) -> dict[str, str]: return {"name": name, "type": type_} -def create_placeholder_value(name: str, type_: str, value: Any) -> dict[str, Any]: +def create_placeholder_value(placeholder_query: MessageQueryInformation, type_: str, value: Any) -> dict[str, Any]: """ Create the message data of a placeholder value message containing name, type and the actual value. + If the query only requests a subset of the data and the placeholder type supports this, + the response will contain only a subset and the information about the subset. Parameters ---------- - name : str - Name of the placeholder. + placeholder_query : MessageQueryInformation + Query of the placeholder. type_ : str Type of the placeholder. value : Any @@ -206,7 +261,22 @@ def create_placeholder_value(name: str, type_: str, value: Any) -> dict[str, Any dict[str, str] Message data of "placeholder_value" messages. """ - return {"name": name, "type": type_, "value": value} + message = {"name": placeholder_query.name, "type": type_} + # Start Index >= 0 + start_index = max(placeholder_query.window_begin if placeholder_query.window_begin is not None else 0, 0) + # End Index >= Start Index + end_index = (start_index + max(placeholder_query.window_size, 0)) if placeholder_query.window_size is not None else None + if isinstance(value, safeds.data.tabular.containers.Table) and (placeholder_query.window_begin is not None or placeholder_query.window_size is not None): + max_index = value.number_of_rows + # End Index <= Number Of Rows + end_index = min(end_index, value.number_of_rows) if end_index is not None else None + value = value.slice_rows(start=start_index, end=end_index) + message["windowed"] = True + message["window_begin"] = start_index + message["window_size"] = value.number_of_rows + message["window_max"] = max_index + message["value"] = value + return message def create_runtime_error_description(message: str, backtrace: list[dict[str, Any]]) -> dict[str, Any]: @@ -313,7 +383,7 @@ def validate_program_message_data(message_data: dict[str, Any] | str) -> tuple[M return MessageDataProgram.from_dict(message_data), None -def validate_placeholder_query_message_data(message_data: dict[str, Any] | str) -> tuple[str | None, str | None]: +def validate_placeholder_query_message_data(message_data: dict[str, Any] | str) -> tuple[MessageQueryInformation | None, str | None]: """ Validate the message data of a placeholder query message. @@ -324,9 +394,15 @@ def validate_placeholder_query_message_data(message_data: dict[str, Any] | str) Returns ------- - tuple[str | None, str | None] - A tuple containing either a validated message data as a string or an error message. + tuple[MessageQueryInformation | None, str | None] + A tuple containing either the validated message data or an error message. """ - if not isinstance(message_data, str): - return None, "Message data is not a string" - return message_data, None + if not isinstance(message_data, dict): + return None, "Message data is not a JSON object" + if "name" not in message_data: + return None, "No 'name' parameter given" + if "window_begin" in message_data and not isinstance(message_data["window_begin"], int): + return None, "Invalid 'window_begin' parameter given" + if "window_size" in message_data and not isinstance(message_data["window_size"], int): + return None, "Invalid 'window_size' parameter given" + return MessageQueryInformation.from_dict(message_data), None diff --git a/src/safeds_runner/server/server.py b/src/safeds_runner/server/server.py index ec2b857..47afdc1 100644 --- a/src/safeds_runner/server/server.py +++ b/src/safeds_runner/server/server.py @@ -152,7 +152,7 @@ def _ws_main(ws: simple_websocket.Server, pipeline_manager: PipelineManager) -> return placeholder_type, placeholder_value = pipeline_manager.get_placeholder( received_object.id, - placeholder_query_data, + placeholder_query_data.name, ) # send back a value message if placeholder_type is not None: diff --git a/tests/safeds_runner/server/test_websocket_mock.py b/tests/safeds_runner/server/test_websocket_mock.py index 49cd79b..29ef11e 100644 --- a/tests/safeds_runner/server/test_websocket_mock.py +++ b/tests/safeds_runner/server/test_websocket_mock.py @@ -5,10 +5,15 @@ import sys import threading import time +import typing import pytest +from safeds.data.tabular.containers import Table + import safeds_runner.server.main import simple_websocket + +from safeds_runner.server.json_encoder import SafeDsEncoder from safeds_runner.server.messages import ( Message, create_placeholder_description, @@ -17,7 +22,7 @@ message_type_placeholder_type, message_type_placeholder_value, message_type_runtime_error, - message_type_runtime_progress, + message_type_runtime_progress, MessageQueryInformation, ) from safeds_runner.server.pipeline_manager import PipelineManager from safeds_runner.server.server import SafeDsServer @@ -69,7 +74,10 @@ def get_next_received_message(self) -> str: (json.dumps({"type": {"program": "2"}, "id": "123", "data": "a"}), "Invalid Message: invalid type"), (json.dumps({"type": "c", "id": {"": "1233"}, "data": "a"}), "Invalid Message: invalid id"), (json.dumps({"type": "program", "id": "1234", "data": "a"}), "Message data is not a JSON object"), - (json.dumps({"type": "placeholder_query", "id": "123", "data": {"a": "v"}}), "Message data is not a string"), + (json.dumps({"type": "placeholder_query", "id": "123", "data": "abc"}), "Message data is not a JSON object"), + (json.dumps({"type": "placeholder_query", "id": "123", "data": {"a": "v"}}), "No 'name' parameter given"), + (json.dumps({"type": "placeholder_query", "id": "123", "data": {"name": "v", "window_begin": "a"}}), "Invalid 'window_begin' parameter given"), + (json.dumps({"type": "placeholder_query", "id": "123", "data": {"name": "v", "window_size": "a"}}), "Invalid 'window_size' parameter given"), ( json.dumps({ "type": "program", @@ -161,7 +169,10 @@ def get_next_received_message(self) -> str: "any_invalid_type", "any_invalid_id", "program_invalid_data", - "placeholder_query_invalid_data", + "placeholder_query_invalid_data1", + "placeholder_query_invalid_data2", + "placeholder_query_invalid_data3", + "placeholder_query_invalid_data4", "program_no_code", "program_no_main", "program_invalid_main1", @@ -273,11 +284,11 @@ def test_should_execute_pipeline_return_exception( 2, [ # Query Placeholder - json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": "value1"}), + json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": {"name": "value1"}}), # Query not displayable Placeholder - json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": "obj"}), + json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": {"name": "obj"}}), # Query invalid placeholder - json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": "value2"}), + json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": {"name": "value2"}}), ], [ # Validate Placeholder Information @@ -286,15 +297,15 @@ def test_should_execute_pipeline_return_exception( # Validate Progress Information Message(message_type_runtime_progress, "abcdefg", create_runtime_progress_done()), # Query Result Valid - Message(message_type_placeholder_value, "abcdefg", create_placeholder_value("value1", "Int", 1)), + Message(message_type_placeholder_value, "abcdefg", create_placeholder_value(MessageQueryInformation("value1", None, None), "Int", 1)), # Query Result not displayable Message( message_type_placeholder_value, "abcdefg", - create_placeholder_value("obj", "object", ""), + create_placeholder_value(MessageQueryInformation("obj", None, None), "object", ""), ), # Query Result Invalid - Message(message_type_placeholder_value, "abcdefg", create_placeholder_value("value2", "", "")), + Message(message_type_placeholder_value, "abcdefg", create_placeholder_value(MessageQueryInformation("value2", None, None), "", "")), ], ), ], @@ -370,12 +381,12 @@ def test_should_execute_pipeline_return_valid_placeholder( # Query Result Invalid (no pipeline exists) [ json.dumps({"type": "invalid_message_type", "id": "unknown-code-id-never-generated", "data": ""}), - json.dumps({"type": "placeholder_query", "id": "unknown-code-id-never-generated", "data": "v"}), + json.dumps({"type": "placeholder_query", "id": "unknown-code-id-never-generated", "data": {"name": "v"}}), ], Message( message_type_placeholder_value, "unknown-code-id-never-generated", - create_placeholder_value("v", "", ""), + create_placeholder_value(MessageQueryInformation("v", None, None), "", ""), ), ), ], @@ -463,3 +474,22 @@ def helper_should_accept_at_least_2_parallel_connections_in_subprocess_server( sys.stderr.write = lambda value: pipe.send(value) # type: ignore[method-assign, assignment] sys.stdout.write = lambda value: pipe.send(value) # type: ignore[method-assign, assignment] safeds_runner.server.main.start_server(port) + + +@pytest.mark.parametrize( + argnames="query,type_,value,result", + argvalues=[ + (MessageQueryInformation("name", None, None), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "value": {"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}}')), + (MessageQueryInformation("name", 0, 1), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 1, "window_max": 7, "value": {"a": [1], "b": [3]}}')), + (MessageQueryInformation("name", 4, 3), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max": 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}')), + (MessageQueryInformation("name", 0, 0), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 0, "window_max": 7, "value": {"a": [], "b": []}}')), + (MessageQueryInformation("name", 4, 30), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max": 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}')), + (MessageQueryInformation("name", 4, None), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max": 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}')), + (MessageQueryInformation("name", 0, -5), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 0, "window_max": 7, "value": {"a": [], "b": []}}')), + (MessageQueryInformation("name", -5, None), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 7, "window_max": 7, "value": {"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}}')), + ], + ids=["query_nowindow", "query_windowed_0_1", "query_windowed_4_3", "query_windowed_empty", "query_windowed_size_too_large", "query_windowed_4_max", "query_windowed_negative_size", "query_windowed_negative_offset"], +) +def test_windowed_placeholder(query: MessageQueryInformation, type_: str, value: typing.Any, result: str) -> None: + message = create_placeholder_value(query, type_, value) + assert json.dumps(message, cls=SafeDsEncoder) == result From 66439491f5b4c1b05139e839cb1381cc96ceba0e Mon Sep 17 00:00:00 2001 From: WinPlay02 Date: Thu, 18 Jan 2024 02:45:23 +0100 Subject: [PATCH 02/12] style: fix linter warnings --- src/safeds_runner/server/messages.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/safeds_runner/server/messages.py b/src/safeds_runner/server/messages.py index aa7a596..1ec1d7c 100644 --- a/src/safeds_runner/server/messages.py +++ b/src/safeds_runner/server/messages.py @@ -244,6 +244,7 @@ def create_placeholder_description(name: str, type_: str) -> dict[str, str]: def create_placeholder_value(placeholder_query: MessageQueryInformation, type_: str, value: Any) -> dict[str, Any]: """ Create the message data of a placeholder value message containing name, type and the actual value. + If the query only requests a subset of the data and the placeholder type supports this, the response will contain only a subset and the information about the subset. @@ -261,7 +262,7 @@ def create_placeholder_value(placeholder_query: MessageQueryInformation, type_: dict[str, str] Message data of "placeholder_value" messages. """ - message = {"name": placeholder_query.name, "type": type_} + message: dict[str, Any] = {"name": placeholder_query.name, "type": type_} # Start Index >= 0 start_index = max(placeholder_query.window_begin if placeholder_query.window_begin is not None else 0, 0) # End Index >= Start Index From d176c9106c57978067d18b5387461df112203dd7 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Thu, 18 Jan 2024 01:47:09 +0000 Subject: [PATCH 03/12] style: apply automated linter fixes --- src/safeds_runner/server/messages.py | 13 +- .../server/test_websocket_mock.py | 121 +++++++++++++++--- 2 files changed, 113 insertions(+), 21 deletions(-) diff --git a/src/safeds_runner/server/messages.py b/src/safeds_runner/server/messages.py index 1ec1d7c..a294211 100644 --- a/src/safeds_runner/server/messages.py +++ b/src/safeds_runner/server/messages.py @@ -185,6 +185,7 @@ class MessageQueryInformation: window_size : int | None Max. amount of entries that should be sent. Should be present if a windowed query is required. """ + name: str window_begin: int | None window_size: int | None @@ -266,8 +267,12 @@ def create_placeholder_value(placeholder_query: MessageQueryInformation, type_: # Start Index >= 0 start_index = max(placeholder_query.window_begin if placeholder_query.window_begin is not None else 0, 0) # End Index >= Start Index - end_index = (start_index + max(placeholder_query.window_size, 0)) if placeholder_query.window_size is not None else None - if isinstance(value, safeds.data.tabular.containers.Table) and (placeholder_query.window_begin is not None or placeholder_query.window_size is not None): + end_index = ( + (start_index + max(placeholder_query.window_size, 0)) if placeholder_query.window_size is not None else None + ) + if isinstance(value, safeds.data.tabular.containers.Table) and ( + placeholder_query.window_begin is not None or placeholder_query.window_size is not None + ): max_index = value.number_of_rows # End Index <= Number Of Rows end_index = min(end_index, value.number_of_rows) if end_index is not None else None @@ -384,7 +389,9 @@ def validate_program_message_data(message_data: dict[str, Any] | str) -> tuple[M return MessageDataProgram.from_dict(message_data), None -def validate_placeholder_query_message_data(message_data: dict[str, Any] | str) -> tuple[MessageQueryInformation | None, str | None]: +def validate_placeholder_query_message_data( + message_data: dict[str, Any] | str, +) -> tuple[MessageQueryInformation | None, str | None]: """ Validate the message data of a placeholder query message. diff --git a/tests/safeds_runner/server/test_websocket_mock.py b/tests/safeds_runner/server/test_websocket_mock.py index 29ef11e..9ade276 100644 --- a/tests/safeds_runner/server/test_websocket_mock.py +++ b/tests/safeds_runner/server/test_websocket_mock.py @@ -8,21 +8,20 @@ import typing import pytest -from safeds.data.tabular.containers import Table - import safeds_runner.server.main import simple_websocket - +from safeds.data.tabular.containers import Table from safeds_runner.server.json_encoder import SafeDsEncoder from safeds_runner.server.messages import ( Message, + MessageQueryInformation, create_placeholder_description, create_placeholder_value, create_runtime_progress_done, message_type_placeholder_type, message_type_placeholder_value, message_type_runtime_error, - message_type_runtime_progress, MessageQueryInformation, + message_type_runtime_progress, ) from safeds_runner.server.pipeline_manager import PipelineManager from safeds_runner.server.server import SafeDsServer @@ -76,8 +75,14 @@ def get_next_received_message(self) -> str: (json.dumps({"type": "program", "id": "1234", "data": "a"}), "Message data is not a JSON object"), (json.dumps({"type": "placeholder_query", "id": "123", "data": "abc"}), "Message data is not a JSON object"), (json.dumps({"type": "placeholder_query", "id": "123", "data": {"a": "v"}}), "No 'name' parameter given"), - (json.dumps({"type": "placeholder_query", "id": "123", "data": {"name": "v", "window_begin": "a"}}), "Invalid 'window_begin' parameter given"), - (json.dumps({"type": "placeholder_query", "id": "123", "data": {"name": "v", "window_size": "a"}}), "Invalid 'window_size' parameter given"), + ( + json.dumps({"type": "placeholder_query", "id": "123", "data": {"name": "v", "window_begin": "a"}}), + "Invalid 'window_begin' parameter given", + ), + ( + json.dumps({"type": "placeholder_query", "id": "123", "data": {"name": "v", "window_size": "a"}}), + "Invalid 'window_size' parameter given", + ), ( json.dumps({ "type": "program", @@ -297,7 +302,11 @@ def test_should_execute_pipeline_return_exception( # Validate Progress Information Message(message_type_runtime_progress, "abcdefg", create_runtime_progress_done()), # Query Result Valid - Message(message_type_placeholder_value, "abcdefg", create_placeholder_value(MessageQueryInformation("value1", None, None), "Int", 1)), + Message( + message_type_placeholder_value, + "abcdefg", + create_placeholder_value(MessageQueryInformation("value1", None, None), "Int", 1), + ), # Query Result not displayable Message( message_type_placeholder_value, @@ -305,7 +314,11 @@ def test_should_execute_pipeline_return_exception( create_placeholder_value(MessageQueryInformation("obj", None, None), "object", ""), ), # Query Result Invalid - Message(message_type_placeholder_value, "abcdefg", create_placeholder_value(MessageQueryInformation("value2", None, None), "", "")), + Message( + message_type_placeholder_value, + "abcdefg", + create_placeholder_value(MessageQueryInformation("value2", None, None), "", ""), + ), ], ), ], @@ -381,7 +394,9 @@ def test_should_execute_pipeline_return_valid_placeholder( # Query Result Invalid (no pipeline exists) [ json.dumps({"type": "invalid_message_type", "id": "unknown-code-id-never-generated", "data": ""}), - json.dumps({"type": "placeholder_query", "id": "unknown-code-id-never-generated", "data": {"name": "v"}}), + json.dumps({ + "type": "placeholder_query", "id": "unknown-code-id-never-generated", "data": {"name": "v"}, + }), ], Message( message_type_placeholder_value, @@ -479,16 +494,86 @@ def helper_should_accept_at_least_2_parallel_connections_in_subprocess_server( @pytest.mark.parametrize( argnames="query,type_,value,result", argvalues=[ - (MessageQueryInformation("name", None, None), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "value": {"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}}')), - (MessageQueryInformation("name", 0, 1), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 1, "window_max": 7, "value": {"a": [1], "b": [3]}}')), - (MessageQueryInformation("name", 4, 3), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max": 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}')), - (MessageQueryInformation("name", 0, 0), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 0, "window_max": 7, "value": {"a": [], "b": []}}')), - (MessageQueryInformation("name", 4, 30), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max": 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}')), - (MessageQueryInformation("name", 4, None), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max": 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}')), - (MessageQueryInformation("name", 0, -5), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 0, "window_max": 7, "value": {"a": [], "b": []}}')), - (MessageQueryInformation("name", -5, None), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ('{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 7, "window_max": 7, "value": {"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}}')), + ( + MessageQueryInformation("name", None, None), + "Table", + Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), + '{"name": "name", "type": "Table", "value": {"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}}', + ), + ( + MessageQueryInformation("name", 0, 1), + "Table", + Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), + ( + '{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 1, "window_max":' + ' 7, "value": {"a": [1], "b": [3]}}' + ), + ), + ( + MessageQueryInformation("name", 4, 3), + "Table", + Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), + ( + '{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max":' + ' 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' + ), + ), + ( + MessageQueryInformation("name", 0, 0), + "Table", + Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), + ( + '{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 0, "window_max":' + ' 7, "value": {"a": [], "b": []}}' + ), + ), + ( + MessageQueryInformation("name", 4, 30), + "Table", + Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), + ( + '{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max":' + ' 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' + ), + ), + ( + MessageQueryInformation("name", 4, None), + "Table", + Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), + ( + '{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max":' + ' 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' + ), + ), + ( + MessageQueryInformation("name", 0, -5), + "Table", + Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), + ( + '{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 0, "window_max":' + ' 7, "value": {"a": [], "b": []}}' + ), + ), + ( + MessageQueryInformation("name", -5, None), + "Table", + Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), + ( + '{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 7, "window_max":' + ' 7, "value": {"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}}' + ), + ), + ], + ids=[ + "query_nowindow", + "query_windowed_0_1", + "query_windowed_4_3", + "query_windowed_empty", + "query_windowed_size_too_large", + "query_windowed_4_max", + "query_windowed_negative_size", + "query_windowed_negative_offset", ], - ids=["query_nowindow", "query_windowed_0_1", "query_windowed_4_3", "query_windowed_empty", "query_windowed_size_too_large", "query_windowed_4_max", "query_windowed_negative_size", "query_windowed_negative_offset"], ) def test_windowed_placeholder(query: MessageQueryInformation, type_: str, value: typing.Any, result: str) -> None: message = create_placeholder_value(query, type_, value) From fd126f6a7b95b832c31511db26e687491f0d119a Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Thu, 18 Jan 2024 01:48:37 +0000 Subject: [PATCH 04/12] style: apply automated linter fixes --- tests/safeds_runner/server/test_websocket_mock.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/safeds_runner/server/test_websocket_mock.py b/tests/safeds_runner/server/test_websocket_mock.py index 9ade276..cb08574 100644 --- a/tests/safeds_runner/server/test_websocket_mock.py +++ b/tests/safeds_runner/server/test_websocket_mock.py @@ -395,7 +395,9 @@ def test_should_execute_pipeline_return_valid_placeholder( [ json.dumps({"type": "invalid_message_type", "id": "unknown-code-id-never-generated", "data": ""}), json.dumps({ - "type": "placeholder_query", "id": "unknown-code-id-never-generated", "data": {"name": "v"}, + "type": "placeholder_query", + "id": "unknown-code-id-never-generated", + "data": {"name": "v"}, }), ], Message( From bf1736f846782b3d8d602beed9e7888d4534e3b9 Mon Sep 17 00:00:00 2001 From: WinPlay02 Date: Thu, 18 Jan 2024 03:09:20 +0100 Subject: [PATCH 05/12] fix: reappearing warning before monkey-patching because of import from safeds-library --- src/safeds_runner/server/messages.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/safeds_runner/server/messages.py b/src/safeds_runner/server/messages.py index 1ec1d7c..7b61ac0 100644 --- a/src/safeds_runner/server/messages.py +++ b/src/safeds_runner/server/messages.py @@ -7,8 +7,6 @@ from dataclasses import dataclass from typing import Any -import safeds.data.tabular.containers - message_type_program = "program" message_type_placeholder_query = "placeholder_query" message_type_placeholder_type = "placeholder_type" @@ -262,6 +260,7 @@ def create_placeholder_value(placeholder_query: MessageQueryInformation, type_: dict[str, str] Message data of "placeholder_value" messages. """ + import safeds.data.tabular.containers message: dict[str, Any] = {"name": placeholder_query.name, "type": type_} # Start Index >= 0 start_index = max(placeholder_query.window_begin if placeholder_query.window_begin is not None else 0, 0) From 1be30bf9d942346e05e8dce536f444a5eedc3b7f Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Thu, 18 Jan 2024 02:11:23 +0000 Subject: [PATCH 06/12] style: apply automated linter fixes --- src/safeds_runner/server/messages.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/safeds_runner/server/messages.py b/src/safeds_runner/server/messages.py index 122a13b..5365709 100644 --- a/src/safeds_runner/server/messages.py +++ b/src/safeds_runner/server/messages.py @@ -262,6 +262,7 @@ def create_placeholder_value(placeholder_query: MessageQueryInformation, type_: Message data of "placeholder_value" messages. """ import safeds.data.tabular.containers + message: dict[str, Any] = {"name": placeholder_query.name, "type": type_} # Start Index >= 0 start_index = max(placeholder_query.window_begin if placeholder_query.window_begin is not None else 0, 0) From 5edc4f297b3e4262f582fcae8d855042fd5cc124 Mon Sep 17 00:00:00 2001 From: WinPlay02 Date: Thu, 18 Jan 2024 09:39:11 +0100 Subject: [PATCH 07/12] style: changed fields in MessageQueryInformation to be None by default --- src/safeds_runner/server/messages.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/safeds_runner/server/messages.py b/src/safeds_runner/server/messages.py index 122a13b..f51478a 100644 --- a/src/safeds_runner/server/messages.py +++ b/src/safeds_runner/server/messages.py @@ -185,8 +185,8 @@ class MessageQueryInformation: """ name: str - window_begin: int | None - window_size: int | None + window_begin: int | None = None + window_size: int | None = None @staticmethod def from_dict(d: dict[str, Any]) -> MessageQueryInformation: @@ -203,10 +203,6 @@ def from_dict(d: dict[str, Any]) -> MessageQueryInformation: MessageQueryInformation Dataclass which contains information copied from the provided dictionary. """ - if "window_begin" not in d: - d["window_begin"] = None - if "window_size" not in d: - d["window_size"] = None return MessageQueryInformation(**d) def to_dict(self) -> dict[str, Any]: From 2465e75fb1e5d8993142aded740b984816499fa4 Mon Sep 17 00:00:00 2001 From: WinPlay02 Date: Tue, 23 Jan 2024 16:31:47 +0100 Subject: [PATCH 08/12] misc: put windowing information in their own structs for request and response --- src/safeds_runner/server/messages.py | 77 ++++++++++++++----- .../server/test_websocket_mock.py | 63 +++++++-------- 2 files changed, 86 insertions(+), 54 deletions(-) diff --git a/src/safeds_runner/server/messages.py b/src/safeds_runner/server/messages.py index d60aac4..b41d5e6 100644 --- a/src/safeds_runner/server/messages.py +++ b/src/safeds_runner/server/messages.py @@ -169,6 +169,48 @@ def to_dict(self) -> dict[str, Any]: return dataclasses.asdict(self) # pragma: no cover +@dataclass(frozen=True) +class QueryWindow: + """ + Parameters + ---------- + begin : int | None + Index of the first entry that should be sent. May be present if a windowed query is required. + size : int | None + Max. amount of entries that should be sent. May be present if a windowed query is required. + """ + begin: int | None = None + size: int | None = None + + @staticmethod + def from_dict(d: dict[str, Any]) -> QueryWindow: + """ + Create a new QueryWindow object from a dictionary. + + Parameters + ---------- + d : dict[str, Any] + Dictionary which should contain all needed fields. + + Returns + ------- + QueryWindow + Dataclass which contains information copied from the provided dictionary. + """ + return QueryWindow(**d) + + def to_dict(self) -> dict[str, Any]: + """ + Convert this dataclass to a dictionary. + + Returns + ------- + dict[str, Any] + Dictionary containing all the fields which are part of this dataclass. + """ + return dataclasses.asdict(self) # pragma: no cover + + @dataclass(frozen=True) class MessageQueryInformation: """ @@ -177,16 +219,13 @@ class MessageQueryInformation: Parameters ---------- name : str - Placeholder name that is queried - window_begin : int | None - Index of the first entry that should be sent. Should be present if a windowed query is required. - window_size : int | None - Max. amount of entries that should be sent. Should be present if a windowed query is required. + Placeholder name that is queried. + window : QueryWindow + Window bounds for requesting only a subset of the available data. """ name: str - window_begin: int | None = None - window_size: int | None = None + window: QueryWindow = QueryWindow() @staticmethod def from_dict(d: dict[str, Any]) -> MessageQueryInformation: @@ -203,7 +242,7 @@ def from_dict(d: dict[str, Any]) -> MessageQueryInformation: MessageQueryInformation Dataclass which contains information copied from the provided dictionary. """ - return MessageQueryInformation(**d) + return MessageQueryInformation(name=d["name"], window=QueryWindow.from_dict(d["window"])) def to_dict(self) -> dict[str, Any]: """ @@ -261,22 +300,20 @@ def create_placeholder_value(placeholder_query: MessageQueryInformation, type_: message: dict[str, Any] = {"name": placeholder_query.name, "type": type_} # Start Index >= 0 - start_index = max(placeholder_query.window_begin if placeholder_query.window_begin is not None else 0, 0) + start_index = max(placeholder_query.window.begin if placeholder_query.window.begin is not None else 0, 0) # End Index >= Start Index end_index = ( - (start_index + max(placeholder_query.window_size, 0)) if placeholder_query.window_size is not None else None + (start_index + max(placeholder_query.window.size, 0)) if placeholder_query.window.size is not None else None ) if isinstance(value, safeds.data.tabular.containers.Table) and ( - placeholder_query.window_begin is not None or placeholder_query.window_size is not None + placeholder_query.window.begin is not None or placeholder_query.window.size is not None ): max_index = value.number_of_rows # End Index <= Number Of Rows end_index = min(end_index, value.number_of_rows) if end_index is not None else None value = value.slice_rows(start=start_index, end=end_index) - message["windowed"] = True - message["window_begin"] = start_index - message["window_size"] = value.number_of_rows - message["window_max"] = max_index + window_information: dict[str, int] = {"begin": start_index, "size": value.number_of_rows, "max": max_index} + message["window"] = window_information message["value"] = value return message @@ -405,8 +442,10 @@ def validate_placeholder_query_message_data( return None, "Message data is not a JSON object" if "name" not in message_data: return None, "No 'name' parameter given" - if "window_begin" in message_data and not isinstance(message_data["window_begin"], int): - return None, "Invalid 'window_begin' parameter given" - if "window_size" in message_data and not isinstance(message_data["window_size"], int): - return None, "Invalid 'window_size' parameter given" + if "window" in message_data and "begin" in message_data["window"] and not isinstance( + message_data["window"]["begin"], int): + return None, "Invalid 'window'.'begin' parameter given" + if "window" in message_data and "size" in message_data["window"] and not isinstance(message_data["window"]["size"], + int): + return None, "Invalid 'window'.'size' parameter given" return MessageQueryInformation.from_dict(message_data), None diff --git a/tests/safeds_runner/server/test_websocket_mock.py b/tests/safeds_runner/server/test_websocket_mock.py index cb08574..23fe6ed 100644 --- a/tests/safeds_runner/server/test_websocket_mock.py +++ b/tests/safeds_runner/server/test_websocket_mock.py @@ -21,7 +21,7 @@ message_type_placeholder_type, message_type_placeholder_value, message_type_runtime_error, - message_type_runtime_progress, + message_type_runtime_progress, QueryWindow, ) from safeds_runner.server.pipeline_manager import PipelineManager from safeds_runner.server.server import SafeDsServer @@ -76,12 +76,12 @@ def get_next_received_message(self) -> str: (json.dumps({"type": "placeholder_query", "id": "123", "data": "abc"}), "Message data is not a JSON object"), (json.dumps({"type": "placeholder_query", "id": "123", "data": {"a": "v"}}), "No 'name' parameter given"), ( - json.dumps({"type": "placeholder_query", "id": "123", "data": {"name": "v", "window_begin": "a"}}), - "Invalid 'window_begin' parameter given", + json.dumps({"type": "placeholder_query", "id": "123", "data": {"name": "v", "window": {"begin": "a"}}}), + "Invalid 'window'.'begin' parameter given", ), ( - json.dumps({"type": "placeholder_query", "id": "123", "data": {"name": "v", "window_size": "a"}}), - "Invalid 'window_size' parameter given", + json.dumps({"type": "placeholder_query", "id": "123", "data": {"name": "v", "window": {"size": "a"}}}), + "Invalid 'window'.'size' parameter given", ), ( json.dumps({ @@ -289,11 +289,11 @@ def test_should_execute_pipeline_return_exception( 2, [ # Query Placeholder - json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": {"name": "value1"}}), + json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": {"name": "value1", "window": {}}}), # Query not displayable Placeholder - json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": {"name": "obj"}}), + json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": {"name": "obj", "window": {}}}), # Query invalid placeholder - json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": {"name": "value2"}}), + json.dumps({"type": "placeholder_query", "id": "abcdefg", "data": {"name": "value2", "window": {}}}), ], [ # Validate Placeholder Information @@ -305,19 +305,19 @@ def test_should_execute_pipeline_return_exception( Message( message_type_placeholder_value, "abcdefg", - create_placeholder_value(MessageQueryInformation("value1", None, None), "Int", 1), + create_placeholder_value(MessageQueryInformation("value1"), "Int", 1), ), # Query Result not displayable Message( message_type_placeholder_value, "abcdefg", - create_placeholder_value(MessageQueryInformation("obj", None, None), "object", ""), + create_placeholder_value(MessageQueryInformation("obj"), "object", ""), ), # Query Result Invalid Message( message_type_placeholder_value, "abcdefg", - create_placeholder_value(MessageQueryInformation("value2", None, None), "", ""), + create_placeholder_value(MessageQueryInformation("value2"), "", ""), ), ], ), @@ -397,13 +397,13 @@ def test_should_execute_pipeline_return_valid_placeholder( json.dumps({ "type": "placeholder_query", "id": "unknown-code-id-never-generated", - "data": {"name": "v"}, + "data": {"name": "v", "window": {}}, }), ], Message( message_type_placeholder_value, "unknown-code-id-never-generated", - create_placeholder_value(MessageQueryInformation("v", None, None), "", ""), + create_placeholder_value(MessageQueryInformation("v"), "", ""), ), ), ], @@ -497,72 +497,65 @@ def helper_should_accept_at_least_2_parallel_connections_in_subprocess_server( argnames="query,type_,value,result", argvalues=[ ( - MessageQueryInformation("name", None, None), + MessageQueryInformation("name"), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), '{"name": "name", "type": "Table", "value": {"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}}', ), ( - MessageQueryInformation("name", 0, 1), + MessageQueryInformation("name", QueryWindow(0, 1)), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 1, "window_max":' - ' 7, "value": {"a": [1], "b": [3]}}' + '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 1, "max": 7}, "value": {"a": [1], "b": [3]}}' ), ), ( - MessageQueryInformation("name", 4, 3), + MessageQueryInformation("name", QueryWindow(4, 3)), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max":' - ' 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' + '{"name": "name", "type": "Table", "window": {"begin": 4, "size": 3, "max": 7}, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' ), ), ( - MessageQueryInformation("name", 0, 0), + MessageQueryInformation("name", QueryWindow(0, 0)), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 0, "window_max":' - ' 7, "value": {"a": [], "b": []}}' + '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 0, "max": 7}, "value": {"a": [], "b": []}}' ), ), ( - MessageQueryInformation("name", 4, 30), + MessageQueryInformation("name", QueryWindow(4, 30)), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max":' - ' 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' + '{"name": "name", "type": "Table", "window": {"begin": 4, "size": 3, "max": 7}, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' ), ), ( - MessageQueryInformation("name", 4, None), + MessageQueryInformation("name", QueryWindow(4, None)), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "windowed": true, "window_begin": 4, "window_size": 3, "window_max":' - ' 7, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' + '{"name": "name", "type": "Table", "window": {"begin": 4, "size": 3, "max": 7}, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' ), ), ( - MessageQueryInformation("name", 0, -5), + MessageQueryInformation("name", QueryWindow(0, -5)), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 0, "window_max":' - ' 7, "value": {"a": [], "b": []}}' + '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 0, "max": 7}, "value": {"a": [], "b": []}}' ), ), ( - MessageQueryInformation("name", -5, None), + MessageQueryInformation("name", QueryWindow(-5, None)), "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "windowed": true, "window_begin": 0, "window_size": 7, "window_max":' - ' 7, "value": {"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}}' + '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 7, "max": 7}, "value": {"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}}' ), ), ], From 80e157a04391ac6b08328085febcd79fb5745def Mon Sep 17 00:00:00 2001 From: WinPlay02 Date: Tue, 23 Jan 2024 16:44:11 +0100 Subject: [PATCH 09/12] style: fix linter warnings --- src/safeds_runner/server/messages.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/safeds_runner/server/messages.py b/src/safeds_runner/server/messages.py index b41d5e6..a10403f 100644 --- a/src/safeds_runner/server/messages.py +++ b/src/safeds_runner/server/messages.py @@ -172,6 +172,8 @@ def to_dict(self) -> dict[str, Any]: @dataclass(frozen=True) class QueryWindow: """ + Information that is used to create a subset of the data of a placeholder. + Parameters ---------- begin : int | None @@ -225,7 +227,7 @@ class MessageQueryInformation: """ name: str - window: QueryWindow = QueryWindow() + window: QueryWindow = dataclasses.field(default_factory=QueryWindow) @staticmethod def from_dict(d: dict[str, Any]) -> MessageQueryInformation: From 6f9412a32640e5a12ad65e6cc068c06d0192b887 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:45:58 +0000 Subject: [PATCH 10/12] style: apply automated linter fixes --- src/safeds_runner/server/messages.py | 15 ++++++++---- .../server/test_websocket_mock.py | 24 ++++++++++++------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/safeds_runner/server/messages.py b/src/safeds_runner/server/messages.py index a10403f..888289d 100644 --- a/src/safeds_runner/server/messages.py +++ b/src/safeds_runner/server/messages.py @@ -181,6 +181,7 @@ class QueryWindow: size : int | None Max. amount of entries that should be sent. May be present if a windowed query is required. """ + begin: int | None = None size: int | None = None @@ -444,10 +445,16 @@ def validate_placeholder_query_message_data( return None, "Message data is not a JSON object" if "name" not in message_data: return None, "No 'name' parameter given" - if "window" in message_data and "begin" in message_data["window"] and not isinstance( - message_data["window"]["begin"], int): + if ( + "window" in message_data + and "begin" in message_data["window"] + and not isinstance(message_data["window"]["begin"], int) + ): return None, "Invalid 'window'.'begin' parameter given" - if "window" in message_data and "size" in message_data["window"] and not isinstance(message_data["window"]["size"], - int): + if ( + "window" in message_data + and "size" in message_data["window"] + and not isinstance(message_data["window"]["size"], int) + ): return None, "Invalid 'window'.'size' parameter given" return MessageQueryInformation.from_dict(message_data), None diff --git a/tests/safeds_runner/server/test_websocket_mock.py b/tests/safeds_runner/server/test_websocket_mock.py index 23fe6ed..f25892f 100644 --- a/tests/safeds_runner/server/test_websocket_mock.py +++ b/tests/safeds_runner/server/test_websocket_mock.py @@ -15,13 +15,14 @@ from safeds_runner.server.messages import ( Message, MessageQueryInformation, + QueryWindow, create_placeholder_description, create_placeholder_value, create_runtime_progress_done, message_type_placeholder_type, message_type_placeholder_value, message_type_runtime_error, - message_type_runtime_progress, QueryWindow, + message_type_runtime_progress, ) from safeds_runner.server.pipeline_manager import PipelineManager from safeds_runner.server.server import SafeDsServer @@ -507,7 +508,8 @@ def helper_should_accept_at_least_2_parallel_connections_in_subprocess_server( "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 1, "max": 7}, "value": {"a": [1], "b": [3]}}' + '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 1, "max": 7}, "value": {"a": [1],' + ' "b": [3]}}' ), ), ( @@ -515,7 +517,8 @@ def helper_should_accept_at_least_2_parallel_connections_in_subprocess_server( "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "window": {"begin": 4, "size": 3, "max": 7}, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' + '{"name": "name", "type": "Table", "window": {"begin": 4, "size": 3, "max": 7}, "value": {"a": [3, 2,' + ' 1], "b": [1, 2, 3]}}' ), ), ( @@ -523,7 +526,8 @@ def helper_should_accept_at_least_2_parallel_connections_in_subprocess_server( "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 0, "max": 7}, "value": {"a": [], "b": []}}' + '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 0, "max": 7}, "value": {"a": [], "b":' + " []}}" ), ), ( @@ -531,7 +535,8 @@ def helper_should_accept_at_least_2_parallel_connections_in_subprocess_server( "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "window": {"begin": 4, "size": 3, "max": 7}, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' + '{"name": "name", "type": "Table", "window": {"begin": 4, "size": 3, "max": 7}, "value": {"a": [3, 2,' + ' 1], "b": [1, 2, 3]}}' ), ), ( @@ -539,7 +544,8 @@ def helper_should_accept_at_least_2_parallel_connections_in_subprocess_server( "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "window": {"begin": 4, "size": 3, "max": 7}, "value": {"a": [3, 2, 1], "b": [1, 2, 3]}}' + '{"name": "name", "type": "Table", "window": {"begin": 4, "size": 3, "max": 7}, "value": {"a": [3, 2,' + ' 1], "b": [1, 2, 3]}}' ), ), ( @@ -547,7 +553,8 @@ def helper_should_accept_at_least_2_parallel_connections_in_subprocess_server( "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 0, "max": 7}, "value": {"a": [], "b": []}}' + '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 0, "max": 7}, "value": {"a": [], "b":' + " []}}" ), ), ( @@ -555,7 +562,8 @@ def helper_should_accept_at_least_2_parallel_connections_in_subprocess_server( "Table", Table.from_dict({"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}), ( - '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 7, "max": 7}, "value": {"a": [1, 2, 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}}' + '{"name": "name", "type": "Table", "window": {"begin": 0, "size": 7, "max": 7}, "value": {"a": [1, 2,' + ' 1, 2, 3, 2, 1], "b": [3, 4, 6, 2, 1, 2, 3]}}' ), ), ], From 0f9eda8fcf77ecbdbd3f3ff7b5aa73d7dcb15b39 Mon Sep 17 00:00:00 2001 From: WinPlay02 Date: Tue, 23 Jan 2024 22:47:58 +0100 Subject: [PATCH 11/12] style: convert if to if-elif-else --- src/safeds_runner/server/messages.py | 47 +++++++++++++++------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/safeds_runner/server/messages.py b/src/safeds_runner/server/messages.py index a10403f..e1a9bfa 100644 --- a/src/safeds_runner/server/messages.py +++ b/src/safeds_runner/server/messages.py @@ -371,15 +371,16 @@ def parse_validate_message(message: str) -> tuple[Message | None, str | None, st return None, f"Invalid message received: {message}", "Invalid Message: not JSON" if "type" not in message_dict: return None, f"No message type specified in: {message}", "Invalid Message: no type" - if "id" not in message_dict: + elif "id" not in message_dict: return None, f"No message id specified in: {message}", "Invalid Message: no id" - if "data" not in message_dict: + elif "data" not in message_dict: return None, f"No message data specified in: {message}", "Invalid Message: no data" - if not isinstance(message_dict["type"], str): + elif not isinstance(message_dict["type"], str): return None, f"Message type is not a string: {message}", "Invalid Message: invalid type" - if not isinstance(message_dict["id"], str): + elif not isinstance(message_dict["id"], str): return None, f"Message id is not a string: {message}", "Invalid Message: invalid id" - return Message.from_dict(message_dict), None, None + else: + return Message.from_dict(message_dict), None, None def validate_program_message_data(message_data: dict[str, Any] | str) -> tuple[MessageDataProgram | None, str | None]: @@ -398,30 +399,31 @@ def validate_program_message_data(message_data: dict[str, Any] | str) -> tuple[M """ if not isinstance(message_data, dict): return None, "Message data is not a JSON object" - if "code" not in message_data: + elif "code" not in message_data: return None, "No 'code' parameter given" - if "main" not in message_data: + elif "main" not in message_data: return None, "No 'main' parameter given" - if ( + elif ( not isinstance(message_data["main"], dict) or "modulepath" not in message_data["main"] or "module" not in message_data["main"] or "pipeline" not in message_data["main"] ): return None, "Invalid 'main' parameter given" - if len(message_data["main"]) != 3: + elif len(message_data["main"]) != 3: return None, "Invalid 'main' parameter given" - if not isinstance(message_data["code"], dict): + elif not isinstance(message_data["code"], dict): return None, "Invalid 'code' parameter given" - code: dict = message_data["code"] - for key in code: - if not isinstance(code[key], dict): - return None, "Invalid 'code' parameter given" - next_dict: dict = code[key] - for next_key in next_dict: - if not isinstance(next_dict[next_key], str): + else: + code: dict = message_data["code"] + for key in code: + if not isinstance(code[key], dict): return None, "Invalid 'code' parameter given" - return MessageDataProgram.from_dict(message_data), None + next_dict: dict = code[key] + for next_key in next_dict: + if not isinstance(next_dict[next_key], str): + return None, "Invalid 'code' parameter given" + return MessageDataProgram.from_dict(message_data), None def validate_placeholder_query_message_data( @@ -442,12 +444,13 @@ def validate_placeholder_query_message_data( """ if not isinstance(message_data, dict): return None, "Message data is not a JSON object" - if "name" not in message_data: + elif "name" not in message_data: return None, "No 'name' parameter given" - if "window" in message_data and "begin" in message_data["window"] and not isinstance( + elif "window" in message_data and "begin" in message_data["window"] and not isinstance( message_data["window"]["begin"], int): return None, "Invalid 'window'.'begin' parameter given" - if "window" in message_data and "size" in message_data["window"] and not isinstance(message_data["window"]["size"], + elif "window" in message_data and "size" in message_data["window"] and not isinstance(message_data["window"]["size"], int): return None, "Invalid 'window'.'size' parameter given" - return MessageQueryInformation.from_dict(message_data), None + else: + return MessageQueryInformation.from_dict(message_data), None From 03e55cee12c8197101aca632420b2524e6b06c30 Mon Sep 17 00:00:00 2001 From: WinPlay02 Date: Tue, 23 Jan 2024 23:21:45 +0100 Subject: [PATCH 12/12] style: fix linter warning --- src/safeds_runner/server/messages.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/safeds_runner/server/messages.py b/src/safeds_runner/server/messages.py index 0b192df..32ea9c7 100644 --- a/src/safeds_runner/server/messages.py +++ b/src/safeds_runner/server/messages.py @@ -409,10 +409,9 @@ def validate_program_message_data(message_data: dict[str, Any] | str) -> tuple[M or "modulepath" not in message_data["main"] or "module" not in message_data["main"] or "pipeline" not in message_data["main"] + or len(message_data["main"]) != 3 ): return None, "Invalid 'main' parameter given" - elif len(message_data["main"]) != 3: - return None, "Invalid 'main' parameter given" elif not isinstance(message_data["code"], dict): return None, "Invalid 'code' parameter given" else: