CodeSampleX

샘플

importlib-metadata 9.0.0: Resolve installed package versions when distribution names differ from top-level import names without PackageNotFoundError

검증된 샘플 — pypi importlib-metadata 9.0.0: Resolve installed package versions when distribution names differ from top-level import names without…

sha256:beb8b3121c3cddc83a0038650f092bd1609123c82e31e1209dc60f07e688d56f

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. MIT-0

실행 증거

선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.

증거 기준
서명된 컨트랙트 통과
검증 영수증
3
빌드한 서명 키
3
선언된 환경 python linux x64 python python pip

검증 실행 환경

환경 컨트랙트 단계 실행일
python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1
2026-08-15
python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1
2026-08-18
python 3.12 · linux debian/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · python@1python:3.12-slim@sha256:09f7da3bc104…
2026-09-08

케이스

HOW
목표
Resolve installed package versions when distribution names differ from top-level import names without PackageNotFoundError
패키지
심벌
  • importlib_metadata.version
  • importlib_metadata.PackageNotFoundError
  • importlib_metadata.packages_distributions
  • importlib_metadata.distribution
환경
python
생성일
2026-08-16T06:31:23Z

컨트랙트

  1. assert version() queries distribution name and raises PackageNotFoundError when given top-level import package name
  2. assert PackageNotFoundError inherits from ModuleNotFoundError and ImportError but not KeyError
  3. assert distribution name normalization under PEP 503 equates hyphens, underscores, dots, and case variants
  4. assert packages_distributions() maps import package names to list of distribution names
  5. assert passing module object or non-string to version() raises TypeError

파일

  • NOTES.md
  • csx.json
  • requirements.txt
  • src/__init__.py
  • src/resolver.py
  • test/contract.py

소스 아티팩트 내려받기 (tar.gz)

소스

NOTES.md
# importlib-metadata Distribution Name vs Import Name Traps

## CSX Search Result
`search_known_solution` answered MISS (no existing samples found for `importlib-metadata` distribution-vs-import package name resolution; returned unrelated alternatives).

## What a Model Would Have Written Instead
A model would assume that `importlib_metadata.version(name)` accepts the top-level Python import package name (such as `version("yaml")`, `version("PIL")`, `version("sklearn")`) or imported module objects (`version(mod)`), expecting it to return the version directly or return `None`/`KeyError` if absent.

