from typing import Callable, List, Union
import pandas as pd
from desdeo_tools.interaction.validators import validate_ref_point_with_ideal_and_nadir
from desdeo_tools.utils.frozen import FrozenClass
class RequestError(Exception):
"""Raised when an error related to the Request class is encountered.
"""
class BaseRequest(FrozenClass):
"""The base class for all Request classes. Request classes are to be used
to handle interaction between the user and the methods, as well as within
various methods. This class is frozen, so no variables other than that
already defined in current __init__ can be defined in derived classes.
"""
def __init__(
self,
request_type: str,
interaction_priority: str,
content=None,
request_id: int = None,
):
"""Initialize a BaseRequest class. This method contains a lot of
boilerplate.
Args:
request_type (str): The type of request. Currently, one of ["print",
"simple_plot", "reference_point_preference",
"classification_preference"].
interaction_priority (str): The priority of preference, as decided
by the method. One of ["no_interaction", "not_required",
"recommended", "required"], with trivial meanings.
content ([type], optional): The data relevant to the request packet.
For example, if the request type is print, content may contain
strings to be printed. Typically a dict. Defaults to None.
request_id (int, optional): A unique identifier. Defaults to None.
Raises:
RequestError: If request type is not recognized
RequestError: If request priority is not recognized
RequestError: If request id is not an integer
"""
acceptable_types = [
"print",
"simple_plot",
"reference_point_preference",
"classification_preference",
]
priority_types = ["no_interaction", "not_required", "recommended", "required"]
if request_type not in acceptable_types:
msg = f"Request type should be one of {acceptable_types}"
raise RequestError(msg)
if interaction_priority not in priority_types:
msg = f"Request priority should be one of {priority_types}"
raise RequestError(msg)
if not isinstance(request_id, (int, type(None))):
msg = "Request id should be int or None"
raise RequestError(msg)
# Attributes
self._request_type: str = request_type
self._interaction_priority: str = interaction_priority # Example: one of:
self._request_id: int = request_id # Some random number as id
self._content = content
self._response = None
# Freezing this class
self._freeze()
@property
def request_type(self):
return self._request_type
@property
def interaction_priority(self):
return self._interaction_priority
@property
def request_id(self):
return self._request_id
@property
def content(self):
return self._content
@property
def response(self):
return self._response
@response.setter
def response(self):
# Code to validate the response
return
class PrintRequest(BaseRequest):
"""Methods can use this request class to send out textual information to be
displayed to the decision maker. This could be a single message in the form
of a string, or multiple messages in a list of strings. The method of
displaying these messages is left to the UI.
"""
def __init__(self, message: Union[str, List[str]], request_id: int=None):
"""Initialise the PrintRequest.
Args:
message (Union[str, List[str]]): A single message (str) or a list of
messages to be displayed to the decision maker
request_id (int, optional): A unique identifier for this request.
Defaults to None.
Raises:
RequestError: If message is not a str or a list
RequestError: If message is a list but one or more elements are not
str.
"""
if not isinstance(message, str):
if not isinstance(message, list):
msg = (
f"Message/s to be printed should be string or list of strings"
f"Message provided is of type: {type(message)}"
)
raise RequestError(msg)
elif not all(isinstance(x, str) for x in message):
msg = (
f"Message/s to be printed should be string or list of strings"
f"Some elements of the list are not strings"
)
raise RequestError(msg)
super().__init__(
request_type="print",
interaction_priority="no_interaction",
content=message,
request_id=request_id,
)
class SimplePlotRequest(BaseRequest):
"""Methods can use this request class to send out some data to be shown to
the decision maker (typically in the form of a plot). This data is usually a
set of solutions, stored in the content variable of this class. The manner
of visualization is left to the UI.
content is a dict that contains the following keys:
"data" (pandas.DataFrame): The data to be plotted.
"dimensional_data" (pandas.Dataframe): The data contained in this key can be
used to scale the data to be plotted.
"chart_title" (str): A recommended title for the visualization.
"message" (Union[str, List[str]]): A message or list of messages to be
displayed to the decision maker.
"""
def __init__(
self,
data: pd.DataFrame,
message: Union[str, List[str]],
dimensions_data: pd.DataFrame = None,
chart_title: str = None,
request_id=None,
):
"""Initialize the request packet
Args:
data (pd.DataFrame): The data to be plotted.
message (Union[str, List[str]]): A message or list of messages to be
displayed to the decision maker.
dimensions_data (pd.DataFrame, optional): Data used to used to scale
the data to be plotted. Defaults to None.
chart_title (str, optional): A recommended title for the
visualization. Defaults to None.
request_id ([type], optional): A unique identifier. Defaults to None.
Raises:
RequestError: data is not a pandas DataFrame.
RequestError: dimensions_data is not a pandas DataFrame or None.
RequestError: A mismatch in the column names of data and
dimensions_data.
RequestError: If dimensions_data DataFrame contains indices other
that "minimize", "ideal", or "nadir".
RequestError: If chart_title is not str or None.
RequestError: If message is not a str or a list.
RequestError: If message is a list but one or more elements are not
str.
"""
acceptable_dimensions_data_indices = [
"minimize", # 1 if minimized, -1 if maximized
"ideal",
"nadir",
]
if not isinstance(data, pd.DataFrame):
msg = (
f"Provided data to be plotted should be in a pandas dataframe, with"
f"columns names being the same as objective names.\n"
f"Provided data is of type: {type(data)}"
)
raise RequestError(msg)
if not isinstance(dimensions_data, (pd.DataFrame, type(None))):
msg = (
f"Dimensional data should be in a pandas dataframe.\n"
f"Provided data is of type: {type(dimensions_data)}"
)
raise RequestError(msg)
if not all(data.columns