Repository: mlflow/mlflow

Issue:
[BUG] mlflow run won't accept parameters' value containing "="
### Willingness to contribute
The MLflow Community encourages bug fix contributions. Would you or another member of your organization be willing to contribute a fix for this bug to the MLflow code base?

- [x] Yes. I can contribute a fix for this bug independently.
- [ ] Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.
- [ ] No. I cannot contribute a bug fix at this time.

### System information
- **Have I written custom code (as opposed to using a stock example script provided in MLflow)**: Yes I do
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Manjaro Linux 20.1
- **MLflow installed from (source or binary)**: binary
- **MLflow version (run ``mlflow --version``)**: mlflow, version 1.10.0
- **Python version**: Python 3.7.7
- **npm version, if running the dev UI**: N/A
- **Exact command to reproduce**: `mlflow run . -P equation="1 + 2 = 3"`

### Describe the problem
Command `mlflow run` doesn't parse parameters correctly. It fails when the value of a parameter contains "=".

### Code to reproduce issue
Here I have an `MLproject` file
```yaml
entry_points:
  main:
    parameters:
      equation: string
    command: python equation-parser.py {equation}
```
and a python code `equation-parser.py`.
```python
import sys

print('got equation:', sys.argv[1])
```

Then I run the following commands
```sh
$ mlflow run . -P equation="1 + 2 = 3"
Invalid format for -P parameter: 'equation=1 + 2 = 3'. Use -P name=value.
$ mlflow run . -P "equation=1 + 2 = 3"
Invalid format for -P parameter: 'equation=1 + 2 = 3'. Use -P name=value.
```

### Other info / logs
N/A

### What component(s), interfaces, languages, and integrations does this bug affect?
Components 
- [ ] `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
- [x] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: Local serving, model deployment tools, spark UDFs
- [ ] `area/server-infra`: MLflow server, JavaScript dev server
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

Interface 
- [ ] `area/uiux`: Front-end, user experience, JavaScript, plotting
- [ ] `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

Language 
- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

Integrations
- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations



Discussion:
@alpancs Thanks for filing this issue. 

With MLproject file: 
```
entry_points:
  main:
    parameters:
      equation: {type: string, default: "y=mx+c"}
    command: python equation-parser.py --equation {equation}
```
and the equation_parser.py:
```
import sys

if __name__ == "__main__":
   equation = sys.argv[1] if len(sys.argv)  > 1 else "y=mx+c"
   print(f"equation={equation}")
```

`mlflow run . -e main -P equation="2mx+c"`         # <- seems to work
`mlflow run . -e main`                                                     # <- seems to work
`mlflow run . -e main -P equation="y=2mx+c"`   # <- gives you the error

Invalid format for -P parameter: 'equation=y=2mx+c'. Use -P name=value.

Curious, what's the use case where you want an expression with an equality sign in the `-P <name>=<value=expression>`?

cc: @juntai-zheng @smurching is this something that `mlflow run .... -P<name>=<expression>` should support? It's conceivable that you may use an expression to calculate a parameter to be used for training.  
> Curious, what's the use case where you want an expression with an equality sign in the -P <name>=<value=expression>?

@dmatrix: for instance, I need to pass an URI that points to a table's snapshot in a data warehouse, e.g.
```
gs://some-bucket/some-db/some-table/snapshot/date=2020-08-27
```

Actually it is valid on mlflow 1.9.1, but invalid on mlflow 1.10.0.

Resolving patch:
diff --git a/mlflow/cli.py b/mlflow/cli.py
--- a/mlflow/cli.py
+++ b/mlflow/cli.py
@@ -194,7 +194,7 @@ def run(
 def _user_args_to_dict(arguments, argument_type="P"):
     user_dict = {}
     for arg in arguments:
-        split = arg.split("=")
+        split = arg.split("=", maxsplit=1)
         # Docker arguments such as `t` don't require a value -> set to True if specified
         if len(split) == 1 and argument_type == "A":
             name = split[0]


Test patch:
diff --git a/tests/projects/test_projects_cli.py b/tests/projects/test_projects_cli.py
--- a/tests/projects/test_projects_cli.py
+++ b/tests/projects/test_projects_cli.py
@@ -12,20 +12,22 @@
 from mlflow.tracking.client import MlflowClient
 from mlflow.utils import process
 from tests.integration.utils import invoke_cli_runner
+from tests.projects.utils import docker_example_base_image  # pylint: disable=unused-import
 from tests.projects.utils import (
     TEST_PROJECT_DIR,
     GIT_PROJECT_URI,
     SSH_PROJECT_URI,
     TEST_NO_SPEC_PROJECT_DIR,
+    TEST_DOCKER_PROJECT_DIR,
 )
 
 _logger = logging.getLogger(__name__)
 
 
 @pytest.mark.large
-def test_run_local_params():
+@pytest.mark.parametrize("name", ["friend", "friend=you", "='friend'"])
+def test_run_local_params(name):
     excitement_arg = 2
-    name = "friend"
     invoke_cli_runner(
         cli.run,
         [
@@ -42,6 +44,13 @@ def test_run_local_params():
     )
 
 
+@pytest.mark.large
+def test_run_local_with_docker_args(docker_example_base_image):  # pylint: disable=unused-argument
+    # Verify that Docker project execution is successful when Docker flag and string
+    # commandline arguments are supplied (`tty` and `name`, respectively)
+    invoke_cli_runner(cli.run, [TEST_DOCKER_PROJECT_DIR, "-A", "tty", "-A", "name=mycontainer"])
+
+
 @pytest.mark.large
 @pytest.mark.parametrize("experiment_name", [b"test-experiment".decode("utf-8"), "test-experiment"])
 def test_run_local_experiment_specification(experiment_name):