def __init__( self, func_body: Union[Callable, str], deployment_name, func_args, func_kwargs, func_options, other_args_to_resolve=None, ): self._body = func_body self._deployment_name = deployment_name super().__init__( func_args, func_kwargs, func_options, other_args_to_resolve=other_args_to_resolve, ) if "deployment_schema" in self._bound_other_args_to_resolve: deployment_schema: DeploymentSchema = self._bound_other_args_to_resolve[ "deployment_schema" ] deployment_shell = schema_to_deployment(deployment_schema) # Prefer user specified name to override the generated one. if ( inspect.isfunction(func_body) and deployment_shell.name != func_body.__name__ ): self._deployment_name = deployment_shell.name # Set the route prefix, prefer the one user supplied, # otherwise set it to /deployment_name if ( deployment_shell.route_prefix is None or deployment_shell.route_prefix != f"/{deployment_shell.name}" ): route_prefix = deployment_shell.route_prefix else: route_prefix = f"/{deployment_name}" self._deployment = deployment_shell.options( func_or_class=func_body, name=self._deployment_name, init_args=(), init_kwargs={}, route_prefix=route_prefix, ) else: self._deployment: Deployment = Deployment( func_body, deployment_name, DeploymentConfig(), init_args=tuple(), init_kwargs=dict(), ray_actor_options=func_options, _internal=True, ) # TODO (jiaodong): Polish with async handle support later self._deployment_handle = RayServeLazySyncHandle(self._deployment.name)
def test_use_deployment_import_path(): """Ensure deployment func_or_class becomes import path when schematized.""" d = schema_to_deployment(deployment_to_schema(decorated_f)) assert isinstance(d.func_or_class, str) # CI may change the parent path, so check only that the suffix matches. assert d.func_or_class.endswith("ray.serve.tests.test_schema.decorated_f") serve.start() d.deploy() assert (requests.get("http://localhost:8000/decorated_f").text == "reached decorated_f")
def test_unset_fields_schema_to_deployment_ray_actor_options(): # Ensure unset fields are excluded from ray_actor_options @serve.deployment( num_replicas=3, route_prefix="/hello", ray_actor_options={}, ) def f(): pass deployment = schema_to_deployment(deployment_to_schema(f)) deployment.set_options(func_or_class="ray.serve.tests.test_schema.global_f") assert len(deployment.ray_actor_options) == 0
def from_dict(cls, d: Dict) -> "Application": """Converts a dictionary of deployment data to an application. Takes in a dictionary matching the Serve REST API schema and converts it to an application containing those deployments. Args: d (Dict): A dictionary containing the deployments' data that matches the Serve REST API schema. Returns: Application: a new application object containing the deployments. """ schema = ServeApplicationSchema.parse_obj(d) return cls([schema_to_deployment(s) for s in schema.deployments])
def test_deployment_to_schema_to_deployment(): @serve.deployment( num_replicas=3, route_prefix="/hello", ray_actor_options={ "runtime_env": { "working_dir": ( "https://github.com/shrekris-anyscale/" "test_module/archive/HEAD.zip" ), "py_modules": [ ( "https://github.com/shrekris-anyscale/" "test_deploy_group/archive/HEAD.zip" ), ], } }, ) def f(): # The body of this function doesn't matter. It gets replaced by # global_f() when the import path in f._func_or_class is overwritten. # This function is used as a convenience to apply the @serve.deployment # decorator without converting global_f() into a Deployment object. pass f._func_or_class = "ray.serve.tests.test_schema.global_f" deployment = schema_to_deployment(deployment_to_schema(f)) assert deployment.num_replicas == 3 assert deployment.route_prefix == "/hello" assert deployment.ray_actor_options["runtime_env"]["working_dir"] == ( "https://github.com/shrekris-anyscale/test_module/archive/HEAD.zip" ) assert deployment.ray_actor_options["runtime_env"]["py_modules"] == [ "https://github.com/shrekris-anyscale/test_deploy_group/archive/HEAD.zip", "https://github.com/shrekris-anyscale/test_module/archive/HEAD.zip", ] serve.start() deployment.deploy() assert ray.get(deployment.get_handle().remote()) == "Hello world!" assert requests.get("http://localhost:8000/hello").text == "Hello world!" serve.shutdown()
def __init__( self, # For serve structured deployment, deployment body can be import path # to the class or function instead. func_or_class: Union[Callable, str], deployment_name: str, deployment_init_args: Tuple[Any], deployment_init_kwargs: Dict[str, Any], ray_actor_options: Dict[str, Any], other_args_to_resolve: Optional[Dict[str, Any]] = None, ): # Assign instance variables in base class constructor. super().__init__( deployment_init_args, deployment_init_kwargs, ray_actor_options, other_args_to_resolve=other_args_to_resolve, ) if self._contains_input_node(): raise ValueError( "InputNode handles user dynamic input the the DAG, and " "cannot be used as args, kwargs, or other_args_to_resolve " "in the DeploymentNode constructor because it is not available " "at class construction or binding time.") # Deployment can be passed into other DAGNodes as init args. This is # supported pattern in ray DAG that user can instantiate and pass class # instances as init args to others. # However in ray serve we send init args via .remote() that requires # pickling, and all DAGNode types are not picklable by design. # Thus we need convert all DeploymentNode used in init args into # deployment handles (executable and picklable) in ray serve DAG to make # serve DAG end to end executable. def replace_with_handle(node): if isinstance(node, DeploymentNode): return node._get_serve_deployment_handle( node._deployment, node._bound_other_args_to_resolve) elif isinstance(node, (DeploymentMethodNode, DeploymentFunctionNode)): from ray.serve.pipeline.json_serde import DAGNodeEncoder serve_dag_root_json = json.dumps(node, cls=DAGNodeEncoder) return RayServeDAGHandle(serve_dag_root_json) ( replaced_deployment_init_args, replaced_deployment_init_kwargs, ) = self.apply_functional( [deployment_init_args, deployment_init_kwargs], predictate_fn=lambda node: isinstance(node, ( DeploymentNode, DeploymentMethodNode, DeploymentFunctionNode)), apply_fn=replace_with_handle, ) if "deployment_schema" in self._bound_other_args_to_resolve: deployment_schema: DeploymentSchema = self._bound_other_args_to_resolve[ "deployment_schema"] deployment_shell = schema_to_deployment(deployment_schema) # Prefer user specified name to override the generated one. if (inspect.isclass(func_or_class) and deployment_shell.name != func_or_class.__name__): deployment_name = deployment_shell.name # Set the route prefix, prefer the one user supplied, # otherwise set it to /deployment_name if (deployment_shell.route_prefix is None or deployment_shell.route_prefix != f"/{deployment_shell.name}"): route_prefix = deployment_shell.route_prefix else: route_prefix = f"/{deployment_name}" self._deployment = deployment_shell.options( func_or_class=func_or_class, name=deployment_name, init_args=replaced_deployment_init_args, init_kwargs=replaced_deployment_init_kwargs, route_prefix=route_prefix, ) else: self._deployment: Deployment = Deployment( func_or_class, deployment_name, # TODO: (jiaodong) Support deployment config from user input DeploymentConfig(), init_args=replaced_deployment_init_args, init_kwargs=replaced_deployment_init_kwargs, ray_actor_options=ray_actor_options, _internal=True, ) self._deployment_handle: Union[ RayServeLazySyncHandle, RayServeHandle, RayServeSyncHandle] = self._get_serve_deployment_handle( self._deployment, other_args_to_resolve)
def __init__( self, # For serve structured deployment, deployment body can be import path # to the class or function instead. func_or_class: Union[Callable, str], deployment_name: str, deployment_init_args: Tuple[Any], deployment_init_kwargs: Dict[str, Any], ray_actor_options: Dict[str, Any], other_args_to_resolve: Optional[Dict[str, Any]] = None, ): # Assign instance variables in base class constructor. super().__init__( deployment_init_args, deployment_init_kwargs, ray_actor_options, other_args_to_resolve=other_args_to_resolve, ) # Deployment can be passed into other DAGNodes as init args. This is # supported pattern in ray DAG that user can instantiate and pass class # instances as init args to others. # However in ray serve we send init args via .remote() that requires # pickling, and all DAGNode types are not picklable by design. # Thus we need convert all DeploymentNode used in init args into # deployment handles (executable and picklable) in ray serve DAG to make # serve DAG end to end executable. # TODO(jiaodong): This part does some magic for DAGDriver and will throw # error with weird pickle replace table error. Move this out. def replace_with_handle(node): if isinstance(node, DeploymentNode): return RayServeLazySyncHandle(node._deployment.name) elif isinstance(node, DeploymentExecutorNode): return node._deployment_handle ( replaced_deployment_init_args, replaced_deployment_init_kwargs, ) = self.apply_functional( [deployment_init_args, deployment_init_kwargs], predictate_fn=lambda node: isinstance( node, # We need to match and replace all DAGNodes even though they # could be None, because no DAGNode replacement should run into # re-resolved child DAGNodes, otherwise with KeyError ( DeploymentNode, DeploymentMethodNode, DeploymentFunctionNode, DeploymentExecutorNode, DeploymentFunctionExecutorNode, DeploymentMethodExecutorNode, ), ), apply_fn=replace_with_handle, ) if "deployment_schema" in self._bound_other_args_to_resolve: deployment_schema: DeploymentSchema = self._bound_other_args_to_resolve[ "deployment_schema" ] deployment_shell = schema_to_deployment(deployment_schema) # Prefer user specified name to override the generated one. if ( inspect.isclass(func_or_class) and deployment_shell.name != func_or_class.__name__ ): deployment_name = deployment_shell.name # Set the route prefix, prefer the one user supplied, # otherwise set it to /deployment_name if ( deployment_shell.route_prefix is None or deployment_shell.route_prefix != f"/{deployment_shell.name}" ): route_prefix = deployment_shell.route_prefix else: route_prefix = f"/{deployment_name}" self._deployment = deployment_shell.options( func_or_class=func_or_class, name=deployment_name, init_args=replaced_deployment_init_args, init_kwargs=replaced_deployment_init_kwargs, route_prefix=route_prefix, ) else: self._deployment: Deployment = Deployment( func_or_class, deployment_name, # TODO: (jiaodong) Support deployment config from user input DeploymentConfig(), init_args=replaced_deployment_init_args, init_kwargs=replaced_deployment_init_kwargs, ray_actor_options=ray_actor_options, _internal=True, ) self._deployment_handle = RayServeLazySyncHandle(self._deployment.name)