Repository: mlflow/mlflow

Issue:
fix: importlib.metadata get
<details><summary>&#x1F6E0 DevTools &#x1F6E0</summary>
<p>

[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/raphaelauv/mlflow/pull/10752?quickstart=1)

#### Install mlflow from this PR

```
pip install git+https://github.com/mlflow/mlflow.git@refs/pull/10752/merge
```

#### Checkout with GitHub CLI

```
gh pr checkout 10752
```

</p>
</details>

### Related Issues/PRs

<!-- Uncomment 'Resolve' if this PR can close the linked items. -->
Resolve #10752

### What changes are proposed in this pull request?


with python 3.12 ->

```python
Traceback (most recent call last):
   File "/usr/local/bin/mlflow", line 5, in <module>
     from mlflow.cli import cli
   File "/usr/local/lib/python3.12/site-packages/mlflow/cli.py", line 356, in <module>
     type=click.Choice([e.name for e in importlib.metadata.entry_points().get("mlflow.app", [])]),
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 AttributeError: 'EntryPoints' object has no attribute 'get'
```

### How is this PR tested?

- [X] Existing unit/integration tests
- [ ] New unit/integration tests
- [ ] Manual tests

<!-- Attach code, screenshot, video used for manual testing here. -->

### Does this PR require documentation update?

- [X] No. You can skip the rest of this section.
- [ ] Yes. I've updated:
  - [ ] Examples
  - [ ] API references
  - [ ] Instructions

### Release Notes

#### Is this a user-facing change?

- [X] No. You can skip the rest of this section.
- [ ] Yes. Give a description of this change to be included in the release notes for MLflow users.

<!-- Details in 1-2 sentences. You can just refer to another PR with a description if this PR is part of a larger change. -->

#### What component(s), interfaces, languages, and integrations does this PR affect?

Components

- [ ] `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
- [X] `area/server-infra`: MLflow Tracking server backend
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

Interface

- [ ] `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

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

<!--
Insert an empty named anchor here to allow jumping to this section with a fragment URL
(e.g. https://github.com/mlflow/mlflow/pull/123#user-content-release-note-category).
Note that GitHub prefixes anchor names in markdown with "user-content-".
-->

<a name="release-note-category"></a>

#### How should the PR be classified in the release notes? Choose one:

- [ ] `rn/none` - No description will be included. The PR will be mentioned only by the PR number in the "Small Bugfixes and Documentation Updates" section
- [ ] `rn/breaking-change` - The PR will be mentioned in the "Breaking Changes" section
- [ ] `rn/feature` - A new user-facing feature worth mentioning in the release notes
- [X] `rn/bug-fix` - A user-facing bug fix worth mentioning in the release notes
- [ ] `rn/documentation` - A user-facing documentation change worth mentioning in the release notes



Discussion:

Resolving patch:
diff --git a/mlflow/cli.py b/mlflow/cli.py
--- a/mlflow/cli.py
+++ b/mlflow/cli.py
@@ -1,5 +1,4 @@
 import contextlib
-import importlib.metadata
 import json
 import logging
 import os
@@ -27,7 +26,7 @@
 from mlflow.tracking import _get_store
 from mlflow.utils import cli_args
 from mlflow.utils.logging_utils import eprint
-from mlflow.utils.os import is_windows
+from mlflow.utils.os import get_entry_points, is_windows
 from mlflow.utils.process import ShellCommandException
 from mlflow.utils.server_cli_utils import (
     artifacts_only_config_validation,
@@ -353,7 +352,7 @@ def _validate_static_prefix(ctx, param, value):  # pylint: disable=unused-argume
 @click.option(
     "--app-name",
     default=None,
-    type=click.Choice([e.name for e in importlib.metadata.entry_points().get("mlflow.app", [])]),
+    type=click.Choice([e.name for e in get_entry_points("mlflow.app")]),
     show_default=True,
     help=(
         "Application name to be used for the tracking server. "
diff --git a/mlflow/server/__init__.py b/mlflow/server/__init__.py
--- a/mlflow/server/__init__.py
+++ b/mlflow/server/__init__.py
@@ -22,7 +22,7 @@
     search_datasets_handler,
     upload_artifact_handler,
 )
-from mlflow.utils.os import is_windows
+from mlflow.utils.os import get_entry_points, is_windows
 from mlflow.utils.process import _exec_cmd
 from mlflow.version import VERSION
 
@@ -142,7 +142,7 @@ def serve():
 
 
 def _find_app(app_name: str) -> str:
-    apps = importlib.metadata.entry_points().get("mlflow.app", [])
+    apps = get_entry_points("mlflow.app")
     for app in apps:
         if app.name == app_name:
             return app.value
@@ -177,7 +177,7 @@ def get_app_client(app_name: str, *args, **kwargs):
     Returns:
         An app client instance.
     """
-    clients = importlib.metadata.entry_points().get("mlflow.app.client", [])
+    clients = get_entry_points("mlflow.app.client")
     for client in clients:
         if client.name == app_name:
             cls = client.load()
diff --git a/mlflow/utils/os.py b/mlflow/utils/os.py
--- a/mlflow/utils/os.py
+++ b/mlflow/utils/os.py
@@ -1,4 +1,6 @@
+import importlib.metadata
 import os
+import sys
 
 
 def is_windows():
@@ -10,3 +12,13 @@ def is_windows():
 
     """
     return os.name == "nt"
+
+
+def get_entry_points(namespace):
+    if sys.version_info >= (3, 10):
+        return importlib.metadata.entry_points(group=namespace)
+    else:
+        try:
+            return importlib.metadata.entry_points().get(namespace, [])
+        except AttributeError:
+            return importlib.metadata.entry_points().select(group=namespace)


Test patch: