Example #1
0
def step(*args, **kwargs):
    """A decorator used for creating workflow steps.

    Examples:
        >>> @workflow.step
        ... def book_flight(origin: str, dest: str) -> Flight:
        ...    return Flight(...)

        >>> @workflow.step(max_retries=3, catch_exceptions=True)
        ... def book_hotel(dest: str) -> Hotel:
        ...    return Hotel(...)

    """
    if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
        options = WorkflowStepRuntimeOptions.make(step_type=StepType.FUNCTION)
        return make_step_decorator(options)(args[0])
    if len(args) != 0:
        raise ValueError(f"Invalid arguments for step decorator {args}")
    max_retries = kwargs.pop("max_retries", None)
    catch_exceptions = kwargs.pop("catch_exceptions", None)
    name = kwargs.pop("name", None)
    metadata = kwargs.pop("metadata", None)
    allow_inplace = kwargs.pop("allow_inplace", False)
    ray_options = kwargs

    options = WorkflowStepRuntimeOptions.make(
        step_type=StepType.FUNCTION,
        catch_exceptions=catch_exceptions,
        max_retries=max_retries,
        allow_inplace=allow_inplace,
        ray_options=ray_options)
    return make_step_decorator(options, name, metadata)
Example #2
0
    def options(self,
                *,
                max_retries=1,
                catch_exceptions=False,
                name=None,
                metadata=None,
                **ray_options) -> "_VirtualActorMethodHelper":
        if metadata is not None:
            if not isinstance(metadata, dict):
                raise ValueError("metadata must be a dict.")
            for k, v in metadata.items():
                try:
                    json.dumps(v)
                except TypeError as e:
                    raise ValueError(
                        "metadata values must be JSON serializable, "
                        "however '{}' has a value whose {}.".format(k, e))
        options = WorkflowStepRuntimeOptions.make(
            step_type=self._options.step_type,
            catch_exceptions=catch_exceptions,
            max_retries=max_retries,
            ray_options=ray_options)
        _self = _VirtualActorMethodHelper(self._original_class,
                                          self._original_method,
                                          self._method_name,
                                          runtime_options=options)

        _self._name = name
        _self._user_metadata = metadata
        return _self
Example #3
0
        def _build_workflow(*args, **kwargs) -> Workflow:
            flattened_args = signature.flatten_args(self._func_signature, args, kwargs)

            def prepare_inputs():
                ensure_ray_initialized()
                return serialization_context.make_workflow_inputs(flattened_args)

            nonlocal step_options
            if step_options is None:
                step_options = WorkflowStepRuntimeOptions.make(
                    step_type=StepType.FUNCTION
                )
            # We could have "checkpoint=None" when we use @workflow.step
            # with arguments. Avoid this by updating it here.
            step_options.checkpoint = _inherit_checkpoint_option(
                step_options.checkpoint
            )

            workflow_data = WorkflowData(
                func_body=self._func,
                inputs=None,
                step_options=step_options,
                name=self._name,
                user_metadata=self._user_metadata,
            )
            return Workflow(workflow_data, prepare_inputs)
Example #4
0
def _make_workflow_step_function(node: FunctionNode):
    from ray.workflow.step_function import WorkflowStepFunction

    bound_options = node._bound_options.copy()
    workflow_options = bound_options.pop("_metadata",
                                         {}).get(WORKFLOW_OPTIONS, {})
    # "_resolve_like_object_ref_in_args" indicates we should resolve the
    # workflow like an ObjectRef, when included in the arguments of
    # another workflow.
    bound_options["_resolve_like_object_ref_in_args"] = True
    step_options = WorkflowStepRuntimeOptions.make(
        step_type=StepType.FUNCTION,
        catch_exceptions=workflow_options.get("catch_exceptions", None),
        max_retries=workflow_options.get("max_retries", None),
        allow_inplace=workflow_options.get("allow_inplace", False),
        checkpoint=workflow_options.get("checkpoint", None),
        ray_options=bound_options,
    )

    return WorkflowStepFunction(
        node._body,
        step_options=step_options,
        name=workflow_options.get("name", None),
        metadata=workflow_options.pop("metadata", None),
    )
