Repository: mlflow/mlflow

Issue:
[BUG] mlflow deployments cli predict help menu broken
### Willingness to contribute

Yes. I can contribute a fix for this bug independently.

### MLflow version

1.27.0

### System information

- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: ubuntu 16.04
- **Python version**: 3.8
- **yarn version, if running the dev UI**:


### Describe the problem

mlflow deployments cli - predict help menu is broken .

When predict is invoked without any argument, the cli returns a help message to include target.

```
$ mlflow deployments predict
/home/ubuntu/anaconda3/lib/python3.8/site-packages/mlflow/server/handlers.py:122: UserWarning: Failure attempting to register store for scheme "file-plugin": No module named 'mlflow_test_plugin.sqlalchemy_store'
  self.register_entrypoints()
Usage: mlflow deployments predict [OPTIONS]
Try 'mlflow deployments predict --help' for help.

Error: Missing option '--target' / '-t'.
```

However, when target and input path are specified, the cli throws an error. The expected result is a help menu specifying --name parameter as mandatory input.

```
$ mlflow deployments predict --target test --input-path test
/home/ubuntu/anaconda3/lib/python3.8/site-packages/mlflow/server/handlers.py:122: UserWarning: Failure attempting to register store for scheme "file-plugin": No module named 'mlflow_test_plugin.sqlalchemy_store'
  self.register_entrypoints()
Traceback (most recent call last):
  File "/home/ubuntu/anaconda3/bin/mlflow", line 8, in <module>
    sys.exit(cli())
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/mlflow/deployments/cli.py", line 302, in predict
    df = pd.read_json(input_path)
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/pandas/util/_decorators.py", line 207, in wrapper
    return func(*args, **kwargs)
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/pandas/util/_decorators.py", line 311, in wrapper
    return func(*args, **kwargs)
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/pandas/io/json/_json.py", line 612, in read_json
    return json_reader.read()
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/pandas/io/json/_json.py", line 746, in read
    obj = self._get_object_parser(self.data)
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/pandas/io/json/_json.py", line 768, in _get_object_parser
    obj = FrameParser(json, **kwargs).parse()
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/pandas/io/json/_json.py", line 880, in parse
    self._parse_no_numpy()
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/pandas/io/json/_json.py", line 1133, in _parse_no_numpy
    loads(json, precise_float=self.precise_float), dtype=None
ValueError: Unexpected character found when decoding 'true'
```

### Tracking information

_No response_

### Code to reproduce issue

```
mlflow deployments predict --target test --input-path test
```

### Other info / logs

The latest update on cli to include `endpoint` parameter has broken the help menu - https://github.com/mlflow/mlflow/blob/master/mlflow/deployments/cli.py#L296 . 

The endpoint and name parameters has to be validated properly (as they are mutually exclusive)

### What component(s) does this bug affect?

- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/pipelines`: Pipelines, Pipeline APIs, Pipeline configs, Pipeline Templates
- [ ] `area/projects`: MLproject format, project running backends
- [X] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [ ] `area/server-infra`: MLflow Tracking server backend
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

### What interface(s) does this bug affect?

- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

### What language(s) does this bug affect?

- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

### What integration(s) does this bug affect?

- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations


Discussion:
@trangevi Would you be able to take a look at this? Looks like we're missing a bit of validation asserting that exactly one of deployment `name` or `endpoint` is specified. 

Resolving patch:
diff --git a/mlflow/deployments/cli.py b/mlflow/deployments/cli.py
--- a/mlflow/deployments/cli.py
+++ b/mlflow/deployments/cli.py
@@ -161,7 +161,7 @@ def create_deployment(flavor, model_uri, target, name, config, endpoint):
     " about supported remote URIs for model artifacts, see"
     " https://mlflow.org/docs/latest/tracking.html"
     "#artifact-stores",
-)  # optional model_uri
+)
 @click.option(
     "--flavor",
     "-f",
@@ -288,8 +288,15 @@ def predictions_to_json(raw_predictions, output):
 
 
 @commands.command("predict")
-@optional_endpoint_param
-@optional_deployment_name
+@click.option(
+    "--name",
+    "name",
+    help="Name of the deployment. Exactly one of --name or --endpoint must be specified.",
+)
+@click.option(
+    "--endpoint",
+    help="Name of the endpoint. Exactly one of --name or --endpoint must be specified.",
+)
 @target_details
 @parse_input
 @parse_output
@@ -299,6 +306,9 @@ def predict(target, name, input_path, output_path, endpoint):
     """
     import pandas as pd
 
+    if (name, endpoint).count(None) != 1:
+        raise click.UsageError("Must specify exactly one of --name or --endpoint.")
+
     df = pd.read_json(input_path)
     client = interface.get_deploy_client(target)
 
@@ -315,8 +325,15 @@ def predict(target, name, input_path, output_path, endpoint):
 
 
 @commands.command("explain")
-@optional_endpoint_param
-@optional_deployment_name
+@click.option(
+    "--name",
+    "name",
+    help="Name of the deployment. Exactly one of --name or --endpoint must be specified.",
+)
+@click.option(
+    "--endpoint",
+    help="Name of the endpoint. Exactly one of --name or --endpoint must be specified.",
+)
 @target_details
 @parse_input
 @parse_output
@@ -333,6 +350,9 @@ def explain(target, name, input_path, output_path, endpoint):
     """
     import pandas as pd
 
+    if (name, endpoint).count(None) != 1:
+        raise click.UsageError("Must specify exactly one of --name or --endpoint.")
+
     df = pd.read_json(input_path)
     client = interface.get_deploy_client(target)
 


Test patch: