Repository: mlflow/mlflow

Issue:
[BUG] Incorrectly nested runs in python package cannot raise Exception
### Issues Policy acknowledgement

- [X] I have read and agree to submit bug reports in accordance with the [issues policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md)

### Where did you encounter this bug?

Local machine

### Willingness to contribute

Yes. I can contribute a fix for this bug independently.

### MLflow version

- Client: 2.12.1
- Tracking server: 2.15.1

### System information

- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Mac
- **Python version**: 3.10
- **yarn version, if running the dev UI**:


### Describe the problem

Exception handler does not have the correct format arguments, expects 3 but only 2 given.
Useful exception therefore cannot be raised.

https://github.com/mlflow/mlflow/blob/e25709db05c4e7edc606516a3892af161edd60d5/mlflow/tracking/fluent.py#L380-L386

### Tracking information

N/A

### Code to reproduce issue

N/A

### Stack trace

  File "/var/dataproc/tmp/srvls-batch-26dc167a-0289-47c8-861f-8845117a0a05/xgboost_optuna_logging_test.py", line 297, in objective
    with mlflow.start_run(
  File "/opt/miniconda3/lib/python3.10/site-packages/mlflow/tracking/fluent.py", line 384, in start_run
    ).format(_active_run_stack[-1].info.run_id, parent_run_id)
IndexError: Replacement index 2 out of range for positional args tuple

### Other info / logs

_No response_

### What component(s) does this bug affect?

- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/deployments`: MLflow Deployments client APIs, server, and third-party Deployments integrations
- [ ] `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/recipes`: Recipes, Recipe APIs, Recipe configs, Recipe 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:
github-actions[bot]: <!-- documentation preview -->


Documentation preview for 2dd3846898f1e2d649b994cbb27092d4a1a79de7 will be available when [this CircleCI job](https://circleci.com/gh/mlflow/mlflow/120218)
completes successfully.

- [Top page](https://output.circle-artifacts.com/output/job/b3ca6ca4-aa5d-4586-9837-c01d0bd8a8fa/artifacts/0/docs/build/html/index.html)
- [Changed pages](https://output.circle-artifacts.com/output/job/b3ca6ca4-aa5d-4586-9837-c01d0bd8a8fa/artifacts/0/docs/build/html/diff.html)

<details>
<summary>More info</summary>

- Ignore this comment if this PR does not change the documentation.
- It takes a few minutes for the preview to be available.
- The preview is updated when a new commit is pushed to this PR.
- This comment was created by https://github.com/mlflow/mlflow/actions/runs/10676170078.

</details>


Patch:
diff --git a/mlflow/ml-package-versions.yml b/mlflow/ml-package-versions.yml
index ea07c50b6..7edbf41f5 100644
--- a/mlflow/ml-package-versions.yml
+++ b/mlflow/ml-package-versions.yml
@@ -518,7 +518,7 @@ shap:
     minimum: "0.41.0"
     maximum: "0.46.0"
     requirements:
-      "<= 0.41.0": ["numpy<1.24.0"]
+      "< 0.46.0": ["numpy<1.24.0"]
     run: |
       pytest tests/shap
 
diff --git a/mlflow/tracking/fluent.py b/mlflow/tracking/fluent.py
index 257627f31..c1680af4a 100644
--- a/mlflow/tracking/fluent.py
+++ b/mlflow/tracking/fluent.py
@@ -377,12 +377,12 @@ def start_run(
             _validate_run_id(parent_run_id)
             # Make sure parent_run_id matches the current run id, if there is an active run
             if len(_active_run_stack) > 0 and parent_run_id != _active_run_stack[-1].info.run_id:
-                raise Exception(
-                    (
-                        "Current run with UUID {} does not match the specified parent_run_id {}"
-                        + " To start a new nested run under the parent run with UUID {}, "
-                        + "first end the current run with mlflow.end_run()."
-                    ).format(_active_run_stack[-1].info.run_id, parent_run_id)
+                current_run_id = _active_run_stack[-1].info.run_id
+                raise MlflowException(
+                    f"Current run with UUID {current_run_id} does not match the specified "
+                    f"parent_run_id {parent_run_id}. To start a new nested run under "
+                    f"the parent run with UUID {current_run_id}, first end the current run "
+                    "with mlflow.end_run()."
                 )
             parent_run_obj = client.get_run(parent_run_id)
             # Check if the specified parent_run has been deleted.


Test patch:
diff --git a/tests/tracking/fluent/test_fluent.py b/tests/tracking/fluent/test_fluent.py
index f0f90a0de..aac1a0bdb 100644
--- a/tests/tracking/fluent/test_fluent.py
+++ b/tests/tracking/fluent/test_fluent.py
@@ -759,6 +759,14 @@ def test_start_run_with_parent_id():
     assert mlflow.get_parent_run(nested_run_id).info.run_id == parent_run_id
 
 
+@pytest.mark.usefixtures(empty_active_run_stack.__name__)
+def test_start_run_with_invalid_parent_id():
+    with mlflow.start_run() as run:
+        with pytest.raises(MlflowException, match=f"Current run with UUID {run.info.run_id}"):
+            with mlflow.start_run(nested=True, parent_run_id="hello"):
+                pass
+
+
 def test_start_run_with_parent_non_nested():
     with mock.patch("mlflow.tracking.fluent._active_run_stack", [mock.Mock()]):
         with pytest.raises(Exception, match=r"Run with UUID .+ is already active"):