Example #5
0
 def options(
     self,
     *,
     max_retries=0,
     catch_exceptions=False,
     name=None,
     metadata=None,
     **ray_options,
 ) -> "_VirtualActorMethodHelper":
     validate_user_metadata(metadata)
     options = WorkflowStepRuntimeOptions.make(
         step_type=self._options.step_type,
         catch_exceptions=catch_exceptions if catch_exceptions is not None
         else self._options.catch_exceptions,
         max_retries=max_retries
         if max_retries is not None else self._options.max_retries,
         ray_options={
             **self._options.ray_options,
             **(ray_options if ray_options is not None else {}),
         },
     )
     _self = _VirtualActorMethodHelper(
         self._original_class,
         self._original_method,
         self._method_name,
         runtime_options=options,
     )
     _self._name = name if name is not None else self._name
     _self._user_metadata = {
         **self._user_metadata,
         **(metadata if metadata is not None else {}),
     }
     return _self
Example #6
0
    def options(
        self,
        *,
        max_retries: int = None,
        catch_exceptions: bool = None,
        name: str = None,
        metadata: Dict[str, Any] = None,
        allow_inplace: bool = None,
        checkpoint: "Optional[CheckpointModeType]" = None,
        **ray_options,
    ) -> "WorkflowStepFunction":
        """This function set how the step function is going to be executed.

        Args:
            max_retries: num of retries the step for an application
                level error.
            catch_exceptions: Whether the user want to take care of the
                failure mannually.
                If it's set to be true, (Optional[R], Optional[E]) will be
                returned.
                If it's false, the normal result will be returned.
            name: The name of this step, which will be used to
                generate the step_id of the step. The name will be used
                directly as the step id if possible, otherwise deduplicated by
                appending .N suffixes.
            metadata: metadata to add to the step.
            allow_inplace: Execute the workflow step inplace.
            checkpoint: The option for checkpointing.
            **ray_options: All parameters in this fields will be passed
                to ray remote function options.

        Returns:
            The step function itself.
        """
        validate_user_metadata(metadata)
        name = name if name is not None else self._name
        metadata = {
            **self._user_metadata,
            **(metadata if metadata is not None else {})
        }
        step_options = WorkflowStepRuntimeOptions.make(
            step_type=StepType.FUNCTION,
            catch_exceptions=catch_exceptions if catch_exceptions is not None
            else self._step_options.catch_exceptions,
            max_retries=max_retries
            if max_retries is not None else self._step_options.max_retries,
            allow_inplace=allow_inplace
            if allow_inplace is not None else self._step_options.allow_inplace,
            checkpoint=_inherit_checkpoint_option(checkpoint),
            ray_options={
                **self._step_options.ray_options,
                **(ray_options if ray_options is not None else {}),
            },
        )
        return WorkflowStepFunction(self._func,
                                    step_options=step_options,
                                    name=name,
                                    metadata=metadata)
Example #7
0
    def options(
        self,
        *,
        max_retries: int = 3,
        catch_exceptions: bool = False,
        name: str = None,
        metadata: Dict[str, Any] = None,
        allow_inplace: bool = False,
        checkpoint: "Optional[CheckpointModeType]" = None,
        **ray_options,
    ) -> "WorkflowStepFunction":
        """This function set how the step function is going to be executed.

        Args:
            max_retries: num of retries the step for an application
                level error.
            catch_exceptions: Whether the user want to take care of the
                failure mannually.
                If it's set to be true, (Optional[R], Optional[E]) will be
                returned.
                If it's false, the normal result will be returned.
            name: The name of this step, which will be used to
                generate the step_id of the step. The name will be used
                directly as the step id if possible, otherwise deduplicated by
                appending .N suffixes.
            metadata: metadata to add to the step.
            allow_inplace: Execute the workflow step inplace.
            checkpoint: The option for checkpointing.
            **ray_options: All parameters in this fields will be passed
                to ray remote function options.

        Returns:
            The step function itself.
        """
        # TODO(suquark): The options seems drops items that we did not
        # specify (e.g., the name become "None" if we did not pass
        # name to the options). This does not seem correct to me.
        step_options = WorkflowStepRuntimeOptions.make(
            step_type=StepType.FUNCTION,
            catch_exceptions=catch_exceptions,
            max_retries=max_retries,
            allow_inplace=allow_inplace,
            checkpoint=_inherit_checkpoint_option(checkpoint),
            ray_options=ray_options,
        )
        return WorkflowStepFunction(
            self._func, step_options=step_options, name=name, metadata=metadata
        )