## How the Wrong Version Fails
It fails loudly at runtime with `importlib_metadata.PackageNotFoundError` whenever the top-level import name differs from the installed distribution name (even when the module is imported and functioning), and fails with `TypeError` if passed a module object instead of a string.
csx.json
{"case":{"caseId":"case:sha256:eefb19e24a51e90936bad25255107c269b62ef1ee7ee776eb6006475252aac08","constraints":{"runtime":"python"},"contract":["assert version() queries distribution name and raises PackageNotFoundError when given top-level import package name","assert PackageNotFoundError inherits from ModuleNotFoundError and ImportError but not KeyError","assert distribution name normalization under PEP 503 equates hyphens, underscores, dots, and case variants","assert packages_distributions() maps import package names to list of distribution names","assert passing module object or non-string to version() raises TypeError"],"goal":"Resolve installed package versions when distribution names differ from top-level import names without PackageNotFoundError","kind":"HOW","packages":["pkg:pypi/importlib-metadata@9.0.0","pkg:pypi/zipp@4.1.0"],"schemaVersion":1,"symbols":["importlib_metadata.version","importlib_metadata.PackageNotFoundError","importlib_metadata.packages_distributions","importlib_metadata.distribution"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/importlib-metadata@9.0.0","pkg:pypi/zipp@4.1.0"],"schemaVersion":1,"symbols":["importlib_metadata.version","importlib_metadata.PackageNotFoundError","importlib_metadata.packages_distributions","importlib_metadata.distribution"],"verifierAdapter":"python@1"}
requirements.txt
importlib-metadata==9.0.0
zipp==4.1.0
src/__init__.py
"""Package initialization."""
src/resolver.py
"""Distribution and import package metadata version resolver utilities."""

from typing import List, Optional, Tuple, Union
import importlib_metadata


def resolve_distribution_version(distribution_name: str) -> str:
    """Retrieve the version for an installed distribution name.

    Distribution names are normalized (case-insensitive, hyphens/underscores/dots
    are equivalent under PEP 503). Raises PackageNotFoundError if not installed.
    """
    return importlib_metadata.version(distribution_name)


def resolve_import_package_version(import_name: str) -> List[Tuple[str, str]]:
    """Resolve distribution versions for a top-level Python import package name.

    Top-level import package names (e.g. 'yaml', 'PIL', 'sklearn') differ from
    their distribution names ('PyYAML', 'Pillow', 'scikit-learn').
    Directly calling version(import_name) raises PackageNotFoundError.
    This function queries packages_distributions() to find the distribution name(s),
    then retrieves their versions.
    """
    pkg_to_dists = importlib_metadata.packages_distributions()
    dist_names = pkg_to_dists.get(import_name, [])
    results: List[Tuple[str, str]] = []
    for dist_name in dist_names:
        ver = importlib_metadata.version(dist_name)
        results.append((dist_name, ver))
    return results


def safe_get_version(target: Union[str, object]) -> Optional[str]:
    """Safely obtain package version given either a distribution name or an import name.

    Returns the resolved version string, or None if no distribution metadata is found.
    Raises TypeError if target is not a string.
    """
    if not isinstance(target, str):
        raise TypeError(f"Expected str, got {type(target).__name__}")

    try:
        return importlib_metadata.version(target)
    except importlib_metadata.PackageNotFoundError:
        # Fall back to looking up import name in packages_distributions
        pkg_to_dists = importlib_metadata.packages_distributions()
        dist_names = pkg_to_dists.get(target)
        if dist_names:
            return importlib_metadata.version(dist_names[0])
        return None
test/contract.py
import os
import shutil
import sys
import tempfile

# Ensure project root is in sys.path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import importlib_metadata
from src.resolver import (
    resolve_distribution_version,
    resolve_import_package_version,
    safe_get_version,
)


def create_fixture_distribution(
    base_dir: str, dist_name: str, version: str, top_level_modules: list
) -> None:
    """Create a minimal .dist-info fixture directory on sys.path."""
    safe_name = dist_name.replace("-", "_")
    dist_info_dir = os.path.join(base_dir, f"{safe_name}-{version}.dist-info")
    os.makedirs(dist_info_dir, exist_ok=True)
    with open(os.path.join(dist_info_dir, "METADATA"), "w", encoding="utf-8") as f:
        f.write(f"Metadata-Version: 2.1\nName: {dist_name}\nVersion: {version}\n")
    with open(os.path.join(dist_info_dir, "top_level.txt"), "w", encoding="utf-8") as f:
        for mod in top_level_modules:
            f.write(f"{mod}\n")


# Set up an isolated temporary directory with custom distribution fixtures
temp_dir = tempfile.mkdtemp()
try:
    # Fixture 1: Distribution name 'Custom-Toolkit' provides import package 'toolkit_core'
    create_fixture_distribution(temp_dir, "Custom-Toolkit", "3.2.1", ["toolkit_core"])
    # Fixture 2: Namespace package 'shared_ns' provided by two distributions
    create_fixture_distribution(temp_dir, "Plugin-Alpha", "1.0.0", ["shared_ns"])
    create_fixture_distribution(temp_dir, "Plugin-Beta", "2.0.0", ["shared_ns"])

    sys.path.insert(0, temp_dir)

    # -----------------------------------------------------------------------------
    # 1. Distribution Name vs Import Name Trap
    # Naive model expectation: version("toolkit_core") returns "3.2.1" because
    # toolkit_core is the imported module name.
    # importlib-metadata reality: version() accepts ONLY distribution names. Passing
    # the top-level import name raises PackageNotFoundError.
    # -----------------------------------------------------------------------------
    assert resolve_distribution_version("Custom-Toolkit") == "3.2.1"

    raised_package_not_found = False
    try:
        importlib_metadata.version("toolkit_core")
    except importlib_metadata.PackageNotFoundError:
        raised_package_not_found = True
    assert raised_package_not_found, (
        "Calling version() with top-level import name must raise PackageNotFoundError"
    )

    # -----------------------------------------------------------------------------
    # 2. Exception Hierarchy Trap
    # Naive model expectation: Mismatches raise KeyError (like dict lookup) or
    # ValueError.
    # importlib-metadata reality: PackageNotFoundError inherits from ModuleNotFoundError
    # and ImportError, but NOT KeyError.
    # -----------------------------------------------------------------------------
    assert issubclass(importlib_metadata.PackageNotFoundError, ModuleNotFoundError)
    assert issubclass(importlib_metadata.PackageNotFoundError, ImportError)
    assert not issubclass(importlib_metadata.PackageNotFoundError, KeyError)

    # -----------------------------------------------------------------------------
    # 3. PEP 503 / PEP 566 Distribution Name Normalization
    # Naive model expectation: Lookup is strict case-sensitive string matching where
    # hyphens and underscores are incompatible.
    # importlib-metadata reality: All punctuation (-, _, .) and case variants of
    # a distribution name normalize to the same distribution.
    # -----------------------------------------------------------------------------
    assert resolve_distribution_version("custom-toolkit") == "3.2.1"
    assert resolve_distribution_version("CUSTOM_TOOLKIT") == "3.2.1"
    assert resolve_distribution_version("custom.toolkit") == "3.2.1"

    # Real installed distribution normalization check
    assert resolve_distribution_version("importlib-metadata") == "9.0.0"
    assert resolve_distribution_version("importlib_metadata") == "9.0.0"
    assert resolve_distribution_version("IMPORTLIB-METADATA") == "9.0.0"

    # -----------------------------------------------------------------------------
    # 4. Resolving Import Names via packages_distributions()
    # Naive model expectation: packages_distributions() returns a 1:1 dict[str, str].
    # importlib-metadata reality: It returns dict[str, list[str]] because multiple
    # distributions can provide the same top-level package or namespace.
    # -----------------------------------------------------------------------------
    resolved_single = resolve_import_package_version("toolkit_core")
    assert resolved_single == [("Custom-Toolkit", "3.2.1")]

    resolved_multi = resolve_import_package_version("shared_ns")
    assert len(resolved_multi) == 2
    assert ("Plugin-Alpha", "1.0.0") in resolved_multi
    assert ("Plugin-Beta", "2.0.0") in resolved_multi

    # safe_get_version resolves both distribution names and import package names
    assert safe_get_version("Custom-Toolkit") == "3.2.1"
    assert safe_get_version("toolkit_core") == "3.2.1"
    assert safe_get_version("nonexistent_pkg") is None

    # -----------------------------------------------------------------------------
    # 5. Non-String / Module Object Input Trap
    # Naive model expectation: Passing an imported module object (e.g. version(mod))
    # reads mod.__version__.
    # importlib-metadata reality: version() accepts only string names; passing a module
    # or non-string raises TypeError.
    # -----------------------------------------------------------------------------
    raised_type_error = False
    try:
        importlib_metadata.version(importlib_metadata)
    except TypeError:
        raised_type_error = True
    assert raised_type_error, "Passing module object to version() must raise TypeError"

    raised_type_error_int = False
    try:
        safe_get_version(12345)
    except TypeError:
        raised_type_error_int = True
    assert raised_type_error_int, "Passing non-string must raise TypeError"

    print("CONTRACT PASS: importlib-metadata distribution vs import name resolution verified")
finally:
    if temp_dir in sys.path:
        sys.path.remove(temp_dir)
    shutil.rmtree(temp_dir, ignore_errors=True)

오리진 시더

csx-seed