Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Actor revalidation #289

Merged
merged 84 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
7d104f2
validation changed
pseusys Nov 22, 2023
d1e2c1f
some errors fixed
pseusys Nov 22, 2023
6664120
tests fixed
pseusys Nov 24, 2023
748befc
Merge branch 'dev' into feat/actor_revalidation
pseusys Nov 24, 2023
4c2792b
lint applied
pseusys Nov 27, 2023
4d15bd6
Merge branch 'feat/actor_revalidation' of https://github.com/deeppavl…
pseusys Nov 27, 2023
2f59d24
blank line removed
pseusys Nov 27, 2023
ed21272
format applied
pseusys Nov 27, 2023
bcaa5cc
function docs provided
pseusys Nov 27, 2023
e214350
lint applied
pseusys Nov 27, 2023
5ddc70c
reviews fixed
pseusys Dec 1, 2023
e2b04f7
actor updated
pseusys Feb 6, 2024
fc588bb
Merge branch 'dev' into feat/actor_revalidation
pseusys Feb 6, 2024
5f9ff22
aftermerge
pseusys Feb 6, 2024
809fc45
inspect moved out of scope
pseusys Feb 6, 2024
cab2d88
user functions types mocked
pseusys Feb 6, 2024
42d9112
any import added
pseusys Feb 6, 2024
e7eaacb
most of the errors fixed
pseusys Feb 7, 2024
79ae0b1
last failing test fixed
pseusys Feb 7, 2024
913887e
lint applied
pseusys Feb 7, 2024
cdf5348
Merge branch 'dev' into feat/actor_revalidation
pseusys Mar 18, 2024
9c848dc
repeat import added
pseusys Mar 18, 2024
d19ea74
in-script validation first attempt
pseusys Mar 26, 2024
0f0edc8
Merge branch 'feat/actor_revalidation' of https://github.com/deeppavl…
pseusys Mar 26, 2024
6a67f34
Merge branch 'dev' into feat/actor_revalidation
pseusys Mar 27, 2024
48cbc9e
validate_script reference removed
pseusys Mar 27, 2024
4d81d31
runtime imports added instead of typechecking imports
pseusys Mar 27, 2024
c3d5d25
lint fixed
pseusys Mar 27, 2024
aab8020
script creation test changed
pseusys Mar 27, 2024
247bac3
global import removed, test function fixed
pseusys Mar 27, 2024
fe55f02
make `normalize_label` raise on TypeError
RLKRo Mar 28, 2024
bd64bc8
remove irrelevant docs
RLKRo Mar 28, 2024
1c761d0
processing returns None
pseusys Mar 28, 2024
303b0f3
names replaced with enum, type_checking removed
pseusys Mar 28, 2024
9244921
strenum removed
pseusys Mar 28, 2024
0278ffc
local flow label fixed
pseusys Mar 28, 2024
db37dd6
type comparison fixed in case of strings
pseusys Mar 28, 2024
5c129b3
type description added
pseusys Mar 28, 2024
e4591fa
condition fixed
pseusys Mar 28, 2024
00b9a67
none matching improved
pseusys Mar 29, 2024
2632c7a
label normalization returned
pseusys Mar 29, 2024
a343b1a
label functions signature changed
pseusys Mar 29, 2024
040b84e
separate errors with new lines
RLKRo Mar 29, 2024
2b10cfd
return label representation instead of their str
RLKRo Mar 29, 2024
238359e
lable typing reverted
pseusys Mar 29, 2024
be648c1
Merge branch 'feat/actor_revalidation' of https://github.com/deeppavl…
pseusys Mar 29, 2024
4897859
types replaced with strings
pseusys Mar 29, 2024
5be4b86
function names changed + lint fixed
pseusys Mar 29, 2024
2fe6dc4
docs validated, validation tests added
pseusys Apr 2, 2024
d68fa74
testing function fix
pseusys Apr 2, 2024
b231433
execution mode changed to "before"
pseusys Apr 2, 2024
bffd43f
error expecting fixed
pseusys Apr 2, 2024
78df45a
testing errors fixed, excessive validation removed
pseusys Apr 2, 2024
af4bd33
lint applied
pseusys Apr 2, 2024
ba47769
few tutorials fixed
pseusys Apr 2, 2024
3ad859e
type annotations correct hanfling added
pseusys Apr 2, 2024
c5e3560
test functions -> test classes
pseusys Apr 3, 2024
271469f
lint applied (again)
pseusys Apr 3, 2024
8ccbfbb
remove unnecessary comments
RLKRo Apr 4, 2024
fc74d76
group user function for validation tests
RLKRo Apr 4, 2024
c75d0ba
assert number of found errors
RLKRo Apr 4, 2024
c3ce08a
logging function removed
pseusys Apr 8, 2024
067283a
multiple label type options added
pseusys Apr 8, 2024
dce18ac
fix error message addition
RLKRo Apr 9, 2024
4c82749
fix return type validation
RLKRo Apr 9, 2024
5206ce0
update tests
RLKRo Apr 9, 2024
f928ac4
improve error messages
RLKRo Apr 9, 2024
e01f459
lint
RLKRo Apr 9, 2024
aa7cf4f
add more possible return type annotations for labels
RLKRo Apr 9, 2024
5d1736c
reformat
RLKRo Apr 9, 2024
5cb48fe
signature name lowered
pseusys Apr 9, 2024
d9f6b25
Merge branch 'feat/actor_revalidation' of https://github.com/deeppavl…
pseusys Apr 11, 2024
d0718a2
types fix
pseusys Apr 11, 2024
90d071d
types renamed
pseusys Apr 13, 2024
c59d248
type names fixed
pseusys Apr 15, 2024
f353fed
node label type changed
pseusys Apr 15, 2024
66372c4
constlabel reverted
pseusys Apr 15, 2024
2e19912
test regex fixed
pseusys Apr 15, 2024
597a82e
regex attuned pnce again
pseusys Apr 15, 2024
3fa6d14
last regex error fixed
pseusys Apr 15, 2024
1e81d12
fprmatting applied
pseusys Apr 15, 2024
0522752
doc improvements
RLKRo Apr 15, 2024
50b354a
add some todos
RLKRo Apr 15, 2024
20717a6
ignore unused import
RLKRo Apr 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 172 additions & 60 deletions dff/pipeline/pipeline/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@

