Skip to content

Remove the use of deprecated pkg_resources in the importutil and tested compatibility with HSDK v4.0 CUDA 12#579

Open
MMelQin wants to merge 8 commits intomainfrom
mq/fix_import_util
Open

Remove the use of deprecated pkg_resources in the importutil and tested compatibility with HSDK v4.0 CUDA 12#579
MMelQin wants to merge 8 commits intomainfrom
mq/fix_import_util

Conversation

@MMelQin
Copy link
Collaborator

@MMelQin MMelQin commented Mar 12, 2026

Summary by CodeRabbit

  • Bug Fixes

    • More reliable detection of installed packages and dependencies, with improved handling of editable and direct‑URL installs.
  • Refactor

    • Modernized internal metadata handling and normalized package name matching for greater robustness and consistency while preserving existing public behavior and interfaces.

Signed-off-by: M Q <mingmelvinq@nvidia.com>
@MMelQin MMelQin requested a review from Copilot March 12, 2026 05:39
@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Migrates distribution discovery from pkg_resources to importlib.metadata.distributions, adding a _DistributionAdapter, _normalize_project_name(), and _get_working_set() to preserve existing public APIs while using metadata-driven distribution inspection and enhanced egg_info/direct_url.json path handling. (30 words)

Changes

Cohort / File(s) Summary
Package Metadata Migration
monai/deploy/utils/importutil.py
Replaces pkg_resources.working_set usage with importlib.metadata.distributions(). Adds _normalize_project_name(), _get_working_set(), and _DistributionAdapter to provide key/egg_info/module_path/requires() compatibility. Updates is_dist_editable(), dist_module_path(), is_module_installed(), and dist_requires() to use adapter-backed lookup and robust egg_info/direct_url.json extraction.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through metadata, light and spry,
adapters stitched paths beneath the sky,
old eggs unwrapped, new dist-info found,
direct urls led me round and round,
I nibble carrots while imports sigh 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title mentions removing deprecated pkg_resources and testing HSDK v4.0 CUDA 12 compatibility, but the changeset only addresses the pkg_resources replacement; HSDK/CUDA testing is not reflected in the code changes. Revise the title to focus on the actual change: 'Replace deprecated pkg_resources with importlib.metadata in importutil' or similar, removing the unrelated HSDK v4.0 CUDA 12 reference.
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mq/fix_import_util
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes the deprecated pkg_resources dependency from monai.deploy.utils.importutil by switching to importlib.metadata, and updates the segmentation tutorial notebook outputs to reflect running with Holoscan SDK v4.0.

Changes:

  • Replaced pkg_resources.working_set usage with an importlib.metadata-based distribution index and adapter.
  • Updated tutorial notebook install cell and re-ran the notebook, capturing new runtime/build logs (including Holoscan SDK v4.0 output).

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 2 comments.

File Description
monai/deploy/utils/importutil.py Removes pkg_resources usage by introducing importlib.metadata distribution lookup and a small adapter layer.
notebooks/tutorials/03_segmentation_app.ipynb Updates tutorial execution outputs and environment/build logs after re-running under a newer Holoscan SDK stack.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
monai/deploy/utils/importutil.py (1)

58-63: Delegate to the stdlib requires property instead of reparsing metadata.

importlib.metadata.Distribution already exposes requires as a public property. Re-reading Requires-Dist manually adds another behavior surface to keep in sync for no real gain. (docs.python.org)

Proposed simplification
     def requires(self) -> List[str]:
-        meta = self._dist.metadata
-        if hasattr(meta, "get_all"):
-            reqs = meta.get_all("Requires-Dist")
-            return list(reqs) if reqs else []
-        return []
+        return list(self._dist.requires or [])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@monai/deploy/utils/importutil.py` around lines 58 - 63, The requires() method
re-parses metadata instead of using importlib.metadata.Distribution's built-in
requires property; change the implementation of requires (in the class that
currently uses self._dist.metadata and meta.get_all("Requires-Dist")) to simply
return self._dist.requires or an empty list if None, preserving the return type
as List[str] — i.e., replace the manual metadata inspection with a direct call
to the Distribution.requires attribute in the requires() method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@monai/deploy/utils/importutil.py`:
- Around line 22-24: The _normalize_project_name function currently only
lowercases and replaces underscores; change it to implement PEP 503
normalization by lowercasing the input and collapsing every run of '.', '_' and
'-' into a single '-' (e.g., use a regex like re.sub(r"[-_.]+", "-",
name.lower())); update the implementation of _normalize_project_name accordingly
so calls from _get_working_set, is_dist_editable, dist_module_path,
is_module_installed, and dist_requires will get the corrected normalized names.

---

Nitpick comments:
In `@monai/deploy/utils/importutil.py`:
- Around line 58-63: The requires() method re-parses metadata instead of using
importlib.metadata.Distribution's built-in requires property; change the
implementation of requires (in the class that currently uses self._dist.metadata
and meta.get_all("Requires-Dist")) to simply return self._dist.requires or an
empty list if None, preserving the return type as List[str] — i.e., replace the
manual metadata inspection with a direct call to the Distribution.requires
attribute in the requires() method.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 47ee1ca0-c265-464b-997b-0bf98aed3202

📥 Commits

Reviewing files that changed from the base of the PR and between b23278b and 67d0ae5.

📒 Files selected for processing (2)
  • monai/deploy/utils/importutil.py
  • notebooks/tutorials/03_segmentation_app.ipynb

MMelQin added 3 commits March 13, 2026 11:35
Signed-off-by: M Q <mingmelvinq@nvidia.com>
Signed-off-by: M Q <mingmelvinq@nvidia.com>
Signed-off-by: M Q <mingmelvinq@nvidia.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
monai/deploy/utils/importutil.py (1)

33-40: Cache working-set construction to avoid repeated full environment scans.

_get_working_set() is called by multiple helpers and currently re-enumerates distributions each time. Caching this map once per process would reduce repeated overhead.

♻️ Proposed refactor
+from functools import lru_cache
 from importlib import import_module
 from importlib.metadata import distributions
@@
+@lru_cache(maxsize=1)
 def _get_working_set() -> Dict[str, Any]:
     """Build a dict of distribution name -> dist-like object using importlib.metadata."""
     result: Dict[str, Any] = {}
     for d in distributions():
         key = _normalize_project_name(d.name)
         result[key] = _DistributionAdapter(d)
     return result
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@monai/deploy/utils/importutil.py` around lines 33 - 40, _cache a single
process-wide working-set dict so repeated calls to _get_working_set() do not
re-scan distributions: add a module-level cache (e.g., _WORKING_SET_CACHE =
None) and change _get_working_set() to return the cached dict if present; on
first call build the map by iterating distributions(), using
_normalize_project_name(d.name) as the key and wrapping values with
_DistributionAdapter(d), store that result in the cache and return it. Ensure
subsequent callers of _get_working_set() (and helpers that rely on it) receive
the same cached dict.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@monai/deploy/utils/importutil.py`:
- Around line 33-40: _cache a single process-wide working-set dict so repeated
calls to _get_working_set() do not re-scan distributions: add a module-level
cache (e.g., _WORKING_SET_CACHE = None) and change _get_working_set() to return
the cached dict if present; on first call build the map by iterating
distributions(), using _normalize_project_name(d.name) as the key and wrapping
values with _DistributionAdapter(d), store that result in the cache and return
it. Ensure subsequent callers of _get_working_set() (and helpers that rely on
it) receive the same cached dict.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20cd0582-4cf6-4e3e-a5ee-95b9e81e6569

📥 Commits

Reviewing files that changed from the base of the PR and between 67d0ae5 and 441340a.

📒 Files selected for processing (1)
  • monai/deploy/utils/importutil.py

Signed-off-by: M Q <mingmelvinq@nvidia.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
monai/deploy/utils/importutil.py (1)

45-48: Switch to public APIs to avoid relying on importlib.metadata.Distribution._path.

