Skip to content

Commit

Permalink
Merge branch 'main' into bugfix/multiplexed-descendants
Browse files Browse the repository at this point in the history
  • Loading branch information
jaraco committed Feb 17, 2023
2 parents 7fb0260 + ff16bd3 commit 289aadb
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 68 deletions.
3 changes: 2 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
omit =
# leading `*/` for pytest-dev/pytest-cov#456
*/.tox/*
*/pep517-build-env-*
*/_itertools.py
*/_legacy.py
*/simple.py
*/_path.py
disable_warnings =
couldnt-parse

[report]
show_missing = True
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ v5.11.0
* #265: ``MultiplexedPath`` now honors multiple subdirectories
in ``iterdir`` and ``joinpath``.

v5.10.3
=======

* Packaging refresh, including fixing EncodingWarnings
and some tests cleanup.

v5.10.2
=======

Expand Down
21 changes: 21 additions & 0 deletions docs/using.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,27 @@ example could also be written as::
eml = files(email.tests.data).joinpath('message.eml').read_text()


Namespace Packages
==================

``importlib_resources`` supports namespace packages as anchors just like
any other package. Similar to modules in a namespace package,
resources in a namespace package are not allowed to collide by name.
For example, if two packages both expose ``nspkg/data/foo.txt``, those
resources are unsupported by this library. The package will also likely
experience problems due to the collision with installers.

It's perfectly valid, however, for two packages to present different resources
in the same namespace package, regular package, or subdirectory.
For example, one package could expose ``nspkg/data/foo.txt`` and another
expose ``nspkg/data/bar.txt`` and those two packages could be installed
into separate paths, and the resources should be queryable::

data = importlib_resources.files('nspkg').joinpath('data')
data.joinpath('foo.txt').read_text()
data.joinpath('bar.txt').read_text()


File system or zip file
=======================

Expand Down
18 changes: 12 additions & 6 deletions importlib_resources/tests/_path.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import pathlib
import functools

from typing import Dict, Union


####
# from jaraco.path 3.4
# from jaraco.path 3.4.1

FilesSpec = Dict[str, Union[str, bytes, 'FilesSpec']] # type: ignore


def build(spec, prefix=pathlib.Path()):
def build(spec: FilesSpec, prefix=pathlib.Path()):
"""
Build a set of files/directories, as described by the spec.
Expand All @@ -23,15 +27,17 @@ def build(spec, prefix=pathlib.Path()):
... "baz.py": "# Some code",
... }
... }
>>> tmpdir = getfixture('tmpdir')
>>> build(spec, tmpdir)
>>> target = getfixture('tmp_path')
>>> build(spec, target)
>>> target.joinpath('foo/baz.py').read_text(encoding='utf-8')
'# Some code'
"""
for name, contents in spec.items():
create(contents, pathlib.Path(prefix) / name)


@functools.singledispatch
def create(content, path):
def create(content: Union[str, bytes, FilesSpec], path):
path.mkdir(exist_ok=True)
build(content, prefix=path) # type: ignore

Expand All @@ -43,7 +49,7 @@ def _(content: bytes, path):

@create.register
def _(content: str, path):
path.write_text(content)
path.write_text(content, encoding='utf-8')


# end from jaraco.path
Expand Down
6 changes: 4 additions & 2 deletions importlib_resources/tests/test_compatibilty_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ def test_orphan_path_name(self):

def test_spec_path_open(self):
self.assertEqual(self.files.read_bytes(), b'Hello, world!')
self.assertEqual(self.files.read_text(), 'Hello, world!')
self.assertEqual(self.files.read_text(encoding='utf-8'), 'Hello, world!')

def test_child_path_open(self):
self.assertEqual((self.files / 'a').read_bytes(), b'Hello, world!')
self.assertEqual((self.files / 'a').read_text(), 'Hello, world!')
self.assertEqual(
(self.files / 'a').read_text(encoding='utf-8'), 'Hello, world!'
)