.. figure:: /_static/drawio/dfe/user_actor.png
"""
import inspect
import logging
from typing import Union, Callable, Optional, Dict, List, Any, ForwardRef
from typing import Tuple, Union, Callable, Optional, Dict, List, Any, ForwardRef, Type
import copy

from dff.utils.turn_caching import cache_clear
Expand All @@ -32,7 +33,7 @@

from dff.script.core.context import Context
from dff.script.core.script import Script, Node
from dff.script.core.normalization import normalize_label, normalize_response
from dff.script.core.normalization import normalize_label
from dff.script.core.keywords import GLOBAL, LOCAL

logger = logging.getLogger(__name__)
Expand All @@ -56,6 +57,79 @@ def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = N
logger.error(msg, exc_info=exception)


def types_match(type1: Type, type2: Type) -> bool:
"""
This function compares types with assumption that one of the types might be a :py:class:`typing.ForwardRef`.
pseusys marked this conversation as resolved.
Show resolved Hide resolved
If it is so, it compares type and forward reference by name.

:param type1: First type to compare.
:param type2: Second type to compare.
:return: True if types are equal, False otherwise.
"""
if type1 == type2:
return True
elif type(type1) is ForwardRef or type(type2) is ForwardRef:
type1_name = type1.__forward_arg__ if type(type1) is ForwardRef else type1.__name__
type2_name = type2.__forward_arg__ if type(type2) is ForwardRef else type2.__name__
return type1_name == type2_name
else:
return False


def validate_callable(
callable: Callable,
name: str,
flow_label: str,
node_label: str,
error_msgs: list,
logging_flag: bool = True,
pseusys marked this conversation as resolved.
Show resolved Hide resolved
expected_types: Optional[List[Type]] = None,
pseusys marked this conversation as resolved.
Show resolved Hide resolved
return_type: Optional[Tuple[Type]] = None,
ruthenian8 marked this conversation as resolved.
Show resolved Hide resolved
):
"""
This function validates a function during :py:class:`~dff.script.Script` validation.
It checks parameter number (unconditionally), parameter types (if specified) and return type (if specified).

