Repository: mlflow/mlflow

Issue:
[FR] rename_run in API
### Willingness to contribute

Yes. I would be willing to contribute this feature with guidance from the MLflow community.

### Proposal Summary

I need to rename many runs (~100 of them). I can do via the UI, but it would take too long.

Since the mlflow command doesn't provide this function, I planned to create my own basic "rename" python command.

BUT: **the tracking API does not offer a rename_run** .

I would enjoy in the tracking API a method like:

`rename_run(experiment_id, run_id, new_name)
`

Then it could be convenient to extend the mlflow command line to provide access to _rename_experiment_ and _rename _run_.

JL

### Motivation

> #### What is the use case for this feature?
renaming bunch of runs programmatically
> #### Why is this use case valuable to support for MLflow users in general?
An administrative feature useful from time to time.
> #### Why is this use case valuable to support for your project(s) or organization?
Saving time
> #### Why is it currently difficult to achieve this use case?
Need to do it for each run individually via the UI

### Details

Maybe in the API a method like:

`rename_run(experiment_id, run_id, new_name)
`

### 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
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [ ] `area/server-infra`: MLflow Tracking server backend
- [X] `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:

Resolving patch:
diff --git a/mlflow/tracking/_tracking_service/client.py b/mlflow/tracking/_tracking_service/client.py
--- a/mlflow/tracking/_tracking_service/client.py
+++ b/mlflow/tracking/_tracking_service/client.py
@@ -357,6 +357,29 @@ def delete_tag(self, run_id, key):
         """
         self.store.delete_tag(run_id, key)
 
+    def update_run(self, run_id, status=None, name=None):
+        """
+        Update a run with the specified ID to a new status or name.
+
+        :param run_id: The ID of the Run to update.
+        :param status: The new status of the run to set, if specified.
+                       At least one of ``status`` or ``name`` should be specified.
+        :param name: The new name of the run to set, if specified.
+                     At least one of ``name`` or ``status`` should be specified.
+        """
+        # Exit early
+        if status is None and name is None:
+            return
+
+        run = self.get_run(run_id)
+        status = status or run.info.status
+        self.store.update_run_info(
+            run_id=run_id,
+            run_status=RunStatus.from_string(status),
+            end_time=run.info.end_time,
+            run_name=name,
+        )
+
     def log_batch(self, run_id, metrics=(), params=(), tags=()):
         """
         Log multiple metrics, params, and/or tags.
diff --git a/mlflow/tracking/client.py b/mlflow/tracking/client.py
--- a/mlflow/tracking/client.py
+++ b/mlflow/tracking/client.py
@@ -992,6 +992,53 @@ def print_run_info(run):
         """
         self._tracking_client.delete_tag(run_id, key)
 
+    def update_run(
+        self, run_id: str, status: Optional[str] = None, name: Optional[str] = None
+    ) -> None:
+        """
+        Update a run with the specified ID to a new status or name.
+
+        :param run_id: The ID of the Run to update.
+        :param status: The new status of the run to set, if specified.
+                       At least one of ``status`` or ``name`` should be specified.
+        :param name: The new name of the run to set, if specified.
+                     At least one of ``name`` or ``status`` should be specified.
+
+        .. code-block:: python
+            :caption: Example
+
+            from mlflow import MlflowClient
+
+            def print_run_info(run):
+                print("run_id: {}".format(run.info.run_id))
+                print("run_name: {}".format(run.info.run_name))
+                print("status: {}".format(run.info.status))
+
+            # Create a run under the default experiment (whose id is '0').
+            client = MlflowClient()
+            experiment_id = "0"
+            run = client.create_run(experiment_id)
+            print_run_info(run)
+            print("--")
+
+            # Update run and fetch info
+            client.update_run(run.info.run_id, "FINISHED", "new_name")
+            run = client.get_run(run.info.run_id)
+            print_run_info(run)
+
+        .. code-block:: text
+            :caption: Output
+
+            run_id: 1cf6bf8bf6484dd8a598bd43be367b20
+            run_name: judicious-hog-915
+            status: RUNNING
+            --
+            run_id: 1cf6bf8bf6484dd8a598bd43be367b20
+            run_name: new_name
+            status: FINISHED
+        """
+        self._tracking_client.update_run(run_id, status, name)
+
     def log_batch(
         self,
         run_id: str,


Test patch:
diff --git a/tests/tracking/test_client.py b/tests/tracking/test_client.py
--- a/tests/tracking/test_client.py
+++ b/tests/tracking/test_client.py
@@ -2,7 +2,7 @@
 import pickle
 from unittest import mock
 
-from mlflow.entities import SourceType, ViewType, RunTag, Run, RunInfo, ExperimentTag
+from mlflow.entities import SourceType, ViewType, RunTag, Run, RunInfo, ExperimentTag, RunStatus
 from mlflow.entities.model_registry import ModelVersion, ModelVersionTag
 from mlflow.entities.model_registry.model_version_status import ModelVersionStatus
 from mlflow.exceptions import MlflowException
@@ -731,3 +731,13 @@ def test_delete_model_version_tag(mock_registry_store_with_get_latest_version):
     # delete_model_version_tag with version and stage not set
     with pytest.raises(MlflowException, match="version or stage must be set"):
         MlflowClient().delete_model_version_tag("model_name", key="tag1")
+
+
+def test_update_run(mock_store):
+    MlflowClient().update_run(run_id="run_id", status="FINISHED", name="my name")
+    mock_store.update_run_info.assert_called_once_with(
+        run_id="run_id",
+        run_status=RunStatus.from_string("FINISHED"),
+        end_time=mock.ANY,
+        run_name="my name",
+    )