pypa/pip · error · NotImplementedError

resource_filename() only supported for .egg, not .zip

Error message

resource_filename() only supported for .egg, not .zip

What it means

Raised as NotImplementedError by ZipProvider.get_resource_filename() when self.egg_name is falsy. ZipProvider (registered for zipimport.zipimporter) needs an egg name to compute a stable cache directory for extracted files; it derives egg_name from the archive path via EggProvider._setup_prefix, which only sets it when an ancestor path matches _is_egg_path. A plain .zip (not named/structured as an egg) leaves egg_name empty, so resource_filename cannot extract.

Source

Thrown at src/pip/_vendor/pkg_resources/__init__.py:2011

        if fspath.startswith(self.zip_pre):
            return fspath[len(self.zip_pre) :]
        raise AssertionError("%s is not a subpath of %s" % (fspath, self.zip_pre))

    def _parts(self, zip_path):
        # Convert a zipfile subpath into an egg-relative path part list.
        # pseudo-fs path
        fspath = self.zip_pre + zip_path
        if fspath.startswith(self.egg_root + os.sep):
            return fspath[len(self.egg_root) + 1 :].split(os.sep)
        raise AssertionError("%s is not a subpath of %s" % (fspath, self.egg_root))

    @property
    def zipinfo(self):
        return self._zip_manifests.load(self.loader.archive)

    def get_resource_filename(self, manager: ResourceManager, resource_name: str):
        if not self.egg_name:
            raise NotImplementedError(
                "resource_filename() only supported for .egg, not .zip"
            )
        # no need to lock for extraction, since we use temp names
        zip_path = self._resource_to_zip(resource_name)
        eagers = self._get_eager_resources()
        if '/'.join(self._parts(zip_path)) in eagers:
            for name in eagers:
                self._extract_resource(manager, self._eager_to_zip(name))
        return self._extract_resource(manager, zip_path)

    @staticmethod
    def _get_date_and_size(zip_stat):
        size = zip_stat.file_size
        # ymdhms+wday, yday, dst
        date_time = zip_stat.date_time + (0, 0, -1)
        # 1980 offset already done
        timestamp = time.mktime(date_time)
        return timestamp, size

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Use resource_string()/get_resource_stream() to read bytes directly from the zip without extraction, avoiding the egg_name requirement.
  2. Rename/repackage the archive as an .egg (or ensure an EGG-INFO entry exists) so _is_egg_path recognizes it and sets egg_name.
  3. Migrate to importlib.resources.as_file / files(), which provides a temporary extracted path for zip-backed resources without egg semantics.

Example fix

// before
path = pkg_resources.resource_filename('mymod', 'data.bin')  # NotImplementedError in .zip

// after
data = pkg_resources.resource_string('mymod', 'data.bin')  # read bytes, no extraction

# or, modern API with temp extraction:
from importlib.resources import files, as_file
with as_file(files('mymod') / 'data.bin') as p:
    use(p)
Defensive patterns

Strategy: validation

Validate before calling

import pkg_resources

def safe_resource_filename(module, name):
    provider = pkg_resources.get_provider(module)
    if not getattr(provider, 'egg_name', None):
        # zip/egg without egg name cannot extract to a filename
        return None  # caller reads bytes instead
    return pkg_resources.resource_filename(module, name)

Type guard

def provider_can_extract_filename(provider) -> bool:
    return bool(getattr(provider, 'egg_name', None))

Try / catch

try:
    path = pkg_resources.resource_filename(pkg, name)
except NotImplementedError as e:
    if 'only supported for .egg' in str(e):
        data = pkg_resources.resource_string(pkg, name)  # read bytes, no extraction
    else:
        raise

Prevention

When it happens

Trigger: Calling resource_filename()/get_resource_filename() on a module imported from a plain .zip archive (or a zipapp) whose name is not recognized as an egg, so egg_name stays None and extraction is refused.

Common situations: Running a Python zipapp (.pyz), shipping code in a zip for portability, or zipimporting a non-egg archive, then trying to get a real filesystem path for a bundled resource.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/2c08b72b318a4b01.json. Report an issue: GitHub.