Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed handling of namespace packages in zip files #290

Merged
merged 2 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion importlib_resources/readers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import collections
import contextlib
import itertools
import pathlib
import operator
import re

from . import abc

Expand Down Expand Up @@ -130,7 +132,33 @@ class NamespaceReader(abc.TraversableResources):
def __init__(self, namespace_path):
if 'NamespacePath' not in str(namespace_path):
raise ValueError('Invalid path')
self.path = MultiplexedPath(*list(namespace_path))
self.path = MultiplexedPath(*map(self._resolve, namespace_path))

@classmethod
def _resolve(cls, path_str) -> abc.Traversable:
r"""
Given an item from a namespace path, resolve it to a Traversable.

path_str might be a directory on the filesystem or a path to a
zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or
``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``.
"""
(dir,) = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir())
return dir

@classmethod
def _candidate_paths(cls, path_str):
yield pathlib.Path(path_str)
yield from cls._resolve_zip_path(path_str)

@staticmethod
def _resolve_zip_path(path_str):
for match in reversed(list(re.finditer(r'[\\/]', path_str))):
with contextlib.suppress(
FileNotFoundError, IsADirectoryError, PermissionError
):
inner = path_str[match.end() :].replace('\\', '/') + '/'
yield ZipPath(path_str[: match.start()], inner.lstrip('/'))

def resource_path(self, resource):
"""
Expand Down
3 changes: 0 additions & 3 deletions importlib_resources/tests/test_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import importlib_resources as resources
import pathlib

import pytest

from . import data01
from . import util
from importlib import import_module
Expand Down Expand Up @@ -211,7 +209,6 @@ def tearDownClass(cls):
sys.path.remove(cls.site_dir)


@pytest.mark.xfail
class ResourceFromNamespaceZipTests(
util.ZipSetupBase,
ResourceFromNamespaceTests,
Expand Down
1 change: 1 addition & 0 deletions newsfragments/287.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enabled support for resources in namespace packages in zip files.