Lines 45–48 and downstream usage (lines 55–62, 343–390) depend on the private _path attribute. This is undocumented and treated internally by pip as a hack; it may change or be absent in future Python versions or alternative distribution providers, causing editable detection to fail.

Recommended approach:

  • Replace direct open() calls to direct_url.json (lines 361, 375) with Distribution.read_text("direct_url.json"), which returns None if absent
  • Remove or refactor the egg_info property to use public metadata APIs
  • If filesystem paths are needed, use importlib.metadata.files(name) + PackagePath.locate() instead of _path

This ensures compatibility across Python 3.10–3.12 and maintains forward compatibility.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@monai/deploy/utils/importutil.py` around lines 45 - 48, The code currently
relies on the private Distribution._path (set in __init__ and later used by the
egg_info property and direct file open calls), which is fragile; refactor to use
public APIs: stop accessing dist._path in __init__ and remove/refactor the
egg_info property to derive metadata via importlib.metadata APIs, replace direct
open("direct_url.json") usages with dist.read_text("direct_url.json") (returns
None if absent), and when a filesystem path is required use
importlib.metadata.files(dist.metadata["Name"] or dist.metadata.name) combined
with importlib.metadata.PackagePath.locate() (or PackagePath.locate on the
returned files) to obtain real paths; update any code that assumed self._path
(including methods using egg_info and the direct_url.json reads) to work with
the read_text/files + PackagePath.locate approach instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@monai/deploy/utils/importutil.py`:
- Around line 33-39: _get_working_set currently rescans distributions on every
call and overwrites earlier matches; change it to cache the mapping and keep the
first-discovered distribution for each normalized name. Implement a module-level
cache (or decorate _get_working_set with functools.lru_cache) and when building
result use result.setdefault(key, _DistributionAdapter(d)) (or check "if key not
in result" before assignment) so earlier distributions are preserved; this will
ensure helpers like is_dist_editable, dist_module_path, and fix_holoscan_import
use the first matching distribution.

---

Nitpick comments:
In `@monai/deploy/utils/importutil.py`:
- Around line 45-48: The code currently relies on the private Distribution._path
(set in __init__ and later used by the egg_info property and direct file open
calls), which is fragile; refactor to use public APIs: stop accessing dist._path
in __init__ and remove/refactor the egg_info property to derive metadata via
importlib.metadata APIs, replace direct open("direct_url.json") usages with
dist.read_text("direct_url.json") (returns None if absent), and when a
filesystem path is required use importlib.metadata.files(dist.metadata["Name"]
or dist.metadata.name) combined with importlib.metadata.PackagePath.locate() (or
PackagePath.locate on the returned files) to obtain real paths; update any code
that assumed self._path (including methods using egg_info and the
direct_url.json reads) to work with the read_text/files + PackagePath.locate
approach instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 97052f2c-676b-4a8c-ab00-14d8095b9f5a

📥 Commits

Reviewing files that changed from the base of the PR and between 441340a and 16d63c0.

📒 Files selected for processing (1)
  • monai/deploy/utils/importutil.py

@MMelQin MMelQin requested review from bluna301 and chezhia March 13, 2026 22:58
Signed-off-by: M Q <mingmelvinq@nvidia.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@monai/deploy/utils/importutil.py`:
- Around line 66-69: The module_path property currently returns the parent only
if self._path.is_dir(), which is unnecessary and too restrictive; update
module_path in importutil.py (the module_path method) to remove the is_dir()
check so it returns str(self._path.parent) when self._path is set (and still
returns "" when None/falsey), ensuring it handles both file and directory
metadata paths correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 95d07f23-3cb4-41f1-8cc6-9f560ae1ebc0

📥 Commits

Reviewing files that changed from the base of the PR and between 16d63c0 and d19a8a9.

📒 Files selected for processing (1)
  • monai/deploy/utils/importutil.py

Signed-off-by: M Q <mingmelvinq@nvidia.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
monai/deploy/utils/importutil.py (3)

400-406: Simplify the return logic.

The function can be simplified to a single return statement.

♻️ Proposed simplification
 def is_module_installed(project_name: str) -> bool:
     working_set = _get_working_set()
     dist: Any = working_set.get(_normalize_project_name(project_name))
-    if dist:
-        return True
-    else:
-        return False
+    return dist is not None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@monai/deploy/utils/importutil.py` around lines 400 - 406, The
is_module_installed function contains redundant conditional logic; simplify it
to a single return by returning the boolean result of the lookup directly.
Replace the if/else block in is_module_installed (which uses _get_working_set(),
dist, and _normalize_project_name(project_name)) with a single return that
evaluates whether working_set.get(_normalize_project_name(project_name)) is
truthy.

360-371: Consider catching TypeError for robustness.

If data["dir_info"] exists but is not a dict (e.g., null), accessing ["editable"] will raise TypeError instead of KeyError. This is unlikely but could happen with malformed metadata.

♻️ Proposed fix
                     try:
                         if data["dir_info"]["editable"]:
                             return True
-                    except KeyError:
+                    except (KeyError, TypeError):
                         pass
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@monai/deploy/utils/importutil.py` around lines 360 - 371, The code that reads
direct_url.json may raise TypeError if data["dir_info"] exists but is not a
dict; update the editable-check in importutil.py so it handles that case—either
validate that data.get("dir_info") is a dict before indexing or expand the
exception handler to catch TypeError as well (e.g., change the except KeyError
to except (KeyError, TypeError)) when checking data["dir_info"]["editable"] so
malformed/null dir_info won't crash.

386-393: Same TypeError consideration applies here.

Similar to is_dist_editable, if data["url"] unexpectedly returns a non-string type, the startswith call would raise AttributeError.

♻️ Proposed fix
                     try:
                         file_url = data["url"]
                         if file_url.startswith("file://"):
                             return str(file_url[7:])
-                    except KeyError:
+                    except (KeyError, TypeError, AttributeError):
                         pass
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@monai/deploy/utils/importutil.py` around lines 386 - 393, The direct_url.json
parsing assumes data["url"] is a string and calls file_url.startswith(), which
can raise AttributeError if the value is non-string; modify the block that reads
direct_url.json (the file_url = data["url"] handling in importutil.py) to first
verify file_url is a string (e.g., isinstance(file_url, str)) before calling
startswith, and only then return the sliced path; if it's not a string,
skip/ignore it the same way KeyError is handled so behavior remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@monai/deploy/utils/importutil.py`:
- Around line 400-406: The is_module_installed function contains redundant
conditional logic; simplify it to a single return by returning the boolean
result of the lookup directly. Replace the if/else block in is_module_installed
(which uses _get_working_set(), dist, and _normalize_project_name(project_name))
with a single return that evaluates whether
working_set.get(_normalize_project_name(project_name)) is truthy.
- Around line 360-371: The code that reads direct_url.json may raise TypeError
if data["dir_info"] exists but is not a dict; update the editable-check in
importutil.py so it handles that case—either validate that data.get("dir_info")
is a dict before indexing or expand the exception handler to catch TypeError as
well (e.g., change the except KeyError to except (KeyError, TypeError)) when
checking data["dir_info"]["editable"] so malformed/null dir_info won't crash.
- Around line 386-393: The direct_url.json parsing assumes data["url"] is a
string and calls file_url.startswith(), which can raise AttributeError if the
value is non-string; modify the block that reads direct_url.json (the file_url =
data["url"] handling in importutil.py) to first verify file_url is a string
(e.g., isinstance(file_url, str)) before calling startswith, and only then
return the sliced path; if it's not a string, skip/ignore it the same way
KeyError is handled so behavior remains unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65395b41-04de-4f40-9214-fe4d5dc5debe

📥 Commits

Reviewing files that changed from the base of the PR and between d19a8a9 and fd1b045.

📒 Files selected for processing (1)
  • monai/deploy/utils/importutil.py

Signed-off-by: M Q <mingmelvinq@nvidia.com>
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants