def test_invalid_use_sync_handle(): deployment = Deployment( Actor, "test", DeploymentConfig(), _internal=True, ) with pytest.raises( ValueError, match=f"{USE_SYNC_HANDLE_KEY} should only be set with a boolean value", ): _ = DeploymentNode( Actor, "test", [], {}, {}, other_args_to_resolve={USE_SYNC_HANDLE_KEY: {"options_a": "hii"}}, ) with pytest.raises( ValueError, match=f"{USE_SYNC_HANDLE_KEY} should only be set with a boolean value", ): _ = DeploymentMethodNode( deployment, "method", [], {}, {}, other_args_to_resolve={ USE_SYNC_HANDLE_KEY: None, }, )
def transform_ray_dag_to_serve_dag(dag_node): """ Transform a Ray DAG to a Serve DAG. Map ClassNode to DeploymentNode with ray decorated body passed in, ans ClassMethodNode to DeploymentMethodNode. """ if isinstance(dag_node, ClassNode): deployment_name = DeploymentNameGenerator.get_deployment_name(dag_node) ray_actor_options = _remove_non_default_ray_actor_options( dag_node.get_options()) return DeploymentNode( dag_node._body, deployment_name, dag_node.get_args(), dag_node.get_kwargs(), ray_actor_options, # TODO: (jiaodong) Support .options(metadata=xxx) for deployment other_args_to_resolve=dag_node.get_other_args_to_resolve(), ) elif isinstance(dag_node, ClassMethodNode): other_args_to_resolve = dag_node.get_other_args_to_resolve() # TODO: (jiaodong) Need to capture DAGNodes in the parent node parent_deployment_node = other_args_to_resolve[PARENT_CLASS_NODE_KEY] return DeploymentMethodNode( parent_deployment_node._deployment, dag_node._method_name, dag_node.get_args(), dag_node.get_kwargs(), dag_node.get_options(), other_args_to_resolve=dag_node.get_other_args_to_resolve(), ) else: # TODO: (jiaodong) Support FunctionNode or leave it as ray task return dag_node
def test_no_input_node_as_init_args(): """ User should NOT directly create instances of Deployment or DeploymentNode. """ with pytest.raises( ValueError, match="cannot be used as args, kwargs, or other_args_to_resolve", ): _ = DeploymentNode( Actor, "test", (InputNode()), {}, {}, other_args_to_resolve={USE_SYNC_HANDLE_KEY: True}, ) with pytest.raises( ValueError, match="cannot be used as args, kwargs, or other_args_to_resolve", ): _ = DeploymentNode( Actor, "test", (), {"a": InputNode()}, {}, other_args_to_resolve={USE_SYNC_HANDLE_KEY: True}, ) with pytest.raises( ValueError, match="cannot be used as args, kwargs, or other_args_to_resolve", ): _ = DeploymentNode( Actor, "test", (), {}, {}, other_args_to_resolve={"arg": { "options_a": InputNode() }}, )
def dagnode_from_json(input_json: Any) -> Union[DAGNode, RayServeHandle, Any]: """ Decode a DAGNode from given input json dictionary. JSON serialization is only used and enforced in ray serve from ray core API authored DAGNode(s). Covers both RayServeHandle and DAGNode types. Assumptions: - User object's JSON dict does not have keys that collide with our reserved DAGNODE_TYPE_KEY - RayServeHandle and Deployment can be re-constructed without losing states needed for their functionality or correctness. - DAGNode type can be re-constructed with new stable_uuid upon each deserialization without effective correctness of execution. - Only exception is ClassNode used as parent of ClassMethodNode that we perserve the same parent node. - .options() does not contain any DAGNode type """ # Deserialize RayServeHandle type if SERVE_HANDLE_JSON_KEY in input_json: return serve_handle_from_json_dict(input_json) # Base case for plain objects elif DAGNODE_TYPE_KEY not in input_json: return input_json elif input_json[DAGNODE_TYPE_KEY] == RayServeDAGHandle.__name__: return RayServeDAGHandle(input_json["dag_node_json"]) elif input_json[DAGNODE_TYPE_KEY] == "DeploymentSchema": return DeploymentSchema.parse_obj(input_json["schema"]) elif input_json[DAGNODE_TYPE_KEY] == RayServeLazySyncHandle.__name__: return RayServeLazySyncHandle( input_json["deployment_name"], HandleOptions(input_json["handle_options_method_name"]), ) # Deserialize DAGNode type elif input_json[DAGNODE_TYPE_KEY] == InputNode.__name__: return InputNode.from_json(input_json) elif input_json[DAGNODE_TYPE_KEY] == InputAttributeNode.__name__: return InputAttributeNode.from_json(input_json) elif input_json[DAGNODE_TYPE_KEY] == ClassMethodNode.__name__: return ClassMethodNode.from_json(input_json) elif input_json[DAGNODE_TYPE_KEY] == DeploymentNode.__name__: return DeploymentNode.from_json(input_json) elif input_json[DAGNODE_TYPE_KEY] == DeploymentMethodNode.__name__: return DeploymentMethodNode.from_json(input_json) elif input_json[DAGNODE_TYPE_KEY] == DeploymentFunctionNode.__name__: return DeploymentFunctionNode.from_json(input_json) else: # Class and Function nodes require original module as body. module_name, attr_name = parse_import_path(input_json["import_path"]) module = getattr(import_module(module_name), attr_name) if input_json[DAGNODE_TYPE_KEY] == FunctionNode.__name__: return FunctionNode.from_json(input_json, module) elif input_json[DAGNODE_TYPE_KEY] == ClassNode.__name__: return ClassNode.from_json(input_json, module)
def test_invalid_use_sync_handle(): with pytest.raises( ValueError, match=f"{USE_SYNC_HANDLE_KEY} should only be set with a boolean value", ): _ = DeploymentNode( Actor, "test", [], {}, {}, other_args_to_resolve={USE_SYNC_HANDLE_KEY: {"options_a": "hii"}}, )
def transform_ray_dag_to_serve_dag( dag_node: DAGNode, deployment_name_generator: DeploymentNameGenerator): """ Transform a Ray DAG to a Serve DAG. Map ClassNode to DeploymentNode with ray decorated body passed in, and ClassMethodNode to DeploymentMethodNode. """ if isinstance(dag_node, ClassNode): deployment_name = deployment_name_generator.get_deployment_name( dag_node) return DeploymentNode( dag_node._body, deployment_name, dag_node.get_args(), dag_node.get_kwargs(), dag_node.get_options(), # TODO: (jiaodong) Support .options(metadata=xxx) for deployment other_args_to_resolve=dag_node.get_other_args_to_resolve(), ) elif isinstance(dag_node, ClassMethodNode): other_args_to_resolve = dag_node.get_other_args_to_resolve() # TODO: (jiaodong) Need to capture DAGNodes in the parent node parent_deployment_node = other_args_to_resolve[PARENT_CLASS_NODE_KEY] return DeploymentMethodNode( parent_deployment_node._deployment, dag_node._method_name, dag_node.get_args(), dag_node.get_kwargs(), dag_node.get_options(), other_args_to_resolve=dag_node.get_other_args_to_resolve(), ) elif isinstance( dag_node, FunctionNode # TODO (jiaodong): We do not convert ray function to deployment function # yet, revisit this later ) and dag_node.get_other_args_to_resolve().get("is_from_serve_deployment"): deployment_name = deployment_name_generator.get_deployment_name( dag_node) return DeploymentFunctionNode( dag_node._body, deployment_name, dag_node.get_args(), dag_node.get_kwargs(), dag_node.get_options(), other_args_to_resolve=dag_node.get_other_args_to_resolve(), ) else: # TODO: (jiaodong) Support FunctionNode or leave it as ray task return dag_node
def test_simple_deployment_sync(serve_instance): """Internal testing only for simple creation and execution. User should NOT directly create instances of Deployment or DeploymentNode. """ node = DeploymentNode( Actor, "test", (10, ), {}, {}, ) node._deployment.deploy() handle = node._deployment_handle assert ray.get(node.get.execute()) == 10 ray.get(node.inc.execute()) assert ray.get(node.get.execute()) == 11 assert ray.get(node.get.execute()) == ray.get(handle.get.remote())
async def test_simple_deployment_async(serve_instance): """Internal testing only for simple creation and execution. User should NOT directly create instances of Deployment or DeploymentNode. """ node = DeploymentNode( Actor, "test", (10,), {}, {}, other_args_to_resolve={USE_SYNC_HANDLE_KEY: False}, ) node._deployment.deploy() handle = node._deployment_handle assert ray.get(await node.get.execute()) == 10 ray.get(await node.inc.execute()) assert ray.get(await node.get.execute()) == 11 assert ray.get(await node.get.execute()) == ray.get(await handle.get.remote())