Example #8
0
    def __init__(
        self,
        func: Callable,
        *,
        step_options: "WorkflowStepRuntimeOptions" = None,
        name: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ):
        if metadata is not None:
            if not isinstance(metadata, dict):
                raise ValueError("metadata must be a dict.")
            for k, v in metadata.items():
                try:
                    json.dumps(v)
                except TypeError as e:
                    raise ValueError(
                        "metadata values must be JSON serializable, "
                        "however '{}' has a value whose {}.".format(k, e)
                    )
        if step_options is None:
            step_options = WorkflowStepRuntimeOptions.make(step_type=StepType.FUNCTION)
        self._func = func
        self._step_options = step_options
        self._func_signature = signature.extract_signature(func)
        self._name = name or ""
        self._user_metadata = metadata or {}

        # Override signature and docstring
        @functools.wraps(func)
        def _build_workflow(*args, **kwargs) -> Workflow:
            flattened_args = signature.flatten_args(self._func_signature, args, kwargs)

            def prepare_inputs():
                ensure_ray_initialized()
                return serialization_context.make_workflow_inputs(flattened_args)

            workflow_data = WorkflowData(
                func_body=self._func,
                inputs=None,
                step_options=step_options,
                name=self._name,
                user_metadata=self._user_metadata,
            )
            return Workflow(workflow_data, prepare_inputs)

        self.step = _build_workflow
Example #9
0
    def __init__(self, original_class: type):
        actor_methods = inspect.getmembers(original_class, is_function_or_method)

        self.cls = original_class
        self.module = original_class.__module__
        self.name = original_class.__name__
        self.qualname = original_class.__qualname__
        self.methods = {}
        for method_name, method in actor_methods:
            self._readonly = getattr(method, "__virtual_actor_readonly__", False)
            if self._readonly:
                step_type = StepType.READONLY_ACTOR_METHOD
            else:
                step_type = StepType.ACTOR_METHOD
            options = WorkflowStepRuntimeOptions.make(step_type=step_type)
            self.methods[method_name] = _VirtualActorMethodHelper(
                original_class, method, method_name, runtime_options=options
            )
Example #10
0
def test_workflow_storage(workflow_start_regular):
    workflow_id = test_workflow_storage.__name__
    wf_storage = workflow_storage.WorkflowStorage(workflow_id)
    step_id = "some_step"
    step_options = WorkflowStepRuntimeOptions.make(step_type=StepType.FUNCTION)
    input_metadata = {
        "name": "test_basic_workflows.append1",
        "workflows": ["def"],
        "workflow_refs": ["some_ref"],
        "step_options": step_options.to_dict(),
    }
    output_metadata = {
        "output_step_id": "a12423",
        "dynamic_output_step_id": "b1234"
    }
    root_output_metadata = {"output_step_id": "c123"}
    flattened_args = [
        signature.DUMMY_TYPE, 1, signature.DUMMY_TYPE, "2", "k", b"543"
    ]
    args = signature.recover_args(flattened_args)
    output = ["the_answer"]
    object_resolved = 42
    obj_ref = ray.put(object_resolved)

    # test basics
    wf_storage._put(wf_storage._key_step_input_metadata(step_id),
                    input_metadata, True)

    wf_storage._put(wf_storage._key_step_function_body(step_id), some_func)
    wf_storage._put(wf_storage._key_step_args(step_id), flattened_args)

    wf_storage._put(wf_storage._key_obj_id(obj_ref.hex()), ray.get(obj_ref))
    wf_storage._put(wf_storage._key_step_output_metadata(step_id),
                    output_metadata, True)
    wf_storage._put(wf_storage._key_step_output_metadata(""),
                    root_output_metadata, True)
    wf_storage._put(wf_storage._key_step_output(step_id), output)

    assert wf_storage.load_step_output(step_id) == output
    assert wf_storage.load_step_args(step_id, [], []) == args
    assert wf_storage.load_step_func_body(step_id)(33) == 34
    assert ray.get(wf_storage.load_object_ref(
        obj_ref.hex())) == object_resolved

    # test s3 path
    # here we hardcode the path to make sure s3 path is parsed correctly
    from ray._private.storage import _storage_uri

    if _storage_uri.startswith("s3://"):
        assert wf_storage._get("steps/outputs.json",
                               True) == root_output_metadata

    # test "inspect_step"
    inspect_result = wf_storage.inspect_step(step_id)
    assert inspect_result == workflow_storage.StepInspectResult(
        output_object_valid=True)
    assert inspect_result.is_recoverable()

    step_id = "some_step2"
    wf_storage._put(wf_storage._key_step_input_metadata(step_id),
                    input_metadata, True)
    wf_storage._put(wf_storage._key_step_function_body(step_id), some_func)
    wf_storage._put(wf_storage._key_step_args(step_id), args)
    wf_storage._put(wf_storage._key_step_output_metadata(step_id),
                    output_metadata, True)

    inspect_result = wf_storage.inspect_step(step_id)
    assert inspect_result == workflow_storage.StepInspectResult(
        output_step_id=output_metadata["dynamic_output_step_id"])
    assert inspect_result.is_recoverable()

    step_id = "some_step3"
    wf_storage._put(wf_storage._key_step_input_metadata(step_id),
                    input_metadata, True)
    wf_storage._put(wf_storage._key_step_function_body(step_id), some_func)
    wf_storage._put(wf_storage._key_step_args(step_id), args)
    inspect_result = wf_storage.inspect_step(step_id)
    assert inspect_result == workflow_storage.StepInspectResult(
        args_valid=True,
        func_body_valid=True,
        workflows=input_metadata["workflows"],
        workflow_refs=input_metadata["workflow_refs"],
        step_options=step_options,
    )
    assert inspect_result.is_recoverable()

    step_id = "some_step4"
    wf_storage._put(wf_storage._key_step_input_metadata(step_id),
                    input_metadata, True)

    wf_storage._put(wf_storage._key_step_function_body(step_id), some_func)
    inspect_result = wf_storage.inspect_step(step_id)
    assert inspect_result == workflow_storage.StepInspectResult(
        func_body_valid=True,
        workflows=input_metadata["workflows"],
        workflow_refs=input_metadata["workflow_refs"],
        step_options=step_options,
    )
    assert not inspect_result.is_recoverable()

    step_id = "some_step5"
    wf_storage._put(wf_storage._key_step_input_metadata(step_id),
                    input_metadata, True)

    inspect_result = wf_storage.inspect_step(step_id)
    assert inspect_result == workflow_storage.StepInspectResult(
        workflows=input_metadata["workflows"],
        workflow_refs=input_metadata["workflow_refs"],
        step_options=step_options,
    )
    assert not inspect_result.is_recoverable()

    step_id = "some_step6"
    inspect_result = wf_storage.inspect_step(step_id)
    print(inspect_result)
    assert inspect_result == workflow_storage.StepInspectResult()
    assert not inspect_result.is_recoverable()
