Skip to content

Commit

Permalink
feat: convert values
Browse files Browse the repository at this point in the history
  • Loading branch information
lars-reimann committed May 4, 2024
1 parent 4999aed commit e0d0296
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 165 deletions.
131 changes: 23 additions & 108 deletions src/safeds_runner/interface/_reporters.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from typing import Any

from safeds_runner.server._pipeline_manager import get_current_pipeline_process
from safeds_runner.server.messages._from_server import create_progress_message
from safeds_runner.server.messages._from_server import create_placeholder_value_message, create_progress_message
from safeds_runner.utils._get_type_name import get_type_name
from safeds_runner.utils._make_value_json_serializable import make_value_json_serializable


def report_placeholder_computed(placeholder_name: str) -> None:
Expand Down Expand Up @@ -41,112 +43,25 @@ def report_placeholder_value(placeholder_name: str, value: Any) -> None:
if current_pipeline is None:
return # pragma: no cover

# # TODO
# from safeds.data.image.containers import Image
#
# if isinstance(value, Image):
# import torch
#
# value = Image(value._image_tensor, torch.device("cpu"))
# placeholder_type = _get_placeholder_type(value)
# if _is_deterministically_hashable(value) and _has_explicit_identity_memory(value):
# value = ExplicitIdentityWrapperLazy.existing(value)
# elif (
# not _is_deterministically_hashable(value)
# and _is_not_primitive(value)
# and _has_explicit_identity_memory(value)
# ):
# value = ExplicitIdentityWrapper.existing(value)
# TODO
# self._placeholder_map[placeholder_name] = value
# self._send_message(
# message_type_placeholder_type,
# create_placeholder_description(placeholder_name, placeholder_type),
# )


# @sio.event
# async def placeholder_query(_sid: str, payload: Any) -> None:
# try:
# placeholder_query_message = QueryMessage(**payload)
# except (TypeError, ValidationError):
# logging.exception("Invalid message data specified in: %s", payload)
# return
#
# placeholder_type, placeholder_value = self._pipeline_manager.get_placeholder(
# placeholder_query_message.id,
# placeholder_query_message.data.name,
# )
#
# if placeholder_type is None:
# # Send back empty type / value, to communicate that no placeholder exists (yet)
# # Use name from query to allow linking a response to a request on the peer
# data = json.dumps(create_placeholder_value(placeholder_query_message.data, "", ""))
# await sio.emit(message_type_placeholder_value, data, to=placeholder_query_message.id)
# return
#
# try:
# data = json.dumps(
# create_placeholder_value(
# placeholder_query_message.data,
# placeholder_type,
# placeholder_value,
# ),
# cls=SafeDsEncoder,
# )
# except TypeError:
# # if the value can't be encoded send back that the value exists but is not displayable
# data = json.dumps(
# create_placeholder_value(
# placeholder_query_message.data,
# placeholder_type,
# "<Not displayable>",
# ),
# )
#
# await sio.emit(message_type_placeholder_value, data, to=placeholder_query_message.id)

# Also send a progress message
current_pipeline.send_message(
create_progress_message(
run_id=current_pipeline._payload.run_id,
placeholder_name=placeholder_name,
percentage=100,
),
)

# Send the actual value
requested_table_window = current_pipeline._payload.table_window
serialized_value, chosen_window = make_value_json_serializable(value, requested_table_window)

# TODO: move into process that creates placeholder value messages
# def create_placeholder_value(placeholder_query: QueryMessageData, 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
# ----------
# placeholder_query:
# Query of the placeholder.
# type_:
# Type of the placeholder.
# value:
# Value of the placeholder.
#
# Returns
# -------
# message_data:
# 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)
# # 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)
# 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
current_pipeline.send_message(
create_placeholder_value_message(
run_id=current_pipeline._payload.run_id,
placeholder_name=placeholder_name,
value=serialized_value,
type_=get_type_name(value),
window=chosen_window,
),
)
54 changes: 0 additions & 54 deletions src/safeds_runner/server/_json_encoder.py

This file was deleted.

7 changes: 4 additions & 3 deletions src/safeds_runner/server/messages/_from_server.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from abc import ABC
from typing import Any

from pydantic import BaseModel, ConfigDict

Expand Down Expand Up @@ -49,15 +50,15 @@ class PlaceholderValueMessagePayload(MessageFromServerPayload):
type:
Python type of the placeholder at runtime.
value:
Value of the placeholder.
Value of the placeholder. Must be JSON-serializable.
window:
Window of the full value included as value in the message.
"""

run_id: str
placeholder_name: str
type: str
value: str
value: Any
window: Window | None = None

model_config = ConfigDict(extra="forbid")
Expand Down Expand Up @@ -187,7 +188,7 @@ def create_placeholder_value_message(
run_id: str,
placeholder_name: str,
type_: str,
value: str,
value: Any,
window: Window | None = None,
) -> MessageFromServer:
"""Create a 'placeholder_value' message."""
Expand Down
96 changes: 96 additions & 0 deletions src/safeds_runner/utils/_make_value_json_serializable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import base64
import json
import math
from typing import Any

from safeds.data.image.containers import Image
from safeds.data.labeled.containers import TabularDataset
from safeds.data.tabular.containers import Table

from safeds_runner.server.messages._from_server import Window as ChosenWindow
from safeds_runner.server.messages._to_server import Window as RequestedWindow


def make_value_json_serializable(value: Any, requested_table_window: RequestedWindow) -> tuple[Any, ChosenWindow | None]:
"""
Convert a value to a JSON-serializable format.
Parameters
----------
value:
The value to serialize.
requested_table_window:
Window to get for placeholders of type 'Table'.
Returns
-------
serialized_value:
The serialized value.
chosen_window:
The window of the value that was serialized.
"""
if isinstance(value, Table):
return make_table_json_serializable(value, requested_table_window)
elif isinstance(value, TabularDataset):
return make_table_json_serializable(value.to_table(), requested_table_window)
elif isinstance(value, Image):
return make_image_json_serializable(value)
else:
return make_other_json_serializable(value)


def make_table_json_serializable(
table: Table,
requested_window: RequestedWindow,
) -> tuple[Any, ChosenWindow | None]:
# Compute sizes
full_size = table.number_of_rows

requested_size = requested_window.size if requested_window.size is not None else full_size
requested_size = max(requested_size, 0)

# Compute indices
start_index = requested_window.start if requested_window.start is not None else 0
start_index = max(start_index, 0)

end_index = start_index + requested_size
end_index = min(end_index, full_size)

# Compute value
slice_ = table.slice_rows(start=start_index, end=end_index)
value = _replace_nan_and_infinity(slice_.to_dict())

# Compute window
if requested_window.start is not None or requested_window.size is not None:
chosen_window = ChosenWindow(start=start_index, size=end_index - start_index, full_size=full_size)
else:
chosen_window = None

return value, chosen_window


def _replace_nan_and_infinity(dict_: dict) -> dict:
return {
key: [
value if not isinstance(value, float) or math.isfinite(value) else None
for value in dict_[key]
]
for key in dict_
}


def make_image_json_serializable(image: Image) -> tuple[Any, ChosenWindow | None]:
dict_ = {
"format": "png",
"bytes": str(base64.encodebytes(image._repr_png_()), "utf-8"),
}
return dict_, None


def make_other_json_serializable(value: Any) -> tuple[Any, ChosenWindow | None]:
try:
json.dumps(value)
except TypeError:
return "<Not displayable>", None
else:
return value, None

0 comments on commit e0d0296

Please sign in to comment.