Skip to content

Commit 1f1292f

Browse files
authored
Remove the use of deprecated pkg_resources in the importutil and tested compatibility with HSDK v4.0 CUDA 12 (#579)
* Fixed import_util and tested the SDK with HSDK v4.0 Cuda 12 Signed-off-by: M Q <mingmelvinq@nvidia.com> * Addressed commnent on PEP 503 compliance Signed-off-by: M Q <mingmelvinq@nvidia.com> * Address comment on name collision Signed-off-by: M Q <mingmelvinq@nvidia.com> * Update the license date range Signed-off-by: M Q <mingmelvinq@nvidia.com> * Address nits Signed-off-by: M Q <mingmelvinq@nvidia.com> * Address coderabbit review comments Signed-off-by: M Q <mingmelvinq@nvidia.com> * Address more nits Signed-off-by: M Q <mingmelvinq@nvidia.com> * Even more minor nits Signed-off-by: M Q <mingmelvinq@nvidia.com> --------- Signed-off-by: M Q <mingmelvinq@nvidia.com>
1 parent b23278b commit 1f1292f

File tree

2 files changed

+535
-1196
lines changed

2 files changed

+535
-1196
lines changed

monai/deploy/utils/importutil.py

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2021-2022 MONAI Consortium
1+
# Copyright 2021-2026 MONAI Consortium
22
# Licensed under the Apache License, Version 2.0 (the "License");
33
# you may not use this file except in compliance with the License.
44
# You may obtain a copy of the License at
@@ -10,14 +10,67 @@
1010
# limitations under the License.
1111

1212
import inspect
13+
import re
1314
import runpy
1415
import sys
1516
import warnings
17+
from functools import lru_cache
1618
from importlib import import_module
19+
from importlib.metadata import distributions
1720
from pathlib import Path
1821
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Type, Union
1922

20-
import pkg_resources
23+
24+
def _normalize_project_name(name: str) -> str:
25+
"""Normalize project name for lookup (PEP 503).
26+
27+
PEP 503 normalizes by lowercasing and replacing any run of [-_.]
28+
with a single hyphen, so distribution key matching works for names
29+
containing dots or mixed separators.
30+
"""
31+
return re.sub(r"[-_.]+", "-", name.lower())
32+
33+
34+
@lru_cache(maxsize=1)
35+
def _get_working_set() -> Dict[str, Any]:
36+
"""Build a dict of distribution name -> dist-like object using importlib.metadata.
37+
38+
Cached so we do not rescan on every call. First-discovered distribution
39+
is kept for each normalized name when multiple distributions match.
40+
"""
41+
result: Dict[str, Any] = {}
42+
for d in distributions():
43+
key = _normalize_project_name(d.name)
44+
if key not in result:
45+
result[key] = _DistributionAdapter(d)
46+
return result
47+
48+
49+
class _DistributionAdapter:
50+
"""Adapter so importlib.metadata Distribution can be used like pkg_resources Distribution."""
51+
52+
def __init__(self, dist: Any) -> None:
53+
self._dist = dist
54+
path = getattr(dist, "path", getattr(dist, "_path", None))
55+
self._path = Path(path).resolve() if path else None
56+
57+
@property
58+
def key(self) -> str:
59+
return _normalize_project_name(self._dist.name)
60+
61+
@property
62+
def egg_info(self) -> Optional[Path]:
63+
return self._path
64+
65+
@property
66+
def module_path(self) -> str:
67+
if self._path:
68+
return str(self._path.parent)
69+
return ""
70+
71+
def requires(self) -> List[str]:
72+
return list(self._dist.requires or [])
73+
2174

2275
if TYPE_CHECKING:
2376
from monai.deploy.core import Application
@@ -295,9 +348,9 @@ def __init__(self, *_args, **kwargs):
295348

296349

297350
def is_dist_editable(project_name: str) -> bool:
298-
distributions: Dict = {v.key: v for v in pkg_resources.working_set}
299-
dist: Any = distributions.get(project_name)
300-
if not hasattr(dist, "egg_info"):
351+
working_set = _get_working_set()
352+
dist: Any = working_set.get(_normalize_project_name(project_name))
353+
if not hasattr(dist, "egg_info") or dist.egg_info is None:
301354
return False
302355
egg_info = Path(dist.egg_info)
303356
if egg_info.is_dir():
@@ -314,15 +367,15 @@ def is_dist_editable(project_name: str) -> bool:
314367
try:
315368
if data["dir_info"]["editable"]:
316369
return True
317-
except KeyError:
370+
except (KeyError, TypeError):
318371
pass
319372
return False
320373

321374

322375
def dist_module_path(project_name: str) -> str:
323-
distributions: Dict = {v.key: v for v in pkg_resources.working_set}
324-
dist: Any = distributions.get(project_name)
325-
if hasattr(dist, "egg_info"):
376+
working_set = _get_working_set()
377+
dist: Any = working_set.get(_normalize_project_name(project_name))
378+
if hasattr(dist, "egg_info") and dist.egg_info is not None:
326379
egg_info = Path(dist.egg_info)
327380
if egg_info.is_dir() and egg_info.suffix == ".dist-info":
328381
if (egg_info / "direct_url.json").exists():
@@ -336,7 +389,7 @@ def dist_module_path(project_name: str) -> str:
336389
file_url = data["url"]
337390
if file_url.startswith("file://"):
338391
return str(file_url[7:])
339-
except KeyError:
392+
except (KeyError, TypeError, AttributeError):
340393
pass
341394

342395
if hasattr(dist, "module_path"):
@@ -345,17 +398,14 @@ def dist_module_path(project_name: str) -> str:
345398

346399

347400
def is_module_installed(project_name: str) -> bool:
348-
distributions: Dict = {v.key: v for v in pkg_resources.working_set}
349-
dist: Any = distributions.get(project_name)
350-
if dist:
351-
return True
352-
else:
353-
return False
401+
working_set = _get_working_set()
402+
dist: Any = working_set.get(_normalize_project_name(project_name))
403+
return dist is not None
354404

355405

356406
def dist_requires(project_name: str) -> List[str]:
357-
distributions: Dict = {v.key: v for v in pkg_resources.working_set}
358-
dist: Any = distributions.get(project_name)
407+
working_set = _get_working_set()
408+
dist: Any = working_set.get(_normalize_project_name(project_name))
359409
if hasattr(dist, "requires"):
360410
return [str(req) for req in dist.requires()]
361411
return []

0 commit comments

Comments
 (0)