From 7d104f2f7bc20c093158d3d09bdcf757018f4294 Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 22 Nov 2023 10:53:38 +0100 Subject: [PATCH 01/76] validation changed --- dff/pipeline/pipeline/actor.py | 149 +++++++++++++++++++----------- dff/pipeline/pipeline/pipeline.py | 13 +-- dff/pipeline/types.py | 1 - dff/script/core/context.py | 8 -- dff/script/core/normalization.py | 7 +- 5 files changed, 99 insertions(+), 79 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index 1bd5878b0..a557bd293 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -22,6 +22,7 @@ .. figure:: /_static/drawio/dfe/user_actor.png """ +import inspect import logging from typing import Union, Callable, Optional, Dict, List, Any, ForwardRef import copy @@ -56,6 +57,36 @@ def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = N logger.error(msg, exc_info=exception) +def validate_callable(callable: Callable, name: str, flow_label: str, node_label: str, error_msgs: list, verbose: bool, expected: Optional[list] = None, rtrn = None): + signature = inspect.signature(callable) + if expected is not None: + params = list(signature.parameters.values()) + if len(params) != len(expected): + msg = ( + f"Incorrect parameter number of {name}={callable.__name__}: " + f"should be {len(expected)}, found {len(params)}, " + f"error was found in (flow_label, node_label)={(flow_label, node_label)}" + ) + error_handler(error_msgs, msg, None, verbose) + for idx, param in enumerate(params): + if param.annotation != inspect.Parameter.empty and param.annotation != expected[idx]: + msg = ( + f"Incorrect {idx} parameter annotation of {name}={callable.__name__}: " + f"should be {expected[idx]}, found {param.annotation}, " + f"error was found in (flow_label, node_label)={(flow_label, node_label)}" + ) + error_handler(error_msgs, msg, None, verbose) + if rtrn is not None: + rtrn_type = signature.return_annotation + if rtrn_type != inspect.Parameter.empty and rtrn_type != rtrn: + msg = ( + f"Incorrect return type annotation of {name}={callable.__name__}: " + f"should be {len(rtrn)}, found {len(rtrn_type)}, " + f"error was found in (flow_label, node_label)={(flow_label, node_label)}" + ) + error_handler(error_msgs, msg, None, verbose) + + class Actor: """ The class which is used to process :py:class:`~dff.script.Context` @@ -325,68 +356,74 @@ 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]) + 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_fl, norm_nl, _ = norm_label + if norm_fl not in self.script.keys(): + msg = ( + f"Flow label {norm_fl} can not be found for label={label}, " + f"error was found in (flow_label, node_label)={(flow_name, node_name)}" + ) + elif norm_nl not in flow.keys() or norm_nl not in self.script[norm_fl].keys(): + msg = ( + f"Node label {norm_nl} 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], Message) + elif node.response is not None and type(node.response) != Message: 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 {Message}, 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], 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], 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 diff --git a/dff/pipeline/pipeline/pipeline.py b/dff/pipeline/pipeline/pipeline.py index c65ac9e7a..5b717fe1a 100644 --- a/dff/pipeline/pipeline/pipeline.py +++ b/dff/pipeline/pipeline/pipeline.py @@ -51,8 +51,6 @@ class Pipeline: :param fallback_label: Actor fallback label. :param label_priority: Default priority value for all actor :py:const:`labels ` 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 @@ -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, @@ -116,7 +113,6 @@ def __init__( start_label, fallback_label, label_priority, - validation_stage, condition_handler, verbose, handlers, @@ -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, @@ -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 ` 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 @@ -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, @@ -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, @@ -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 ` 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 @@ -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( diff --git a/dff/pipeline/types.py b/dff/pipeline/types.py index 39584a303..69448478e 100644 --- a/dff/pipeline/types.py +++ b/dff/pipeline/types.py @@ -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]]]], diff --git a/dff/script/core/context.py b/dff/script/core/context.py index 78ee18072..cc3622633 100644 --- a/dff/script/core/context.py +++ b/dff/script/core/context.py @@ -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 diff --git a/dff/script/core/normalization.py b/dff/script/core/normalization.py index a0f91407f..2d8ba9eb9 100644 --- a/dff/script/core/normalization.py +++ b/dff/script/core/normalization.py @@ -21,7 +21,7 @@ Pipeline = ForwardRef("Pipeline") -def normalize_label(label: NodeLabelType, default_flow_label: LabelType = "") -> Union[Callable, NodeLabel3Type]: +def normalize_label(label: NodeLabelType, default_flow_label: LabelType = "") -> Optional[Union[Callable, NodeLabel3Type]]: """ The function that is used for normalization of :py:const:`default_flow_label `. @@ -30,7 +30,8 @@ def normalize_label(label: NodeLabelType, default_flow_label: LabelType = "") -> 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. + if Callable is returned, the normalized result is returned + if label can not be normalized, None is returned. """ if callable(label): @@ -60,6 +61,8 @@ def get_label_handler(ctx: Context, pipeline: Pipeline, *args, **kwargs) -> Node elif isinstance(label, tuple) and len(label) == 3: flow_label = label[0] or default_flow_label return (flow_label, label[1], label[2]) + else: + return None def normalize_condition(condition: ConditionType) -> Callable: From d1e2c1f87d37ed1ec5d6862e8ac846ff2415961e Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 22 Nov 2023 11:08:52 +0100 Subject: [PATCH 02/76] some errors fixed --- dff/pipeline/pipeline/actor.py | 12 ++++++------ tests/stats/test_defaults.py | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index a557bd293..425a41149 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -364,7 +364,7 @@ def validate_script(self, verbose: bool = True): # validate labeling for label in node.transitions.keys(): if callable(label): - validate_callable(label, "label", flow_name, node_name, error_msgs, verbose, [Context, Pipeline]) + 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: @@ -392,10 +392,10 @@ def validate_script(self, verbose: bool = True): # validate responses if callable(node.response): - validate_callable(node.response, "response", flow_name, node_name, error_msgs, verbose, [Context, Pipeline], Message) - elif node.response is not None and type(node.response) != Message: + 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): msg = ( - f"Expected type of response is {Message}, got type(response)={type(node.response)}, " + f"Expected type of response is subclass of {Message}, 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) @@ -403,7 +403,7 @@ def validate_script(self, verbose: bool = True): # 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], bool) + 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}, " @@ -416,7 +416,7 @@ def validate_script(self, verbose: bool = True): 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], Context) + 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}, " diff --git a/tests/stats/test_defaults.py b/tests/stats/test_defaults.py index bd4982eaf..ff2e0265b 100644 --- a/tests/stats/test_defaults.py +++ b/tests/stats/test_defaults.py @@ -21,9 +21,7 @@ ], ) async def test_get_current_label(context: Context, expected: set): - pipeline = Pipeline.from_script( - {"greeting_flow": {"start_node": {}}}, ("greeting_flow", "start_node"), validation_stage=False - ) + pipeline = Pipeline.from_script({"greeting_flow": {"start_node": {}}}, ("greeting_flow", "start_node")) runtime_info = ExtraHandlerRuntimeInfo( func=lambda x: x, stage="BEFORE", From 6664120959e47089a93c12bfd0b2b5d645f1eac6 Mon Sep 17 00:00:00 2001 From: pseusys Date: Fri, 24 Nov 2023 01:10:50 +0100 Subject: [PATCH 03/76] tests fixed --- dff/pipeline/pipeline/actor.py | 49 +++++++++++++++++----------- tests/pipeline/test_pipeline.py | 14 +++----- tests/script/core/test_actor.py | 29 ++-------------- tutorials/script/core/3_responses.py | 4 +-- tutorials/script/core/8_misc.py | 2 -- tutorials/utils/1_cache.py | 2 -- tutorials/utils/2_lru_cache.py | 2 -- 7 files changed, 39 insertions(+), 63 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index 425a41149..bfb16a76a 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -24,7 +24,7 @@ """ import inspect import logging -from typing import Union, Callable, Optional, Dict, List, Any, ForwardRef +from typing import Union, Callable, Optional, Dict, List, Any, ForwardRef, Type import copy from dff.utils.turn_caching import cache_clear @@ -33,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__) @@ -57,31 +57,42 @@ def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = N logger.error(msg, exc_info=exception) -def validate_callable(callable: Callable, name: str, flow_label: str, node_label: str, error_msgs: list, verbose: bool, expected: Optional[list] = None, rtrn = None): +def types_match(type1: Type, type2: Type) -> bool: + if type1 == type2: + return True + elif type(type1) == ForwardRef or type(type2) == ForwardRef: + type1_name = type1.__forward_arg__ if type(type1) == ForwardRef else type1.__name__ + type2_name = type2.__forward_arg__ if type(type2) == 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, verbose: bool, expected_types: Optional[List[Type]] = None, return_type: Optional[Type] = None): signature = inspect.signature(callable) - if expected is not None: + if expected_types is not None: params = list(signature.parameters.values()) - if len(params) != len(expected): + if len(params) != len(expected_types): msg = ( f"Incorrect parameter number of {name}={callable.__name__}: " - f"should be {len(expected)}, found {len(params)}, " + 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, verbose) for idx, param in enumerate(params): - if param.annotation != inspect.Parameter.empty and param.annotation != expected[idx]: + 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[idx]}, found {param.annotation}, " + 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, verbose) - if rtrn is not None: - rtrn_type = signature.return_annotation - if rtrn_type != inspect.Parameter.empty and rtrn_type != rtrn: + if return_type is not None: + retrun_annotation = signature.return_annotation + if retrun_annotation != inspect.Parameter.empty and not types_match(retrun_annotation, return_type): msg = ( f"Incorrect return type annotation of {name}={callable.__name__}: " - f"should be {len(rtrn)}, found {len(rtrn_type)}, " + f"should be {return_type}, found {retrun_annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_handler(error_msgs, msg, None, verbose) @@ -118,11 +129,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}") @@ -374,15 +385,15 @@ def validate_script(self, verbose: bool = True): ) error_handler(error_msgs, msg, None, verbose) continue - norm_fl, norm_nl, _ = norm_label - if norm_fl not in self.script.keys(): + norm_flow_label, norm_node_label, _ = norm_label + if norm_flow_label not in self.script.keys(): msg = ( - f"Flow label {norm_fl} can not be found for label={label}, " + 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_nl not in flow.keys() or norm_nl not in self.script[norm_fl].keys(): + elif norm_node_label not in self.script[norm_flow_label].keys(): msg = ( - f"Node label {norm_nl} can not be found for label={label}, " + 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: diff --git a/tests/pipeline/test_pipeline.py b/tests/pipeline/test_pipeline.py index d4fbe20a3..455223740 100644 --- a/tests/pipeline/test_pipeline.py +++ b/tests/pipeline/test_pipeline.py @@ -16,24 +16,20 @@ def test_pretty_format(): tutorial_module.pipeline.pretty_format() -@pytest.mark.parametrize("validation", (True, False)) -def test_from_script_with_validation(validation): +def test_from_script_with_validation(): def response(ctx, pipeline: Pipeline): raise RuntimeError() script = {"": {"": {RESPONSE: response, TRANSITIONS: {"": cnd.true()}}}} - if validation: - with pytest.raises(ValueError): - _ = Pipeline.from_script(script=script, start_label=("", ""), validation_stage=validation) - else: - _ = Pipeline.from_script(script=script, start_label=("", ""), validation_stage=validation) + with pytest.raises(ValueError): + _ = Pipeline.from_script(script=script, start_label=("", "")) def test_script_getting_and_setting(): - script = {"old_flow": {"": {RESPONSE: lambda c, p: Message(), TRANSITIONS: {"": cnd.true()}}}} + script = {"old_flow": {"": {RESPONSE: lambda c, p, _, __: Message(), TRANSITIONS: {"": cnd.true()}}}} pipeline = Pipeline.from_script(script=script, start_label=("old_flow", "")) - new_script = {"new_flow": {"": {RESPONSE: lambda c, p: Message(), TRANSITIONS: {"": cnd.false()}}}} + new_script = {"new_flow": {"": {RESPONSE: lambda c, p, _, __: Message(), TRANSITIONS: {"": cnd.false()}}}} pipeline.set_actor(script=new_script, start_label=("new_flow", "")) assert list(pipeline.script.script.keys())[0] == list(new_script.keys())[0] diff --git a/tests/script/core/test_actor.py b/tests/script/core/test_actor.py index 5dd14e27a..0d390016a 100644 --- a/tests/script/core/test_actor.py +++ b/tests/script/core/test_actor.py @@ -34,20 +34,10 @@ def negative_test(samples, custom_class): raise Exception(f"sample={sample} can not be passed") -def std_func(ctx, actor, *args, **kwargs): - pass - - def fake_label(ctx: Context, actor, *args, **kwargs): - if not ctx.validation: - return ("123", "123", 0) return ("flow", "node1", 1) -def raised_response(ctx: Context, actor, *args, **kwargs): - raise Exception("") - - def test_actor(): try: # fail of start label @@ -67,12 +57,6 @@ def test_actor(): raise Exception("can not be passed: fail of missing node") except ValueError: pass - try: - # fail of condition returned type - Pipeline.from_script({"flow": {"node1": {TRANSITIONS: {"node1": std_func}}}}, start_label=("flow", "node1")) - raise Exception("can not be passed: fail of condition returned type") - except ValueError: - pass try: # fail of response returned Callable pipeline = Pipeline.from_script( @@ -84,15 +68,6 @@ def test_actor(): raise Exception("can not be passed: fail of response returned Callable") except ValueError: pass - try: - # failed response - Pipeline.from_script( - {"flow": {"node1": {RESPONSE: raised_response, TRANSITIONS: {repeat(): true()}}}}, - start_label=("flow", "node1"), - ) - raise Exception("can not be passed: failed response") - except ValueError: - pass # empty ctx stability pipeline = Pipeline.from_script( @@ -101,7 +76,7 @@ def test_actor(): ctx = Context() pipeline.actor(pipeline, ctx) - # fake label stability + # fake label stability # TODO: what does it mean? pipeline = Pipeline.from_script( {"flow": {"node1": {TRANSITIONS: {fake_label: true()}}}}, start_label=("flow", "node1") ) @@ -210,7 +185,7 @@ def test_call_limit(): } # script = {"flow": {"node1": {TRANSITIONS: {"node1": true()}}}} ctx = Context() - pipeline = Pipeline.from_script(script=script, start_label=("flow1", "node1"), validation_stage=False) + pipeline = Pipeline.from_script(script=script, start_label=("flow1", "node1")) for i in range(4): ctx.add_request(Message(text="req1")) ctx = pipeline.actor(pipeline, ctx) diff --git a/tutorials/script/core/3_responses.py b/tutorials/script/core/3_responses.py index 3d12a75ce..04fdf6118 100644 --- a/tutorials/script/core/3_responses.py +++ b/tutorials/script/core/3_responses.py @@ -126,7 +126,7 @@ def fallback_trace_response(ctx: Context, _: Pipeline, *args, **kwargs) -> Messa Message(text="Sorry, I can not talk about music now."), ), # node2 -> node3 (Message(text="Ok, goodbye."), Message(text="BYE")), # node3 -> node4 - (Message(text="Hi"), Message(text="Hi, what is up?")), # node4 -> node1 + (Message(text="Hi"), Message(text="Hello, how are you?")), # node4 -> node1 ( Message(text="stop"), Message( @@ -161,7 +161,7 @@ def fallback_trace_response(ctx: Context, _: Pipeline, *args, **kwargs) -> Messa } ), ), # f_n->f_n - (Message(text="Hi"), Message(text="Hello, how are you?")), # fallback_node -> node1 + (Message(text="Hi"), Message(text="Hi, what is up?")), # fallback_node -> node1 ( Message(text="I'm fine, how are you?"), Message(text="Good. What do you want to talk about?"), diff --git a/tutorials/script/core/8_misc.py b/tutorials/script/core/8_misc.py index 7756eca12..a0bd3a268 100644 --- a/tutorials/script/core/8_misc.py +++ b/tutorials/script/core/8_misc.py @@ -34,8 +34,6 @@ # %% def custom_response(ctx: Context, _: Pipeline, *args, **kwargs) -> Message: - if ctx.validation: - return Message() current_node = ctx.current_node return Message(text=f"ctx.last_label={ctx.last_label}: current_node.misc={current_node.misc}") diff --git a/tutorials/utils/1_cache.py b/tutorials/utils/1_cache.py index bc1235551..db10468fb 100644 --- a/tutorials/utils/1_cache.py +++ b/tutorials/utils/1_cache.py @@ -48,8 +48,6 @@ def cached_response(_): def response(ctx: Context, _, *__, **___) -> Message: - if ctx.validation: - return Message() return Message( text=f"{cached_response(1)}-{cached_response(2)}-" f"{cached_response(1)}-{cached_response(2)}" diff --git a/tutorials/utils/2_lru_cache.py b/tutorials/utils/2_lru_cache.py index 0aaf326bc..408379ad5 100644 --- a/tutorials/utils/2_lru_cache.py +++ b/tutorials/utils/2_lru_cache.py @@ -47,8 +47,6 @@ def cached_response(_): def response(ctx: Context, _, *__, **___) -> Message: - if ctx.validation: - return Message() return Message( text=f"{cached_response(1)}-{cached_response(2)}-{cached_response(3)}-" f"{cached_response(2)}-{cached_response(1)}" From 4c2792b3410780f8d500c6ba702f013c7cd8cef0 Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 27 Nov 2023 08:41:15 +0100 Subject: [PATCH 04/76] lint applied --- dff/pipeline/pipeline/actor.py | 61 ++++++++++++++++++++++++++------ dff/script/core/normalization.py | 4 ++- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index bfb16a76a..d303059f9 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -60,15 +60,24 @@ def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = N def types_match(type1: Type, type2: Type) -> bool: if type1 == type2: return True - elif type(type1) == ForwardRef or type(type2) == ForwardRef: - type1_name = type1.__forward_arg__ if type(type1) == ForwardRef else type1.__name__ - type2_name = type2.__forward_arg__ if type(type2) == ForwardRef else type2.__name__ + 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, verbose: bool, expected_types: Optional[List[Type]] = None, return_type: Optional[Type] = None): +def validate_callable( + callable: Callable, + name: str, + flow_label: str, + node_label: str, + error_msgs: list, + verbose: bool, + expected_types: Optional[List[Type]] = None, + return_type: Optional[Type] = None, +): signature = inspect.signature(callable) if expected_types is not None: params = list(signature.parameters.values()) @@ -375,7 +384,9 @@ def validate_script(self, verbose: bool = True): # 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]) + 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: @@ -403,10 +414,20 @@ def validate_script(self, verbose: bool = True): # validate responses if callable(node.response): - validate_callable(node.response, "response", flow_name, node_name, error_msgs, verbose, [Context, Pipeline, Any, Any], Message) + 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): msg = ( - f"Expected type of response is subclass of {Message}, got type(response)={type(node.response)}, " + 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) @@ -414,7 +435,16 @@ def validate_script(self, verbose: bool = True): # 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) + 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}, " @@ -424,10 +454,21 @@ def validate_script(self, verbose: bool = True): 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 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) + 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}, " diff --git a/dff/script/core/normalization.py b/dff/script/core/normalization.py index 2d8ba9eb9..12722b020 100644 --- a/dff/script/core/normalization.py +++ b/dff/script/core/normalization.py @@ -21,7 +21,9 @@ Pipeline = ForwardRef("Pipeline") -def normalize_label(label: NodeLabelType, default_flow_label: LabelType = "") -> Optional[Union[Callable, NodeLabel3Type]]: +def normalize_label( + label: NodeLabelType, default_flow_label: LabelType = "" +) -> Optional[Union[Callable, NodeLabel3Type]]: """ The function that is used for normalization of :py:const:`default_flow_label `. From 2f59d24f6a674c1dfb02f8e04fc3c5af6a9ce59c Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 27 Nov 2023 08:42:14 +0100 Subject: [PATCH 05/76] blank line removed --- .../messengers/web_api_interface/3_load_testing_with_locust.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/messengers/web_api_interface/3_load_testing_with_locust.py b/tutorials/messengers/web_api_interface/3_load_testing_with_locust.py index a2ae9a647..ccd1e13a7 100644 --- a/tutorials/messengers/web_api_interface/3_load_testing_with_locust.py +++ b/tutorials/messengers/web_api_interface/3_load_testing_with_locust.py @@ -23,7 +23,7 @@ ```python import sys from locust import main - + sys.argv = ["locust", "-f", {file_name}] main.main() ``` From ed21272a229623dc7369e462216070573f92096f Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 27 Nov 2023 09:06:16 +0100 Subject: [PATCH 06/76] format applied --- tutorials/script/core/3_responses.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tutorials/script/core/3_responses.py b/tutorials/script/core/3_responses.py index 5ed853cde..df334cbb3 100644 --- a/tutorials/script/core/3_responses.py +++ b/tutorials/script/core/3_responses.py @@ -182,7 +182,10 @@ def fallback_trace_response( } ), ), # f_n->f_n - (Message(text="Hi"), Message(text="Hi, what is up?")), # fallback_node -> node1 + ( + Message(text="Hi"), + Message(text="Hi, what is up?"), + ), # fallback_node -> node1 ( Message(text="I'm fine, how are you?"), Message(text="Good. What do you want to talk about?"), From bcaa5cc11c2039f676510b5f805112ae022bb4bd Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 27 Nov 2023 10:16:49 +0100 Subject: [PATCH 07/76] function docs provided --- dff/pipeline/pipeline/actor.py | 47 +++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index d303059f9..1502a252a 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -24,7 +24,7 @@ """ import inspect import logging -from typing import Union, Callable, Optional, Dict, List, Any, ForwardRef, Type +from typing import Tuple, Union, Callable, Optional, Dict, List, Any, ForwardRef, Type import copy from dff.utils.turn_caching import cache_clear @@ -58,6 +58,14 @@ def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = N 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`. + 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: @@ -74,10 +82,25 @@ def validate_callable( flow_label: str, node_label: str, error_msgs: list, - verbose: bool, + logging_flag: bool = True, expected_types: Optional[List[Type]] = None, - return_type: Optional[Type] = None, + return_type: Optional[Tuple[Type]] = None, ): + """ + 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. + :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()) @@ -87,7 +110,7 @@ def validate_callable( 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, verbose) + 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 = ( @@ -95,16 +118,16 @@ def validate_callable( 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, verbose) - if return_type is not None: + 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): + if retrun_annotation != inspect.Parameter.empty and not types_match(retrun_annotation, return_type[0]): msg = ( f"Incorrect return type annotation of {name}={callable.__name__}: " - f"should be {return_type}, found {retrun_annotation}, " + f"should be {return_type[0]}, found {retrun_annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) - error_handler(error_msgs, msg, None, verbose) + error_handler(error_msgs, msg, None, logging_flag) class Actor: @@ -422,7 +445,7 @@ def validate_script(self, verbose: bool = True): error_msgs, verbose, [Context, Pipeline, Any, Any], - Message, + (Message,), ) elif node.response is not None and not issubclass(type(node.response), Message): msg = ( @@ -443,7 +466,7 @@ def validate_script(self, verbose: bool = True): error_msgs, verbose, [Context, Pipeline, Any, Any], - bool, + (bool,), ) else: msg = ( @@ -467,7 +490,7 @@ def validate_script(self, verbose: bool = True): error_msgs, verbose, [Context, Pipeline, Any, Any], - Context, + (Context,), ) else: msg = ( From e21435015d0fd779f2c4884436f710aad4f80266 Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 27 Nov 2023 10:17:13 +0100 Subject: [PATCH 08/76] lint applied --- dff/pipeline/pipeline/actor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index 1502a252a..284d7149e 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -61,7 +61,7 @@ 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`. 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. From 5ddc70c98a6f89fc6d1e93ae4c8057a0365f5b85 Mon Sep 17 00:00:00 2001 From: pseusys Date: Fri, 1 Dec 2023 11:58:02 +0100 Subject: [PATCH 09/76] reviews fixed --- dff/pipeline/pipeline/actor.py | 44 +++++++++++++++++++--------------- dff/script/core/context.py | 2 +- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index 284d7149e..9b3dcf96b 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -83,7 +83,7 @@ def validate_callable( node_label: str, error_msgs: list, logging_flag: bool = True, - expected_types: Optional[List[Type]] = None, + expected_types: Optional[Tuple[Type, ...]] = None, return_type: Optional[Tuple[Type]] = None, ): """ @@ -96,7 +96,7 @@ def validate_callable( :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. :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. + :param expected_types: Tuple 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`. @@ -120,11 +120,11 @@ def validate_callable( ) 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]): + return_annotation = signature.return_annotation + if return_annotation != inspect.Parameter.empty and not types_match(return_annotation, return_type[0]): msg = ( f"Incorrect return type annotation of {name}={callable.__name__}: " - f"should be {return_type[0]}, found {retrun_annotation}, " + f"should be {return_type[0]}, found {return_annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_handler(error_msgs, msg, None, logging_flag) @@ -399,7 +399,7 @@ def _choose_label( chosen_label = self.fallback_label return chosen_label - def validate_script(self, verbose: bool = True): + def validate_script(self, logging_flag: bool = True): # TODO: script has to not contain priority == -inf, because it uses for miss values error_msgs = [] for flow_name, flow in self.script.items(): @@ -408,7 +408,13 @@ def validate_script(self, verbose: bool = True): for label in node.transitions.keys(): if callable(label): validate_callable( - label, "label", flow_name, node_name, error_msgs, verbose, [Context, Pipeline, Any, Any] + label, + "label", + flow_name, + node_name, + error_msgs, + logging_flag, + (Context, Pipeline, Any, Any), ) else: norm_label = normalize_label(label, flow_name) @@ -417,7 +423,7 @@ def validate_script(self, verbose: bool = True): 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) + error_handler(error_msgs, msg, None, logging_flag) continue norm_flow_label, norm_node_label, _ = norm_label if norm_flow_label not in self.script.keys(): @@ -433,7 +439,7 @@ def validate_script(self, verbose: bool = True): else: msg = None if msg is not None: - error_handler(error_msgs, msg, None, verbose) + error_handler(error_msgs, msg, None, logging_flag) # validate responses if callable(node.response): @@ -443,17 +449,17 @@ def validate_script(self, verbose: bool = True): flow_name, node_name, error_msgs, - verbose, - [Context, Pipeline, Any, Any], + logging_flag, + (Context, Pipeline, Any, Any), (Message,), ) - elif node.response is not None and not issubclass(type(node.response), Message): + elif node.response is not None and not isinstance(node.response, Message): msg = ( 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) + error_handler(error_msgs, msg, None, logging_flag) # validate conditions for label, condition in node.transitions.items(): @@ -464,8 +470,8 @@ def validate_script(self, verbose: bool = True): flow_name, node_name, error_msgs, - verbose, - [Context, Pipeline, Any, Any], + logging_flag, + (Context, Pipeline, Any, Any), (bool,), ) else: @@ -474,7 +480,7 @@ def validate_script(self, verbose: bool = True): 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) + error_handler(error_msgs, msg, None, logging_flag) # validate pre_transitions- and pre_response_processing for place, functions in zip( @@ -488,8 +494,8 @@ def validate_script(self, verbose: bool = True): flow_name, node_name, error_msgs, - verbose, - [Context, Pipeline, Any, Any], + logging_flag, + (Context, Pipeline, Any, Any), (Context,), ) else: @@ -498,7 +504,7 @@ def validate_script(self, verbose: bool = True): 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) + error_handler(error_msgs, msg, None, logging_flag) return error_msgs diff --git a/dff/script/core/context.py b/dff/script/core/context.py index cc3622633..0e70f4ffb 100644 --- a/dff/script/core/context.py +++ b/dff/script/core/context.py @@ -131,7 +131,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)}" From e2b04f7e268c82a330e34f630d94ee40279c476a Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 6 Feb 2024 19:01:55 +0100 Subject: [PATCH 10/76] actor updated --- dff/pipeline/pipeline/actor.py | 101 +++++++++++++-------------------- 1 file changed, 39 insertions(+), 62 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index 9b3dcf96b..2ab7fbd35 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -24,7 +24,7 @@ """ import inspect import logging -from typing import Tuple, Union, Callable, Optional, Dict, List, Any, ForwardRef, Type +from typing import Union, Callable, Optional, Dict, List, Any, ForwardRef import copy from dff.utils.turn_caching import cache_clear @@ -41,6 +41,13 @@ Pipeline = ForwardRef("Pipeline") +USER_FUNCTION_TYPES = { + "label": ((Context, Pipeline, Any, Any), None), + "response": ((Context, Pipeline, Any, Any), Message), + "condition": ((Context, Pipeline, Any, Any), bool), + "processing": ((Context, Pipeline, Any, Any), Context), +} + 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. @@ -57,35 +64,13 @@ 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`. - 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, - expected_types: Optional[Tuple[Type, ...]] = None, - return_type: Optional[Tuple[Type]] = None, -): +) -> List: """ 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). @@ -100,34 +85,37 @@ def validate_callable( 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`. + :return: list of produced error messages. """ + error_msgs = list() 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: - return_annotation = signature.return_annotation - if return_annotation != inspect.Parameter.empty and not types_match(return_annotation, return_type[0]): + function_type = name if name in USER_FUNCTION_TYPES.keys() else "processing" + arguments_type, return_type = USER_FUNCTION_TYPES[function_type] + params = list(signature.parameters.values()) + if len(params) != len(arguments_type): + msg = ( + f"Incorrect parameter number of {name}={callable.__name__}: " + f"should be {len(arguments_type)}, 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 param.annotation != arguments_type[idx]: msg = ( - f"Incorrect return type annotation of {name}={callable.__name__}: " - f"should be {return_type[0]}, found {return_annotation}, " + f"Incorrect {idx} parameter annotation of {name}={callable.__name__}: " + f"should be {arguments_type[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) + return_annotation = signature.return_annotation + if return_annotation != inspect.Parameter.empty and return_annotation != return_type: + msg = ( + f"Incorrect return type annotation of {name}={callable.__name__}: " + f"should be {return_type}, found {return_annotation}, " + f"error was found in (flow_label, node_label)={(flow_label, node_label)}" + ) + error_handler(error_msgs, msg, None, logging_flag) + return error_msgs class Actor: @@ -407,14 +395,12 @@ def validate_script(self, logging_flag: bool = True): # validate labeling for label in node.transitions.keys(): if callable(label): - validate_callable( + error_msgs += validate_callable( label, "label", flow_name, node_name, - error_msgs, - logging_flag, - (Context, Pipeline, Any, Any), + logging_flag ) else: norm_label = normalize_label(label, flow_name) @@ -443,15 +429,12 @@ def validate_script(self, logging_flag: bool = True): # validate responses if callable(node.response): - validate_callable( + error_msgs += validate_callable( node.response, "response", flow_name, node_name, - error_msgs, logging_flag, - (Context, Pipeline, Any, Any), - (Message,), ) elif node.response is not None and not isinstance(node.response, Message): msg = ( @@ -464,15 +447,12 @@ def validate_script(self, logging_flag: bool = True): # validate conditions for label, condition in node.transitions.items(): if callable(condition): - validate_callable( + error_msgs += validate_callable( condition, "condition", flow_name, node_name, - error_msgs, logging_flag, - (Context, Pipeline, Any, Any), - (bool,), ) else: msg = ( @@ -488,15 +468,12 @@ def validate_script(self, logging_flag: bool = True): ): for name, function in functions.items(): if callable(function): - validate_callable( + error_msgs += validate_callable( function, f"pre_{place}_processing {name}", flow_name, node_name, - error_msgs, logging_flag, - (Context, Pipeline, Any, Any), - (Context,), ) else: msg = ( From 5f9ff22153c7e4b59d0ab84e77d5d95b91cb04aa Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 6 Feb 2024 19:18:32 +0100 Subject: [PATCH 11/76] aftermerge --- dff/pipeline/pipeline/actor.py | 76 +++++++++------------------------ tutorials/script/core/8_misc.py | 2 +- 2 files changed, 22 insertions(+), 56 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index c05cbe6e4..f979fe0a6 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -22,11 +22,7 @@ .. figure:: /_static/drawio/dfe/user_actor.png """ -<<<<<<< HEAD -import inspect -======= from __future__ import annotations ->>>>>>> dev import logging import asyncio from typing import Union, Callable, Optional, Dict, List, TYPE_CHECKING @@ -38,22 +34,22 @@ from dff.script.core.context import Context from dff.script.core.script import Script, Node -from dff.script.core.normalization import normalize_label +from dff.script.core.normalization import normalize_label, normalize_response from dff.script.core.keywords import GLOBAL, LOCAL from dff.pipeline.service.utils import wrap_sync_function_in_async logger = logging.getLogger(__name__) if TYPE_CHECKING: + import inspect from dff.pipeline.pipeline.pipeline import Pipeline - -USER_FUNCTION_TYPES = { - "label": ((Context, Pipeline, Any, Any), None), - "response": ((Context, Pipeline, Any, Any), Message), - "condition": ((Context, Pipeline, Any, Any), bool), - "processing": ((Context, Pipeline, Any, Any), Context), -} + USER_FUNCTION_TYPES = { + "label": ((Context, Pipeline), None), + "response": ((Context, Pipeline), Message), + "condition": ((Context, Pipeline), bool), + "processing": ((Context, Pipeline), Context), + } def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = None, logging_flag: bool = True): """ @@ -106,22 +102,23 @@ def validate_callable( 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 param.annotation != arguments_type[idx]: + if TYPE_CHECKING: + for idx, param in enumerate(params): + if param.annotation != inspect.Parameter.empty and param.annotation != arguments_type[idx]: + msg = ( + f"Incorrect {idx} parameter annotation of {name}={callable.__name__}: " + f"should be {arguments_type[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) + return_annotation = signature.return_annotation + if return_annotation != inspect.Parameter.empty and return_annotation != return_type: msg = ( - f"Incorrect {idx} parameter annotation of {name}={callable.__name__}: " - f"should be {arguments_type[idx]}, found {param.annotation}, " + f"Incorrect return type annotation of {name}={callable.__name__}: " + f"should be {return_type}, found {return_annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_handler(error_msgs, msg, None, logging_flag) - return_annotation = signature.return_annotation - if return_annotation != inspect.Parameter.empty and return_annotation != return_type: - msg = ( - f"Incorrect return type annotation of {name}={callable.__name__}: " - f"should be {return_type}, found {return_annotation}, " - f"error was found in (flow_label, node_label)={(flow_label, node_label)}" - ) - error_handler(error_msgs, msg, None, logging_flag) return error_msgs @@ -493,7 +490,6 @@ def validate_script(self, logging_flag: bool = True): if msg is not None: error_handler(error_msgs, msg, None, logging_flag) -<<<<<<< HEAD # validate responses if callable(node.response): error_msgs += validate_callable( @@ -502,36 +498,6 @@ def validate_script(self, logging_flag: bool = True): flow_name, node_name, logging_flag, -======= - 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 = asyncio.run(wrap_sync_function_in_async(response_func, ctx, pipeline)) - if not isinstance(response_result, Message): - 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)}" ->>>>>>> dev ) elif node.response is not None and not isinstance(node.response, Message): msg = ( diff --git a/tutorials/script/core/8_misc.py b/tutorials/script/core/8_misc.py index f0c5e6385..5d82558af 100644 --- a/tutorials/script/core/8_misc.py +++ b/tutorials/script/core/8_misc.py @@ -37,7 +37,7 @@ def custom_response(ctx: Context, _: Pipeline) -> Message: current_node = ctx.current_node return Message( text=f"ctx.last_label={ctx.last_label}: " - f"current_node.misc={current_node.misc}" + f"current_node.misc={current_node.misc if current_node is not None else None}" ) From 809fc45b18dd4cbbb87a6ec08ce18ef3ba8fa218 Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 6 Feb 2024 19:32:32 +0100 Subject: [PATCH 12/76] inspect moved out of scope --- dff/pipeline/pipeline/actor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index f979fe0a6..4c0491b61 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -23,6 +23,7 @@ .. figure:: /_static/drawio/dfe/user_actor.png """ from __future__ import annotations +import inspect import logging import asyncio from typing import Union, Callable, Optional, Dict, List, TYPE_CHECKING @@ -41,7 +42,6 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: - import inspect from dff.pipeline.pipeline.pipeline import Pipeline USER_FUNCTION_TYPES = { From cab2d88569b15dbd17500657d09705ff621dd165 Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 6 Feb 2024 19:42:23 +0100 Subject: [PATCH 13/76] user functions types mocked --- dff/pipeline/pipeline/actor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index 4c0491b61..bd01b7bb2 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -50,6 +50,13 @@ "condition": ((Context, Pipeline), bool), "processing": ((Context, Pipeline), Context), } +else: + USER_FUNCTION_TYPES = { + "label": ((Any, Any), Any), + "response": ((Any, Any), Any), + "condition": ((Any, Any), Any), + "processing": ((Any, Any), Any), + } def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = None, logging_flag: bool = True): """ From 42d9112d5d92edc5a3b35520f9961a1488ce25eb Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 6 Feb 2024 21:38:53 +0100 Subject: [PATCH 14/76] any import added --- dff/pipeline/pipeline/actor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index bd01b7bb2..7825399d1 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -51,6 +51,8 @@ "processing": ((Context, Pipeline), Context), } else: + from typing import Any + USER_FUNCTION_TYPES = { "label": ((Any, Any), Any), "response": ((Any, Any), Any), From e7eaacbcfa2d118fae495b4bddcc13b958037b89 Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 7 Feb 2024 21:54:10 +0100 Subject: [PATCH 15/76] most of the errors fixed --- tests/pipeline/test_pipeline.py | 10 ---------- tests/script/core/test_actor.py | 4 +--- tutorials/utils/1_cache.py | 2 +- tutorials/utils/2_lru_cache.py | 2 +- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/tests/pipeline/test_pipeline.py b/tests/pipeline/test_pipeline.py index 455223740..bb0e80b17 100644 --- a/tests/pipeline/test_pipeline.py +++ b/tests/pipeline/test_pipeline.py @@ -16,16 +16,6 @@ def test_pretty_format(): tutorial_module.pipeline.pretty_format() -def test_from_script_with_validation(): - def response(ctx, pipeline: Pipeline): - raise RuntimeError() - - script = {"": {"": {RESPONSE: response, TRANSITIONS: {"": cnd.true()}}}} - - with pytest.raises(ValueError): - _ = Pipeline.from_script(script=script, start_label=("", "")) - - def test_script_getting_and_setting(): script = {"old_flow": {"": {RESPONSE: lambda c, p, _, __: Message(), TRANSITIONS: {"": cnd.true()}}}} pipeline = Pipeline.from_script(script=script, start_label=("old_flow", "")) diff --git a/tests/script/core/test_actor.py b/tests/script/core/test_actor.py index d66910842..c48b980fb 100644 --- a/tests/script/core/test_actor.py +++ b/tests/script/core/test_actor.py @@ -40,8 +40,6 @@ def std_func(ctx, pipeline): def fake_label(ctx: Context, pipeline): - if not ctx.validation: - return ("123", "123", 0) return ("flow", "node1", 1) @@ -197,7 +195,7 @@ async def test_call_limit(): }, } # script = {"flow": {"node1": {TRANSITIONS: {"node1": true()}}}} - pipeline = Pipeline.from_script(script=script, start_label=("flow1", "node1"), validation_stage=False) + pipeline = Pipeline.from_script(script=script, start_label=("flow1", "node1")) for i in range(4): await pipeline._run_pipeline(Message(text="req1"), 0) if limit_errors: diff --git a/tutorials/utils/1_cache.py b/tutorials/utils/1_cache.py index eccdf3923..4464ca286 100644 --- a/tutorials/utils/1_cache.py +++ b/tutorials/utils/1_cache.py @@ -47,7 +47,7 @@ def cached_response(_): return external_data["counter"] -def response(ctx: Context, _, *__, **___) -> Message: +def response(_: Context, __: Pipeline) -> Message: return Message( text=f"{cached_response(1)}-{cached_response(2)}-" f"{cached_response(1)}-{cached_response(2)}" diff --git a/tutorials/utils/2_lru_cache.py b/tutorials/utils/2_lru_cache.py index 32bd556eb..71f274d4e 100644 --- a/tutorials/utils/2_lru_cache.py +++ b/tutorials/utils/2_lru_cache.py @@ -46,7 +46,7 @@ def cached_response(_): return external_data["counter"] -def response(ctx: Context, _, *__, **___) -> Message: +def response(_: Context, __: Pipeline) -> Message: return Message( text=f"{cached_response(1)}-{cached_response(2)}-{cached_response(3)}-" f"{cached_response(2)}-{cached_response(1)}" From 79ae0b139e0bcd2a432a49f812ef982ab59a712d Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 7 Feb 2024 21:58:59 +0100 Subject: [PATCH 16/76] last failing test fixed --- tests/pipeline/test_pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/pipeline/test_pipeline.py b/tests/pipeline/test_pipeline.py index bb0e80b17..6b424b313 100644 --- a/tests/pipeline/test_pipeline.py +++ b/tests/pipeline/test_pipeline.py @@ -17,9 +17,9 @@ def test_pretty_format(): def test_script_getting_and_setting(): - script = {"old_flow": {"": {RESPONSE: lambda c, p, _, __: Message(), TRANSITIONS: {"": cnd.true()}}}} + script = {"old_flow": {"": {RESPONSE: lambda _, __: Message(), TRANSITIONS: {"": cnd.true()}}}} pipeline = Pipeline.from_script(script=script, start_label=("old_flow", "")) - new_script = {"new_flow": {"": {RESPONSE: lambda c, p, _, __: Message(), TRANSITIONS: {"": cnd.false()}}}} + new_script = {"new_flow": {"": {RESPONSE: lambda _, __: Message(), TRANSITIONS: {"": cnd.false()}}}} pipeline.set_actor(script=new_script, start_label=("new_flow", "")) assert list(pipeline.script.script.keys())[0] == list(new_script.keys())[0] From 913887edc67151afb81b89fef60545b9db2b667b Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 7 Feb 2024 22:00:40 +0100 Subject: [PATCH 17/76] lint applied --- dff/pipeline/pipeline/actor.py | 9 ++------- tests/pipeline/test_pipeline.py | 1 - tutorials/script/core/8_misc.py | 3 ++- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index 7825399d1..6cde5b8f0 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -60,6 +60,7 @@ "processing": ((Any, Any), Any), } + 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. @@ -467,13 +468,7 @@ def validate_script(self, logging_flag: bool = True): # validate labeling for label in node.transitions.keys(): if callable(label): - error_msgs += validate_callable( - label, - "label", - flow_name, - node_name, - logging_flag - ) + error_msgs += validate_callable(label, "label", flow_name, node_name, logging_flag) else: norm_label = normalize_label(label, flow_name) if norm_label is None: diff --git a/tests/pipeline/test_pipeline.py b/tests/pipeline/test_pipeline.py index 6b424b313..8cf96f551 100644 --- a/tests/pipeline/test_pipeline.py +++ b/tests/pipeline/test_pipeline.py @@ -1,5 +1,4 @@ import importlib -import pytest from dff.script import Message from tests.test_utils import get_path_from_tests_to_current_dir diff --git a/tutorials/script/core/8_misc.py b/tutorials/script/core/8_misc.py index 5d82558af..69ef69afc 100644 --- a/tutorials/script/core/8_misc.py +++ b/tutorials/script/core/8_misc.py @@ -35,9 +35,10 @@ # %% def custom_response(ctx: Context, _: Pipeline) -> Message: current_node = ctx.current_node + current_misc = current_node.misc if current_node is not None else None return Message( text=f"ctx.last_label={ctx.last_label}: " - f"current_node.misc={current_node.misc if current_node is not None else None}" + f"current_node.misc={current_misc}" ) From 9c848dc34d087c515b3e91200468e8cd20549c65 Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 18 Mar 2024 13:05:20 +0100 Subject: [PATCH 18/76] repeat import added --- tests/script/core/test_actor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/script/core/test_actor.py b/tests/script/core/test_actor.py index b5d9239f1..9c4dcc7f5 100644 --- a/tests/script/core/test_actor.py +++ b/tests/script/core/test_actor.py @@ -12,6 +12,7 @@ Message, ) from dff.script.conditions import true +from dff.script.labels import repeat def positive_test(samples, custom_class): From d19ea744809354c7c4e9e3ecfa888d11c41e9476 Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 26 Mar 2024 20:19:45 +0100 Subject: [PATCH 19/76] in-script validation first attempt --- dff/pipeline/pipeline/actor.py | 179 +-------------------------------- dff/script/core/script.py | 178 +++++++++++++++++++++++++++++++- 2 files changed, 178 insertions(+), 179 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index 6cde5b8f0..a669a81ef 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -42,95 +42,9 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: + from dff.script.core.context import Context from dff.pipeline.pipeline.pipeline import Pipeline - USER_FUNCTION_TYPES = { - "label": ((Context, Pipeline), None), - "response": ((Context, Pipeline), Message), - "condition": ((Context, Pipeline), bool), - "processing": ((Context, Pipeline), Context), - } -else: - from typing import Any - - USER_FUNCTION_TYPES = { - "label": ((Any, Any), Any), - "response": ((Any, Any), Any), - "condition": ((Any, Any), Any), - "processing": ((Any, Any), Any), - } - - -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) - - -def validate_callable( - callable: Callable, - name: str, - flow_label: str, - node_label: str, - logging_flag: bool = True, -) -> List: - """ - 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. - :param logging_flag: The flag which defines whether logging is necessary. Defaults to `True`. - :param expected_types: Tuple 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`. - :return: list of produced error messages. - """ - error_msgs = list() - signature = inspect.signature(callable) - function_type = name if name in USER_FUNCTION_TYPES.keys() else "processing" - arguments_type, return_type = USER_FUNCTION_TYPES[function_type] - params = list(signature.parameters.values()) - if len(params) != len(arguments_type): - msg = ( - f"Incorrect parameter number of {name}={callable.__name__}: " - f"should be {len(arguments_type)}, found {len(params)}, " - f"error was found in (flow_label, node_label)={(flow_label, node_label)}" - ) - error_handler(error_msgs, msg, None, logging_flag) - if TYPE_CHECKING: - for idx, param in enumerate(params): - if param.annotation != inspect.Parameter.empty and param.annotation != arguments_type[idx]: - msg = ( - f"Incorrect {idx} parameter annotation of {name}={callable.__name__}: " - f"should be {arguments_type[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) - return_annotation = signature.return_annotation - if return_annotation != inspect.Parameter.empty and return_annotation != return_type: - msg = ( - f"Incorrect return type annotation of {name}={callable.__name__}: " - f"should be {return_type}, found {return_annotation}, " - f"error was found in (flow_label, node_label)={(flow_label, node_label)}" - ) - error_handler(error_msgs, msg, None, logging_flag) - return error_msgs - class Actor: """ @@ -460,97 +374,6 @@ def _choose_label( chosen_label = self.fallback_label return chosen_label - def validate_script(self, logging_flag: bool = True): - # TODO: script has to not contain priority == -inf, because it uses for miss values - error_msgs = [] - for flow_name, flow in self.script.items(): - for node_name, node in flow.items(): - # validate labeling - for label in node.transitions.keys(): - if callable(label): - error_msgs += validate_callable(label, "label", flow_name, node_name, logging_flag) - 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, logging_flag) - 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, logging_flag) - - # validate responses - if callable(node.response): - error_msgs += validate_callable( - node.response, - "response", - flow_name, - node_name, - logging_flag, - ) - elif node.response is not None and not isinstance(node.response, Message): - msg = ( - 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, logging_flag) - - # validate conditions - for label, condition in node.transitions.items(): - if callable(condition): - error_msgs += validate_callable( - condition, - "condition", - flow_name, - node_name, - logging_flag, - ) - 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, logging_flag) - - # 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): - error_msgs += validate_callable( - function, - f"pre_{place}_processing {name}", - flow_name, - node_name, - logging_flag, - ) - 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, logging_flag) - return error_msgs - async def default_condition_handler( condition: Callable, ctx: Context, pipeline: Pipeline diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 25c60cc5c..6052be7dd 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -7,8 +7,9 @@ """ # %% from __future__ import annotations +import inspect import logging -from typing import Callable, Optional, Any, Dict, Union, TYPE_CHECKING +from typing import Callable, List, Optional, Any, Dict, Union, TYPE_CHECKING from pydantic import BaseModel, field_validator, validate_call @@ -21,9 +22,89 @@ from dff.script.core.context import Context from dff.pipeline.pipeline.pipeline import Pipeline + USER_FUNCTION_TYPES = { + "label": ((Context, Pipeline), None), + "response": ((Context, Pipeline), Message), + "condition": ((Context, Pipeline), bool), + "processing": ((Context, Pipeline), Context), + } +else: + from typing import Any + + USER_FUNCTION_TYPES = { + "label": ((Any, Any), Any), + "response": ((Any, Any), Any), + "condition": ((Any, Any), Any), + "processing": ((Any, Any), Any), + } + logger = logging.getLogger(__name__) +def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = None): + """ + 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) + logger.error(msg, exc_info=exception) + + +def validate_callable(callable: Callable, name: str, flow_label: str, node_label: str) -> List: + """ + 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. + :param logging_flag: The flag which defines whether logging is necessary. Defaults to `True`. + :param expected_types: Tuple 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`. + :return: list of produced error messages. + """ + error_msgs = list() + signature = inspect.signature(callable) + function_type = name if name in USER_FUNCTION_TYPES.keys() else "processing" + arguments_type, return_type = USER_FUNCTION_TYPES[function_type] + params = list(signature.parameters.values()) + if len(params) != len(arguments_type): + msg = ( + f"Incorrect parameter number of {name}={callable.__name__}: " + f"should be {len(arguments_type)}, found {len(params)}, " + f"error was found in (flow_label, node_label)={(flow_label, node_label)}" + ) + error_handler(error_msgs, msg, None) + if TYPE_CHECKING: + for idx, param in enumerate(params): + if param.annotation != inspect.Parameter.empty and param.annotation != arguments_type[idx]: + msg = ( + f"Incorrect {idx} parameter annotation of {name}={callable.__name__}: " + f"should be {arguments_type[idx]}, found {param.annotation}, " + f"error was found in (flow_label, node_label)={(flow_label, node_label)}" + ) + error_handler(error_msgs, msg, None) + return_annotation = signature.return_annotation + if return_annotation != inspect.Parameter.empty and return_annotation != return_type: + msg = ( + f"Incorrect return type annotation of {name}={callable.__name__}: " + f"should be {return_type}, found {return_annotation}, " + f"error was found in (flow_label, node_label)={(flow_label, node_label)}" + ) + error_handler(error_msgs, msg, None) + return error_msgs + + class Node(BaseModel, extra="forbid", validate_assignment=True): """ The class for the `Node` object. @@ -80,6 +161,101 @@ def normalize_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[ script[Keywords.GLOBAL] = {Keywords.GLOBAL: script[Keywords.GLOBAL]} return script + @field_validator("script", mode="after") + @classmethod + @validate_call + def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[LabelType, Dict[str, Any]]]: + error_msgs = [] + for flow_name, flow in script.items(): + for node_name, node in flow.items(): + # validate labeling + for label in node.transitions.keys(): + if callable(label): + error_msgs += validate_callable(label, "label", flow_name, node_name) + 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) + continue + norm_flow_label, norm_node_label, _ = norm_label + if norm_flow_label not in 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 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) + + # validate responses + if callable(node.response): + error_msgs += validate_callable( + node.response, + "response", + flow_name, + node_name, + ) + elif node.response is not None and not isinstance(node.response, Message): + msg = ( + 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) + + # validate conditions + for label, condition in node.transitions.items(): + if callable(condition): + error_msgs += validate_callable( + condition, + "condition", + flow_name, + node_name, + ) + 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) + + # 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): + error_msgs += validate_callable( + function, + f"pre_{place}_processing {name}", + flow_name, + node_name, + ) + 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) + if error_msgs: + raise ValueError( + f"Found {len(error_msgs)} errors: " + " ".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) + ) + else: + return script + def __getitem__(self, key): return self.script[key] From 48cbc9ee909b6933117bfebe28ebbba0d15f99d5 Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 27 Mar 2024 11:28:01 +0100 Subject: [PATCH 20/76] validate_script reference removed --- dff/pipeline/pipeline/pipeline.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/dff/pipeline/pipeline/pipeline.py b/dff/pipeline/pipeline/pipeline.py index 4a3a7cd71..4f2aea97c 100644 --- a/dff/pipeline/pipeline/pipeline.py +++ b/dff/pipeline/pipeline/pipeline.py @@ -297,14 +297,7 @@ def set_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(verbose) - 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": From 4d81d3158c06c78505ec96ad187e19cf4bb5b38e Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 27 Mar 2024 11:30:27 +0100 Subject: [PATCH 21/76] runtime imports added instead of typechecking imports --- dff/script/core/script.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index e284fd1a7..7b9cff254 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -23,22 +23,6 @@ from dff.script.core.context import Context from dff.pipeline.pipeline.pipeline import Pipeline - USER_FUNCTION_TYPES = { - "label": ((Context, Pipeline), None), - "response": ((Context, Pipeline), Message), - "condition": ((Context, Pipeline), bool), - "processing": ((Context, Pipeline), Context), - } -else: - from typing import Any - - USER_FUNCTION_TYPES = { - "label": ((Any, Any), Any), - "response": ((Any, Any), Any), - "condition": ((Any, Any), Any), - "processing": ((Any, Any), Any), - } - logger = logging.getLogger(__name__) @@ -74,6 +58,16 @@ def validate_callable(callable: Callable, name: str, flow_label: str, node_label If `None` or contains more or less than 1 element, return type check is skipped. Defaults to `None`. :return: list of produced error messages. """ + from dff.script.core.context import Context + from dff.pipeline.pipeline.pipeline import Pipeline + + USER_FUNCTION_TYPES = { + "label": ((Context, Pipeline), None), + "response": ((Context, Pipeline), Message), + "condition": ((Context, Pipeline), bool), + "processing": ((Context, Pipeline), Context), + } + error_msgs = list() signature = inspect.signature(callable) function_type = name if name in USER_FUNCTION_TYPES.keys() else "processing" From c3d5d25686d4dcb3af4d80b78dd7f24a9db70b43 Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 27 Mar 2024 11:33:39 +0100 Subject: [PATCH 22/76] lint fixed --- dff/pipeline/pipeline/actor.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index 269ce37a7..b02ac21aa 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -24,7 +24,6 @@ """ from __future__ import annotations -import inspect import logging import asyncio from typing import Union, Callable, Optional, Dict, List, TYPE_CHECKING @@ -43,7 +42,6 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: - from dff.script.core.context import Context from dff.pipeline.pipeline.pipeline import Pipeline From aab8020b966b5fe31c1d29b99bd4614da86f579e Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 27 Mar 2024 14:08:30 +0100 Subject: [PATCH 23/76] script creation test changed --- tests/script/core/test_script.py | 37 +++++++++++--------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/tests/script/core/test_script.py b/tests/script/core/test_script.py index 72f6a0175..b5fd4e257 100644 --- a/tests/script/core/test_script.py +++ b/tests/script/core/test_script.py @@ -1,6 +1,8 @@ # %% import itertools +import pytest + from dff.script import ( GLOBAL, TRANSITIONS, @@ -12,6 +14,7 @@ Node, Message, ) +from dff.utils.testing.toy_script import TOY_SCRIPT, MULTIFLOW_SCRIPT def positive_test(samples, custom_class): @@ -97,16 +100,6 @@ def node_test(node: Node): def test_node_exec(): - # node = Node( - # **{ - # TRANSITIONS.name.lower(): {"node": std_func}, - # RESPONSE.name.lower(): "text", - # PROCESSING.name.lower(): {1: std_func}, - # PRE_TRANSITIONS_PROCESSING.name.lower(): {1: std_func}, - # MISC.name.lower(): {"key": "val"}, - # } - # ) - # node_test(node) node = Node( **{ TRANSITIONS.name.lower(): {"node": std_func}, @@ -123,18 +116,12 @@ def test_script(): script_test(PRE_RESPONSE_PROCESSING) -def script_test(pre_response_proc): - node_template = { - TRANSITIONS: {"node": std_func}, - RESPONSE: Message("text"), - pre_response_proc: {1: std_func}, - PRE_TRANSITIONS_PROCESSING: {1: std_func}, - MISC: {"key": "val"}, - } - script = Script(script={GLOBAL: node_template.copy(), "flow": {"node": node_template.copy()}}) - node_test(script[GLOBAL][GLOBAL]) - node_test(script["flow"]["node"]) - assert list(script.keys()) == [GLOBAL, "flow"] - assert len(script.values()) == 2 - assert list(script) == [GLOBAL, "flow"] - assert len(list(script.items())) == 2 +@pytest.mark.parametrize( + ["script"], + [ + (TOY_SCRIPT,), + (MULTIFLOW_SCRIPT,), + ], +) +def script_test(script): + Script(script=script) From 247bac3d357f7391f52538f5376c781d33ad19e1 Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 27 Mar 2024 14:14:26 +0100 Subject: [PATCH 24/76] global import removed, test function fixed --- tests/script/core/test_script.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/script/core/test_script.py b/tests/script/core/test_script.py index b5fd4e257..b4f5dd371 100644 --- a/tests/script/core/test_script.py +++ b/tests/script/core/test_script.py @@ -4,7 +4,6 @@ import pytest from dff.script import ( - GLOBAL, TRANSITIONS, RESPONSE, MISC, @@ -112,10 +111,6 @@ def test_node_exec(): node_test(node) -def test_script(): - script_test(PRE_RESPONSE_PROCESSING) - - @pytest.mark.parametrize( ["script"], [ @@ -123,5 +118,5 @@ def test_script(): (MULTIFLOW_SCRIPT,), ], ) -def script_test(script): +def test_script(script): Script(script=script) From fe55f02239f3de8d71f33cbbce4639894fd01531 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 28 Mar 2024 13:21:43 +0300 Subject: [PATCH 25/76] make `normalize_label` raise on TypeError --- dff/script/core/normalization.py | 9 ++++----- dff/script/core/script.py | 18 +++++------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/dff/script/core/normalization.py b/dff/script/core/normalization.py index 4c7b55ab6..46d5837ff 100644 --- a/dff/script/core/normalization.py +++ b/dff/script/core/normalization.py @@ -23,7 +23,7 @@ def normalize_label( label: NodeLabelType, default_flow_label: LabelType = "" -) -> Optional[Union[Callable[[Context, Pipeline], NodeLabel3Type], NodeLabel3Type]]: +) -> Union[Callable[[Context, Pipeline], NodeLabel3Type], NodeLabel3Type]: """ The function that is used for normalization of :py:const:`default_flow_label `. @@ -31,9 +31,7 @@ def normalize_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 - if label can not be normalized, None is returned. + :return: Result of the label normalization """ if callable(label): @@ -64,7 +62,8 @@ def get_label_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: flow_label = label[0] or default_flow_label return (flow_label, label[1], label[2]) else: - return None + raise TypeError(f"Label '{label!r}' is of incorrect type. It has to follow the `NodeLabelType`:\n" + f"{NodeLabelType!r}") def normalize_condition(condition: ConditionType) -> Callable[[Context, Pipeline], bool]: diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 7b9cff254..7e076d457 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -168,23 +168,15 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L if callable(label): error_msgs += validate_callable(label, "label", flow_name, node_name) else: - norm_label = normalize_label(label, flow_name) - if norm_label is None: + flow_label, node_label, _ = label + if flow_label not in script.keys(): msg = ( - f"Label can not be normalized for label={label}, " + f"Flow label {flow_label} can not be found for label={label}, " f"error was found in (flow_label, node_label)={(flow_name, node_name)}" ) - error_handler(error_msgs, msg, None) - continue - norm_flow_label, norm_node_label, _ = norm_label - if norm_flow_label not in 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 script[norm_flow_label].keys(): + elif node_label not in script[flow_label].keys(): msg = ( - f"Node label {norm_node_label} can not be found for label={label}, " + f"Node label {node_label} can not be found for label={label}, " f"error was found in (flow_label, node_label)={(flow_name, node_name)}" ) else: From bd64bc83e8d937d05c6981a880f6c24beae9e2a0 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 28 Mar 2024 13:22:00 +0300 Subject: [PATCH 26/76] remove irrelevant docs --- dff/script/core/script.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 7e076d457..2a5ecbde4 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -50,12 +50,6 @@ def validate_callable(callable: Callable, name: str, flow_label: str, node_label :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. - :param logging_flag: The flag which defines whether logging is necessary. Defaults to `True`. - :param expected_types: Tuple 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`. :return: list of produced error messages. """ from dff.script.core.context import Context From 1c761d0ce718c7bbf5974baf09b309eeb71a20f8 Mon Sep 17 00:00:00 2001 From: pseusys Date: Thu, 28 Mar 2024 13:16:29 +0100 Subject: [PATCH 27/76] processing returns None --- dff/script/core/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 2a5ecbde4..b18b22fb2 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -59,7 +59,7 @@ def validate_callable(callable: Callable, name: str, flow_label: str, node_label "label": ((Context, Pipeline), None), "response": ((Context, Pipeline), Message), "condition": ((Context, Pipeline), bool), - "processing": ((Context, Pipeline), Context), + "processing": ((Context, Pipeline), None), } error_msgs = list() From 303b0f330896e079a6ff6e79d8fb8beb5f1a2ec2 Mon Sep 17 00:00:00 2001 From: pseusys Date: Thu, 28 Mar 2024 14:41:01 +0100 Subject: [PATCH 28/76] names replaced with enum, type_checking removed --- dff/script/core/script.py | 63 ++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index b18b22fb2..c303f8e55 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -8,6 +8,7 @@ # %% from __future__ import annotations +from enum import StrEnum import inspect import logging from typing import Callable, List, Optional, Any, Dict, Union, TYPE_CHECKING @@ -26,6 +27,14 @@ logger = logging.getLogger(__name__) +class UserFunctionType(StrEnum): + LABEL = "label" + RESPONSE = "response" + CONDITION = "condition" + TRANSITION_PROCESSING = "pre_transitions_processing" + RESPONSE_PROCESSING = "pre_response_processing" + + def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = None): """ This function handles errors during :py:class:`~dff.script.Script` validation. @@ -41,13 +50,13 @@ def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = N logger.error(msg, exc_info=exception) -def validate_callable(callable: Callable, name: str, flow_label: str, node_label: str) -> List: +def validate_callable(callable: Callable, func_type: UserFunctionType, flow_label: str, node_label: str) -> List: """ 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 func_type: 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). :return: list of produced error messages. @@ -56,41 +65,40 @@ def validate_callable(callable: Callable, name: str, flow_label: str, node_label from dff.pipeline.pipeline.pipeline import Pipeline USER_FUNCTION_TYPES = { - "label": ((Context, Pipeline), None), - "response": ((Context, Pipeline), Message), - "condition": ((Context, Pipeline), bool), - "processing": ((Context, Pipeline), None), + UserFunctionType.LABEL: ((Context, Pipeline), None), + UserFunctionType.RESPONSE: ((Context, Pipeline), Message), + UserFunctionType.CONDITION: ((Context, Pipeline), bool), + UserFunctionType.RESPONSE_PROCESSING: ((Context, Pipeline), None), + UserFunctionType.TRANSITION_PROCESSING: ((Context, Pipeline), None), } error_msgs = list() signature = inspect.signature(callable) - function_type = name if name in USER_FUNCTION_TYPES.keys() else "processing" - arguments_type, return_type = USER_FUNCTION_TYPES[function_type] + arguments_type, return_type = USER_FUNCTION_TYPES[func_type] params = list(signature.parameters.values()) if len(params) != len(arguments_type): msg = ( - f"Incorrect parameter number of {name}={callable.__name__}: " + f"Incorrect parameter number of {func_type.name}={callable.__name__}: " f"should be {len(arguments_type)}, found {len(params)}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_handler(error_msgs, msg, None) - if TYPE_CHECKING: - for idx, param in enumerate(params): - if param.annotation != inspect.Parameter.empty and param.annotation != arguments_type[idx]: - msg = ( - f"Incorrect {idx} parameter annotation of {name}={callable.__name__}: " - f"should be {arguments_type[idx]}, found {param.annotation}, " - f"error was found in (flow_label, node_label)={(flow_label, node_label)}" - ) - error_handler(error_msgs, msg, None) - return_annotation = signature.return_annotation - if return_annotation != inspect.Parameter.empty and return_annotation != return_type: + for idx, param in enumerate(params): + if param.annotation != inspect.Parameter.empty and param.annotation != arguments_type[idx]: msg = ( - f"Incorrect return type annotation of {name}={callable.__name__}: " - f"should be {return_type}, found {return_annotation}, " + f"Incorrect {idx} parameter annotation of {func_type.name}={callable.__name__}: " + f"should be {arguments_type[idx]}, found {param.annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_handler(error_msgs, msg, None) + return_annotation = signature.return_annotation + if return_annotation != inspect.Parameter.empty and return_annotation != return_type: + msg = ( + f"Incorrect return type annotation of {func_type.name}={callable.__name__}: " + f"should be {return_type}, found {return_annotation}, " + f"error was found in (flow_label, node_label)={(flow_label, node_label)}" + ) + error_handler(error_msgs, msg, None) return error_msgs @@ -160,7 +168,7 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L # validate labeling for label in node.transitions.keys(): if callable(label): - error_msgs += validate_callable(label, "label", flow_name, node_name) + error_msgs += validate_callable(label, UserFunctionType.LABEL, flow_name, node_name) else: flow_label, node_label, _ = label if flow_label not in script.keys(): @@ -182,7 +190,7 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L if callable(node.response): error_msgs += validate_callable( node.response, - "response", + UserFunctionType.RESPONSE, flow_name, node_name, ) @@ -199,7 +207,7 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L if callable(condition): error_msgs += validate_callable( condition, - "condition", + UserFunctionType.CONDITION, flow_name, node_name, ) @@ -213,13 +221,14 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L # validate pre_transitions- and pre_response_processing for place, functions in zip( - ("transitions", "response"), (node.pre_transitions_processing, node.pre_response_processing) + (UserFunctionType.TRANSITION_PROCESSING, UserFunctionType.RESPONSE_PROCESSING), + (node.pre_transitions_processing, node.pre_response_processing) ): for name, function in functions.items(): if callable(function): error_msgs += validate_callable( function, - f"pre_{place}_processing {name}", + place, flow_name, node_name, ) From 9244921b57f9bedbdbc9891b8e9856dab291d1eb Mon Sep 17 00:00:00 2001 From: pseusys Date: Thu, 28 Mar 2024 14:50:03 +0100 Subject: [PATCH 29/76] strenum removed --- dff/script/core/script.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index c303f8e55..3eb8506c8 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -8,7 +8,7 @@ # %% from __future__ import annotations -from enum import StrEnum +from enum import Enum import inspect import logging from typing import Callable, List, Optional, Any, Dict, Union, TYPE_CHECKING @@ -27,7 +27,7 @@ logger = logging.getLogger(__name__) -class UserFunctionType(StrEnum): +class UserFunctionType(str, Enum): LABEL = "label" RESPONSE = "response" CONDITION = "condition" @@ -78,7 +78,7 @@ def validate_callable(callable: Callable, func_type: UserFunctionType, flow_labe params = list(signature.parameters.values()) if len(params) != len(arguments_type): msg = ( - f"Incorrect parameter number of {func_type.name}={callable.__name__}: " + f"Incorrect parameter number of {func_type}={callable.__name__}: " f"should be {len(arguments_type)}, found {len(params)}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) @@ -86,7 +86,7 @@ def validate_callable(callable: Callable, func_type: UserFunctionType, flow_labe for idx, param in enumerate(params): if param.annotation != inspect.Parameter.empty and param.annotation != arguments_type[idx]: msg = ( - f"Incorrect {idx} parameter annotation of {func_type.name}={callable.__name__}: " + f"Incorrect {idx} parameter annotation of {func_type}={callable.__name__}: " f"should be {arguments_type[idx]}, found {param.annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) @@ -94,7 +94,7 @@ def validate_callable(callable: Callable, func_type: UserFunctionType, flow_labe return_annotation = signature.return_annotation if return_annotation != inspect.Parameter.empty and return_annotation != return_type: msg = ( - f"Incorrect return type annotation of {func_type.name}={callable.__name__}: " + f"Incorrect return type annotation of {func_type}={callable.__name__}: " f"should be {return_type}, found {return_annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) From 0278ffc4659883d173704e6a21a1620ec1287d4d Mon Sep 17 00:00:00 2001 From: pseusys Date: Thu, 28 Mar 2024 15:03:00 +0100 Subject: [PATCH 30/76] local flow label fixed --- dff/script/core/script.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 3eb8506c8..6c40f009b 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -171,6 +171,8 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L error_msgs += validate_callable(label, UserFunctionType.LABEL, flow_name, node_name) else: flow_label, node_label, _ = label + if flow_label == "": + flow_label = flow_name if flow_label not in script.keys(): msg = ( f"Flow label {flow_label} can not be found for label={label}, " From db37dd6ac6161d6cbd188e3ba0de2b6d8aef422a Mon Sep 17 00:00:00 2001 From: pseusys Date: Thu, 28 Mar 2024 15:23:54 +0100 Subject: [PATCH 31/76] type comparison fixed in case of strings --- dff/script/core/script.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 6c40f009b..567e75b7f 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -11,7 +11,7 @@ from enum import Enum import inspect import logging -from typing import Callable, List, Optional, Any, Dict, Union, TYPE_CHECKING +from typing import Callable, List, Optional, Any, Dict, Type, Union, TYPE_CHECKING from pydantic import BaseModel, field_validator, validate_call @@ -50,6 +50,24 @@ def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = N logger.error(msg, exc_info=exception) +def types_equal(signature_type: Any, expected_type: Type) -> bool: + """ + This function checks equality of signature type with expected type. + Three cases are handled. If no signature is present, it is presumed that types are equal. + If signature is a type, it is compared with expected type as is. + If signature is a string, it is compared with expected type name. + + :param signature_type: type received from function signature. + :param expected_type: expected type - a class. + :return: true if types are equal, false otherwise. + """ + signature_empty = signature_type == inspect.Parameter.empty + types_match = signature_type == expected_type + expected_string = signature_type == expected_type.__name__ + return signature_empty or types_match or expected_string + + + def validate_callable(callable: Callable, func_type: UserFunctionType, flow_label: str, node_label: str) -> List: """ This function validates a function during :py:class:`~dff.script.Script` validation. @@ -84,18 +102,17 @@ def validate_callable(callable: Callable, func_type: UserFunctionType, flow_labe ) error_handler(error_msgs, msg, None) for idx, param in enumerate(params): - if param.annotation != inspect.Parameter.empty and param.annotation != arguments_type[idx]: + if types_equal(param.annotation, arguments_type[idx]): msg = ( f"Incorrect {idx} parameter annotation of {func_type}={callable.__name__}: " f"should be {arguments_type[idx]}, found {param.annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_handler(error_msgs, msg, None) - return_annotation = signature.return_annotation - if return_annotation != inspect.Parameter.empty and return_annotation != return_type: + if types_equal(signature.return_annotation, return_type): msg = ( f"Incorrect return type annotation of {func_type}={callable.__name__}: " - f"should be {return_type}, found {return_annotation}, " + f"should be {return_type}, found {signature.return_annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_handler(error_msgs, msg, None) From 5c129b3d6348ebc9d8cb2f791f7e1c74715dda7c Mon Sep 17 00:00:00 2001 From: pseusys Date: Thu, 28 Mar 2024 15:31:51 +0100 Subject: [PATCH 32/76] type description added --- dff/script/core/script.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 567e75b7f..a8a216298 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -105,14 +105,16 @@ def validate_callable(callable: Callable, func_type: UserFunctionType, flow_labe if types_equal(param.annotation, arguments_type[idx]): msg = ( f"Incorrect {idx} parameter annotation of {func_type}={callable.__name__}: " - f"should be {arguments_type[idx]}, found {param.annotation}, " + f"should be {arguments_type[idx]} ({type(arguments_type[idx])}), " + f"found {param.annotation} ({param.annotation}), " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_handler(error_msgs, msg, None) if types_equal(signature.return_annotation, return_type): msg = ( f"Incorrect return type annotation of {func_type}={callable.__name__}: " - f"should be {return_type}, found {signature.return_annotation}, " + f"should be {return_type} ({type(return_type)}), " + f"found {signature.return_annotation} ({type(signature.return_annotation)}), " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_handler(error_msgs, msg, None) From e4591fab0776b801ef0fc554e6de77158a240259 Mon Sep 17 00:00:00 2001 From: pseusys Date: Thu, 28 Mar 2024 16:17:46 +0100 Subject: [PATCH 33/76] condition fixed --- dff/script/core/script.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index a8a216298..c9c8ec6a4 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -102,15 +102,15 @@ def validate_callable(callable: Callable, func_type: UserFunctionType, flow_labe ) error_handler(error_msgs, msg, None) for idx, param in enumerate(params): - if types_equal(param.annotation, arguments_type[idx]): + if not types_equal(param.annotation, arguments_type[idx]): msg = ( f"Incorrect {idx} parameter annotation of {func_type}={callable.__name__}: " f"should be {arguments_type[idx]} ({type(arguments_type[idx])}), " - f"found {param.annotation} ({param.annotation}), " + f"found {param.annotation} ({type(param.annotation)}), " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_handler(error_msgs, msg, None) - if types_equal(signature.return_annotation, return_type): + if not types_equal(signature.return_annotation, return_type): msg = ( f"Incorrect return type annotation of {func_type}={callable.__name__}: " f"should be {return_type} ({type(return_type)}), " From 00b9a679625b9b365ba0e7f3cde3c077e6e5e93d Mon Sep 17 00:00:00 2001 From: pseusys Date: Fri, 29 Mar 2024 11:14:27 +0100 Subject: [PATCH 34/76] none matching improved --- dff/script/core/script.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index c9c8ec6a4..b50635fcd 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -61,9 +61,10 @@ def types_equal(signature_type: Any, expected_type: Type) -> bool: :param expected_type: expected type - a class. :return: true if types are equal, false otherwise. """ + expected_str = expected_type.__name__ if hasattr(expected_type, "__name__") else str(expected_type) signature_empty = signature_type == inspect.Parameter.empty types_match = signature_type == expected_type - expected_string = signature_type == expected_type.__name__ + expected_string = signature_type == expected_str return signature_empty or types_match or expected_string From 2632c7ad436c96a2ed9a04bfaa3eef70fff14f86 Mon Sep 17 00:00:00 2001 From: pseusys Date: Fri, 29 Mar 2024 11:24:54 +0100 Subject: [PATCH 35/76] label normalization returned --- dff/script/core/script.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index b50635fcd..7d4c4dcf8 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -190,17 +190,21 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L if callable(label): error_msgs += validate_callable(label, UserFunctionType.LABEL, flow_name, node_name) else: - flow_label, node_label, _ = label - if flow_label == "": - flow_label = flow_name - if flow_label not in script.keys(): + norm_label = normalize_label(label, flow_name) + if norm_label is None: msg = ( - f"Flow label {flow_label} can not be found for label={label}, " + f"Label can not be normalized for label={label}, " f"error was found in (flow_label, node_label)={(flow_name, node_name)}" ) - elif node_label not in script[flow_label].keys(): + norm_flow_label, norm_node_label, _ = norm_label + if norm_flow_label not in script.keys(): msg = ( - f"Node label {node_label} can not be found for label={label}, " + 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 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: From a343b1a0d4fc06f782f43c189d0c9192825fe732 Mon Sep 17 00:00:00 2001 From: pseusys Date: Fri, 29 Mar 2024 11:45:05 +0100 Subject: [PATCH 36/76] label functions signature changed --- dff/script/core/script.py | 2 +- dff/script/labels/std_labels.py | 28 ++++++++++++------------- tests/script/core/test_normalization.py | 6 +++--- tutorials/script/core/4_transitions.py | 16 ++++++++++---- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 7d4c4dcf8..7a6642436 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -84,7 +84,7 @@ def validate_callable(callable: Callable, func_type: UserFunctionType, flow_labe from dff.pipeline.pipeline.pipeline import Pipeline USER_FUNCTION_TYPES = { - UserFunctionType.LABEL: ((Context, Pipeline), None), + UserFunctionType.LABEL: ((Context, Pipeline), NodeLabelType), UserFunctionType.RESPONSE: ((Context, Pipeline), Message), UserFunctionType.CONDITION: ((Context, Pipeline), bool), UserFunctionType.RESPONSE_PROCESSING: ((Context, Pipeline), None), diff --git a/dff/script/labels/std_labels.py b/dff/script/labels/std_labels.py index 663c89461..643453b32 100644 --- a/dff/script/labels/std_labels.py +++ b/dff/script/labels/std_labels.py @@ -13,13 +13,13 @@ from __future__ import annotations from typing import Optional, Callable, TYPE_CHECKING -from dff.script import Context, NodeLabel3Type +from dff.script import Context, NodeLabelType if TYPE_CHECKING: from dff.pipeline.pipeline.pipeline import Pipeline -def repeat(priority: Optional[float] = None) -> Callable: +def repeat(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabelType]: """ Returns transition handler that takes :py:class:`.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. @@ -30,7 +30,7 @@ def repeat(priority: Optional[float] = None) -> Callable: :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def repeat_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def repeat_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: current_priority = pipeline.actor.label_priority if priority is None else priority if len(ctx.labels) >= 1: flow_label, label = list(ctx.labels.values())[-1] @@ -41,7 +41,7 @@ def repeat_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Typ return repeat_transition_handler -def previous(priority: Optional[float] = None) -> Callable: +def previous(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabelType]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. @@ -53,7 +53,7 @@ def previous(priority: Optional[float] = None) -> Callable: :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def previous_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def previous_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: current_priority = pipeline.actor.label_priority if priority is None else priority if len(ctx.labels) >= 2: flow_label, label = list(ctx.labels.values())[-2] @@ -66,7 +66,7 @@ def previous_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3T return previous_transition_handler -def to_start(priority: Optional[float] = None) -> Callable: +def to_start(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabelType]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. @@ -77,14 +77,14 @@ def to_start(priority: Optional[float] = None) -> Callable: :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def to_start_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def to_start_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: current_priority = pipeline.actor.label_priority if priority is None else priority return (*pipeline.actor.start_label[:2], current_priority) return to_start_transition_handler -def to_fallback(priority: Optional[float] = None) -> Callable: +def to_fallback(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabelType]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. @@ -95,7 +95,7 @@ def to_fallback(priority: Optional[float] = None) -> Callable: :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def to_fallback_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def to_fallback_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: current_priority = pipeline.actor.label_priority if priority is None else priority return (*pipeline.actor.fallback_label[:2], current_priority) @@ -108,7 +108,7 @@ def _get_label_by_index_shifting( priority: Optional[float] = None, increment_flag: bool = True, cyclicality_flag: bool = True, -) -> NodeLabel3Type: +) -> NodeLabelType: """ Function that returns node label from the context and pipeline after shifting the index. @@ -137,7 +137,7 @@ def _get_label_by_index_shifting( return (flow_label, labels[label_index], current_priority) -def forward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> Callable: +def forward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> Callable[[Context, Pipeline], NodeLabelType]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. @@ -150,7 +150,7 @@ def forward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> (e.g the element with `index = len(labels)` has `index = 0`). Defaults to `True`. """ - def forward_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def forward_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: return _get_label_by_index_shifting( ctx, pipeline, priority, increment_flag=True, cyclicality_flag=cyclicality_flag ) @@ -158,7 +158,7 @@ def forward_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Ty return forward_transition_handler -def backward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> Callable: +def backward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> Callable[[Context, Pipeline], NodeLabelType]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. @@ -171,7 +171,7 @@ def backward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> (e.g the element with `index = len(labels)` has `index = 0`). Defaults to `True`. """ - def back_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def back_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: return _get_label_by_index_shifting( ctx, pipeline, priority, increment_flag=False, cyclicality_flag=cyclicality_flag ) diff --git a/tests/script/core/test_normalization.py b/tests/script/core/test_normalization.py index fc871b889..d6c39c325 100644 --- a/tests/script/core/test_normalization.py +++ b/tests/script/core/test_normalization.py @@ -12,7 +12,7 @@ Context, Script, Node, - NodeLabel3Type, + NodeLabelType, Message, ) from dff.script.labels import repeat @@ -36,10 +36,10 @@ def create_env() -> Tuple[Context, Pipeline]: def test_normalize_label(): ctx, actor = create_env() - def true_label_func(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def true_label_func(ctx: Context, pipeline: Pipeline) -> NodeLabelType: return ("flow", "node1", 1) - def false_label_func(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def false_label_func(ctx: Context, pipeline: Pipeline) -> NodeLabelType: return ("flow", "node2", 1) n_f = normalize_label(true_label_func) diff --git a/tutorials/script/core/4_transitions.py b/tutorials/script/core/4_transitions.py index 123d226da..7b5572a59 100644 --- a/tutorials/script/core/4_transitions.py +++ b/tutorials/script/core/4_transitions.py @@ -19,7 +19,7 @@ # %% import re -from dff.script import TRANSITIONS, RESPONSE, Context, NodeLabel3Type, Message +from dff.script import TRANSITIONS, RESPONSE, Context, NodeLabelType, Message import dff.script.conditions as cnd import dff.script.labels as lbl from dff.pipeline import Pipeline @@ -31,22 +31,30 @@ # %% [markdown] """ -Let's define the functions with a special type of return value: +Transition functions should return one of the "label" types. +The label types can be tuples, strings and callables. +In this particular example we'll be using 3-part tuple labels +(also known as `NodeLabel3Type`s). + +We'll define the functions with a special type of return value: NodeLabel3Type == tuple[str, str, float] which means that transition returns a `tuple` with flow name, node name and priority. + +In general, all possible transition function return types +can be described as `NodeLabelType` type. """ # %% -def greeting_flow_n2_transition(_: Context, __: Pipeline) -> NodeLabel3Type: +def greeting_flow_n2_transition(_: Context, __: Pipeline) -> NodeLabelType: return ("greeting_flow", "node2", 1.0) def high_priority_node_transition(flow_label, label): - def transition(_: Context, __: Pipeline) -> NodeLabel3Type: + def transition(_: Context, __: Pipeline) -> NodeLabelType: return (flow_label, label, 2.0) return transition From 040b84e7fcd84ddbd3e464f8a827619489efa2b8 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 29 Mar 2024 14:08:35 +0300 Subject: [PATCH 37/76] separate errors with new lines --- dff/script/core/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 7a6642436..e3a2ec0aa 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -267,7 +267,7 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L error_handler(error_msgs, msg, None) if error_msgs: raise ValueError( - f"Found {len(error_msgs)} errors: " + " ".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) + f"Found {len(error_msgs)} errors:\n" + "\n".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) ) else: return script From 2b10cfdc1b588127ec4c10f1cffee590cfd8d278 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 29 Mar 2024 14:09:40 +0300 Subject: [PATCH 38/76] return label representation instead of their str this puts quotes around labels --- dff/script/core/script.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index e3a2ec0aa..5f2968c18 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -199,12 +199,12 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L norm_flow_label, norm_node_label, _ = norm_label if norm_flow_label not in script.keys(): msg = ( - f"Flow label {norm_flow_label} can not be found for label={label}, " + f"Flow label {norm_flow_label!r} 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 script[norm_flow_label].keys(): msg = ( - f"Node label {norm_node_label} can not be found for label={label}, " + f"Node label {norm_node_label!r} can not be found for label={label}, " f"error was found in (flow_label, node_label)={(flow_name, node_name)}" ) else: From 238359e553444b755a31c8d56990614b17cfb61c Mon Sep 17 00:00:00 2001 From: pseusys Date: Fri, 29 Mar 2024 12:53:12 +0100 Subject: [PATCH 39/76] lable typing reverted --- dff/script/core/script.py | 2 +- dff/script/labels/std_labels.py | 42 ++++++++++++------------- tests/script/core/test_normalization.py | 6 ++-- tutorials/script/core/4_transitions.py | 16 +++------- 4 files changed, 29 insertions(+), 37 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 7a6642436..976bf027d 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -84,7 +84,7 @@ def validate_callable(callable: Callable, func_type: UserFunctionType, flow_labe from dff.pipeline.pipeline.pipeline import Pipeline USER_FUNCTION_TYPES = { - UserFunctionType.LABEL: ((Context, Pipeline), NodeLabelType), + UserFunctionType.LABEL: ((Context, Pipeline), NodeLabel3Type), UserFunctionType.RESPONSE: ((Context, Pipeline), Message), UserFunctionType.CONDITION: ((Context, Pipeline), bool), UserFunctionType.RESPONSE_PROCESSING: ((Context, Pipeline), None), diff --git a/dff/script/labels/std_labels.py b/dff/script/labels/std_labels.py index 643453b32..f3754615a 100644 --- a/dff/script/labels/std_labels.py +++ b/dff/script/labels/std_labels.py @@ -7,30 +7,30 @@ Labels can also be used in combination with other conditions, such as the current context or user data, to create more complex and dynamic conversations. -This module contains a standard set of scripting :py:const:`labels ` that +This module contains a standard set of scripting :py:const:`labels ` that can be used by developers to define the conversation flow. """ from __future__ import annotations from typing import Optional, Callable, TYPE_CHECKING -from dff.script import Context, NodeLabelType +from dff.script import Context, NodeLabel3Type if TYPE_CHECKING: from dff.pipeline.pipeline.pipeline import Pipeline -def repeat(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabelType]: +def repeat(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabel3Type]: """ Returns transition handler that takes :py:class:`.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the last node with a given :py:const:`priority `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def repeat_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: + def repeat_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: current_priority = pipeline.actor.label_priority if priority is None else priority if len(ctx.labels) >= 1: flow_label, label = list(ctx.labels.values())[-1] @@ -41,11 +41,11 @@ def repeat_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType return repeat_transition_handler -def previous(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabelType]: +def previous(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabel3Type]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the previous node with a given :py:const:`priority `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. If the current node is the start node, fallback is returned. @@ -53,7 +53,7 @@ def previous(priority: Optional[float] = None) -> Callable[[Context, Pipeline], :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def previous_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: + def previous_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: current_priority = pipeline.actor.label_priority if priority is None else priority if len(ctx.labels) >= 2: flow_label, label = list(ctx.labels.values())[-2] @@ -66,36 +66,36 @@ def previous_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelTy return previous_transition_handler -def to_start(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabelType]: +def to_start(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabel3Type]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the start node with a given :py:const:`priority `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def to_start_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: + def to_start_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: current_priority = pipeline.actor.label_priority if priority is None else priority return (*pipeline.actor.start_label[:2], current_priority) return to_start_transition_handler -def to_fallback(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabelType]: +def to_fallback(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabel3Type]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the fallback node with a given :py:const:`priority `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def to_fallback_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: + def to_fallback_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: current_priority = pipeline.actor.label_priority if priority is None else priority return (*pipeline.actor.fallback_label[:2], current_priority) @@ -108,7 +108,7 @@ def _get_label_by_index_shifting( priority: Optional[float] = None, increment_flag: bool = True, cyclicality_flag: bool = True, -) -> NodeLabelType: +) -> NodeLabel3Type: """ Function that returns node label from the context and pipeline after shifting the index. @@ -137,11 +137,11 @@ def _get_label_by_index_shifting( return (flow_label, labels[label_index], current_priority) -def forward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> Callable[[Context, Pipeline], NodeLabelType]: +def forward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> Callable[[Context, Pipeline], NodeLabel3Type]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the forward node with a given :py:const:`priority ` and :py:const:`cyclicality_flag `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. @@ -150,7 +150,7 @@ def forward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> (e.g the element with `index = len(labels)` has `index = 0`). Defaults to `True`. """ - def forward_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: + def forward_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: return _get_label_by_index_shifting( ctx, pipeline, priority, increment_flag=True, cyclicality_flag=cyclicality_flag ) @@ -158,11 +158,11 @@ def forward_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelTyp return forward_transition_handler -def backward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> Callable[[Context, Pipeline], NodeLabelType]: +def backward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> Callable[[Context, Pipeline], NodeLabel3Type]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the backward node with a given :py:const:`priority ` and :py:const:`cyclicality_flag `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. @@ -171,7 +171,7 @@ def backward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> (e.g the element with `index = len(labels)` has `index = 0`). Defaults to `True`. """ - def back_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabelType: + def back_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: return _get_label_by_index_shifting( ctx, pipeline, priority, increment_flag=False, cyclicality_flag=cyclicality_flag ) diff --git a/tests/script/core/test_normalization.py b/tests/script/core/test_normalization.py index d6c39c325..fc871b889 100644 --- a/tests/script/core/test_normalization.py +++ b/tests/script/core/test_normalization.py @@ -12,7 +12,7 @@ Context, Script, Node, - NodeLabelType, + NodeLabel3Type, Message, ) from dff.script.labels import repeat @@ -36,10 +36,10 @@ def create_env() -> Tuple[Context, Pipeline]: def test_normalize_label(): ctx, actor = create_env() - def true_label_func(ctx: Context, pipeline: Pipeline) -> NodeLabelType: + def true_label_func(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: return ("flow", "node1", 1) - def false_label_func(ctx: Context, pipeline: Pipeline) -> NodeLabelType: + def false_label_func(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: return ("flow", "node2", 1) n_f = normalize_label(true_label_func) diff --git a/tutorials/script/core/4_transitions.py b/tutorials/script/core/4_transitions.py index 7b5572a59..123d226da 100644 --- a/tutorials/script/core/4_transitions.py +++ b/tutorials/script/core/4_transitions.py @@ -19,7 +19,7 @@ # %% import re -from dff.script import TRANSITIONS, RESPONSE, Context, NodeLabelType, Message +from dff.script import TRANSITIONS, RESPONSE, Context, NodeLabel3Type, Message import dff.script.conditions as cnd import dff.script.labels as lbl from dff.pipeline import Pipeline @@ -31,30 +31,22 @@ # %% [markdown] """ -Transition functions should return one of the "label" types. -The label types can be tuples, strings and callables. -In this particular example we'll be using 3-part tuple labels -(also known as `NodeLabel3Type`s). - -We'll define the functions with a special type of return value: +Let's define the functions with a special type of return value: NodeLabel3Type == tuple[str, str, float] which means that transition returns a `tuple` with flow name, node name and priority. - -In general, all possible transition function return types -can be described as `NodeLabelType` type. """ # %% -def greeting_flow_n2_transition(_: Context, __: Pipeline) -> NodeLabelType: +def greeting_flow_n2_transition(_: Context, __: Pipeline) -> NodeLabel3Type: return ("greeting_flow", "node2", 1.0) def high_priority_node_transition(flow_label, label): - def transition(_: Context, __: Pipeline) -> NodeLabelType: + def transition(_: Context, __: Pipeline) -> NodeLabel3Type: return (flow_label, label, 2.0) return transition From 489785906c967e1b84f113171d1b1a01787166d3 Mon Sep 17 00:00:00 2001 From: pseusys Date: Fri, 29 Mar 2024 13:16:04 +0100 Subject: [PATCH 40/76] types replaced with strings --- dff/script/core/script.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index d49717a64..da0021d07 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -34,6 +34,14 @@ class UserFunctionType(str, Enum): TRANSITION_PROCESSING = "pre_transitions_processing" RESPONSE_PROCESSING = "pre_response_processing" +USER_FUNCTION_TYPES = { + UserFunctionType.LABEL: (("Context", "Pipeline"), "NodeLabel3Type"), + UserFunctionType.RESPONSE: (("Context", "Pipeline"), "Message"), + UserFunctionType.CONDITION: (("Context", "Pipeline"), "bool"), + UserFunctionType.RESPONSE_PROCESSING: (("Context", "Pipeline"), "None"), + UserFunctionType.TRANSITION_PROCESSING: (("Context", "Pipeline"), "None"), +} + def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = None): """ @@ -50,7 +58,7 @@ def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = N logger.error(msg, exc_info=exception) -def types_equal(signature_type: Any, expected_type: Type) -> bool: +def types_equal(signature_type: Any, expected_type: str) -> bool: """ This function checks equality of signature type with expected type. Three cases are handled. If no signature is present, it is presumed that types are equal. @@ -61,11 +69,10 @@ def types_equal(signature_type: Any, expected_type: Type) -> bool: :param expected_type: expected type - a class. :return: true if types are equal, false otherwise. """ - expected_str = expected_type.__name__ if hasattr(expected_type, "__name__") else str(expected_type) + signature_str = signature_type.__name__ if hasattr(signature_type, "__name__") else str(signature_type) signature_empty = signature_type == inspect.Parameter.empty - types_match = signature_type == expected_type - expected_string = signature_type == expected_str - return signature_empty or types_match or expected_string + expected_string = signature_str == expected_type + return signature_empty or expected_string @@ -80,16 +87,6 @@ def validate_callable(callable: Callable, func_type: UserFunctionType, flow_labe :param node_label: Node label this function is related to (used for error localization only). :return: list of produced error messages. """ - from dff.script.core.context import Context - from dff.pipeline.pipeline.pipeline import Pipeline - - USER_FUNCTION_TYPES = { - UserFunctionType.LABEL: ((Context, Pipeline), NodeLabel3Type), - UserFunctionType.RESPONSE: ((Context, Pipeline), Message), - UserFunctionType.CONDITION: ((Context, Pipeline), bool), - UserFunctionType.RESPONSE_PROCESSING: ((Context, Pipeline), None), - UserFunctionType.TRANSITION_PROCESSING: ((Context, Pipeline), None), - } error_msgs = list() signature = inspect.signature(callable) From 5be4b864f23f556b8b34ac00c4ee0142f5766736 Mon Sep 17 00:00:00 2001 From: pseusys Date: Fri, 29 Mar 2024 14:17:10 +0100 Subject: [PATCH 41/76] function names changed + lint fixed --- dff/script/core/normalization.py | 5 +++-- dff/script/core/script.py | 38 ++++++++++++++++---------------- dff/script/labels/std_labels.py | 8 +++++-- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/dff/script/core/normalization.py b/dff/script/core/normalization.py index 46d5837ff..39c272acc 100644 --- a/dff/script/core/normalization.py +++ b/dff/script/core/normalization.py @@ -62,8 +62,9 @@ def get_label_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: 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 `NodeLabelType`:\n" - f"{NodeLabelType!r}") + raise TypeError( + f"Label '{label!r}' is of incorrect type. It has to follow the `NodeLabelType`:\n" f"{NodeLabelType!r}" + ) def normalize_condition(condition: ConditionType) -> Callable[[Context, Pipeline], bool]: diff --git a/dff/script/core/script.py b/dff/script/core/script.py index da0021d07..e5be1a157 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -11,7 +11,7 @@ from enum import Enum import inspect import logging -from typing import Callable, List, Optional, Any, Dict, Type, Union, TYPE_CHECKING +from typing import Callable, List, Optional, Any, Dict, Union, TYPE_CHECKING from pydantic import BaseModel, field_validator, validate_call @@ -34,6 +34,7 @@ class UserFunctionType(str, Enum): TRANSITION_PROCESSING = "pre_transitions_processing" RESPONSE_PROCESSING = "pre_response_processing" + USER_FUNCTION_TYPES = { UserFunctionType.LABEL: (("Context", "Pipeline"), "NodeLabel3Type"), UserFunctionType.RESPONSE: (("Context", "Pipeline"), "Message"), @@ -43,7 +44,7 @@ class UserFunctionType(str, Enum): } -def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = None): +def _error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = None): """ This function handles errors during :py:class:`~dff.script.Script` validation. @@ -58,7 +59,7 @@ def error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = N logger.error(msg, exc_info=exception) -def types_equal(signature_type: Any, expected_type: str) -> bool: +def _types_equal(signature_type: Any, expected_type: str) -> bool: """ This function checks equality of signature type with expected type. Three cases are handled. If no signature is present, it is presumed that types are equal. @@ -75,8 +76,7 @@ def types_equal(signature_type: Any, expected_type: str) -> bool: return signature_empty or expected_string - -def validate_callable(callable: Callable, func_type: UserFunctionType, flow_label: str, node_label: str) -> List: +def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_label: str, node_label: str) -> List: """ 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). @@ -98,24 +98,24 @@ def validate_callable(callable: Callable, func_type: UserFunctionType, flow_labe f"should be {len(arguments_type)}, found {len(params)}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) - error_handler(error_msgs, msg, None) + _error_handler(error_msgs, msg, None) for idx, param in enumerate(params): - if not types_equal(param.annotation, arguments_type[idx]): + if not _types_equal(param.annotation, arguments_type[idx]): msg = ( f"Incorrect {idx} parameter annotation of {func_type}={callable.__name__}: " f"should be {arguments_type[idx]} ({type(arguments_type[idx])}), " f"found {param.annotation} ({type(param.annotation)}), " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) - error_handler(error_msgs, msg, None) - if not types_equal(signature.return_annotation, return_type): + _error_handler(error_msgs, msg, None) + if not _types_equal(signature.return_annotation, return_type): msg = ( f"Incorrect return type annotation of {func_type}={callable.__name__}: " f"should be {return_type} ({type(return_type)}), " f"found {signature.return_annotation} ({type(signature.return_annotation)}), " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) - error_handler(error_msgs, msg, None) + _error_handler(error_msgs, msg, None) return error_msgs @@ -185,7 +185,7 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L # validate labeling for label in node.transitions.keys(): if callable(label): - error_msgs += validate_callable(label, UserFunctionType.LABEL, flow_name, node_name) + error_msgs += _validate_callable(label, UserFunctionType.LABEL, flow_name, node_name) else: norm_label = normalize_label(label, flow_name) if norm_label is None: @@ -207,11 +207,11 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L else: msg = None if msg is not None: - error_handler(error_msgs, msg, None) + _error_handler(error_msgs, msg, None) # validate responses if callable(node.response): - error_msgs += validate_callable( + error_msgs += _validate_callable( node.response, UserFunctionType.RESPONSE, flow_name, @@ -223,12 +223,12 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L 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) + _error_handler(error_msgs, msg, None) # validate conditions for label, condition in node.transitions.items(): if callable(condition): - error_msgs += validate_callable( + error_msgs += _validate_callable( condition, UserFunctionType.CONDITION, flow_name, @@ -240,16 +240,16 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L 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) + _error_handler(error_msgs, msg, None) # validate pre_transitions- and pre_response_processing for place, functions in zip( (UserFunctionType.TRANSITION_PROCESSING, UserFunctionType.RESPONSE_PROCESSING), - (node.pre_transitions_processing, node.pre_response_processing) + (node.pre_transitions_processing, node.pre_response_processing), ): for name, function in functions.items(): if callable(function): - error_msgs += validate_callable( + error_msgs += _validate_callable( function, place, flow_name, @@ -261,7 +261,7 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L 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) + _error_handler(error_msgs, msg, None) if error_msgs: raise ValueError( f"Found {len(error_msgs)} errors:\n" + "\n".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) diff --git a/dff/script/labels/std_labels.py b/dff/script/labels/std_labels.py index f3754615a..c2f86b026 100644 --- a/dff/script/labels/std_labels.py +++ b/dff/script/labels/std_labels.py @@ -137,7 +137,9 @@ def _get_label_by_index_shifting( return (flow_label, labels[label_index], current_priority) -def forward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> Callable[[Context, Pipeline], NodeLabel3Type]: +def forward( + priority: Optional[float] = None, cyclicality_flag: bool = True +) -> Callable[[Context, Pipeline], NodeLabel3Type]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. @@ -158,7 +160,9 @@ def forward_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Ty return forward_transition_handler -def backward(priority: Optional[float] = None, cyclicality_flag: bool = True) -> Callable[[Context, Pipeline], NodeLabel3Type]: +def backward( + priority: Optional[float] = None, cyclicality_flag: bool = True +) -> Callable[[Context, Pipeline], NodeLabel3Type]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. From 2fe6dc4f8397fbd13f7d1d28c48f685f70a9163f Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 2 Apr 2024 17:18:51 +0200 Subject: [PATCH 42/76] docs validated, validation tests added --- dff/script/core/script.py | 8 +- tests/script/core/test_actor.py | 2 +- tests/script/core/test_validation.py | 119 +++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 tests/script/core/test_validation.py diff --git a/dff/script/core/script.py b/dff/script/core/script.py index e5be1a157..2244e29e9 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -187,13 +187,7 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L if callable(label): error_msgs += _validate_callable(label, UserFunctionType.LABEL, flow_name, node_name) 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)}" - ) - norm_flow_label, norm_node_label, _ = norm_label + norm_flow_label, norm_node_label, _ = normalize_label(label, flow_name) if norm_flow_label not in script.keys(): msg = ( f"Flow label {norm_flow_label!r} can not be found for label={label}, " diff --git a/tests/script/core/test_actor.py b/tests/script/core/test_actor.py index 9c4dcc7f5..5a9fa6a04 100644 --- a/tests/script/core/test_actor.py +++ b/tests/script/core/test_actor.py @@ -86,7 +86,7 @@ async def test_actor(): ctx = Context() await pipeline.actor(pipeline, ctx) - # fake label stability # TODO: what does it mean? + # fake label stability pipeline = Pipeline.from_script( {"flow": {"node1": {TRANSITIONS: {fake_label: true()}}}}, start_label=("flow", "node1") ) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py new file mode 100644 index 000000000..f352b50ad --- /dev/null +++ b/tests/script/core/test_validation.py @@ -0,0 +1,119 @@ +from typing import Dict +from dff.pipeline import Pipeline +from dff.script import PRE_RESPONSE_PROCESSING, PRE_TRANSITIONS_PROCESSING, RESPONSE, TRANSITIONS, Context, Message, Script, NodeLabel3Type +from dff.script.conditions import exact_match +from dff.script.labels import repeat + + +def wrong_param_number(number: int) -> float: + return 8.0 + number + + +def wrong_param_types(number: int, flag: bool) -> float: + return 8.0 + number if flag else 42.1 + + +def wrong_return_type(_: Context, __: Pipeline) -> float: + return 1.0 + + +def correct_label(_: Context, __: Pipeline) -> NodeLabel3Type: + return ("root", "start", 1) + + +def correct_response(_: Context, __: Pipeline) -> Message: + return Message("hi") + + +def correct_condition(_: Context, __: Pipeline) -> bool: + return True + + +def correct_pre_response_processor(_: Context, __: Pipeline) -> None: + pass + + +def correct_pre_transition_processor(_: Context, __: Pipeline) -> None: + pass + + +def function_signature_test(message: str, param_number: Dict, param_types: Dict, return_type: Dict): + for script, error in map(["param number", "param types", "return type"], [param_number, param_types, return_type]): + try: + Script(script=script) + raise Exception(f"can not be passed: {message}: {error}") + except ValueError: + pass + + +def test_labels(): + param_number_script = {"root": { "start": { TRANSITIONS: { wrong_param_number: exact_match(Message("hi")) } } } } + param_types_script = {"root": { "start": { TRANSITIONS: { wrong_param_types: exact_match(Message("hi")) } } } } + return_type_script = {"root": { "start": { TRANSITIONS: { wrong_return_type: exact_match(Message("hi")) } } } } + function_signature_test("wrong label function signature", param_number_script, param_types_script, return_type_script) + try: + # wrong label tuple flow + Script(script={"root": { "start": { TRANSITIONS: { ("other", "start", 1): exact_match(Message("hi")) } } } }) + raise Exception("can not be passed: wrong label tuple flow") + except ValueError: + pass + try: + # wrong label tuple node + Script(script={"root": { "start": { TRANSITIONS: { ("root", "other", 1): exact_match(Message("hi")) } } } }) + raise Exception("can not be passed: wrong label tuple node") + except ValueError: + pass + Script(script={"root": { "start": { TRANSITIONS: { correct_label: exact_match(Message("hi")) } } } }) + + +def test_responses(): + param_number_script = {"root": { "start": { RESPONSE: { repeat: wrong_param_number } } } } + param_types_script = {"root": { "start": { RESPONSE: { repeat: wrong_param_types } } } } + return_type_script = {"root": { "start": { RESPONSE: { repeat: wrong_return_type } } } } + function_signature_test("wrong response function signature", param_number_script, param_types_script, return_type_script) + try: + # wrong response type + Script(script={"root": { "start": { RESPONSE: { repeat: 7 } } } }) + raise Exception("can not be passed: wrong response type") + except ValueError: + pass + Script(script={"root": { "start": { RESPONSE: { repeat: correct_response } } } }) + + +def test_conditions(): + param_number_script = {"root": { "start": { TRANSITIONS: { ("root", "start", 1): wrong_param_number } } } } + param_types_script = {"root": { "start": { TRANSITIONS: { ("root", "start", 1): wrong_param_types } } } } + return_type_script = {"root": { "start": { TRANSITIONS: { ("root", "start", 1): wrong_return_type } } } } + function_signature_test("wrong condition function signature", param_number_script, param_types_script, return_type_script) + try: + # wrong condition type + Script(script={"root": { "start": { TRANSITIONS: { ("root", "start", 1): 7 } } } }) + raise Exception("can not be passed: wrong condition type") + except ValueError: + pass + Script(script={"root": { "start": { TRANSITIONS: { ("root", "start", 1): correct_condition } } } }) + + +def test_processing(): + param_number_script = {"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": wrong_param_number } } } } + param_types_script = {"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": wrong_param_types } } } } + return_type_script = {"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": wrong_return_type } } } } + function_signature_test("wrong pre-response processing function signature", param_number_script, param_types_script, return_type_script) + param_number_script = {"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": wrong_param_number } } } } + param_types_script = {"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": wrong_param_types } } } } + return_type_script = {"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": wrong_return_type } } } } + function_signature_test("wrong pre-transitions processing function signature", param_number_script, param_types_script, return_type_script) + try: + # wrong pre-response processing type + Script(script={"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": 42 } } } }) + raise Exception("can not be passed: wrong pre-response processing type") + except ValueError: + pass + try: + # wrong pre-transitions processing type + Script(script={"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": 42 } } } }) + raise Exception("can not be passed: wrong pre-transitions processing type") + except ValueError: + pass + Script(script={"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": correct_pre_response_processor } } } }) + Script(script={"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": correct_pre_transition_processor } } } }) From d68fa74878f4071a9033b8a6eb9a0c2db29df871 Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 2 Apr 2024 19:53:15 +0200 Subject: [PATCH 43/76] testing function fix --- tests/script/core/test_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index f352b50ad..5b63c5da8 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -38,7 +38,7 @@ def correct_pre_transition_processor(_: Context, __: Pipeline) -> None: def function_signature_test(message: str, param_number: Dict, param_types: Dict, return_type: Dict): - for script, error in map(["param number", "param types", "return type"], [param_number, param_types, return_type]): + for script, error in zip([param_number, param_types, return_type], ["param number", "param types", "return type"]): try: Script(script=script) raise Exception(f"can not be passed: {message}: {error}") From b231433d5feb1eff2a7ce7804a23a41e1cfb4d80 Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 2 Apr 2024 20:13:07 +0200 Subject: [PATCH 44/76] execution mode changed to "before" --- dff/script/core/script.py | 20 ++++++++++++-------- tests/script/core/test_validation.py | 11 ++++++++--- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 2244e29e9..b4f901972 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -175,7 +175,7 @@ def normalize_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[ script[Keywords.GLOBAL] = {Keywords.GLOBAL: script[Keywords.GLOBAL]} return script - @field_validator("script", mode="after") + @field_validator("script", mode="before") @classmethod @validate_call def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[LabelType, Dict[str, Any]]]: @@ -183,7 +183,8 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L for flow_name, flow in script.items(): for node_name, node in flow.items(): # validate labeling - for label in node.transitions.keys(): + transitions = node.get("transitions", dict()) + for label in transitions.keys(): if callable(label): error_msgs += _validate_callable(label, UserFunctionType.LABEL, flow_name, node_name) else: @@ -204,23 +205,24 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L _error_handler(error_msgs, msg, None) # validate responses - if callable(node.response): + response = node.get("response", None) + if callable(response): error_msgs += _validate_callable( - node.response, + response, UserFunctionType.RESPONSE, flow_name, node_name, ) - elif node.response is not None and not isinstance(node.response, Message): + elif response is not None and not isinstance(response, Message): msg = ( f"Expected type of response is subclass of {Message}, " - f"got type(response)={type(node.response)}, " + f"got type(response)={type(response)}, " f"error was found in (flow_label, node_label)={(flow_name, node_name)}" ) _error_handler(error_msgs, msg, None) # validate conditions - for label, condition in node.transitions.items(): + for label, condition in transitions.items(): if callable(condition): error_msgs += _validate_callable( condition, @@ -237,9 +239,11 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L _error_handler(error_msgs, msg, None) # validate pre_transitions- and pre_response_processing + pre_transitions_processing = node.get("pre_transitions_processing", dict()) + pre_response_processing = node.get("pre_response_processing", dict()) for place, functions in zip( (UserFunctionType.TRANSITION_PROCESSING, UserFunctionType.RESPONSE_PROCESSING), - (node.pre_transitions_processing, node.pre_response_processing), + (pre_transitions_processing, pre_response_processing), ): for name, function in functions.items(): if callable(function): diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 5b63c5da8..fc77b1106 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -17,7 +17,7 @@ def wrong_return_type(_: Context, __: Pipeline) -> float: return 1.0 -def correct_label(_: Context, __: Pipeline) -> NodeLabel3Type: +def correct_label(_: Context, __: Pipeline) -> "NodeLabel3Type": return ("root", "start", 1) @@ -40,8 +40,8 @@ def correct_pre_transition_processor(_: Context, __: Pipeline) -> None: def function_signature_test(message: str, param_number: Dict, param_types: Dict, return_type: Dict): for script, error in zip([param_number, param_types, return_type], ["param number", "param types", "return type"]): try: - Script(script=script) - raise Exception(f"can not be passed: {message}: {error}") + scr=Script(script=script) + raise Exception(f"can not be passed: {message}: {error}: {script} {scr}") except ValueError: pass @@ -117,3 +117,8 @@ def test_processing(): pass Script(script={"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": correct_pre_response_processor } } } }) Script(script={"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": correct_pre_transition_processor } } } }) + + + +if __name__ == "__main__": + test_labels() \ No newline at end of file From bffd43f7db2c9dcfca0bb637f39d66e1af203411 Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 2 Apr 2024 20:35:57 +0200 Subject: [PATCH 45/76] error expecting fixed --- dff/script/core/script.py | 4 +- tests/script/core/test_validation.py | 88 +++++++++++++--------------- 2 files changed, 44 insertions(+), 48 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index b4f901972..a7a4a5e26 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -255,8 +255,8 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L ) else: msg = ( - f"Expected type of pre_{place}_processing {name} is {Callable}, " - f"got type(pre_{place}_processing)={type(function)}, " + f"Expected type of {place} {name} is {Callable}, " + f"got type({place})={type(function)}, " f"error was found in (flow_label, node_label)={(flow_name, node_name)}" ) _error_handler(error_msgs, msg, None) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index fc77b1106..46ff9f31e 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -1,9 +1,19 @@ from typing import Dict + +from pydantic import ValidationError +import pytest + from dff.pipeline import Pipeline from dff.script import PRE_RESPONSE_PROCESSING, PRE_TRANSITIONS_PROCESSING, RESPONSE, TRANSITIONS, Context, Message, Script, NodeLabel3Type from dff.script.conditions import exact_match from dff.script.labels import repeat +ERROR_MESSAGES = [ + r"Incorrect parameter number", + r"Incorrect \d+ parameter annotation", + r"Incorrect return type annotation", +] + def wrong_param_number(number: int) -> float: return 8.0 + number @@ -37,32 +47,27 @@ def correct_pre_transition_processor(_: Context, __: Pipeline) -> None: pass -def function_signature_test(message: str, param_number: Dict, param_types: Dict, return_type: Dict): - for script, error in zip([param_number, param_types, return_type], ["param number", "param types", "return type"]): - try: - scr=Script(script=script) - raise Exception(f"can not be passed: {message}: {error}: {script} {scr}") - except ValueError: - pass +def function_signature_test(param_number: Dict, param_types: Dict, return_type: Dict): + for script, error in zip([param_number, param_types, return_type], ERROR_MESSAGES): + with pytest.raises(ValidationError, match=error) as e: + Script(script=script) + assert e def test_labels(): param_number_script = {"root": { "start": { TRANSITIONS: { wrong_param_number: exact_match(Message("hi")) } } } } param_types_script = {"root": { "start": { TRANSITIONS: { wrong_param_types: exact_match(Message("hi")) } } } } return_type_script = {"root": { "start": { TRANSITIONS: { wrong_return_type: exact_match(Message("hi")) } } } } - function_signature_test("wrong label function signature", param_number_script, param_types_script, return_type_script) - try: - # wrong label tuple flow + function_signature_test(param_number_script, param_types_script, return_type_script) + + with pytest.raises(ValidationError, match=r"Flow label") as e: Script(script={"root": { "start": { TRANSITIONS: { ("other", "start", 1): exact_match(Message("hi")) } } } }) - raise Exception("can not be passed: wrong label tuple flow") - except ValueError: - pass - try: - # wrong label tuple node + assert e + + with pytest.raises(ValidationError, match=r"Node label") as e: Script(script={"root": { "start": { TRANSITIONS: { ("root", "other", 1): exact_match(Message("hi")) } } } }) - raise Exception("can not be passed: wrong label tuple node") - except ValueError: - pass + assert e + Script(script={"root": { "start": { TRANSITIONS: { correct_label: exact_match(Message("hi")) } } } }) @@ -70,13 +75,12 @@ def test_responses(): param_number_script = {"root": { "start": { RESPONSE: { repeat: wrong_param_number } } } } param_types_script = {"root": { "start": { RESPONSE: { repeat: wrong_param_types } } } } return_type_script = {"root": { "start": { RESPONSE: { repeat: wrong_return_type } } } } - function_signature_test("wrong response function signature", param_number_script, param_types_script, return_type_script) - try: - # wrong response type + function_signature_test(param_number_script, param_types_script, return_type_script) + + with pytest.raises(ValidationError, match=r"Expected type of response is") as e: Script(script={"root": { "start": { RESPONSE: { repeat: 7 } } } }) - raise Exception("can not be passed: wrong response type") - except ValueError: - pass + assert e + Script(script={"root": { "start": { RESPONSE: { repeat: correct_response } } } }) @@ -85,12 +89,11 @@ def test_conditions(): param_types_script = {"root": { "start": { TRANSITIONS: { ("root", "start", 1): wrong_param_types } } } } return_type_script = {"root": { "start": { TRANSITIONS: { ("root", "start", 1): wrong_return_type } } } } function_signature_test("wrong condition function signature", param_number_script, param_types_script, return_type_script) - try: - # wrong condition type + + with pytest.raises(ValidationError, match=r"Expected type of condition") as e: Script(script={"root": { "start": { TRANSITIONS: { ("root", "start", 1): 7 } } } }) - raise Exception("can not be passed: wrong condition type") - except ValueError: - pass + assert e + Script(script={"root": { "start": { TRANSITIONS: { ("root", "start", 1): correct_condition } } } }) @@ -98,27 +101,20 @@ def test_processing(): param_number_script = {"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": wrong_param_number } } } } param_types_script = {"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": wrong_param_types } } } } return_type_script = {"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": wrong_return_type } } } } - function_signature_test("wrong pre-response processing function signature", param_number_script, param_types_script, return_type_script) + function_signature_test(param_number_script, param_types_script, return_type_script) + param_number_script = {"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": wrong_param_number } } } } param_types_script = {"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": wrong_param_types } } } } return_type_script = {"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": wrong_return_type } } } } - function_signature_test("wrong pre-transitions processing function signature", param_number_script, param_types_script, return_type_script) - try: - # wrong pre-response processing type + function_signature_test(param_number_script, param_types_script, return_type_script) + + with pytest.raises(ValidationError, match=r"Expected type of pre_response_processing") as e: Script(script={"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": 42 } } } }) - raise Exception("can not be passed: wrong pre-response processing type") - except ValueError: - pass - try: - # wrong pre-transitions processing type + assert e + + with pytest.raises(ValidationError, match=r"Expected type of pre_transitions_processing") as e: Script(script={"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": 42 } } } }) - raise Exception("can not be passed: wrong pre-transitions processing type") - except ValueError: - pass + assert e + Script(script={"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": correct_pre_response_processor } } } }) Script(script={"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": correct_pre_transition_processor } } } }) - - - -if __name__ == "__main__": - test_labels() \ No newline at end of file From 78df45a1b3c55f0e1da4e649baa78bb175b36304 Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 2 Apr 2024 21:05:19 +0200 Subject: [PATCH 46/76] testing errors fixed, excessive validation removed --- dff/script/core/script.py | 66 +++++++++++++--------------- tests/script/core/test_validation.py | 26 +++-------- 2 files changed, 36 insertions(+), 56 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index a7a4a5e26..af2265345 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -178,7 +178,7 @@ def normalize_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[ @field_validator("script", mode="before") @classmethod @validate_call - def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[LabelType, Dict[str, Any]]]: + def validate_script_before(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[LabelType, Dict[str, Any]]]: error_msgs = [] for flow_name, flow in script.items(): for node_name, node in flow.items(): @@ -187,22 +187,6 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L for label in transitions.keys(): if callable(label): error_msgs += _validate_callable(label, UserFunctionType.LABEL, flow_name, node_name) - else: - norm_flow_label, norm_node_label, _ = normalize_label(label, flow_name) - if norm_flow_label not in script.keys(): - msg = ( - f"Flow label {norm_flow_label!r} 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 script[norm_flow_label].keys(): - msg = ( - f"Node label {norm_node_label!r} 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) # validate responses response = node.get("response", None) @@ -213,13 +197,6 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L flow_name, node_name, ) - elif response is not None and not isinstance(response, Message): - msg = ( - f"Expected type of response is subclass of {Message}, " - f"got type(response)={type(response)}, " - f"error was found in (flow_label, node_label)={(flow_name, node_name)}" - ) - _error_handler(error_msgs, msg, None) # validate conditions for label, condition in transitions.items(): @@ -230,13 +207,6 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L flow_name, node_name, ) - 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) # validate pre_transitions- and pre_response_processing pre_transitions_processing = node.get("pre_transitions_processing", dict()) @@ -245,7 +215,7 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L (UserFunctionType.TRANSITION_PROCESSING, UserFunctionType.RESPONSE_PROCESSING), (pre_transitions_processing, pre_response_processing), ): - for name, function in functions.items(): + for function in functions.values(): if callable(function): error_msgs += _validate_callable( function, @@ -253,13 +223,39 @@ def validate_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[L flow_name, node_name, ) - else: + if error_msgs: + raise ValueError( + f"Found {len(error_msgs)} errors:\n" + "\n".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) + ) + else: + return script + + @field_validator("script", mode="after") + @classmethod + @validate_call + def validate_script_after(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[LabelType, Dict[str, Any]]]: + error_msgs = [] + for flow_name, flow in script.items(): + for node_name, node in flow.items(): + # validate labeling + for label in node.transitions.keys(): + if not callable(label): + norm_flow_label, norm_node_label, _ = normalize_label(label, flow_name) + if norm_flow_label not in script.keys(): msg = ( - f"Expected type of {place} {name} is {Callable}, " - f"got type({place})={type(function)}, " + f"Flow label {norm_flow_label!r} 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 script[norm_flow_label].keys(): + msg = ( + f"Node label {norm_node_label!r} 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) + if error_msgs: raise ValueError( f"Found {len(error_msgs)} errors:\n" + "\n".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 46ff9f31e..438dfa21e 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -72,27 +72,19 @@ def test_labels(): def test_responses(): - param_number_script = {"root": { "start": { RESPONSE: { repeat: wrong_param_number } } } } - param_types_script = {"root": { "start": { RESPONSE: { repeat: wrong_param_types } } } } - return_type_script = {"root": { "start": { RESPONSE: { repeat: wrong_return_type } } } } + param_number_script = {"root": { "start": { RESPONSE: wrong_param_number } } } + param_types_script = {"root": { "start": { RESPONSE: wrong_param_types } } } + return_type_script = {"root": { "start": { RESPONSE: wrong_return_type } } } function_signature_test(param_number_script, param_types_script, return_type_script) - - with pytest.raises(ValidationError, match=r"Expected type of response is") as e: - Script(script={"root": { "start": { RESPONSE: { repeat: 7 } } } }) - assert e - Script(script={"root": { "start": { RESPONSE: { repeat: correct_response } } } }) + Script(script={"root": { "start": { RESPONSE: correct_response } } }) def test_conditions(): param_number_script = {"root": { "start": { TRANSITIONS: { ("root", "start", 1): wrong_param_number } } } } param_types_script = {"root": { "start": { TRANSITIONS: { ("root", "start", 1): wrong_param_types } } } } return_type_script = {"root": { "start": { TRANSITIONS: { ("root", "start", 1): wrong_return_type } } } } - function_signature_test("wrong condition function signature", param_number_script, param_types_script, return_type_script) - - with pytest.raises(ValidationError, match=r"Expected type of condition") as e: - Script(script={"root": { "start": { TRANSITIONS: { ("root", "start", 1): 7 } } } }) - assert e + function_signature_test(param_number_script, param_types_script, return_type_script) Script(script={"root": { "start": { TRANSITIONS: { ("root", "start", 1): correct_condition } } } }) @@ -107,14 +99,6 @@ def test_processing(): param_types_script = {"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": wrong_param_types } } } } return_type_script = {"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": wrong_return_type } } } } function_signature_test(param_number_script, param_types_script, return_type_script) - - with pytest.raises(ValidationError, match=r"Expected type of pre_response_processing") as e: - Script(script={"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": 42 } } } }) - assert e - - with pytest.raises(ValidationError, match=r"Expected type of pre_transitions_processing") as e: - Script(script={"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": 42 } } } }) - assert e Script(script={"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": correct_pre_response_processor } } } }) Script(script={"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": correct_pre_transition_processor } } } }) From af4bd33a12a25001342cf4d63049e0d82b479f3c Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 2 Apr 2024 21:08:56 +0200 Subject: [PATCH 47/76] lint applied --- dff/messengers/telegram/message.py | 6 +-- dff/script/core/script.py | 2 +- dff/utils/testing/telegram.py | 3 +- tests/script/core/test_validation.py | 56 ++++++++++++++++------------ 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/dff/messengers/telegram/message.py b/dff/messengers/telegram/message.py index 2061c470f..eea6fff05 100644 --- a/dff/messengers/telegram/message.py +++ b/dff/messengers/telegram/message.py @@ -68,9 +68,9 @@ class ParseMode(Enum): class TelegramMessage(Message): - ui: Optional[Union[TelegramUI, RemoveKeyboard, ReplyKeyboardRemove, ReplyKeyboardMarkup, InlineKeyboardMarkup]] = ( - None - ) + ui: Optional[ + Union[TelegramUI, RemoveKeyboard, ReplyKeyboardRemove, ReplyKeyboardMarkup, InlineKeyboardMarkup] + ] = None location: Optional[Location] = None callback_query: Optional[Union[str, _ClickButton]] = None update: Optional[ diff --git a/dff/script/core/script.py b/dff/script/core/script.py index af2265345..4ffb4bb89 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -229,7 +229,7 @@ def validate_script_before(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, ) else: return script - + @field_validator("script", mode="after") @classmethod @validate_call diff --git a/dff/utils/testing/telegram.py b/dff/utils/testing/telegram.py index 90fcd06b2..4b48f3703 100644 --- a/dff/utils/testing/telegram.py +++ b/dff/utils/testing/telegram.py @@ -245,7 +245,8 @@ async def check_happy_path(self, happy_path, file_download_destination=None, run bot = self.run_bot_loop() else: - async def null(): ... # noqa: E704 + async def null(): + ... # noqa: E704 bot = nullcontext(null) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 438dfa21e..8a148fb38 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -4,9 +4,17 @@ import pytest from dff.pipeline import Pipeline -from dff.script import PRE_RESPONSE_PROCESSING, PRE_TRANSITIONS_PROCESSING, RESPONSE, TRANSITIONS, Context, Message, Script, NodeLabel3Type +from dff.script import ( + PRE_RESPONSE_PROCESSING, + PRE_TRANSITIONS_PROCESSING, + RESPONSE, + TRANSITIONS, + Context, + Message, + Script, + NodeLabel3Type, +) from dff.script.conditions import exact_match -from dff.script.labels import repeat ERROR_MESSAGES = [ r"Incorrect parameter number", @@ -55,50 +63,50 @@ def function_signature_test(param_number: Dict, param_types: Dict, return_type: def test_labels(): - param_number_script = {"root": { "start": { TRANSITIONS: { wrong_param_number: exact_match(Message("hi")) } } } } - param_types_script = {"root": { "start": { TRANSITIONS: { wrong_param_types: exact_match(Message("hi")) } } } } - return_type_script = {"root": { "start": { TRANSITIONS: { wrong_return_type: exact_match(Message("hi")) } } } } + param_number_script = {"root": {"start": {TRANSITIONS: {wrong_param_number: exact_match(Message("hi"))}}}} + param_types_script = {"root": {"start": {TRANSITIONS: {wrong_param_types: exact_match(Message("hi"))}}}} + return_type_script = {"root": {"start": {TRANSITIONS: {wrong_return_type: exact_match(Message("hi"))}}}} function_signature_test(param_number_script, param_types_script, return_type_script) with pytest.raises(ValidationError, match=r"Flow label") as e: - Script(script={"root": { "start": { TRANSITIONS: { ("other", "start", 1): exact_match(Message("hi")) } } } }) + Script(script={"root": {"start": {TRANSITIONS: {("other", "start", 1): exact_match(Message("hi"))}}}}) assert e with pytest.raises(ValidationError, match=r"Node label") as e: - Script(script={"root": { "start": { TRANSITIONS: { ("root", "other", 1): exact_match(Message("hi")) } } } }) + Script(script={"root": {"start": {TRANSITIONS: {("root", "other", 1): exact_match(Message("hi"))}}}}) assert e - Script(script={"root": { "start": { TRANSITIONS: { correct_label: exact_match(Message("hi")) } } } }) + Script(script={"root": {"start": {TRANSITIONS: {correct_label: exact_match(Message("hi"))}}}}) def test_responses(): - param_number_script = {"root": { "start": { RESPONSE: wrong_param_number } } } - param_types_script = {"root": { "start": { RESPONSE: wrong_param_types } } } - return_type_script = {"root": { "start": { RESPONSE: wrong_return_type } } } + param_number_script = {"root": {"start": {RESPONSE: wrong_param_number}}} + param_types_script = {"root": {"start": {RESPONSE: wrong_param_types}}} + return_type_script = {"root": {"start": {RESPONSE: wrong_return_type}}} function_signature_test(param_number_script, param_types_script, return_type_script) - Script(script={"root": { "start": { RESPONSE: correct_response } } }) + Script(script={"root": {"start": {RESPONSE: correct_response}}}) def test_conditions(): - param_number_script = {"root": { "start": { TRANSITIONS: { ("root", "start", 1): wrong_param_number } } } } - param_types_script = {"root": { "start": { TRANSITIONS: { ("root", "start", 1): wrong_param_types } } } } - return_type_script = {"root": { "start": { TRANSITIONS: { ("root", "start", 1): wrong_return_type } } } } + param_number_script = {"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_param_number}}}} + param_types_script = {"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_param_types}}}} + return_type_script = {"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_return_type}}}} function_signature_test(param_number_script, param_types_script, return_type_script) - Script(script={"root": { "start": { TRANSITIONS: { ("root", "start", 1): correct_condition } } } }) + Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): correct_condition}}}}) def test_processing(): - param_number_script = {"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": wrong_param_number } } } } - param_types_script = {"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": wrong_param_types } } } } - return_type_script = {"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": wrong_return_type } } } } + param_number_script = {"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_param_number}}}} + param_types_script = {"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_param_types}}}} + return_type_script = {"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_return_type}}}} function_signature_test(param_number_script, param_types_script, return_type_script) - param_number_script = {"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": wrong_param_number } } } } - param_types_script = {"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": wrong_param_types } } } } - return_type_script = {"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": wrong_return_type } } } } + param_number_script = {"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_param_number}}}} + param_types_script = {"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_param_types}}}} + return_type_script = {"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_return_type}}}} function_signature_test(param_number_script, param_types_script, return_type_script) - Script(script={"root": { "start": { PRE_RESPONSE_PROCESSING: { "PRP": correct_pre_response_processor } } } }) - Script(script={"root": { "start": { PRE_TRANSITIONS_PROCESSING: { "PTP": correct_pre_transition_processor } } } }) + Script(script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": correct_pre_response_processor}}}}) + Script(script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": correct_pre_transition_processor}}}}) From ba47769c7649349d5760d39300acfaf83821423b Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 2 Apr 2024 22:24:41 +0200 Subject: [PATCH 48/76] few tutorials fixed --- dff/messengers/telegram/messenger.py | 2 +- tutorials/script/core/4_transitions.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dff/messengers/telegram/messenger.py b/dff/messengers/telegram/messenger.py index 15cbfc4e6..b88d149f9 100644 --- a/dff/messengers/telegram/messenger.py +++ b/dff/messengers/telegram/messenger.py @@ -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 diff --git a/tutorials/script/core/4_transitions.py b/tutorials/script/core/4_transitions.py index 123d226da..67393984c 100644 --- a/tutorials/script/core/4_transitions.py +++ b/tutorials/script/core/4_transitions.py @@ -41,12 +41,12 @@ # %% -def greeting_flow_n2_transition(_: Context, __: Pipeline) -> NodeLabel3Type: +def greeting_flow_n2_transition(_: Context, __: Pipeline) -> "NodeLabel3Type": return ("greeting_flow", "node2", 1.0) def high_priority_node_transition(flow_label, label): - def transition(_: Context, __: Pipeline) -> NodeLabel3Type: + def transition(_: Context, __: Pipeline) -> "NodeLabel3Type": return (flow_label, label, 2.0) return transition From 3ad859e76b0c58b87244070c796b1b335f63c1f6 Mon Sep 17 00:00:00 2001 From: pseusys Date: Tue, 2 Apr 2024 23:41:49 +0200 Subject: [PATCH 49/76] type annotations correct hanfling added --- dff/script/core/script.py | 9 ++++----- tests/script/core/test_validation.py | 9 ++++++++- tutorials/script/core/4_transitions.py | 4 ++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 4ffb4bb89..2c0b901e9 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -73,7 +73,8 @@ def _types_equal(signature_type: Any, expected_type: str) -> bool: signature_str = signature_type.__name__ if hasattr(signature_type, "__name__") else str(signature_type) signature_empty = signature_type == inspect.Parameter.empty expected_string = signature_str == expected_type - return signature_empty or expected_string + expected_global = str(signature_type) == str(globals().get(expected_type)) + return signature_empty or expected_string or expected_global def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_label: str, node_label: str) -> List: @@ -103,16 +104,14 @@ def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_lab if not _types_equal(param.annotation, arguments_type[idx]): msg = ( f"Incorrect {idx} parameter annotation of {func_type}={callable.__name__}: " - f"should be {arguments_type[idx]} ({type(arguments_type[idx])}), " - f"found {param.annotation} ({type(param.annotation)}), " + f"should be {arguments_type[idx]} found {param.annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) _error_handler(error_msgs, msg, None) if not _types_equal(signature.return_annotation, return_type): msg = ( f"Incorrect return type annotation of {func_type}={callable.__name__}: " - f"should be {return_type} ({type(return_type)}), " - f"found {signature.return_annotation} ({type(signature.return_annotation)}), " + f"should be {return_type} found {signature.return_annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) _error_handler(error_msgs, msg, None) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 8a148fb38..2c72806f4 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -35,7 +35,7 @@ def wrong_return_type(_: Context, __: Pipeline) -> float: return 1.0 -def correct_label(_: Context, __: Pipeline) -> "NodeLabel3Type": +def correct_label(_: Context, __: Pipeline) -> NodeLabel3Type: return ("root", "start", 1) @@ -110,3 +110,10 @@ def test_processing(): Script(script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": correct_pre_response_processor}}}}) Script(script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": correct_pre_transition_processor}}}}) + + +if __name__ == "__main__": + test_labels() + test_responses() + test_conditions() + test_processing() diff --git a/tutorials/script/core/4_transitions.py b/tutorials/script/core/4_transitions.py index 67393984c..123d226da 100644 --- a/tutorials/script/core/4_transitions.py +++ b/tutorials/script/core/4_transitions.py @@ -41,12 +41,12 @@ # %% -def greeting_flow_n2_transition(_: Context, __: Pipeline) -> "NodeLabel3Type": +def greeting_flow_n2_transition(_: Context, __: Pipeline) -> NodeLabel3Type: return ("greeting_flow", "node2", 1.0) def high_priority_node_transition(flow_label, label): - def transition(_: Context, __: Pipeline) -> "NodeLabel3Type": + def transition(_: Context, __: Pipeline) -> NodeLabel3Type: return (flow_label, label, 2.0) return transition From c5e3560a9d1c83b0ba116507a186666a69bc43e9 Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 3 Apr 2024 12:54:36 +0200 Subject: [PATCH 50/76] test functions -> test classes --- tests/script/core/test_validation.py | 140 +++++++++++++++++---------- 1 file changed, 88 insertions(+), 52 deletions(-) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 2c72806f4..34d3c8407 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -1,5 +1,3 @@ -from typing import Dict - from pydantic import ValidationError import pytest @@ -16,12 +14,6 @@ ) from dff.script.conditions import exact_match -ERROR_MESSAGES = [ - r"Incorrect parameter number", - r"Incorrect \d+ parameter annotation", - r"Incorrect return type annotation", -] - def wrong_param_number(number: int) -> float: return 8.0 + number @@ -55,65 +47,109 @@ def correct_pre_transition_processor(_: Context, __: Pipeline) -> None: pass -def function_signature_test(param_number: Dict, param_types: Dict, return_type: Dict): - for script, error in zip([param_number, param_types, return_type], ERROR_MESSAGES): - with pytest.raises(ValidationError, match=error) as e: - Script(script=script) +class TestLabelValidation: + def test_param_number(self): + with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: + Script(script={"root": {"start": {TRANSITIONS: {wrong_param_number: exact_match(Message("hi"))}}}}) assert e + def test_param_types(self): + with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: + Script(script={"root": {"start": {TRANSITIONS: {wrong_param_types: exact_match(Message("hi"))}}}}) + assert e -def test_labels(): - param_number_script = {"root": {"start": {TRANSITIONS: {wrong_param_number: exact_match(Message("hi"))}}}} - param_types_script = {"root": {"start": {TRANSITIONS: {wrong_param_types: exact_match(Message("hi"))}}}} - return_type_script = {"root": {"start": {TRANSITIONS: {wrong_return_type: exact_match(Message("hi"))}}}} - function_signature_test(param_number_script, param_types_script, return_type_script) + def test_return_type(self): + with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: + Script(script={"root": {"start": {TRANSITIONS: {wrong_return_type: exact_match(Message("hi"))}}}}) + assert e - with pytest.raises(ValidationError, match=r"Flow label") as e: - Script(script={"root": {"start": {TRANSITIONS: {("other", "start", 1): exact_match(Message("hi"))}}}}) - assert e + def test_flow_name(self): + with pytest.raises(ValidationError, match=r"Flow label") as e: + Script(script={"root": {"start": {TRANSITIONS: {("other", "start", 1): exact_match(Message("hi"))}}}}) + assert e - with pytest.raises(ValidationError, match=r"Node label") as e: - Script(script={"root": {"start": {TRANSITIONS: {("root", "other", 1): exact_match(Message("hi"))}}}}) - assert e + def test_node_name(self): + with pytest.raises(ValidationError, match=r"Node label") as e: + Script(script={"root": {"start": {TRANSITIONS: {("root", "other", 1): exact_match(Message("hi"))}}}}) + assert e - Script(script={"root": {"start": {TRANSITIONS: {correct_label: exact_match(Message("hi"))}}}}) + def test_correct_script(self): + Script(script={"root": {"start": {TRANSITIONS: {correct_label: exact_match(Message("hi"))}}}}) -def test_responses(): - param_number_script = {"root": {"start": {RESPONSE: wrong_param_number}}} - param_types_script = {"root": {"start": {RESPONSE: wrong_param_types}}} - return_type_script = {"root": {"start": {RESPONSE: wrong_return_type}}} - function_signature_test(param_number_script, param_types_script, return_type_script) +class TestResponseValidation: + def test_param_number(self): + with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: + Script(script={"root": {"start": {RESPONSE: wrong_param_number}}}) + assert e - Script(script={"root": {"start": {RESPONSE: correct_response}}}) + def test_param_types(self): + with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: + Script(script={"root": {"start": {RESPONSE: wrong_param_types}}}) + assert e + + def test_return_type(self): + with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: + Script(script={"root": {"start": {RESPONSE: wrong_return_type}}}) + assert e + def test_correct_script(self): + Script(script={"root": {"start": {RESPONSE: correct_response}}}) -def test_conditions(): - param_number_script = {"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_param_number}}}} - param_types_script = {"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_param_types}}}} - return_type_script = {"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_return_type}}}} - function_signature_test(param_number_script, param_types_script, return_type_script) - Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): correct_condition}}}}) +class TestConditionValidation: + def test_param_number(self): + with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: + Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_param_number}}}}) + assert e + def test_param_types(self): + with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: + Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_param_types}}}}) + assert e -def test_processing(): - param_number_script = {"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_param_number}}}} - param_types_script = {"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_param_types}}}} - return_type_script = {"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_return_type}}}} - function_signature_test(param_number_script, param_types_script, return_type_script) + def test_return_type(self): + with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: + Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_return_type}}}}) + assert e - param_number_script = {"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_param_number}}}} - param_types_script = {"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_param_types}}}} - return_type_script = {"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_return_type}}}} - function_signature_test(param_number_script, param_types_script, return_type_script) + def test_correct_script(self): + Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): correct_condition}}}}) - Script(script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": correct_pre_response_processor}}}}) - Script(script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": correct_pre_transition_processor}}}}) +class TestProcessingValidation: + def test_response_param_number(self): + with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: + Script(script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_param_number}}}}) + assert e + + def test_response_param_types(self): + with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: + Script(script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_param_types}}}}) + assert e + + def test_response_return_type(self): + with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: + Script(script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_return_type}}}}) + assert e + + def test_response_correct_script(self): + Script(script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": correct_pre_response_processor}}}}) + + def test_transition_param_number(self): + with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: + Script(script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_param_number}}}}) + assert e + + def test_transition_param_types(self): + with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: + Script(script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_param_types}}}}) + assert e + + def test_transition_return_type(self): + with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: + Script(script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_return_type}}}}) + assert e -if __name__ == "__main__": - test_labels() - test_responses() - test_conditions() - test_processing() + def test_transition_correct_script(self): + Script(script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": correct_pre_transition_processor}}}}) From 271469fec751511c799a05c10482d2288231afd9 Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 3 Apr 2024 12:55:22 +0200 Subject: [PATCH 51/76] lint applied (again) --- dff/messengers/telegram/message.py | 6 +++--- dff/utils/testing/telegram.py | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/dff/messengers/telegram/message.py b/dff/messengers/telegram/message.py index eea6fff05..2061c470f 100644 --- a/dff/messengers/telegram/message.py +++ b/dff/messengers/telegram/message.py @@ -68,9 +68,9 @@ class ParseMode(Enum): class TelegramMessage(Message): - ui: Optional[ - Union[TelegramUI, RemoveKeyboard, ReplyKeyboardRemove, ReplyKeyboardMarkup, InlineKeyboardMarkup] - ] = None + ui: Optional[Union[TelegramUI, RemoveKeyboard, ReplyKeyboardRemove, ReplyKeyboardMarkup, InlineKeyboardMarkup]] = ( + None + ) location: Optional[Location] = None callback_query: Optional[Union[str, _ClickButton]] = None update: Optional[ diff --git a/dff/utils/testing/telegram.py b/dff/utils/testing/telegram.py index 4b48f3703..90fcd06b2 100644 --- a/dff/utils/testing/telegram.py +++ b/dff/utils/testing/telegram.py @@ -245,8 +245,7 @@ async def check_happy_path(self, happy_path, file_download_destination=None, run bot = self.run_bot_loop() else: - async def null(): - ... # noqa: E704 + async def null(): ... # noqa: E704 bot = nullcontext(null) From 8ccbfbb46362d7d949f844c2126afac32e3c376c Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 4 Apr 2024 18:00:20 +0300 Subject: [PATCH 52/76] remove unnecessary comments --- dff/pipeline/pipeline/actor.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index b02ac21aa..fd957a4ff 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -76,11 +76,9 @@ def __init__( condition_handler: Optional[Callable] = None, handlers: Optional[Dict[ActorStage, List[Callable]]] = None, ): - # script evaluation self.script = script if isinstance(script, Script) else Script(script=script) self.label_priority = label_priority - # 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}") From fc74d7630e2a5928ad5edd04fe38993d715778b4 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 4 Apr 2024 18:02:47 +0300 Subject: [PATCH 53/76] group user function for validation tests --- tests/script/core/test_validation.py | 144 +++++++++++++++++++-------- 1 file changed, 102 insertions(+), 42 deletions(-) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 34d3c8407..b41f42731 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -15,52 +15,76 @@ from dff.script.conditions import exact_match -def wrong_param_number(number: int) -> float: - return 8.0 + number +class UserFunctionSamples: + """ + This class contains various examples of user functions along with their signatures. + """ + @staticmethod + def wrong_param_number(number: int) -> float: + return 8.0 + number -def wrong_param_types(number: int, flag: bool) -> float: - return 8.0 + number if flag else 42.1 + @staticmethod + def wrong_param_types(number: int, flag: bool) -> float: + return 8.0 + number if flag else 42.1 + @staticmethod + def wrong_return_type(_: Context, __: Pipeline) -> float: + return 1.0 -def wrong_return_type(_: Context, __: Pipeline) -> float: - return 1.0 + @staticmethod + def correct_label(_: Context, __: Pipeline) -> NodeLabel3Type: + return ("root", "start", 1) + @staticmethod + def correct_response(_: Context, __: Pipeline) -> Message: + return Message("hi") -def correct_label(_: Context, __: Pipeline) -> NodeLabel3Type: - return ("root", "start", 1) + @staticmethod + def correct_condition(_: Context, __: Pipeline) -> bool: + return True + @staticmethod + def correct_pre_response_processor(_: Context, __: Pipeline) -> None: + pass -def correct_response(_: Context, __: Pipeline) -> Message: - return Message("hi") - - -def correct_condition(_: Context, __: Pipeline) -> bool: - return True - - -def correct_pre_response_processor(_: Context, __: Pipeline) -> None: - pass - - -def correct_pre_transition_processor(_: Context, __: Pipeline) -> None: - pass + @staticmethod + def correct_pre_transition_processor(_: Context, __: Pipeline) -> None: + pass class TestLabelValidation: def test_param_number(self): with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: - Script(script={"root": {"start": {TRANSITIONS: {wrong_param_number: exact_match(Message("hi"))}}}}) + Script( + script={ + "root": { + "start": {TRANSITIONS: {UserFunctionSamples.wrong_param_number: exact_match(Message("hi"))}} + } + } + ) assert e def test_param_types(self): with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: - Script(script={"root": {"start": {TRANSITIONS: {wrong_param_types: exact_match(Message("hi"))}}}}) + Script( + script={ + "root": { + "start": {TRANSITIONS: {UserFunctionSamples.wrong_param_types: exact_match(Message("hi"))}} + } + } + ) assert e def test_return_type(self): with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: - Script(script={"root": {"start": {TRANSITIONS: {wrong_return_type: exact_match(Message("hi"))}}}}) + Script( + script={ + "root": { + "start": {TRANSITIONS: {UserFunctionSamples.wrong_return_type: exact_match(Message("hi"))}} + } + } + ) assert e def test_flow_name(self): @@ -74,82 +98,118 @@ def test_node_name(self): assert e def test_correct_script(self): - Script(script={"root": {"start": {TRANSITIONS: {correct_label: exact_match(Message("hi"))}}}}) + Script( + script={"root": {"start": {TRANSITIONS: {UserFunctionSamples.correct_label: exact_match(Message("hi"))}}}} + ) class TestResponseValidation: def test_param_number(self): with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: - Script(script={"root": {"start": {RESPONSE: wrong_param_number}}}) + Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_param_number}}}) assert e def test_param_types(self): with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: - Script(script={"root": {"start": {RESPONSE: wrong_param_types}}}) + Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_param_types}}}) assert e def test_return_type(self): with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: - Script(script={"root": {"start": {RESPONSE: wrong_return_type}}}) + Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_return_type}}}) assert e def test_correct_script(self): - Script(script={"root": {"start": {RESPONSE: correct_response}}}) + Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.correct_response}}}) class TestConditionValidation: def test_param_number(self): with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: - Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_param_number}}}}) + Script( + script={ + "root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_param_number}}} + } + ) assert e def test_param_types(self): with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: - Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_param_types}}}}) + Script( + script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_param_types}}}} + ) assert e def test_return_type(self): with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: - Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): wrong_return_type}}}}) + Script( + script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_return_type}}}} + ) assert e def test_correct_script(self): - Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): correct_condition}}}}) + Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.correct_condition}}}}) class TestProcessingValidation: def test_response_param_number(self): with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: - Script(script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_param_number}}}}) + Script( + script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_param_number}}}} + ) assert e def test_response_param_types(self): with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: - Script(script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_param_types}}}}) + Script( + script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_param_types}}}} + ) assert e def test_response_return_type(self): with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: - Script(script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": wrong_return_type}}}}) + Script( + script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_return_type}}}} + ) assert e def test_response_correct_script(self): - Script(script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": correct_pre_response_processor}}}}) + Script( + script={ + "root": { + "start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.correct_pre_response_processor}} + } + } + ) def test_transition_param_number(self): with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: - Script(script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_param_number}}}}) + Script( + script={ + "root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_param_number}}} + } + ) assert e def test_transition_param_types(self): with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: - Script(script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_param_types}}}}) + Script( + script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_param_types}}}} + ) assert e def test_transition_return_type(self): with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: - Script(script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": wrong_return_type}}}}) + Script( + script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_return_type}}}} + ) assert e def test_transition_correct_script(self): - Script(script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": correct_pre_transition_processor}}}}) + Script( + script={ + "root": { + "start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.correct_pre_transition_processor}} + } + } + ) From c75d0ba94fa450bd2baa224b9601a0ee9847f39c Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 4 Apr 2024 18:07:42 +0300 Subject: [PATCH 54/76] assert number of found errors --- tests/script/core/test_validation.py | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index b41f42731..97a5174e5 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -55,7 +55,7 @@ def correct_pre_transition_processor(_: Context, __: Pipeline) -> None: class TestLabelValidation: def test_param_number(self): - with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter number") as e: Script( script={ "root": { @@ -66,7 +66,7 @@ def test_param_number(self): assert e def test_param_types(self): - with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: Script( script={ "root": { @@ -77,7 +77,7 @@ def test_param_types(self): assert e def test_return_type(self): - with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: + with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: Script( script={ "root": { @@ -88,12 +88,12 @@ def test_return_type(self): assert e def test_flow_name(self): - with pytest.raises(ValidationError, match=r"Flow label") as e: + with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Flow label") as e: Script(script={"root": {"start": {TRANSITIONS: {("other", "start", 1): exact_match(Message("hi"))}}}}) assert e def test_node_name(self): - with pytest.raises(ValidationError, match=r"Node label") as e: + with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Node label") as e: Script(script={"root": {"start": {TRANSITIONS: {("root", "other", 1): exact_match(Message("hi"))}}}}) assert e @@ -105,17 +105,17 @@ def test_correct_script(self): class TestResponseValidation: def test_param_number(self): - with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter number") as e: Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_param_number}}}) assert e def test_param_types(self): - with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_param_types}}}) assert e def test_return_type(self): - with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: + with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_return_type}}}) assert e @@ -125,7 +125,7 @@ def test_correct_script(self): class TestConditionValidation: def test_param_number(self): - with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter number") as e: Script( script={ "root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_param_number}}} @@ -134,14 +134,14 @@ def test_param_number(self): assert e def test_param_types(self): - with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: Script( script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_param_types}}}} ) assert e def test_return_type(self): - with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: + with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: Script( script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_return_type}}}} ) @@ -153,21 +153,21 @@ def test_correct_script(self): class TestProcessingValidation: def test_response_param_number(self): - with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter number") as e: Script( script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_param_number}}}} ) assert e def test_response_param_types(self): - with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: Script( script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_param_types}}}} ) assert e def test_response_return_type(self): - with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: + with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: Script( script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_return_type}}}} ) @@ -183,7 +183,7 @@ def test_response_correct_script(self): ) def test_transition_param_number(self): - with pytest.raises(ValidationError, match=r"Incorrect parameter number") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter number") as e: Script( script={ "root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_param_number}}} @@ -192,14 +192,14 @@ def test_transition_param_number(self): assert e def test_transition_param_types(self): - with pytest.raises(ValidationError, match=r"Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: Script( script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_param_types}}}} ) assert e def test_transition_return_type(self): - with pytest.raises(ValidationError, match=r"Incorrect return type annotation") as e: + with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: Script( script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_return_type}}}} ) From c3ce08a12a962cbb8868dc8c5829a4f3fd65a848 Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 8 Apr 2024 21:24:58 +0200 Subject: [PATCH 55/76] logging function removed --- dff/script/core/script.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 2c0b901e9..e383bd201 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -44,21 +44,6 @@ class UserFunctionType(str, Enum): } -def _error_handler(error_msgs: list, msg: str, exception: Optional[Exception] = None): - """ - 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) - logger.error(msg, exc_info=exception) - - def _types_equal(signature_type: Any, expected_type: str) -> bool: """ This function checks equality of signature type with expected type. @@ -99,22 +84,23 @@ def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_lab f"should be {len(arguments_type)}, found {len(params)}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) - _error_handler(error_msgs, msg, None) + error_msgs += msg for idx, param in enumerate(params): if not _types_equal(param.annotation, arguments_type[idx]): msg = ( - f"Incorrect {idx} parameter annotation of {func_type}={callable.__name__}: " + f"Incorrect parameter annotation for parameter #{idx + 1} " + f" of {func_type}={callable.__name__}: " f"should be {arguments_type[idx]} found {param.annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) - _error_handler(error_msgs, msg, None) + error_msgs += msg if not _types_equal(signature.return_annotation, return_type): msg = ( f"Incorrect return type annotation of {func_type}={callable.__name__}: " f"should be {return_type} found {signature.return_annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) - _error_handler(error_msgs, msg, None) + error_msgs += msg return error_msgs @@ -253,7 +239,7 @@ def validate_script_after(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, else: msg = None if msg is not None: - _error_handler(error_msgs, msg, None) + error_msgs += msg if error_msgs: raise ValueError( From 067283a505a1f8e3e93f2f410a08861dc9f87a1a Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 8 Apr 2024 22:45:24 +0200 Subject: [PATCH 56/76] multiple label type options added --- dff/script/core/script.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index e383bd201..a6d54c716 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -11,7 +11,7 @@ from enum import Enum import inspect import logging -from typing import Callable, List, Optional, Any, Dict, Union, TYPE_CHECKING +from typing import Callable, List, Optional, Any, Dict, Tuple, Union, TYPE_CHECKING from pydantic import BaseModel, field_validator, validate_call @@ -36,15 +36,15 @@ class UserFunctionType(str, Enum): USER_FUNCTION_TYPES = { - UserFunctionType.LABEL: (("Context", "Pipeline"), "NodeLabel3Type"), - UserFunctionType.RESPONSE: (("Context", "Pipeline"), "Message"), - UserFunctionType.CONDITION: (("Context", "Pipeline"), "bool"), - UserFunctionType.RESPONSE_PROCESSING: (("Context", "Pipeline"), "None"), - UserFunctionType.TRANSITION_PROCESSING: (("Context", "Pipeline"), "None"), + UserFunctionType.LABEL: (("Context", "Pipeline"), ("NodeLabel3Type", "NodeLabel2Type", "NodeLabel1Type", "str", "NodeLabelTupledType", "NodeLabelType")), + UserFunctionType.RESPONSE: (("Context", "Pipeline"), ("Message",)), + UserFunctionType.CONDITION: (("Context", "Pipeline"), ("bool",)), + UserFunctionType.RESPONSE_PROCESSING: (("Context", "Pipeline"), ("None",)), + UserFunctionType.TRANSITION_PROCESSING: (("Context", "Pipeline"), ("None",)), } -def _types_equal(signature_type: Any, expected_type: str) -> bool: +def _types_equal(signature_type: Any, expected_types: Tuple[str]) -> bool: """ This function checks equality of signature type with expected type. Three cases are handled. If no signature is present, it is presumed that types are equal. @@ -55,11 +55,14 @@ def _types_equal(signature_type: Any, expected_type: str) -> bool: :param expected_type: expected type - a class. :return: true if types are equal, false otherwise. """ - signature_str = signature_type.__name__ if hasattr(signature_type, "__name__") else str(signature_type) - signature_empty = signature_type == inspect.Parameter.empty - expected_string = signature_str == expected_type - expected_global = str(signature_type) == str(globals().get(expected_type)) - return signature_empty or expected_string or expected_global + for expected_type in expected_types: + signature_str = signature_type.__name__ if hasattr(signature_type, "__name__") else str(signature_type) + signature_empty = signature_type == inspect.Parameter.empty + expected_string = signature_str == expected_type + expected_global = str(signature_type) == str(globals().get(expected_type)) + if signature_empty or expected_string or expected_global: + return True + return False def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_label: str, node_label: str) -> List: From dce18acee430b1e6fe35d726b0e694ba05019283 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 9 Apr 2024 12:57:24 +0300 Subject: [PATCH 57/76] fix error message addition --- dff/script/core/script.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index a6d54c716..1527b210b 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -87,7 +87,7 @@ def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_lab f"should be {len(arguments_type)}, found {len(params)}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) - error_msgs += msg + error_msgs.append(msg) for idx, param in enumerate(params): if not _types_equal(param.annotation, arguments_type[idx]): msg = ( @@ -96,14 +96,14 @@ def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_lab f"should be {arguments_type[idx]} found {param.annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) - error_msgs += msg + error_msgs.append(msg) if not _types_equal(signature.return_annotation, return_type): msg = ( f"Incorrect return type annotation of {func_type}={callable.__name__}: " f"should be {return_type} found {signature.return_annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) - error_msgs += msg + error_msgs.append(msg) return error_msgs @@ -242,7 +242,7 @@ def validate_script_after(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, else: msg = None if msg is not None: - error_msgs += msg + error_msgs.append(msg) if error_msgs: raise ValueError( From 4c8274984d8aec589ffd7a5972752c7c055c5761 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 9 Apr 2024 13:26:06 +0300 Subject: [PATCH 58/76] fix return type validation --- dff/script/core/script.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 1527b210b..a186b1dcd 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -44,7 +44,7 @@ class UserFunctionType(str, Enum): } -def _types_equal(signature_type: Any, expected_types: Tuple[str]) -> bool: +def _types_equal(signature_type: Any, expected_type: str) -> bool: """ This function checks equality of signature type with expected type. Three cases are handled. If no signature is present, it is presumed that types are equal. @@ -55,14 +55,11 @@ def _types_equal(signature_type: Any, expected_types: Tuple[str]) -> bool: :param expected_type: expected type - a class. :return: true if types are equal, false otherwise. """ - for expected_type in expected_types: - signature_str = signature_type.__name__ if hasattr(signature_type, "__name__") else str(signature_type) - signature_empty = signature_type == inspect.Parameter.empty - expected_string = signature_str == expected_type - expected_global = str(signature_type) == str(globals().get(expected_type)) - if signature_empty or expected_string or expected_global: - return True - return False + signature_str = signature_type.__name__ if hasattr(signature_type, "__name__") else str(signature_type) + signature_empty = signature_type == inspect.Parameter.empty + expected_string = signature_str == expected_type + expected_global = str(signature_type) == str(globals().get(expected_type)) + return signature_empty or expected_string or expected_global def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_label: str, node_label: str) -> List: @@ -79,7 +76,7 @@ def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_lab error_msgs = list() signature = inspect.signature(callable) - arguments_type, return_type = USER_FUNCTION_TYPES[func_type] + arguments_type, return_types = USER_FUNCTION_TYPES[func_type] params = list(signature.parameters.values()) if len(params) != len(arguments_type): msg = ( @@ -97,10 +94,13 @@ def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_lab f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_msgs.append(msg) - if not _types_equal(signature.return_annotation, return_type): + for potential_type in return_types: + if _types_equal(signature.return_annotation, potential_type): + break + else: msg = ( f"Incorrect return type annotation of {func_type}={callable.__name__}: " - f"should be {return_type} found {signature.return_annotation}, " + f"should be one of {return_types} found {signature.return_annotation}, " f"error was found in (flow_label, node_label)={(flow_label, node_label)}" ) error_msgs.append(msg) From 5206ce0510af7b2d3217dfee4219524124b69fc2 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 9 Apr 2024 13:27:52 +0300 Subject: [PATCH 59/76] update tests --- tests/script/core/test_validation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 97a5174e5..be81b2158 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -66,7 +66,7 @@ def test_param_number(self): assert e def test_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: Script( script={ "root": { @@ -110,7 +110,7 @@ def test_param_number(self): assert e def test_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_param_types}}}) assert e @@ -134,7 +134,7 @@ def test_param_number(self): assert e def test_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: Script( script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_param_types}}}} ) @@ -160,7 +160,7 @@ def test_response_param_number(self): assert e def test_response_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: Script( script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_param_types}}}} ) @@ -192,7 +192,7 @@ def test_transition_param_number(self): assert e def test_transition_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: Script( script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_param_types}}}} ) From f928ac44bdc7a8b3df8aaea5f8e3dfae0d88cbef Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 9 Apr 2024 13:52:16 +0300 Subject: [PATCH 60/76] improve error messages --- dff/script/core/script.py | 34 ++++++++++++++++------------ tests/script/core/test_validation.py | 33 +++++++++++++++++++++------ 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index a186b1dcd..d7b5ed873 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -80,28 +80,30 @@ def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_lab params = list(signature.parameters.values()) if len(params) != len(arguments_type): msg = ( - f"Incorrect parameter number of {func_type}={callable.__name__}: " - f"should be {len(arguments_type)}, found {len(params)}, " - f"error was found in (flow_label, node_label)={(flow_label, node_label)}" + f"Incorrect parameter number for {callable.__name__!r}: " + f"should be {len(arguments_type)}, not {len(params)}. " + f"Error found at {(flow_label, node_label)!r}." ) error_msgs.append(msg) for idx, param in enumerate(params): if not _types_equal(param.annotation, arguments_type[idx]): msg = ( f"Incorrect parameter annotation for parameter #{idx + 1} " - f" of {func_type}={callable.__name__}: " - f"should be {arguments_type[idx]} found {param.annotation}, " - f"error was found in (flow_label, node_label)={(flow_label, node_label)}" + f" of {callable.__name__!r}: " + f"should be {arguments_type[idx]}, not {param.annotation}. " + f"Error found at {(flow_label, node_label)!r}." ) error_msgs.append(msg) for potential_type in return_types: if _types_equal(signature.return_annotation, potential_type): break else: + expected_type_message = f"should be one of {return_types!r}"\ + if len(return_types) > 1 else f"should be {return_types[0]!r}" msg = ( - f"Incorrect return type annotation of {func_type}={callable.__name__}: " - f"should be one of {return_types} found {signature.return_annotation}, " - f"error was found in (flow_label, node_label)={(flow_label, node_label)}" + f"Incorrect return type annotation of {callable.__name__!r}: " + f"{expected_type_message}, not {signature.return_annotation}. " + f"Error found at {(flow_label, node_label)!r}." ) error_msgs.append(msg) return error_msgs @@ -212,8 +214,9 @@ def validate_script_before(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, node_name, ) if error_msgs: + error_number_string = "1 error" if len(error_msgs) == 1 else f"{len(error_msgs)} errors" raise ValueError( - f"Found {len(error_msgs)} errors:\n" + "\n".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) + f"Found {error_number_string}:\n" + "\n".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) ) else: return script @@ -231,13 +234,13 @@ def validate_script_after(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, norm_flow_label, norm_node_label, _ = normalize_label(label, flow_name) if norm_flow_label not in script.keys(): msg = ( - f"Flow label {norm_flow_label!r} can not be found for label={label}, " - f"error was found in (flow_label, node_label)={(flow_name, node_name)}" + f"Flow {norm_flow_label!r} cannot be found for label={label}. " + f"Error found at {(flow_name, node_name)!r}." ) elif norm_node_label not in script[norm_flow_label].keys(): msg = ( - f"Node label {norm_node_label!r} can not be found for label={label}, " - f"error was found in (flow_label, node_label)={(flow_name, node_name)}" + f"Node {norm_node_label!r} cannot be found for label={label}. " + f"Error found at {(flow_name, node_name)!r}." ) else: msg = None @@ -245,8 +248,9 @@ def validate_script_after(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, error_msgs.append(msg) if error_msgs: + error_number_string = "1 error" if len(error_msgs) == 1 else f"{len(error_msgs)} errors" raise ValueError( - f"Found {len(error_msgs)} errors:\n" + "\n".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) + f"Found {error_number_string}:\n" + "\n".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) ) else: return script diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index be81b2158..043ff853e 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -77,7 +77,10 @@ def test_param_types(self): assert e def test_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: + with pytest.raises( + ValidationError, + match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': should be one of" + ) as e: Script( script={ "root": { @@ -88,12 +91,12 @@ def test_return_type(self): assert e def test_flow_name(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Flow label") as e: + with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Flow") as e: Script(script={"root": {"start": {TRANSITIONS: {("other", "start", 1): exact_match(Message("hi"))}}}}) assert e def test_node_name(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Node label") as e: + with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Node") as e: Script(script={"root": {"start": {TRANSITIONS: {("root", "other", 1): exact_match(Message("hi"))}}}}) assert e @@ -115,7 +118,11 @@ def test_param_types(self): assert e def test_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: + with pytest.raises( + ValidationError, + match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " + r"should be (?!one of)" + ) as e: Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_return_type}}}) assert e @@ -141,7 +148,11 @@ def test_param_types(self): assert e def test_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: + with pytest.raises( + ValidationError, + match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " + r"should be (?!one of)" + ) as e: Script( script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_return_type}}}} ) @@ -167,7 +178,11 @@ def test_response_param_types(self): assert e def test_response_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: + with pytest.raises( + ValidationError, + match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " + r"should be (?!one of)" + ) as e: Script( script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_return_type}}}} ) @@ -199,7 +214,11 @@ def test_transition_param_types(self): assert e def test_transition_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: + with pytest.raises( + ValidationError, + match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " + r"should be (?!one of)" + ) as e: Script( script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_return_type}}}} ) From e01f459e23b94285c06d4e47e725c60b11a91054 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 9 Apr 2024 13:53:54 +0300 Subject: [PATCH 61/76] lint --- dff/script/core/script.py | 12 ++++++++---- tests/script/core/test_validation.py | 28 ++++++++++++++-------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index d7b5ed873..269ecb432 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -11,7 +11,7 @@ from enum import Enum import inspect import logging -from typing import Callable, List, Optional, Any, Dict, Tuple, Union, TYPE_CHECKING +from typing import Callable, List, Optional, Any, Dict, Union, TYPE_CHECKING from pydantic import BaseModel, field_validator, validate_call @@ -36,7 +36,10 @@ class UserFunctionType(str, Enum): USER_FUNCTION_TYPES = { - UserFunctionType.LABEL: (("Context", "Pipeline"), ("NodeLabel3Type", "NodeLabel2Type", "NodeLabel1Type", "str", "NodeLabelTupledType", "NodeLabelType")), + UserFunctionType.LABEL: ( + ("Context", "Pipeline"), + ("NodeLabel3Type", "NodeLabel2Type", "NodeLabel1Type", "str", "NodeLabelTupledType", "NodeLabelType"), + ), UserFunctionType.RESPONSE: (("Context", "Pipeline"), ("Message",)), UserFunctionType.CONDITION: (("Context", "Pipeline"), ("bool",)), UserFunctionType.RESPONSE_PROCESSING: (("Context", "Pipeline"), ("None",)), @@ -98,8 +101,9 @@ def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_lab if _types_equal(signature.return_annotation, potential_type): break else: - expected_type_message = f"should be one of {return_types!r}"\ - if len(return_types) > 1 else f"should be {return_types[0]!r}" + expected_type_message = ( + f"should be one of {return_types!r}" if len(return_types) > 1 else f"should be {return_types[0]!r}" + ) msg = ( f"Incorrect return type annotation of {callable.__name__!r}: " f"{expected_type_message}, not {signature.return_annotation}. " diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 043ff853e..79a66f8da 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -78,8 +78,8 @@ def test_param_types(self): def test_return_type(self): with pytest.raises( - ValidationError, - match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': should be one of" + ValidationError, + match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': should be one of", ) as e: Script( script={ @@ -119,9 +119,9 @@ def test_param_types(self): def test_return_type(self): with pytest.raises( - ValidationError, - match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " - r"should be (?!one of)" + ValidationError, + match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " + r"should be (?!one of)", ) as e: Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_return_type}}}) assert e @@ -149,9 +149,9 @@ def test_param_types(self): def test_return_type(self): with pytest.raises( - ValidationError, - match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " - r"should be (?!one of)" + ValidationError, + match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " + r"should be (?!one of)", ) as e: Script( script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_return_type}}}} @@ -179,9 +179,9 @@ def test_response_param_types(self): def test_response_return_type(self): with pytest.raises( - ValidationError, - match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " - r"should be (?!one of)" + ValidationError, + match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " + r"should be (?!one of)", ) as e: Script( script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_return_type}}}} @@ -215,9 +215,9 @@ def test_transition_param_types(self): def test_transition_return_type(self): with pytest.raises( - ValidationError, - match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " - r"should be (?!one of)" + ValidationError, + match=r"Found 1 error:[\w\W]*Incorrect return type annotation of 'wrong_return_type': " + r"should be (?!one of)", ) as e: Script( script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_return_type}}}} From aa7cf4fc0d92af9e799f2d47e80d16fd00f09e02 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 9 Apr 2024 14:03:13 +0300 Subject: [PATCH 62/76] add more possible return type annotations for labels --- dff/script/core/script.py | 2 +- tests/script/core/test_validation.py | 30 ++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 269ecb432..9946a15a1 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -38,7 +38,7 @@ class UserFunctionType(str, Enum): USER_FUNCTION_TYPES = { UserFunctionType.LABEL: ( ("Context", "Pipeline"), - ("NodeLabel3Type", "NodeLabel2Type", "NodeLabel1Type", "str", "NodeLabelTupledType", "NodeLabelType"), + ("NodeLabel3Type", "NodeLabel2Type", "NodeLabel1Type", "str", "NodeLabelTupledType", "NodeLabelType", "Tuple[str, str, float]", "Tuple[str, str]", "Tuple[str, float]"), ), UserFunctionType.RESPONSE: (("Context", "Pipeline"), ("Message",)), UserFunctionType.CONDITION: (("Context", "Pipeline"), ("bool",)), diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 79a66f8da..23a09d6a5 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -1,3 +1,5 @@ +from typing import Tuple + from pydantic import ValidationError import pytest @@ -32,9 +34,24 @@ def wrong_param_types(number: int, flag: bool) -> float: def wrong_return_type(_: Context, __: Pipeline) -> float: return 1.0 - @staticmethod - def correct_label(_: Context, __: Pipeline) -> NodeLabel3Type: - return ("root", "start", 1) + class CorrectLabels: + @staticmethod + def correct_labels(): + for method in UserFunctionSamples.CorrectLabels.__dict__: + if method.startswith("correct_label_"): + yield getattr(UserFunctionSamples.CorrectLabels, method) + + @staticmethod + def correct_label_1(_: Context, __: Pipeline) -> str: + return "start" + + @staticmethod + def correct_label_2(_: Context, __: Pipeline) -> NodeLabel3Type: + return ("root", "start", 1) + + @staticmethod + def correct_label_3(_: Context, __: Pipeline) -> Tuple[str, str]: + return ("root", "start") @staticmethod def correct_response(_: Context, __: Pipeline) -> Message: @@ -101,9 +118,10 @@ def test_node_name(self): assert e def test_correct_script(self): - Script( - script={"root": {"start": {TRANSITIONS: {UserFunctionSamples.correct_label: exact_match(Message("hi"))}}}} - ) + for correct_label in UserFunctionSamples.CorrectLabels.correct_labels(): + Script( + script={"root": {"start": {TRANSITIONS: {correct_label: exact_match(Message("hi"))}}}} + ) class TestResponseValidation: From 5d1736cacf61707f6583c7bf6eb630cfb7d72727 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 9 Apr 2024 14:05:24 +0300 Subject: [PATCH 63/76] reformat --- dff/script/core/script.py | 12 +++++++++++- tests/script/core/test_validation.py | 4 +--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 9946a15a1..515174b2a 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -38,7 +38,17 @@ class UserFunctionType(str, Enum): USER_FUNCTION_TYPES = { UserFunctionType.LABEL: ( ("Context", "Pipeline"), - ("NodeLabel3Type", "NodeLabel2Type", "NodeLabel1Type", "str", "NodeLabelTupledType", "NodeLabelType", "Tuple[str, str, float]", "Tuple[str, str]", "Tuple[str, float]"), + ( + "NodeLabel3Type", + "NodeLabel2Type", + "NodeLabel1Type", + "str", + "NodeLabelTupledType", + "NodeLabelType", + "Tuple[str, str, float]", + "Tuple[str, str]", + "Tuple[str, float]", + ), ), UserFunctionType.RESPONSE: (("Context", "Pipeline"), ("Message",)), UserFunctionType.CONDITION: (("Context", "Pipeline"), ("bool",)), diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 23a09d6a5..3c1114e80 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -119,9 +119,7 @@ def test_node_name(self): def test_correct_script(self): for correct_label in UserFunctionSamples.CorrectLabels.correct_labels(): - Script( - script={"root": {"start": {TRANSITIONS: {correct_label: exact_match(Message("hi"))}}}} - ) + Script(script={"root": {"start": {TRANSITIONS: {correct_label: exact_match(Message("hi"))}}}}) class TestResponseValidation: From 5cb48fe853df0b3aa8e960da17203576a37ec2c2 Mon Sep 17 00:00:00 2001 From: pseusys Date: Wed, 10 Apr 2024 00:21:19 +0200 Subject: [PATCH 64/76] signature name lowered --- dff/script/core/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index a6d54c716..36d1c53d8 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -59,7 +59,7 @@ def _types_equal(signature_type: Any, expected_types: Tuple[str]) -> bool: signature_str = signature_type.__name__ if hasattr(signature_type, "__name__") else str(signature_type) signature_empty = signature_type == inspect.Parameter.empty expected_string = signature_str == expected_type - expected_global = str(signature_type) == str(globals().get(expected_type)) + expected_global = str(signature_type).lower() == str(globals().get(expected_type)).lower() if signature_empty or expected_string or expected_global: return True return False From d0718a28c31fb962535db0092f41d06e3a342a00 Mon Sep 17 00:00:00 2001 From: pseusys Date: Thu, 11 Apr 2024 22:05:36 +0200 Subject: [PATCH 65/76] types fix --- dff/script/core/script.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index b92eb9466..04327a223 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -11,7 +11,7 @@ from enum import Enum import inspect import logging -from typing import Callable, List, Optional, Any, Dict, Union, TYPE_CHECKING +from typing import Callable, List, Optional, Any, Dict, Tuple, Union, TYPE_CHECKING from pydantic import BaseModel, field_validator, validate_call @@ -35,12 +35,12 @@ class UserFunctionType(str, Enum): RESPONSE_PROCESSING = "pre_response_processing" -USER_FUNCTION_TYPES = { - UserFunctionType.LABEL: (("Context", "Pipeline"), NodeLabelType), - UserFunctionType.RESPONSE: (("Context", "Pipeline"), Message), - UserFunctionType.CONDITION: (("Context", "Pipeline"), bool), - UserFunctionType.RESPONSE_PROCESSING: (("Context", "Pipeline"), None), - UserFunctionType.TRANSITION_PROCESSING: (("Context", "Pipeline"), None), +USER_FUNCTION_TYPES: Dict[UserFunctionType, Tuple[Tuple[str, ...], str]] = { + UserFunctionType.LABEL: (("Context", "Pipeline"), "NodeLabelType"), + UserFunctionType.RESPONSE: (("Context", "Pipeline"), "Message"), + UserFunctionType.CONDITION: (("Context", "Pipeline"), "bool"), + UserFunctionType.RESPONSE_PROCESSING: (("Context", "Pipeline"), "None"), + UserFunctionType.TRANSITION_PROCESSING: (("Context", "Pipeline"), "None"), } @@ -76,7 +76,7 @@ def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_lab error_msgs = list() signature = inspect.signature(callable) - arguments_type, return_types = USER_FUNCTION_TYPES[func_type] + arguments_type, return_type = USER_FUNCTION_TYPES[func_type] params = list(signature.parameters.values()) if len(params) != len(arguments_type): msg = ( @@ -94,16 +94,10 @@ def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_lab f"Error found at {(flow_label, node_label)!r}." ) error_msgs.append(msg) - for potential_type in return_types: - if _types_equal(signature.return_annotation, potential_type): - break - else: - expected_type_message = ( - f"should be one of {return_types!r}" if len(return_types) > 1 else f"should be {return_types[0]!r}" - ) + if not _types_equal(signature.return_annotation, return_type): msg = ( f"Incorrect return type annotation of {callable.__name__!r}: " - f"{expected_type_message}, not {signature.return_annotation}. " + f"should be {return_type!r}, not {signature.return_annotation}. " f"Error found at {(flow_label, node_label)!r}." ) error_msgs.append(msg) From 90d071dbc0cc9640d416b3c10eb317c607db5ee9 Mon Sep 17 00:00:00 2001 From: pseusys Date: Sat, 13 Apr 2024 20:56:24 +0200 Subject: [PATCH 66/76] types renamed --- dff/script/__init__.py | 3 ++- dff/script/core/normalization.py | 8 ++++---- dff/script/core/script.py | 8 ++++---- dff/script/core/types.py | 7 +++++-- tutorials/script/core/4_transitions.py | 12 ++++++------ 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/dff/script/__init__.py b/dff/script/__init__.py index 577266bc9..9ac057b45 100644 --- a/dff/script/__init__.py +++ b/dff/script/__init__.py @@ -18,7 +18,8 @@ NodeLabel2Type, NodeLabel3Type, NodeLabelTupledType, - NodeLabelType, + ConstLabel, + Label, ConditionType, ModuleName, ActorStage, diff --git a/dff/script/core/normalization.py b/dff/script/core/normalization.py index 39c272acc..072a04ca8 100644 --- a/dff/script/core/normalization.py +++ b/dff/script/core/normalization.py @@ -12,7 +12,7 @@ from .keywords import Keywords from .context import Context -from .types import NodeLabel3Type, NodeLabelType, ConditionType, LabelType +from .types import NodeLabel3Type, ConstLabel, ConditionType, LabelType from .message import Message if TYPE_CHECKING: @@ -22,11 +22,11 @@ def normalize_label( - label: NodeLabelType, default_flow_label: LabelType = "" + label: ConstLabel, default_flow_label: LabelType = "" ) -> Union[Callable[[Context, Pipeline], NodeLabel3Type], NodeLabel3Type]: """ The function that is used for normalization of - :py:const:`default_flow_label `. + :py:const:`default_flow_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. @@ -63,7 +63,7 @@ def get_label_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: return (flow_label, label[1], label[2]) else: raise TypeError( - f"Label '{label!r}' is of incorrect type. It has to follow the `NodeLabelType`:\n" f"{NodeLabelType!r}" + f"Label '{label!r}' is of incorrect type. It has to follow the `ConstLabel`:\n" f"{ConstLabel!r}" ) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 04327a223..ebc0716ec 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -15,7 +15,7 @@ from pydantic import BaseModel, field_validator, validate_call -from .types import LabelType, NodeLabelType, ConditionType, NodeLabel3Type +from .types import LabelType, ConstLabel, ConditionType, NodeLabel3Type from .message import Message from .keywords import Keywords from .normalization import normalize_condition, normalize_label @@ -36,7 +36,7 @@ class UserFunctionType(str, Enum): USER_FUNCTION_TYPES: Dict[UserFunctionType, Tuple[Tuple[str, ...], str]] = { - UserFunctionType.LABEL: (("Context", "Pipeline"), "NodeLabelType"), + UserFunctionType.LABEL: (("Context", "Pipeline"), "ConstLabel"), UserFunctionType.RESPONSE: (("Context", "Pipeline"), "Message"), UserFunctionType.CONDITION: (("Context", "Pipeline"), "bool"), UserFunctionType.RESPONSE_PROCESSING: (("Context", "Pipeline"), "None"), @@ -109,7 +109,7 @@ class Node(BaseModel, extra="forbid", validate_assignment=True): The class for the `Node` object. """ - transitions: Dict[NodeLabelType, ConditionType] = {} + transitions: Dict[ConstLabel, ConditionType] = {} response: Optional[Union[Message, Callable[[Context, Pipeline], Message]]] = None pre_transitions_processing: Dict[Any, Callable] = {} pre_response_processing: Dict[Any, Callable] = {} @@ -119,7 +119,7 @@ class Node(BaseModel, extra="forbid", validate_assignment=True): @classmethod @validate_call def normalize_transitions( - cls, transitions: Dict[NodeLabelType, ConditionType] + cls, transitions: Dict[ConstLabel, ConditionType] ) -> Dict[Union[Callable, NodeLabel3Type], Callable]: """ The function which is used to normalize transitions and returns normalized dict. diff --git a/dff/script/core/types.py b/dff/script/core/types.py index a4f088dec..ef82460ff 100644 --- a/dff/script/core/types.py +++ b/dff/script/core/types.py @@ -28,8 +28,11 @@ NodeLabelTupledType: TypeAlias = Union[NodeLabel1Type, NodeLabel2Type, NodeLabel3Type] """Label type for transitions can be one of three different types.""" -NodeLabelType: TypeAlias = Union[Callable, NodeLabelTupledType, str] -"""Label type for transitions can be one of three different types.""" +ConstLabel: TypeAlias = Union[NodeLabelTupledType, str] +"""Label functions should be annotated with this type only.""" + +Label: TypeAlias = Union[ConstLabel, Callable] +"""Label type for transitions should be of this type only.""" ConditionType: TypeAlias = Callable """Condition type can be only `Callable`.""" diff --git a/tutorials/script/core/4_transitions.py b/tutorials/script/core/4_transitions.py index 123d226da..5c1402448 100644 --- a/tutorials/script/core/4_transitions.py +++ b/tutorials/script/core/4_transitions.py @@ -66,22 +66,22 @@ def transition(_: Context, __: Pipeline) -> NodeLabel3Type: offers the following methods: * `lbl.repeat()` returns transition handler - which returns `NodeLabelType` to the last node, + which returns `ConstLabel` to the last node, * `lbl.previous()` returns transition handler - which returns `NodeLabelType` to the previous node, + which returns `ConstLabel` to the previous node, * `lbl.to_start()` returns transition handler - which returns `NodeLabelType` to the start node, + which returns `ConstLabel` to the start node, * `lbl.to_fallback()` returns transition - handler which returns `NodeLabelType` to the fallback node, + handler which returns `ConstLabel` to the fallback node, * `lbl.forward()` returns transition handler - which returns `NodeLabelType` to the forward node, + which returns `ConstLabel` to the forward node, * `lbl.backward()` returns transition handler - which returns `NodeLabelType` to the backward node. + which returns `ConstLabel` to the backward node. There are three flows here: `global_flow`, `greeting_flow`, `music_flow`. """ From c59d2483c60eb0ccc83db0aaeecd6427e70ffac7 Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 15 Apr 2024 13:18:35 +0200 Subject: [PATCH 67/76] type names fixed --- dff/pipeline/pipeline/pipeline.py | 10 +----- dff/script/core/normalization.py | 6 ++-- dff/script/labels/std_labels.py | 44 ++++++++++++------------- tests/script/core/test_normalization.py | 6 ++-- tests/script/core/test_validation.py | 4 +-- tutorials/script/core/4_transitions.py | 8 ++--- 6 files changed, 35 insertions(+), 43 deletions(-) diff --git a/dff/pipeline/pipeline/pipeline.py b/dff/pipeline/pipeline/pipeline.py index 4f2aea97c..ca199a5ee 100644 --- a/dff/pipeline/pipeline/pipeline.py +++ b/dff/pipeline/pipeline/pipeline.py @@ -53,7 +53,6 @@ class Pipeline: :param label_priority: Default priority value for all actor :py:const:`labels ` where there is no priority. Defaults to `1.0`. :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`. @@ -87,7 +86,6 @@ def __init__( fallback_label: Optional[NodeLabel2Type] = None, label_priority: float = 1.0, 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, @@ -119,7 +117,6 @@ def __init__( fallback_label, label_priority, condition_handler, - verbose, handlers, ) if self.actor is None: @@ -208,7 +205,6 @@ def from_script( fallback_label: Optional[NodeLabel2Type] = None, label_priority: float = 1.0, 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, @@ -230,7 +226,6 @@ def from_script( :param label_priority: Default priority value for all actor :py:const:`labels ` where there is no priority. Defaults to `1.0`. :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. @@ -259,7 +254,6 @@ def from_script( fallback_label=fallback_label, label_priority=label_priority, condition_handler=condition_handler, - verbose=verbose, parallelize_processing=parallelize_processing, handlers=handlers, messenger_interface=messenger_interface, @@ -274,7 +268,6 @@ def set_actor( fallback_label: Optional[NodeLabel2Type] = None, label_priority: float = 1.0, condition_handler: Optional[Callable] = None, - verbose: bool = True, handlers: Optional[Dict[ActorStage, List[Callable]]] = None, ): """ @@ -287,10 +280,9 @@ 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 ` + :param label_priority: Default priority value for all actor :py:const:`labels ` where there is no priority. Defaults to `1.0`. :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`. diff --git a/dff/script/core/normalization.py b/dff/script/core/normalization.py index 072a04ca8..38a3c08e8 100644 --- a/dff/script/core/normalization.py +++ b/dff/script/core/normalization.py @@ -12,7 +12,7 @@ from .keywords import Keywords from .context import Context -from .types import NodeLabel3Type, ConstLabel, ConditionType, LabelType +from .types import ConstLabel, ConditionType, LabelType from .message import Message if TYPE_CHECKING: @@ -23,7 +23,7 @@ def normalize_label( label: ConstLabel, default_flow_label: LabelType = "" -) -> Union[Callable[[Context, Pipeline], NodeLabel3Type], NodeLabel3Type]: +) -> Union[Callable[[Context, Pipeline], ConstLabel], ConstLabel]: """ The function that is used for normalization of :py:const:`default_flow_label `. @@ -35,7 +35,7 @@ def normalize_label( """ 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) diff --git a/dff/script/labels/std_labels.py b/dff/script/labels/std_labels.py index c2f86b026..6f58c4411 100644 --- a/dff/script/labels/std_labels.py +++ b/dff/script/labels/std_labels.py @@ -1,36 +1,36 @@ """ Labels ------ -:py:const:`Labels ` are one of the important components of the dialog graph, +:py:const:`Labels ` are one of the important components of the dialog graph, which determine the targeted node name of the transition. They are used to identify the next step in the conversation. Labels can also be used in combination with other conditions, such as the current context or user data, to create more complex and dynamic conversations. -This module contains a standard set of scripting :py:const:`labels ` that +This module contains a standard set of scripting :py:const:`labels ` that can be used by developers to define the conversation flow. """ from __future__ import annotations from typing import Optional, Callable, TYPE_CHECKING -from dff.script import Context, NodeLabel3Type +from dff.script import Context, ConstLabel if TYPE_CHECKING: from dff.pipeline.pipeline.pipeline import Pipeline -def repeat(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabel3Type]: +def repeat(priority: Optional[float] = None) -> Callable[[Context, Pipeline], ConstLabel]: """ Returns transition handler that takes :py:class:`.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the last node with a given :py:const:`priority `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def repeat_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def repeat_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: current_priority = pipeline.actor.label_priority if priority is None else priority if len(ctx.labels) >= 1: flow_label, label = list(ctx.labels.values())[-1] @@ -41,11 +41,11 @@ def repeat_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Typ return repeat_transition_handler -def previous(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabel3Type]: +def previous(priority: Optional[float] = None) -> Callable[[Context, Pipeline], ConstLabel]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the previous node with a given :py:const:`priority `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. If the current node is the start node, fallback is returned. @@ -53,7 +53,7 @@ def previous(priority: Optional[float] = None) -> Callable[[Context, Pipeline], :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def previous_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def previous_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: current_priority = pipeline.actor.label_priority if priority is None else priority if len(ctx.labels) >= 2: flow_label, label = list(ctx.labels.values())[-2] @@ -66,36 +66,36 @@ def previous_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3T return previous_transition_handler -def to_start(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabel3Type]: +def to_start(priority: Optional[float] = None) -> Callable[[Context, Pipeline], ConstLabel]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the start node with a given :py:const:`priority `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def to_start_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def to_start_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: current_priority = pipeline.actor.label_priority if priority is None else priority return (*pipeline.actor.start_label[:2], current_priority) return to_start_transition_handler -def to_fallback(priority: Optional[float] = None) -> Callable[[Context, Pipeline], NodeLabel3Type]: +def to_fallback(priority: Optional[float] = None) -> Callable[[Context, Pipeline], ConstLabel]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the fallback node with a given :py:const:`priority `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. """ - def to_fallback_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def to_fallback_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: current_priority = pipeline.actor.label_priority if priority is None else priority return (*pipeline.actor.fallback_label[:2], current_priority) @@ -108,7 +108,7 @@ def _get_label_by_index_shifting( priority: Optional[float] = None, increment_flag: bool = True, cyclicality_flag: bool = True, -) -> NodeLabel3Type: +) -> ConstLabel: """ Function that returns node label from the context and pipeline after shifting the index. @@ -139,11 +139,11 @@ def _get_label_by_index_shifting( def forward( priority: Optional[float] = None, cyclicality_flag: bool = True -) -> Callable[[Context, Pipeline], NodeLabel3Type]: +) -> Callable[[Context, Pipeline], ConstLabel]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the forward node with a given :py:const:`priority ` and :py:const:`cyclicality_flag `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. @@ -152,7 +152,7 @@ def forward( (e.g the element with `index = len(labels)` has `index = 0`). Defaults to `True`. """ - def forward_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def forward_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: return _get_label_by_index_shifting( ctx, pipeline, priority, increment_flag=True, cyclicality_flag=cyclicality_flag ) @@ -162,11 +162,11 @@ def forward_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Ty def backward( priority: Optional[float] = None, cyclicality_flag: bool = True -) -> Callable[[Context, Pipeline], NodeLabel3Type]: +) -> Callable[[Context, Pipeline], ConstLabel]: """ Returns transition handler that takes :py:class:`~dff.script.Context`, :py:class:`~dff.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` + This handler returns a :py:const:`label ` to the backward node with a given :py:const:`priority ` and :py:const:`cyclicality_flag `. If the priority is not given, `Pipeline.actor.label_priority` is used as default. @@ -175,7 +175,7 @@ def backward( (e.g the element with `index = len(labels)` has `index = 0`). Defaults to `True`. """ - def back_transition_handler(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def back_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: return _get_label_by_index_shifting( ctx, pipeline, priority, increment_flag=False, cyclicality_flag=cyclicality_flag ) diff --git a/tests/script/core/test_normalization.py b/tests/script/core/test_normalization.py index fc871b889..66852b222 100644 --- a/tests/script/core/test_normalization.py +++ b/tests/script/core/test_normalization.py @@ -12,7 +12,7 @@ Context, Script, Node, - NodeLabel3Type, + ConstLabel, Message, ) from dff.script.labels import repeat @@ -36,10 +36,10 @@ def create_env() -> Tuple[Context, Pipeline]: def test_normalize_label(): ctx, actor = create_env() - def true_label_func(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def true_label_func(ctx: Context, pipeline: Pipeline) -> ConstLabel: return ("flow", "node1", 1) - def false_label_func(ctx: Context, pipeline: Pipeline) -> NodeLabel3Type: + def false_label_func(ctx: Context, pipeline: Pipeline) -> ConstLabel: return ("flow", "node2", 1) n_f = normalize_label(true_label_func) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 97a5174e5..70fd11f01 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -10,7 +10,7 @@ Context, Message, Script, - NodeLabel3Type, + ConstLabel, ) from dff.script.conditions import exact_match @@ -33,7 +33,7 @@ def wrong_return_type(_: Context, __: Pipeline) -> float: return 1.0 @staticmethod - def correct_label(_: Context, __: Pipeline) -> NodeLabel3Type: + def correct_label(_: Context, __: Pipeline) -> ConstLabel: return ("root", "start", 1) @staticmethod diff --git a/tutorials/script/core/4_transitions.py b/tutorials/script/core/4_transitions.py index 5c1402448..0711cd891 100644 --- a/tutorials/script/core/4_transitions.py +++ b/tutorials/script/core/4_transitions.py @@ -19,7 +19,7 @@ # %% import re -from dff.script import TRANSITIONS, RESPONSE, Context, NodeLabel3Type, Message +from dff.script import TRANSITIONS, RESPONSE, Context, ConstLabel, Message import dff.script.conditions as cnd import dff.script.labels as lbl from dff.pipeline import Pipeline @@ -33,7 +33,7 @@ """ Let's define the functions with a special type of return value: - NodeLabel3Type == tuple[str, str, float] + ConstLabel == tuple[str, str, float] or string which means that transition returns a `tuple` with flow name, node name and priority. @@ -41,12 +41,12 @@ # %% -def greeting_flow_n2_transition(_: Context, __: Pipeline) -> NodeLabel3Type: +def greeting_flow_n2_transition(_: Context, __: Pipeline) -> ConstLabel: return ("greeting_flow", "node2", 1.0) def high_priority_node_transition(flow_label, label): - def transition(_: Context, __: Pipeline) -> NodeLabel3Type: + def transition(_: Context, __: Pipeline) -> ConstLabel: return (flow_label, label, 2.0) return transition From f353feda4b85ea01b1ecae5ffab9e0f536fe7b52 Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 15 Apr 2024 13:30:10 +0200 Subject: [PATCH 68/76] node label type changed --- dff/pipeline/types.py | 1 - dff/script/core/script.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dff/pipeline/types.py b/dff/pipeline/types.py index 554c05994..c26525972 100644 --- a/dff/pipeline/types.py +++ b/dff/pipeline/types.py @@ -264,7 +264,6 @@ class ExtraHandlerRuntimeInfo(BaseModel): "fallback_label": NotRequired[Optional[NodeLabel2Type]], "label_priority": NotRequired[float], "condition_handler": NotRequired[Optional[Callable]], - "verbose": NotRequired[bool], "handlers": NotRequired[Optional[Dict[ActorStage, List[Callable]]]], }, ) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index ebc0716ec..87f48fbad 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -15,7 +15,7 @@ from pydantic import BaseModel, field_validator, validate_call -from .types import LabelType, ConstLabel, ConditionType, NodeLabel3Type +from .types import Label, LabelType, ConstLabel, ConditionType, NodeLabel3Type from .message import Message from .keywords import Keywords from .normalization import normalize_condition, normalize_label @@ -109,7 +109,7 @@ class Node(BaseModel, extra="forbid", validate_assignment=True): The class for the `Node` object. """ - transitions: Dict[ConstLabel, ConditionType] = {} + transitions: Dict[Label, ConditionType] = {} response: Optional[Union[Message, Callable[[Context, Pipeline], Message]]] = None pre_transitions_processing: Dict[Any, Callable] = {} pre_response_processing: Dict[Any, Callable] = {} From 66372c4cb23b1c816fd5b9330647da84a4c5c3ba Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 15 Apr 2024 13:39:35 +0200 Subject: [PATCH 69/76] constlabel reverted --- dff/script/core/normalization.py | 8 ++++---- dff/script/core/script.py | 6 ++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/dff/script/core/normalization.py b/dff/script/core/normalization.py index 38a3c08e8..885bfbcdc 100644 --- a/dff/script/core/normalization.py +++ b/dff/script/core/normalization.py @@ -12,7 +12,7 @@ from .keywords import Keywords from .context import Context -from .types import ConstLabel, ConditionType, LabelType +from .types import ConstLabel, ConditionType, Label, LabelType from .message import Message if TYPE_CHECKING: @@ -22,11 +22,11 @@ def normalize_label( - label: ConstLabel, default_flow_label: LabelType = "" + label: Label, default_flow_label: LabelType = "" ) -> Union[Callable[[Context, Pipeline], ConstLabel], ConstLabel]: """ The function that is used for normalization of - :py:const:`default_flow_label `. + :py:const:`default_flow_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. @@ -63,7 +63,7 @@ def get_label_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: return (flow_label, label[1], label[2]) else: raise TypeError( - f"Label '{label!r}' is of incorrect type. It has to follow the `ConstLabel`:\n" f"{ConstLabel!r}" + f"Label '{label!r}' is of incorrect type. It has to follow the `Label`:\n" f"{Label!r}" ) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 87f48fbad..6e49b4484 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -15,7 +15,7 @@ from pydantic import BaseModel, field_validator, validate_call -from .types import Label, LabelType, ConstLabel, ConditionType, NodeLabel3Type +from .types import Label, LabelType, ConstLabel, ConditionType from .message import Message from .keywords import Keywords from .normalization import normalize_condition, normalize_label @@ -118,9 +118,7 @@ class Node(BaseModel, extra="forbid", validate_assignment=True): @field_validator("transitions", mode="before") @classmethod @validate_call - def normalize_transitions( - cls, transitions: Dict[ConstLabel, ConditionType] - ) -> Dict[Union[Callable, NodeLabel3Type], Callable]: + def normalize_transitions(cls, transitions: Dict[Label, ConditionType]) -> Dict[Label, Callable]: """ The function which is used to normalize transitions and returns normalized dict. From 2e19912a84fc9dfd30e23358d4099f874995265e Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 15 Apr 2024 13:53:13 +0200 Subject: [PATCH 70/76] test regex fixed --- tests/script/core/test_validation.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index 70fd11f01..c76b7ca68 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -66,7 +66,7 @@ def test_param_number(self): assert e def test_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: Script( script={ "root": { @@ -77,7 +77,7 @@ def test_param_types(self): assert e def test_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: + with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Incorrect return type annotation") as e: Script( script={ "root": { @@ -88,12 +88,12 @@ def test_return_type(self): assert e def test_flow_name(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Flow label") as e: + with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Flow label") as e: Script(script={"root": {"start": {TRANSITIONS: {("other", "start", 1): exact_match(Message("hi"))}}}}) assert e def test_node_name(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Node label") as e: + with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Node label") as e: Script(script={"root": {"start": {TRANSITIONS: {("root", "other", 1): exact_match(Message("hi"))}}}}) assert e @@ -115,7 +115,7 @@ def test_param_types(self): assert e def test_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: + with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Incorrect return type annotation") as e: Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_return_type}}}) assert e @@ -141,7 +141,7 @@ def test_param_types(self): assert e def test_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: + with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Incorrect return type annotation") as e: Script( script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_return_type}}}} ) @@ -167,7 +167,7 @@ def test_response_param_types(self): assert e def test_response_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: + with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Incorrect return type annotation") as e: Script( script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_return_type}}}} ) From 597a82e3599519aac06a5b3a40b44309e0dda974 Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 15 Apr 2024 14:01:43 +0200 Subject: [PATCH 71/76] regex attuned pnce again --- tests/script/core/test_validation.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index c76b7ca68..eab46910d 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -88,12 +88,12 @@ def test_return_type(self): assert e def test_flow_name(self): - with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Flow label") as e: + with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Flow '\w*' cannot be found for label") as e: Script(script={"root": {"start": {TRANSITIONS: {("other", "start", 1): exact_match(Message("hi"))}}}}) assert e def test_node_name(self): - with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Node label") as e: + with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Node '\w*' cannot be found for label") as e: Script(script={"root": {"start": {TRANSITIONS: {("root", "other", 1): exact_match(Message("hi"))}}}}) assert e @@ -110,7 +110,7 @@ def test_param_number(self): assert e def test_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_param_types}}}) assert e @@ -134,7 +134,7 @@ def test_param_number(self): assert e def test_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: Script( script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_param_types}}}} ) @@ -160,7 +160,7 @@ def test_response_param_number(self): assert e def test_response_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: Script( script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_param_types}}}} ) @@ -192,7 +192,7 @@ def test_transition_param_number(self): assert e def test_transition_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect \d+ parameter annotation") as e: + with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: Script( script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_param_types}}}} ) From 3fa6d14924044dbd872c8b553b9c81d1e17ae4ac Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 15 Apr 2024 14:06:35 +0200 Subject: [PATCH 72/76] last regex error fixed --- tests/script/core/test_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py index eab46910d..13941ec4a 100644 --- a/tests/script/core/test_validation.py +++ b/tests/script/core/test_validation.py @@ -199,7 +199,7 @@ def test_transition_param_types(self): assert e def test_transition_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 errors:[\w\W]*Incorrect return type annotation") as e: + with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Incorrect return type annotation") as e: Script( script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_return_type}}}} ) From 1e81d1250bd558a1f5a7d9874ec88da2f254ed1f Mon Sep 17 00:00:00 2001 From: pseusys Date: Mon, 15 Apr 2024 14:40:49 +0200 Subject: [PATCH 73/76] fprmatting applied --- dff/script/core/normalization.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dff/script/core/normalization.py b/dff/script/core/normalization.py index 885bfbcdc..53f049817 100644 --- a/dff/script/core/normalization.py +++ b/dff/script/core/normalization.py @@ -62,9 +62,7 @@ def get_label_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: 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}" - ) + 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]: From 05227527998e1b7613131a0ef8eaca747931352c Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 16 Apr 2024 01:14:54 +0300 Subject: [PATCH 74/76] doc improvements doc improvements --- dff/pipeline/pipeline/actor.py | 2 +- dff/pipeline/pipeline/pipeline.py | 6 +++--- dff/script/conditions/std_conditions.py | 3 ++- dff/script/core/normalization.py | 6 ++---- tutorials/script/core/4_transitions.py | 25 +++++++++++++++++++------ 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/dff/pipeline/pipeline/actor.py b/dff/pipeline/pipeline/actor.py index fd957a4ff..3bac71feb 100644 --- a/dff/pipeline/pipeline/actor.py +++ b/dff/pipeline/pipeline/actor.py @@ -57,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 ` + :param label_priority: Default priority value for all :py:const:`labels ` 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 diff --git a/dff/pipeline/pipeline/pipeline.py b/dff/pipeline/pipeline/pipeline.py index ca199a5ee..d901a3ebf 100644 --- a/dff/pipeline/pipeline/pipeline.py +++ b/dff/pipeline/pipeline/pipeline.py @@ -50,7 +50,7 @@ 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 ` + :param label_priority: Default priority value for all actor :py:const:`labels ` where there is no priority. Defaults to `1.0`. :param condition_handler: Handler that processes a call of actor condition functions. Defaults to `None`. :param handlers: This variable is responsible for the usage of external handlers on @@ -223,7 +223,7 @@ 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 ` + :param label_priority: Default priority value for all actor :py:const:`labels ` where there is no priority. Defaults to `1.0`. :param condition_handler: Handler that processes a call of actor condition functions. Defaults to `None`. :param parallelize_processing: This flag determines whether or not the functions @@ -280,7 +280,7 @@ 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 ` + :param label_priority: Default priority value for all actor :py:const:`labels ` where there is no priority. Defaults to `1.0`. :param condition_handler: Handler that processes a call of actor condition functions. Defaults to `None`. :param handlers: This variable is responsible for the usage of external handlers on diff --git a/dff/script/conditions/std_conditions.py b/dff/script/conditions/std_conditions.py index d2d23f4e8..88c086ab4 100644 --- a/dff/script/conditions/std_conditions.py +++ b/dff/script/conditions/std_conditions.py @@ -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 diff --git a/dff/script/core/normalization.py b/dff/script/core/normalization.py index 53f049817..e6926227d 100644 --- a/dff/script/core/normalization.py +++ b/dff/script/core/normalization.py @@ -21,12 +21,10 @@ logger = logging.getLogger(__name__) -def normalize_label( - label: Label, default_flow_label: LabelType = "" -) -> Union[Callable[[Context, Pipeline], ConstLabel], ConstLabel]: +def normalize_label(label: Label, default_flow_label: LabelType = "") -> Label: """ The function that is used for normalization of - :py:const:`default_flow_label `. + :py:const:`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. diff --git a/tutorials/script/core/4_transitions.py b/tutorials/script/core/4_transitions.py index 0711cd891..250cd0d23 100644 --- a/tutorials/script/core/4_transitions.py +++ b/tutorials/script/core/4_transitions.py @@ -33,21 +33,34 @@ """ Let's define the functions with a special type of return value: - ConstLabel == tuple[str, str, float] or string + ConstLabel == Flow Name; Node Name; Priority -which means that transition returns a `tuple` -with flow name, node name and priority. +These functions return Labels that +determine destination and priority of a specific transition. + +Labels consist of: + +1. Flow name of the destination node + (optional; defaults to flow name of the current node). +2. Node name of the destination node + (required). +3. Priority of the transition (more on that later) + (optional; defaults to pipeline's + [label_priority](%doclink(api,pipeline.pipeline.pipeline))). + +An example of omitting optional arguments is shown in the body of the +`greeting_flow_n2_transition` function: """ # %% def greeting_flow_n2_transition(_: Context, __: Pipeline) -> ConstLabel: - return ("greeting_flow", "node2", 1.0) + return "greeting_flow", "node2" -def high_priority_node_transition(flow_label, label): +def high_priority_node_transition(flow_name, node_name): def transition(_: Context, __: Pipeline) -> ConstLabel: - return (flow_label, label, 2.0) + return flow_name, node_name, 2.0 return transition From 50b354a608aa51809c82c6fd11b772fb02a000ba Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 16 Apr 2024 01:15:13 +0300 Subject: [PATCH 75/76] add some todos --- dff/script/core/types.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dff/script/core/types.py b/dff/script/core/types.py index ef82460ff..69b8aafd2 100644 --- a/dff/script/core/types.py +++ b/dff/script/core/types.py @@ -15,6 +15,7 @@ LabelType: TypeAlias = Union[str, Keywords] """Label can be a casual string or :py:class:`~dff.script.Keywords`.""" +# todo: rename these to identifiers NodeLabel1Type: TypeAlias = Tuple[str, float] """Label type for transitions can be `[node_name, transition_priority]`.""" @@ -27,6 +28,7 @@ NodeLabelTupledType: TypeAlias = Union[NodeLabel1Type, NodeLabel2Type, NodeLabel3Type] """Label type for transitions can be one of three different types.""" +# todo: group all these types into a class ConstLabel: TypeAlias = Union[NodeLabelTupledType, str] """Label functions should be annotated with this type only.""" From 20717a6894e6f4226282daf9fc21a4acb8565f1f Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 16 Apr 2024 01:26:36 +0300 Subject: [PATCH 76/76] ignore unused import --- dff/script/core/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dff/script/core/script.py b/dff/script/core/script.py index 6e49b4484..3c7fde9b5 100644 --- a/dff/script/core/script.py +++ b/dff/script/core/script.py @@ -15,7 +15,7 @@ from pydantic import BaseModel, field_validator, validate_call -from .types import Label, LabelType, ConstLabel, ConditionType +from .types import Label, LabelType, ConditionType, ConstLabel # noqa: F401 from .message import Message from .keywords import Keywords from .normalization import normalize_condition, normalize_label