Repository: mlflow/mlflow

Issue:
Error in mlflow experiments delete
### System information
- Linux Ubuntu 16.04
- MLflow installed with pip from latest s3 wheel (mlflow-0.7.1.dev-253.8ccc546-py2.py3-none-any.whl) 
- **MLflow version**: 0.7.1.dev
- **Python version**: 3.6.5
- **Exact command to reproduce**: mlflow experiments delete "existent experiment id"

### Describe the problem
Command fails. It should not.

:~$ mlflow experiments delete 3

Traceback (most recent call last):
  File "/home/areshytko/anaconda3/bin/mlflow", line 11, in <module>
    sys.exit(cli())
  File "/home/areshytko/anaconda3/lib/python3.6/site-packages/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/home/areshytko/anaconda3/lib/python3.6/site-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/home/areshytko/anaconda3/lib/python3.6/site-packages/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/areshytko/anaconda3/lib/python3.6/site-packages/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/areshytko/anaconda3/lib/python3.6/site-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/areshytko/anaconda3/lib/python3.6/site-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/home/areshytko/anaconda3/lib/python3.6/site-packages/mlflow/experiments.py", line 64, in delete_experiment
    store.delete_experiment(experiment_id)
  File "/home/areshytko/anaconda3/lib/python3.6/site-packages/mlflow/store/rest_store.py", line 103, in delete_experiment
    req_body = message_to_json(DeleteExperiment(experiment_id=experiment_id))
TypeError: '3' has type str, but expected one of: int, long



Discussion:

Resolving patch:
diff --git a/mlflow/entities/run_info.py b/mlflow/entities/run_info.py
--- a/mlflow/entities/run_info.py
+++ b/mlflow/entities/run_info.py
@@ -168,10 +168,17 @@ def to_proto(self):
 
     @classmethod
     def from_proto(cls, proto):
-        return cls(proto.run_uuid, proto.experiment_id, proto.name, proto.source_type,
-                   proto.source_name, proto.entry_point_name, proto.user_id, proto.status,
-                   proto.start_time, proto.end_time, proto.source_version, proto.lifecycle_stage,
-                   proto.artifact_uri)
+        end_time = proto.end_time
+        # The proto2 default scalar value of zero indicates that the run's end time is absent.
+        # An absent end time is represented with a NoneType in the `RunInfo` class
+        if end_time == 0:
+            end_time = None
+        return cls(run_uuid=proto.run_uuid, experiment_id=proto.experiment_id, name=proto.name,
+                   source_type=proto.source_type, source_name=proto.source_name,
+                   entry_point_name=proto.entry_point_name, user_id=proto.user_id,
+                   status=proto.status, start_time=proto.start_time, end_time=end_time,
+                   source_version=proto.source_version, lifecycle_stage=proto.lifecycle_stage,
+                   artifact_uri=proto.artifact_uri)
 
     @classmethod
     def _properties(cls):
diff --git a/mlflow/experiments.py b/mlflow/experiments.py
--- a/mlflow/experiments.py
+++ b/mlflow/experiments.py
@@ -9,6 +9,8 @@
 from mlflow.entities import ViewType
 from mlflow.tracking import _get_store
 
+EXPERIMENT_ID = click.argument("experiment_id", type=click.INT)
+
 
 @click.group("experiments")
 def commands():
@@ -53,7 +55,7 @@ def list_experiments(view):
 
 
 @commands.command("delete")
-@click.argument("experiment_id")
+@EXPERIMENT_ID
 def delete_experiment(experiment_id):
     """
     Mark an experiment for deletion. Return an error if the experiment does not exist or
@@ -66,7 +68,7 @@ def delete_experiment(experiment_id):
 
 
 @commands.command("restore")
-@click.argument("experiment_id")
+@EXPERIMENT_ID
 def restore_experiment(experiment_id):
     """
     Restore a deleted experiment.
@@ -78,7 +80,7 @@ def restore_experiment(experiment_id):
 
 
 @commands.command("rename")
