Skip to content

Commit bd20d89

Browse files
committed
Merge tag v5.0.0 into cpython
2 parents cf54b86 + e9031b7 commit bd20d89

19 files changed

Lines changed: 464 additions & 184 deletions

File tree

.coveragerc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[run]
2+
omit = .tox/*
3+
4+
[report]
5+
show_missing = True

.flake8

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[flake8]
2+
max-line-length = 88
3+
4+
# jaraco/skeleton#34
5+
max-complexity = 10
6+
7+
extend-ignore =
8+
# Black creates whitespace before colon
9+
E203
10+
exclude =
11+
# Exclude the entire top-level __init__.py file since its only purpose is
12+
# to expose the version string and to handle Python 2/3 compatibility.
13+
importlib_resources/__init__.py

.github/workflows/automerge.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: automerge
2+
on:
3+
pull_request:
4+
types:
5+
- labeled
6+
- unlabeled
7+
- synchronize
8+
- opened
9+
- edited
10+
- ready_for_review
11+
- reopened
12+
- unlocked
13+
pull_request_review:
14+
types:
15+
- submitted
16+
check_suite:
17+
types:
18+
- completed
19+
status: {}
20+
jobs:
21+
automerge:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: automerge
25+
uses: "pascalgn/automerge-action@v0.12.0"
26+
env:
27+
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

.github/workflows/main.yml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
name: Automated Tests
1+
name: tests
22

33
on: [push, pull_request]
44

55
jobs:
66
test:
77
strategy:
88
matrix:
9-
python: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9]
9+
python: [3.6, 3.8, 3.9]
1010
platform: [ubuntu-latest, macos-latest, windows-latest]
1111
runs-on: ${{ matrix.platform }}
1212
steps:
@@ -20,8 +20,24 @@ jobs:
2020
python -m pip install tox
2121
- name: Run tests
2222
run: tox
23+
24+
diffcov:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v2
28+
with:
29+
fetch-depth: 0
30+
- name: Setup Python
31+
uses: actions/setup-python@v2
32+
with:
33+
python-version: 3.9
34+
- name: Install tox
35+
run: |
36+
python -m pip install tox
37+
- name: Evaluate coverage
38+
run: tox
2339
env:
24-
TOXENV: python
40+
TOXENV: diffcov
2541

2642
release:
2743
needs: test

.pre-commit-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
repos:
2+
- repo: https://github.com/psf/black
3+
rev: 20.8b1
4+
hooks:
5+
- id: black
6+
7+
- repo: https://github.com/asottile/blacken-docs
8+
rev: v1.9.1
9+
hooks:
10+
- id: blacken-docs

Lib/importlib/_common.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,15 @@ def get_resource_reader(package):
4343
# zipimport.zipimporter does not support weak references, resulting in a
4444
# TypeError. That seems terrible.
4545
spec = package.__spec__
46-
reader = getattr(spec.loader, 'get_resource_reader', None)
46+
reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore
4747
if reader is None:
4848
return None
49-
return reader(spec.name)
49+
return reader(spec.name) # type: ignore
5050

5151

5252
def resolve(cand):
5353
# type: (Package) -> types.ModuleType
54-
return (
55-
cand if isinstance(cand, types.ModuleType)
56-
else importlib.import_module(cand)
57-
)
54+
return cand if isinstance(cand, types.ModuleType) else importlib.import_module(cand)
5855

5956

6057
def get_package(package):

Lib/importlib/abc.py

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from ._abc import Loader
1515
import abc
1616
import warnings
17+
from typing import BinaryIO, Iterable, Text
1718
from typing import Protocol, runtime_checkable
1819

1920

@@ -297,49 +298,45 @@ def set_data(self, path, data):
297298

298299

299300
class ResourceReader(metaclass=abc.ABCMeta):
300-
301-
"""Abstract base class to provide resource-reading support.
302-
303-
Loaders that support resource reading are expected to implement
304-
the ``get_resource_reader(fullname)`` method and have it either return None
305-
or an object compatible with this ABC.
306-
"""
301+
"""Abstract base class for loaders to provide resource reading support."""
307302

308303
@abc.abstractmethod
309-
def open_resource(self, resource):
304+
def open_resource(self, resource: Text) -> BinaryIO:
310305
"""Return an opened, file-like object for binary reading.
311306
312-
The 'resource' argument is expected to represent only a file name
313-
and thus not contain any subdirectory components.
314-
307+
The 'resource' argument is expected to represent only a file name.
315308
If the resource cannot be found, FileNotFoundError is raised.
316309
"""
310+
# This deliberately raises FileNotFoundError instead of
311+
# NotImplementedError so that if this method is accidentally called,
312+
# it'll still do the right thing.
317313
raise FileNotFoundError
318314

319315
@abc.abstractmethod
320-
def resource_path(self, resource):
316+
def resource_path(self, resource: Text) -> Text:
321317
"""Return the file system path to the specified resource.
322318
323-
The 'resource' argument is expected to represent only a file name
324-
and thus not contain any subdirectory components.
325-
319+
The 'resource' argument is expected to represent only a file name.
326320
If the resource does not exist on the file system, raise
327321
FileNotFoundError.
328322
"""
323+
# This deliberately raises FileNotFoundError instead of
324+
# NotImplementedError so that if this method is accidentally called,
325+
# it'll still do the right thing.
329326
raise FileNotFoundError
330327

331328
@abc.abstractmethod
332-
def is_resource(self, name):
333-
"""Return True if the named 'name' is consider a resource."""
329+
def is_resource(self, path: Text) -> bool:
330+
"""Return True if the named 'path' is a resource.
331+
332+
Files are resources, directories are not.
333+
"""
334334
raise FileNotFoundError
335335

336336
@abc.abstractmethod
337-
def contents(self):
338-
"""Return an iterable of strings over the contents of the package."""
339-
return []
340-
341-
342-
_register(ResourceReader, machinery.SourceFileLoader)
337+
def contents(self) -> Iterable[str]:
338+
"""Return an iterable of entries in `package`."""
339+
raise FileNotFoundError
343340

344341

345342
@runtime_checkable
@@ -402,8 +399,7 @@ def open(self, mode='r', *args, **kwargs):
402399
"""
403400

404401
@abc.abstractproperty
405-
def name(self):
406-
# type: () -> str
402+
def name(self) -> str:
407403
"""
408404
The base name of this object without any parent references.
409405
"""

Lib/importlib/readers.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
import collections
12
import zipfile
23
import pathlib
34
from . import abc
45

56

7+
def remove_duplicates(items):
8+
return iter(collections.OrderedDict.fromkeys(items))
9+
10+
611
class FileReader(abc.TraversableResources):
712
def __init__(self, loader):
813
self.path = pathlib.Path(loader.path).parent
@@ -39,3 +44,80 @@ def is_resource(self, path):
3944

4045
def files(self):
4146
return zipfile.Path(self.archive, self.prefix)
47+
48+
49+
class MultiplexedPath(abc.Traversable):
50+
"""
51+
Given a series of Traversable objects, implement a merged
52+
version of the interface across all objects. Useful for
53+
namespace packages which may be multihomed at a single
54+
name.
55+
"""
56+
57+
def __init__(self, *paths):
58+
self._paths = list(map(pathlib.Path, remove_duplicates(paths)))
59+
if not self._paths:
60+
message = 'MultiplexedPath must contain at least one path'
61+
raise FileNotFoundError(message)
62+
if not all(path.is_dir() for path in self._paths):
63+
raise NotADirectoryError('MultiplexedPath only supports directories')
64+
65+
def iterdir(self):
66+
visited = []
67+
for path in self._paths:
68+
for file in path.iterdir():
69+
if file.name in visited:
70+
continue
71+
visited.append(file.name)
72+
yield file
73+
74+
def read_bytes(self):
75+
raise FileNotFoundError(f'{self} is not a file')
76+
77+
def read_text(self, *args, **kwargs):
78+
raise FileNotFoundError(f'{self} is not a file')
79+
80+
def is_dir(self):
81+
return True
82+
83+
def is_file(self):
84+
return False
85+
86+
def joinpath(self, child):
87+
# first try to find child in current paths
88+
for file in self.iterdir():
89+
if file.name == child:
90+
return file
91+
# if it does not exist, construct it with the first path
92+
return self._paths[0] / child
93+
94+
__truediv__ = joinpath
95+
96+
def open(self, *args, **kwargs):
97+
raise FileNotFoundError('{} is not a file'.format(self))
98+
99+
def name(self):
100+
return self._paths[0].name
101+
102+
def __repr__(self):
103+
return 'MultiplexedPath({})'.format(
104+
', '.join("'{}'".format(path) for path in self._paths)
105+
)
106+
107+
108+
class NamespaceReader(abc.TraversableResources):
109+
def __init__(self, namespace_path):
110+
if 'NamespacePath' not in str(namespace_path):
111+
raise ValueError('Invalid path')
112+
self.path = MultiplexedPath(*list(namespace_path))
113+
114+
def resource_path(self, resource):
115+
"""
116+
Return the file system path to prevent
117+
`resources.path()` from creating a temporary
118+
copy.
119+
"""
120+
return str(self.path.joinpath(resource))
121+
122+
def files(self):
123+
return self.path

0 commit comments

Comments
 (0)