:param callable: Function to validate.
:param name: Type of the function (label, condition, response, etc.).
:param flow_label: Flow label this function is related to (used for error localization only).
:param node_label: Node label this function is related to (used for error localization only).
:param error_msgs: List that contains error messages. All the error message will be added to that list.
pseusys marked this conversation as resolved.
Show resolved Hide resolved
:param logging_flag: The flag which defines whether logging is necessary. Defaults to `True`.
:param expected_types: List of types that correspond to expected function parameter types.
Also used for parameter number check. If `None`, parameter check is skipped. Defaults to `None`.
:param return_type: Tuple, containing expected function return type.
If `None` or contains more or less than 1 element, return type check is skipped. Defaults to `None`.
"""
signature = inspect.signature(callable)
if expected_types is not None:
params = list(signature.parameters.values())
if len(params) != len(expected_types):
msg = (
f"Incorrect parameter number of {name}={callable.__name__}: "
f"should be {len(expected_types)}, found {len(params)}, "
f"error was found in (flow_label, node_label)={(flow_label, node_label)}"
)
error_handler(error_msgs, msg, None, logging_flag)
for idx, param in enumerate(params):
if param.annotation != inspect.Parameter.empty and not types_match(param.annotation, expected_types[idx]):
msg = (
f"Incorrect {idx} parameter annotation of {name}={callable.__name__}: "
f"should be {expected_types[idx]}, found {param.annotation}, "
f"error was found in (flow_label, node_label)={(flow_label, node_label)}"
)
error_handler(error_msgs, msg, None, logging_flag)
if return_type is not None and len(return_type) == 1:
retrun_annotation = signature.return_annotation
if retrun_annotation != inspect.Parameter.empty and not types_match(retrun_annotation, return_type[0]):
pseusys marked this conversation as resolved.
Show resolved Hide resolved
msg = (
f"Incorrect return type annotation of {name}={callable.__name__}: "
f"should be {return_type[0]}, found {retrun_annotation}, "
pseusys marked this conversation as resolved.
Show resolved Hide resolved
f"error was found in (flow_label, node_label)={(flow_label, node_label)}"
)
error_handler(error_msgs, msg, None, logging_flag)


class Actor:
"""
The class which is used to process :py:class:`~dff.script.Context`
Expand Down Expand Up @@ -87,11 +161,11 @@ def __init__(
condition_handler: Optional[Callable] = None,
handlers: Optional[Dict[ActorStage, List[Callable]]] = None,
):
# script validation
# script evaluation
self.script = script if isinstance(script, Script) else Script(script=script)
self.label_priority = label_priority

# node labels validation
# node labels evaluation
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 @@ -325,68 +399,106 @@ def _choose_label(
chosen_label = self.fallback_label
return chosen_label

def validate_script(self, pipeline: Pipeline, verbose: bool = True):
def validate_script(self, verbose: bool = True):
# TODO: script has to not contain priority == -inf, because it uses for miss values
flow_labels = []
node_labels = []
labels = []
conditions = []
error_msgs = []
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):
ctx = Context()
ctx.validation = True
ctx.add_request(Message(text="text"))

label = label(ctx, pipeline) if callable(label) else 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

# validate responsing
response_func = normalize_response(node.response)
try:
response_result = response_func(ctx, pipeline)
if not isinstance(response_result, Message):
# validate labeling
for label in node.transitions.keys():
if callable(label):
validate_callable(
label, "label", flow_name, node_name, error_msgs, verbose, [Context, Pipeline, Any, Any]
)
else:
norm_label = normalize_label(label, flow_name)
if norm_label is None:
msg = (
f"Label can not be normalized for label={label}, "
f"error was found in (flow_label, node_label)={(flow_name, node_name)}"
)
error_handler(error_msgs, msg, None, verbose)
continue
norm_flow_label, norm_node_label, _ = norm_label
if norm_flow_label not in self.script.keys():
msg = (
f"Flow label {norm_flow_label} can not be found for label={label}, "
f"error was found in (flow_label, node_label)={(flow_name, node_name)}"
)
elif norm_node_label not in self.script[norm_flow_label].keys():
msg = (
f"Node label {norm_node_label} can not be found for label={label}, "
f"error was found in (flow_label, node_label)={(flow_name, node_name)}"
)
else:
msg = None
if msg is not None:
error_handler(error_msgs, msg, None, verbose)