-@click.argument("experiment_id")
+@EXPERIMENT_ID
 @click.argument("new_name")
 def rename_experiment(experiment_id, new_name):
     """
diff --git a/mlflow/store/file_store.py b/mlflow/store/file_store.py
--- a/mlflow/store/file_store.py
+++ b/mlflow/store/file_store.py
@@ -62,7 +62,6 @@ def __init__(self, root_directory=None, artifact_root_uri=None):
         # Create root directory if needed
         if not exists(self.root_directory):
             mkdir(self.root_directory)
-            print("here")
             self._create_experiment_with_id(name="Default",
                                             experiment_id=Experiment.DEFAULT_EXPERIMENT_ID,
                                             artifact_uri=None)


Test patch:
diff --git a/tests/tracking/test_rest_tracking.py b/tests/tracking/test_rest_tracking.py
--- a/tests/tracking/test_rest_tracking.py
+++ b/tests/tracking/test_rest_tracking.py
@@ -10,10 +10,13 @@
 import socket
 import time
 import tempfile
-import unittest
 
-from mlflow.server import app, FILE_STORE_ENV_VAR
+from click.testing import CliRunner
+
+import mlflow.experiments
 from mlflow.entities import RunStatus
+from mlflow.protos.service_pb2 import LOCAL as SOURCE_TYPE_LOCAL
+from mlflow.server import app, FILE_STORE_ENV_VAR
 from mlflow.tracking import MlflowClient
 from mlflow.utils.mlflow_tags import MLFLOW_RUN_NAME, MLFLOW_PARENT_RUN_ID
 
@@ -86,9 +89,26 @@ def init_and_tear_down_server(request):
 
 
 @pytest.fixture()
-def mlflow_client():
-    """Provides an MLflow Tracking API client pointed at the local server."""
-    return MlflowClient("%s:%s" % (LOCALHOST, SERVER_PORT))
+def tracking_server_uri():
+    """Provides a tracking URI for communicating with the local tracking server."""
+    return "http://{hostname}:{port}".format(hostname=LOCALHOST, port=SERVER_PORT)
+
+
+@pytest.fixture()
+def mlflow_client(tracking_server_uri):
+    """Provides an MLflow Tracking API client pointed at the local tracking server."""
+    return MlflowClient(tracking_server_uri)
+
+
+@pytest.fixture()
+def cli_env(tracking_server_uri):
+    """Provides an environment for the MLflow CLI pointed at the local tracking server."""
+    cli_env = {
+        "LC_ALL": "en_US.UTF-8",
+        "LANG": "en_US.UTF-8",
+        "MLFLOW_TRACKING_URI": tracking_server_uri,
+    }
+    return cli_env
 
 
 def test_create_get_list_experiment(mlflow_client):
@@ -99,7 +119,7 @@ def test_create_get_list_experiment(mlflow_client):
     assert exp.artifact_location == 'my_location'
 
     experiments = mlflow_client.list_experiments()
-    assert set([e.name for e in experiments]) == {'My Experiment', 'Default'}
+    assert set([e.name for e in experiments]) == {'My Experiment'}
 
 
 def test_delete_restore_experiment(mlflow_client):
@@ -111,6 +131,17 @@ def test_delete_restore_experiment(mlflow_client):
     assert mlflow_client.get_experiment(experiment_id).lifecycle_stage == 'active'
 
 
+def test_delete_restore_experiment_cli(mlflow_client, cli_env):
+    experiment_name = "DeleteriousCLI"
+    CliRunner(env=cli_env).invoke(mlflow.experiments.commands, ['create', experiment_name])
+    experiment_id = mlflow_client.get_experiment_by_name(experiment_name).experiment_id
+    assert mlflow_client.get_experiment(experiment_id).lifecycle_stage == 'active'
+    CliRunner(env=cli_env).invoke(mlflow.experiments.commands, ['delete', str(experiment_id)])
+    assert mlflow_client.get_experiment(experiment_id).lifecycle_stage == 'deleted'
+    CliRunner(env=cli_env).invoke(mlflow.experiments.commands, ['restore', str(experiment_id)])
+    assert mlflow_client.get_experiment(experiment_id).lifecycle_stage == 'active'
+
+
 def test_rename_experiment(mlflow_client):
     experiment_id = mlflow_client.create_experiment('BadName')
     assert mlflow_client.get_experiment(experiment_id).name == 'BadName'
@@ -118,29 +149,52 @@ def test_rename_experiment(mlflow_client):
     assert mlflow_client.get_experiment(experiment_id).name == 'GoodName'
 
 
+def test_rename_experiment_cli(mlflow_client, cli_env):
+    bad_experiment_name = "BadName"
+    good_experiment_name = "GoodName"
+
+    CliRunner(env=cli_env).invoke(mlflow.experiments.commands, ['create', bad_experiment_name])
+    experiment_id = mlflow_client.get_experiment_by_name(bad_experiment_name).experiment_id
+    assert mlflow_client.get_experiment(experiment_id).name == bad_experiment_name
+    CliRunner(env=cli_env).invoke(
+            mlflow.experiments.commands,
+            ['rename', str(experiment_id), good_experiment_name])
+    assert mlflow_client.get_experiment(experiment_id).name == good_experiment_name
+
+
 def test_create_run_all_args(mlflow_client):
+    create_run_kwargs = {
+        "user_id": "123",
+        "run_name": "My name",
+        "source_type": "LOCAL",
+        "source_name": "Hello",
+        "entry_point_name": "entry",
+        "start_time": 456,
+        "source_version": "abc",
+        "tags": {
+            "my": "tag",
+            "other": "tag",
+        },
+        "parent_run_id": "7",
+    }
     experiment_id = mlflow_client.create_experiment('Run A Lot')
-    expected_tags = {'my': 'tag', 'other': 'tag'}
-    created_run = mlflow_client.create_run(
-        experiment_id, user_id=123, run_name='My name', source_type='LOCAL',
-        source_name='Hello', entry_point_name='entry', start_time=456,
-        source_version='abc', tags=expected_tags, parent_run_id=7)
+    created_run = mlflow_client.create_run(experiment_id, **create_run_kwargs)
     run_id = created_run.info.run_uuid
     print("Run id=%s" % run_id)
     run = mlflow_client.get_run(run_id)
     assert run.info.run_uuid == run_id
     assert run.info.experiment_id == experiment_id
-    assert run.info.user_id == 123
-    assert run.info.source_type == 'LOCAL'
-    assert run.info.source_name == 'Hello'
-    assert run.info.entry_point_name == 'entry'
-    assert run.info.start_time == 456
-    assert run.info.source_version == 'abc'
+    assert run.info.user_id == create_run_kwargs["user_id"]
+    assert run.info.source_type == SOURCE_TYPE_LOCAL
+    assert run.info.source_name == create_run_kwargs["source_name"]
+    assert run.info.entry_point_name == create_run_kwargs["entry_point_name"]
+    assert run.info.start_time == create_run_kwargs["start_time"]
+    assert run.info.source_version == create_run_kwargs["source_version"]
     actual_tags = {t.key: t.value for t in run.data.tags}
-    for tag in expected_tags:
+    for tag in create_run_kwargs["tags"]:
         assert tag in actual_tags
-    assert actual_tags.get(MLFLOW_RUN_NAME) == 'My name'
-    assert actual_tags.get(MLFLOW_PARENT_RUN_ID) == '7'
+    assert actual_tags.get(MLFLOW_RUN_NAME) == create_run_kwargs["run_name"]
+    assert actual_tags.get(MLFLOW_PARENT_RUN_ID) == create_run_kwargs["parent_run_id"]
 
     assert mlflow_client.list_run_infos(experiment_id) == [run.info]