Check if `pkg_name` exist, and optionally try to get its version
(pkg_name: str, return_version: bool = False)
| 48 | |
| 49 | |
| 50 | def _is_package_available(pkg_name: str, return_version: bool = False) -> tuple[bool, str]: |
| 51 | """Check if `pkg_name` exist, and optionally try to get its version""" |
| 52 | spec = importlib.util.find_spec(pkg_name) |
| 53 | package_exists = spec is not None |
| 54 | package_version = "N/A" |
| 55 | if package_exists and return_version: |
| 56 | try: |
| 57 | # importlib.metadata works with the distribution package, which may be different from the import |
| 58 | # name (e.g. `PIL` is the import name, but `pillow` is the distribution name) |
| 59 | distributions = PACKAGE_DISTRIBUTION_MAPPING[pkg_name] |
| 60 | # Per PEP 503, underscores and hyphens are equivalent in package names. |
| 61 | # Prefer the distribution that matches the (normalized) package name. |
| 62 | normalized_pkg_name = pkg_name.replace("_", "-") |
| 63 | if normalized_pkg_name in distributions: |
| 64 | distribution_name = normalized_pkg_name |
| 65 | elif pkg_name in distributions: |
| 66 | distribution_name = pkg_name |
| 67 | else: |
| 68 | distribution_name = distributions[0] |
| 69 | package_version = importlib.metadata.version(distribution_name) |
| 70 | except (importlib.metadata.PackageNotFoundError, KeyError): |
| 71 | # If we cannot find the metadata (because of editable install for example), try to import directly. |
| 72 | # Note that this branch will almost never be run, so we do not import packages for nothing here |
| 73 | package = importlib.import_module(pkg_name) |
| 74 | package_version = getattr(package, "__version__", "N/A") |
| 75 | # No version + no __file__ means a namespace package (PEP 420) shadowing on sys.path, not a real install. |
| 76 | if package_version == "N/A" and getattr(package, "__file__", None) is None: |
| 77 | package_exists = False |
| 78 | logger.debug(f"Detected {pkg_name} version: {package_version}") |
| 79 | |
| 80 | if return_version: |
| 81 | return package_exists, package_version |
| 82 | else: |
| 83 | return package_exists, None |
| 84 | |
| 85 | |
| 86 | def resolve_internal_import(module: ModuleType | None, chained_path: str) -> Callable | ModuleType | None: |