# validate responses
if callable(node.response):
validate_callable(
node.response,
"response",
flow_name,
node_name,
error_msgs,
verbose,
[Context, Pipeline, Any, Any],
(Message,),
)
elif node.response is not None and not issubclass(type(node.response), Message):
pseusys marked this conversation as resolved.
Show resolved Hide resolved
msg = (
"Expected type of response_result is `Message`.\n"
+ f"Got type(response_result)={type(response_result)}"
f" for label={label} , error was found in (flow_label, node_label)={(flow_label, node_label)}"
f"Expected type of response is subclass of {Message}, "
f"got type(response)={type(node.response)}, "
f"error was found in (flow_label, node_label)={(flow_name, node_name)}"
)
error_handler(error_msgs, msg, None, verbose)
continue
except Exception as exc:
msg = (
f"Got exception '''{exc}''' during response execution "
f"for label={label} and node.response={node.response}"
f", error was found in (flow_label, node_label)={(flow_label, node_label)}"
)
error_handler(error_msgs, msg, exc, verbose)
continue

# validate conditioning
try:
condition_result = condition(ctx, pipeline)
if not isinstance(condition(ctx, pipeline), bool):
raise Exception(f"Returned condition_result={condition_result}, but expected bool type")
except Exception as exc:
msg = f"Got exception '''{exc}''' during condition execution for label={label}"
error_handler(error_msgs, msg, exc, verbose)
continue

# validate conditions
for label, condition in node.transitions.items():
if callable(condition):
validate_callable(
condition,
"condition",
flow_name,
node_name,
error_msgs,
verbose,
[Context, Pipeline, Any, Any],
(bool,),
)
else:
msg = (
f"Expected type of condition for label={label} is {Callable}, "
f"got type(condition)={type(condition)}, "
f"error was found in (flow_label, node_label)={(flow_name, node_name)}"
)
error_handler(error_msgs, msg, None, verbose)

# validate pre_transitions- and pre_response_processing
for place, functions in zip(
("transitions", "response"), (node.pre_transitions_processing, node.pre_response_processing)
):
for name, function in functions.items():
if callable(function):
validate_callable(
function,
f"pre_{place}_processing {name}",
flow_name,
node_name,
error_msgs,
verbose,
[Context, Pipeline, Any, Any],
(Context,),
)
else:
msg = (
f"Expected type of pre_{place}_processing {name} is {Callable}, "
f"got type(pre_{place}_processing)={type(function)}, "
f"error was found in (flow_label, node_label)={(flow_name, node_name)}"
)
error_handler(error_msgs, msg, None, verbose)
return error_msgs


Expand Down
13 changes: 1 addition & 12 deletions dff/pipeline/pipeline/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ class Pipeline:
:param fallback_label: Actor fallback label.
:param label_priority: Default priority value for all actor :py:const:`labels <dff.script.NodeLabel3Type>`
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
Expand Down Expand Up @@ -84,7 +82,6 @@ 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,
Expand Down Expand Up @@ -116,7 +113,6 @@ def __init__(
start_label,
fallback_label,
label_priority,
validation_stage,
condition_handler,
verbose,
handlers,
Expand Down Expand Up @@ -204,7 +200,6 @@ 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,
handlers: Optional[Dict[ActorStage, List[Callable]]] = None,
Expand All @@ -226,8 +221,6 @@ def from_script(
:param fallback_label: Actor fallback label.
:param label_priority: Default priority value for all actor :py:const:`labels <dff.script.NodeLabel3Type>`
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
Expand All @@ -254,7 +247,6 @@ 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,
handlers=handlers,
Expand All @@ -269,7 +261,6 @@ 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 @@ -286,8 +277,6 @@ def set_actor(
or there was an error while executing the scenario.
:param label_priority: Default priority value for all actor :py:const:`labels <dff.script.NodeLabel3Type>`
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
Expand All @@ -298,7 +287,7 @@ def set_actor(
"""
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 []
errors = self.actor.validate_script(verbose)
if errors:
self.actor = old_actor
raise ValueError(
Expand Down
1 change: 0 additions & 1 deletion dff/pipeline/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,6 @@ 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
8 changes: 0 additions & 8 deletions dff/script/core/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,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
Loading