Skip to content

Commit

Permalink
Actor revalidation (#289)
Browse files Browse the repository at this point in the history
# Description

Actor validation made required and also improved.

---------

Co-authored-by: Roman Zlobin <[email protected]>
  • Loading branch information
pseusys and RLKRo authored Apr 15, 2024
1 parent fbfc084 commit 5ccead1
Show file tree
Hide file tree
Showing 21 changed files with 501 additions and 195 deletions.
2 changes: 1 addition & 1 deletion dff/messengers/telegram/messenger.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def telegram_condition(
**kwargs,
)

def condition(ctx: Context, _: Pipeline, *__, **___): # pragma: no cover
def condition(ctx: Context, _: Pipeline) -> bool: # pragma: no cover
last_request = ctx.last_request
if last_request is None:
return False
Expand Down
50 changes: 1 addition & 49 deletions dff/pipeline/pipeline/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,6 @@
from dff.pipeline.pipeline.pipeline import Pipeline


def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = None, logging_flag: bool = True):
"""
This function handles errors during :py:class:`~dff.script.Script` validation.
:param error_msgs: List that contains error messages. :py:func:`~dff.script.error_handler`
adds every next error message to that list.
:param msg: Error message which is to be added into `error_msgs`.
:param exception: Invoked exception. If it has been set, it is used to obtain logging traceback.
Defaults to `None`.
:param logging_flag: The flag which defines whether logging is necessary. Defaults to `True`.
"""
error_msgs.append(msg)
if logging_flag:
logger.error(msg, exc_info=exception)