Example #11
0
def wait(workflows: List[Workflow],
         num_returns: int = 1,
         timeout: Optional[float] = None) -> Workflow[WaitResult]:
    """Return a list of result of workflows that are ready and a list of
    workflows that are pending.

    Examples:
        >>> tasks = [task.step() for _ in range(3)]
        >>> wait_step = workflow.wait(tasks, num_returns=1)
        >>> print(wait_step.run())
        ([result_1], [<Workflow object>, <Workflow object>])

        >>> tasks = [task.step() for _ in range(2)] + [forever.step()]
        >>> wait_step = workflow.wait(tasks, num_returns=3, timeout=10)
        >>> print(wait_step.run())
        ([result_1, result_2], [<Workflow object>])

    If timeout is set, the function returns either when the requested number of
    workflows are ready or when the timeout is reached, whichever occurs first.
    If it is not set, the function simply waits until that number of workflows
    is ready and returns that exact number of workflows.

    This method returns two lists. The first list consists of workflows
    references that correspond to workflows that are ready. The second
    list corresponds to the rest of the workflows (which may or may not be
    ready).

    Ordering of the input list of workflows is preserved. That is, if A
    precedes B in the input list, and both are in the ready list, then A will
    precede B in the ready list. This also holds true if A and B are both in
    the remaining list.

    This method will issue a warning if it's running inside an async context.

    Args:
        workflows (List[Workflow]): List of workflows that may
            or may not be ready. Note that these workflows must be unique.
        num_returns (int): The number of workflows that should be returned.
        timeout (float): The maximum amount of time in seconds to wait before
            returning.

    Returns:
        A list of ready workflow results that are ready and a list of the
        remaining workflows.
    """
    from ray.workflow import serialization_context
    from ray.workflow.common import WorkflowData
    for w in workflows:
        if not isinstance(w, Workflow):
            raise TypeError("The input of workflow.wait should be a list "
                            "of workflows.")
    wait_inputs = serialization_context.make_workflow_inputs(workflows)
    step_options = WorkflowStepRuntimeOptions.make(
        step_type=StepType.WAIT,
        # Pass the options through Ray options. "num_returns" conflicts with
        # the "num_returns" for Ray remote functions, so we need to wrap it
        # under "wait_options".
        ray_options={
            "wait_options": {
                "num_returns": num_returns,
                "timeout": timeout,
            }
        },
    )
    workflow_data = WorkflowData(func_body=None,
                                 inputs=wait_inputs,
                                 step_options=step_options,
                                 name="workflow.wait",
                                 user_metadata={})
    return Workflow(workflow_data)