def test_orphan_path_open(self):
with self.assertRaises(FileNotFoundError):
Expand Down
4 changes: 2 additions & 2 deletions importlib_resources/tests/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def test_module_resources(self):
_path.build(spec, self.site_dir)
import mod

actual = resources.files(mod).joinpath('res.txt').read_text()
actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8')
assert actual == spec['res.txt']


Expand All @@ -98,7 +98,7 @@ def test_implicit_files(self):
'__init__.py': textwrap.dedent(
"""
import importlib_resources as res
val = res.files().joinpath('res.txt').read_text()
val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
"""
),
'res.txt': 'resources are the best',
Expand Down
4 changes: 2 additions & 2 deletions importlib_resources/tests/test_open.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def execute(self, package, path):
class CommonTextTests(util.CommonTests, unittest.TestCase):
def execute(self, package, path):
target = resources.files(package).joinpath(path)
with target.open():
with target.open(encoding='utf-8'):
pass


Expand All @@ -28,7 +28,7 @@ def test_open_binary(self):

def test_open_text_default_encoding(self):
target = resources.files(self.data) / 'utf-8.file'
with target.open() as fp:
with target.open(encoding='utf-8') as fp:
result = fp.read()
self.assertEqual(result, 'Hello, UTF-8 world!\n')

Expand Down
8 changes: 6 additions & 2 deletions importlib_resources/tests/test_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def execute(self, package, path):

class CommonTextTests(util.CommonTests, unittest.TestCase):
def execute(self, package, path):
resources.files(package).joinpath(path).read_text()
resources.files(package).joinpath(path).read_text(encoding='utf-8')


class ReadTests:
Expand All @@ -22,7 +22,11 @@ def test_read_bytes(self):
self.assertEqual(result, b'\0\1\2\3')

def test_read_text_default_encoding(self):
result = resources.files(self.data).joinpath('utf-8.file').read_text()
result = (
resources.files(self.data)
.joinpath('utf-8.file')
.read_text(encoding='utf-8')
)
self.assertEqual(result, 'Hello, UTF-8 world!\n')

def test_read_text_given_encoding(self):
Expand Down
90 changes: 39 additions & 51 deletions importlib_resources/tests/test_resource.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
import sys
import unittest
import importlib_resources as resources
Expand All @@ -8,7 +9,7 @@
from . import zipdata01, zipdata02
from . import util
from importlib import import_module
from ._compat import import_helper, unlink
from ._compat import import_helper, os_helper, unlink


class ResourceTests:
Expand Down Expand Up @@ -140,84 +141,71 @@ def test_unrelated_contents(self):
)


@contextlib.contextmanager
def zip_on_path(dir):
data_path = pathlib.Path(zipdata01.__file__)
source_zip_path = data_path.parent.joinpath('ziptestdata.zip')
zip_path = pathlib.Path(dir) / f'{uuid.uuid4()}.zip'
zip_path.write_bytes(source_zip_path.read_bytes())
sys.path.append(str(zip_path))
import_module('ziptestdata')

try:
yield
finally:
with contextlib.suppress(ValueError):
sys.path.remove(str(zip_path))

with contextlib.suppress(KeyError):
del sys.path_importer_cache[str(zip_path)]
del sys.modules['ziptestdata']

with contextlib.suppress(OSError):
unlink(zip_path)


class DeletingZipsTest(unittest.TestCase):
"""Having accessed resources in a zip file should not keep an open
reference to the zip.
"""

ZIP_MODULE = zipdata01

def setUp(self):
self.fixtures = contextlib.ExitStack()
self.addCleanup(self.fixtures.close)

modules = import_helper.modules_setup()
self.addCleanup(import_helper.modules_cleanup, *modules)

data_path = pathlib.Path(self.ZIP_MODULE.__file__)
data_dir = data_path.parent
self.source_zip_path = data_dir / 'ziptestdata.zip'
self.zip_path = pathlib.Path(f'{uuid.uuid4()}.zip').absolute()
self.zip_path.write_bytes(self.source_zip_path.read_bytes())
sys.path.append(str(self.zip_path))
self.data = import_module('ziptestdata')

def tearDown(self):
try:
sys.path.remove(str(self.zip_path))
except ValueError:
pass

try:
del sys.path_importer_cache[str(self.zip_path)]
del sys.modules[self.data.__name__]
except KeyError:
pass

try:
unlink(self.zip_path)
except OSError:
# If the test fails, this will probably fail too
pass
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
self.fixtures.enter_context(zip_on_path(temp_dir))

def test_iterdir_does_not_keep_open(self):
c = [item.name for item in resources.files('ziptestdata').iterdir()]
self.zip_path.unlink()
del c
[item.name for item in resources.files('ziptestdata').iterdir()]

def test_is_file_does_not_keep_open(self):
c = resources.files('ziptestdata').joinpath('binary.file').is_file()
self.zip_path.unlink()
del c
resources.files('ziptestdata').joinpath('binary.file').is_file()

def test_is_file_failure_does_not_keep_open(self):
c = resources.files('ziptestdata').joinpath('not-present').is_file()
self.zip_path.unlink()
del c
resources.files('ziptestdata').joinpath('not-present').is_file()

@unittest.skip("Desired but not supported.")
def test_as_file_does_not_keep_open(self): # pragma: no cover
c = resources.as_file(resources.files('ziptestdata') / 'binary.file')
self.zip_path.unlink()
del c
resources.as_file(resources.files('ziptestdata') / 'binary.file')

def test_entered_path_does_not_keep_open(self):
"""
Mimic what certifi does on import to make its bundle
available for the process duration.
"""
c = resources.as_file(
resources.files('ziptestdata') / 'binary.file'
).__enter__()
self.zip_path.unlink()
del c
resources.as_file(resources.files('ziptestdata') / 'binary.file').__enter__()

def test_read_binary_does_not_keep_open(self):
c = resources.files('ziptestdata').joinpath('binary.file').read_bytes()
self.zip_path.unlink()
del c
resources.files('ziptestdata').joinpath('binary.file').read_bytes()

def test_read_text_does_not_keep_open(self):
c = resources.files('ziptestdata').joinpath('utf-8.file').read_text()
self.zip_path.unlink()
del c
resources.files('ziptestdata').joinpath('utf-8.file').read_text(
encoding='utf-8'
)


class ResourceFromNamespaceTest01(unittest.TestCase):
Expand Down
14 changes: 13 additions & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
[pytest]
norecursedirs=dist build .tox .eggs
addopts=--doctest-modules
doctest_optionflags=ALLOW_UNICODE ELLIPSIS
filterwarnings=
## upstream

# Ensure ResourceWarnings are emitted
default::ResourceWarning

Expand All @@ -18,3 +19,14 @@ filterwarnings=
ignore:<class 'pytest_flake8.Flake8Item'> is not using a cooperative constructor:pytest.PytestDeprecationWarning
ignore:The \(fspath. py.path.local\) argument to Flake8Item is deprecated.:pytest.PytestDeprecationWarning
ignore:Flake8Item is an Item subclass and should not be a collector:pytest.PytestWarning

# shopkeep/pytest-black#67
ignore:'encoding' argument not specified::pytest_black

# realpython/pytest-mypy#152
ignore:'encoding' argument not specified::pytest_mypy

# python/cpython#100750
ignore:'encoding' argument not specified::platform

## end upstream
5 changes: 4 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ toxworkdir={env:TOX_WORK_DIR:.tox}

[testenv]
deps =
setenv =
PYTHONWARNDEFAULTENCODING = 1
commands =
pytest {posargs}
usedevelop = True
extras = testing
extras =
testing

[testenv:docs]
extras =
Expand Down

0 comments on commit 289aadb

Please sign in to comment.