class Actor:
"""
The class which is used to process :py:class:`~dff.script.Context`
Expand All @@ -73,7 +57,7 @@ class Actor:
Dialog comes into that label if all other transitions failed,
or there was an error while executing the scenario.
Defaults to `None`.
:param label_priority: Default priority value for all :py:const:`labels <dff.script.NodeLabel3Type>`
:param label_priority: Default priority value for all :py:const:`labels <dff.script.ConstLabel>`
where there is no priority. Defaults to `1.0`.
:param condition_handler: Handler that processes a call of condition functions. Defaults to `None`.
:param handlers: This variable is responsible for the usage of external handlers on
Expand All @@ -92,11 +76,9 @@ def __init__(
condition_handler: Optional[Callable] = None,
handlers: Optional[Dict[ActorStage, List[Callable]]] = None,
):
# script validation
self.script = script if isinstance(script, Script) else Script(script=script)
self.label_priority = label_priority

# node labels validation
self.start_label = normalize_label(start_label)
if self.script.get(self.start_label[0], {}).get(self.start_label[1]) is None:
raise ValueError(f"Unknown start_label={self.start_label}")
Expand Down Expand Up @@ -389,36 +371,6 @@ def _choose_label(
chosen_label = self.fallback_label
return chosen_label

def validate_script(self, pipeline: Pipeline, verbose: bool = True):
# TODO: script has to not contain priority == -inf, because it uses for miss values
flow_labels = []
node_labels = []
labels = []
conditions = []
for flow_name, flow in self.script.items():
for node_name, node in flow.items():
flow_labels += [flow_name] * len(node.transitions)
node_labels += [node_name] * len(node.transitions)
labels += list(node.transitions.keys())
conditions += list(node.transitions.values())

error_msgs = []
for flow_label, node_label, label, condition in zip(flow_labels, node_labels, labels, conditions):
if not callable(label):
label = normalize_label(label, flow_label)

# validate labeling
try:
node = self.script[label[0]][label[1]]
except Exception as exc:
msg = (
f"Could not find node with label={label}, "
f"error was found in (flow_label, node_label)={(flow_label, node_label)}"
)
error_handler(error_msgs, msg, exc, verbose)
break
return error_msgs


async def default_condition_handler(
condition: Callable, ctx: Context, pipeline: Pipeline
Expand Down
32 changes: 3 additions & 29 deletions dff/pipeline/pipeline/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,9 @@ class Pipeline:
:param script: (required) A :py:class:`~.Script` instance (object or dict).
:param start_label: (required) Actor start label.
:param fallback_label: Actor fallback label.
:param label_priority: Default priority value for all actor :py:const:`labels <dff.script.NodeLabel3Type>`
:param label_priority: Default priority value for all actor :py:const:`labels <dff.script.ConstLabel>`
where there is no priority. Defaults to `1.0`.
:param validation_stage: This flag sets whether the validation stage is executed after actor creation.
It is executed by default. Defaults to `None`.
:param condition_handler: Handler that processes a call of actor condition functions. Defaults to `None`.
:param verbose: If it is `True`, logging is used in actor. Defaults to `True`.
:param handlers: This variable is responsible for the usage of external handlers on
the certain stages of work of :py:class:`~dff.script.Actor`.
Expand Down Expand Up @@ -88,9 +85,7 @@ def __init__(
start_label: NodeLabel2Type,
fallback_label: Optional[NodeLabel2Type] = None,
label_priority: float = 1.0,
validation_stage: Optional[bool] = None,
condition_handler: Optional[Callable] = None,
verbose: bool = True,
handlers: Optional[Dict[ActorStage, List[Callable]]] = None,
messenger_interface: Optional[MessengerInterface] = None,
context_storage: Optional[Union[DBContextStorage, Dict]] = None,
Expand Down Expand Up @@ -121,9 +116,7 @@ def __init__(
start_label,
fallback_label,
label_priority,
validation_stage,
condition_handler,
verbose,
handlers,
)
if self.actor is None:
Expand Down Expand Up @@ -211,9 +204,7 @@ def from_script(
start_label: NodeLabel2Type,
fallback_label: Optional[NodeLabel2Type] = None,
label_priority: float = 1.0,
validation_stage: Optional[bool] = None,
condition_handler: Optional[Callable] = None,
verbose: bool = True,
parallelize_processing: bool = False,
handlers: Optional[Dict[ActorStage, List[Callable]]] = None,
context_storage: Optional[Union[DBContextStorage, Dict]] = None,
Expand All @@ -232,12 +223,9 @@ def from_script(
:param script: (required) A :py:class:`~.Script` instance (object or dict).
:param start_label: (required) Actor start label.
:param fallback_label: Actor fallback label.
:param label_priority: Default priority value for all actor :py:const:`labels <dff.script.NodeLabel3Type>`
:param label_priority: Default priority value for all actor :py:const:`labels <dff.script.ConstLabel>`
where there is no priority. Defaults to `1.0`.
:param validation_stage: This flag sets whether the validation stage is executed after actor creation.
It is executed by default. Defaults to `None`.
:param condition_handler: Handler that processes a call of actor condition functions. Defaults to `None`.
:param verbose: If it is `True`, logging is used in actor. Defaults to `True`.
:param parallelize_processing: This flag determines whether or not the functions
defined in the ``PRE_RESPONSE_PROCESSING`` and ``PRE_TRANSITIONS_PROCESSING`` sections
of the script should be parallelized over respective groups.
Expand Down Expand Up @@ -265,9 +253,7 @@ def from_script(
start_label=start_label,
fallback_label=fallback_label,
label_priority=label_priority,
validation_stage=validation_stage,
condition_handler=condition_handler,
verbose=verbose,
parallelize_processing=parallelize_processing,
handlers=handlers,
messenger_interface=messenger_interface,
Expand All @@ -281,9 +267,7 @@ def set_actor(
start_label: NodeLabel2Type,
fallback_label: Optional[NodeLabel2Type] = None,
label_priority: float = 1.0,
validation_stage: Optional[bool] = None,
condition_handler: Optional[Callable] = None,
verbose: bool = True,
handlers: Optional[Dict[ActorStage, List[Callable]]] = None,
):
"""
Expand All @@ -296,26 +280,16 @@ def set_actor(
:param fallback_label: Actor fallback label. The label of :py:class:`~dff.script.Script`.
Dialog comes into that label if all other transitions failed,
or there was an error while executing the scenario.
:param label_priority: Default priority value for all actor :py:const:`labels <dff.script.NodeLabel3Type>`
:param label_priority: Default priority value for all actor :py:const:`labels <dff.script.ConstLabel>`
where there is no priority. Defaults to `1.0`.
:param validation_stage: This flag sets whether the validation stage is executed in actor.
It is executed by default. Defaults to `None`.
:param condition_handler: Handler that processes a call of actor condition functions. Defaults to `None`.
:param verbose: If it is `True`, logging is used in actor. Defaults to `True`.
:param handlers: This variable is responsible for the usage of external handlers on
the certain stages of work of :py:class:`~dff.script.Actor`.
- key :py:class:`~dff.script.ActorStage` - Stage in which the handler is called.
- value List[Callable] - The list of called handlers for each stage. Defaults to an empty `dict`.
"""
old_actor = self.actor
self.actor = Actor(script, start_label, fallback_label, label_priority, condition_handler, handlers)
errors = self.actor.validate_script(self, verbose) if validation_stage is not False else []
if errors:
self.actor = old_actor
raise ValueError(
f"Found {len(errors)} errors: " + " ".join([f"{i}) {er}" for i, er in enumerate(errors, 1)])
)

@classmethod
def from_dict(cls, dictionary: PipelineBuilder) -> "Pipeline":
Expand Down
2 changes: 0 additions & 2 deletions dff/pipeline/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,7 @@ class ExtraHandlerRuntimeInfo(BaseModel):
"start_label": NodeLabel2Type,
"fallback_label": NotRequired[Optional[NodeLabel2Type]],
"label_priority": NotRequired[float],
"validation_stage": NotRequired[Optional[bool]],
"condition_handler": NotRequired[Optional[Callable]],
"verbose": NotRequired[bool],
"handlers": NotRequired[Optional[Dict[ActorStage, List[Callable]]]],
},
)
Expand Down
3 changes: 2 additions & 1 deletion dff/script/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
NodeLabel2Type,
NodeLabel3Type,
NodeLabelTupledType,
NodeLabelType,
ConstLabel,
Label,
ConditionType,
ModuleName,
ActorStage,
Expand Down
3 changes: 2 additions & 1 deletion dff/script/conditions/std_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,13 @@ def has_last_labels(
Return condition handler. This handler returns `True` if any label from
last `last_n_indices` context labels is in
the `flow_labels` list or in
the `~dff.script.NodeLabel2Type` list.
the `labels` list.
:param flow_labels: List of labels to check. Every label has type `str`. Empty if not set.
:param labels: List of labels corresponding to the nodes. Empty if not set.
:param last_n_indices: Number of last utterances to check.
"""
# todo: rewrite docs & function itself
flow_labels = [] if flow_labels is None else flow_labels
labels = [] if labels is None else labels

Expand Down
10 changes: 1 addition & 9 deletions dff/script/core/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,6 @@ class Context(BaseModel):
- key - Arbitrary data name.
- value - Arbitrary data.
"""
validation: bool = False
"""
`validation` is a flag that signals that :py:class:`~dff.pipeline.pipeline.pipeline.Pipeline`,
while being initialized, checks the :py:class:`~dff.script.core.script.Script`.
The functions that can give not valid data
while being validated must use this flag to take the validation mode into account.
Otherwise the validation will not be passed.
"""
framework_states: Dict[ModuleName, Dict[str, Any]] = {}
"""
`framework_states` is used for addons states or for
Expand Down Expand Up @@ -142,7 +134,7 @@ def cast(cls, ctx: Optional[Union[Context, dict, str]] = None, *args, **kwargs)
ctx = Context.model_validate(ctx)
elif isinstance(ctx, str):
ctx = Context.model_validate_json(ctx)
elif not issubclass(type(ctx), Context):
elif not isinstance(ctx, Context):
raise ValueError(
f"Context expected to be an instance of the Context class "
f"or an instance of the dict/str(json) type. Got: {type(ctx)}"
Expand Down
15 changes: 7 additions & 8 deletions dff/script/core/normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from .keywords import Keywords
from .context import Context
from .types import NodeLabel3Type, NodeLabelType, ConditionType, LabelType
from .types import ConstLabel, ConditionType, Label, LabelType
from .message import Message

if TYPE_CHECKING:
Expand All @@ -21,22 +21,19 @@
logger = logging.getLogger(__name__)


def normalize_label(
label: NodeLabelType, default_flow_label: LabelType = ""
) -> Union[Callable[[Context, Pipeline], NodeLabel3Type], NodeLabel3Type]:
def normalize_label(label: Label, default_flow_label: LabelType = "") -> Label:
"""
The function that is used for normalization of
:py:const:`default_flow_label <dff.script.NodeLabelType>`.
:py:const:`label <dff.script.Label>`.
:param label: If label is Callable the function is wrapped into try/except
and normalization is used on the result of the function call with the name label.
:param default_flow_label: flow_label is used if label does not contain flow_label.
:return: Result of the label normalization,
if Callable is returned, the normalized result is returned.
:return: Result of the label normalization
"""
if callable(label):

def get_label_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type:
def get_label_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel:
try:
new_label = label(ctx, pipeline)
new_label = normalize_label(new_label, default_flow_label)
Expand All @@ -62,6 +59,8 @@ def get_label_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type:
elif isinstance(label, tuple) and len(label) == 3:
flow_label = label[0] or default_flow_label
return (flow_label, label[1], label[2])
else:
raise TypeError(f"Label '{label!r}' is of incorrect type. It has to follow the `Label`:\n" f"{Label!r}")


def normalize_condition(condition: ConditionType) -> Callable[[Context, Pipeline], bool]:
Expand Down
Loading

0 comments on commit 5ccead1

Please sign in to comment.