Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit aaa83cd

Browse filesBrowse files
authored
bpo-44771: Apply changes from importlib_resources 5.2.1 (GH-27436)
* bpo-44771: Apply changes from importlib_resources@3b24bd6307 * Add blurb * Exclude namespacedata01 from eol conversion.
1 parent 851cca8 commit aaa83cd
Copy full SHA for aaa83cd
Expand file treeCollapse file tree

19 files changed

+714
-373
lines changed

‎.gitattributes

Copy file name to clipboardExpand all lines: .gitattributes
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Lib/test/test_email/data/*.txt -text
2828
Lib/test/xmltestdata/* -text
2929
Lib/test/coding20731.py -text
3030
Lib/test/test_importlib/data01/* -text
31+
Lib/test/test_importlib/namespacedata01/* -text
3132

3233
# CRLF files
3334
*.bat text eol=crlf

‎Lib/importlib/_adapters.py

Copy file name to clipboardExpand all lines: Lib/importlib/_adapters.py
+98-10Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from contextlib import suppress
2+
from io import TextIOWrapper
23

34
from . import abc
45

@@ -25,32 +26,119 @@ def __init__(self, spec):
2526
self.spec = spec
2627

2728
def get_resource_reader(self, name):
28-
return DegenerateFiles(self.spec)._native()
29+
return CompatibilityFiles(self.spec)._native()
2930

3031

31-
class DegenerateFiles:
32+
def _io_wrapper(file, mode='r', *args, **kwargs):
33+
if mode == 'r':
34+
return TextIOWrapper(file, *args, **kwargs)
35+
elif mode == 'rb':
36+
return file
37+
raise ValueError(
38+
"Invalid mode value '{}', only 'r' and 'rb' are supported".format(mode)
39+
)
40+
41+
42+
class CompatibilityFiles:
3243
"""
3344
Adapter for an existing or non-existant resource reader
34-
to provide a degenerate .files().
45+
to provide a compability .files().
3546
"""
3647

37-
class Path(abc.Traversable):
48+
class SpecPath(abc.Traversable):
49+
"""
50+
Path tied to a module spec.
51+
Can be read and exposes the resource reader children.
52+
"""
53+
54+
def __init__(self, spec, reader):
55+
self._spec = spec
56+
self._reader = reader
57+
58+
def iterdir(self):
59+
if not self._reader:
60+
return iter(())
61+
return iter(
62+
CompatibilityFiles.ChildPath(self._reader, path)
63+
for path in self._reader.contents()
64+
)
65+
66+
def is_file(self):
67+
return False
68+
69+
is_dir = is_file
70+
71+
def joinpath(self, other):
72+
if not self._reader:
73+
return CompatibilityFiles.OrphanPath(other)
74+
return CompatibilityFiles.ChildPath(self._reader, other)
75+
76+
@property
77+
def name(self):
78+
return self._spec.name
79+
80+
def open(self, mode='r', *args, **kwargs):
81+
return _io_wrapper(self._reader.open_resource(None), mode, *args, **kwargs)
82+
83+
class ChildPath(abc.Traversable):
84+
"""
85+
Path tied to a resource reader child.
86+
Can be read but doesn't expose any meaningfull children.
87+
"""
88+
89+
def __init__(self, reader, name):
90+
self._reader = reader
91+
self._name = name
92+
3893
def iterdir(self):
3994
return iter(())
4095

96+
def is_file(self):
97+
return self._reader.is_resource(self.name)
98+
4199
def is_dir(self):
100+
return not self.is_file()
101+
102+
def joinpath(self, other):
103+
return CompatibilityFiles.OrphanPath(self.name, other)
104+
105+
@property
106+
def name(self):
107+
return self._name
108+
109+
def open(self, mode='r', *args, **kwargs):
110+
return _io_wrapper(
111+
self._reader.open_resource(self.name), mode, *args, **kwargs
112+
)
113+
114+
class OrphanPath(abc.Traversable):
115+
"""
116+
Orphan path, not tied to a module spec or resource reader.
117+
Can't be read and doesn't expose any meaningful children.
118+
"""
119+
120+
def __init__(self, *path_parts):
121+
if len(path_parts) < 1:
122+
raise ValueError('Need at least one path part to construct a path')
123+
self._path = path_parts
124+
125+
def iterdir(self):
126+
return iter(())
127+
128+
def is_file(self):
42129
return False
43130

44-
is_file = exists = is_dir # type: ignore
131+
is_dir = is_file
45132

46133
def joinpath(self, other):
47-
return DegenerateFiles.Path()
134+
return CompatibilityFiles.OrphanPath(*self._path, other)
48135

136+
@property
49137
def name(self):
50-
return ''
138+
return self._path[-1]
51139

52-
def open(self):
53-
raise ValueError()
140+
def open(self, mode='r', *args, **kwargs):
141+
raise FileNotFoundError("Can't open orphan path")
54142

55143
def __init__(self, spec):
56144
self.spec = spec
@@ -71,7 +159,7 @@ def __getattr__(self, attr):
71159
return getattr(self._reader, attr)
72160

73161
def files(self):
74-
return DegenerateFiles.Path()
162+
return CompatibilityFiles.SpecPath(self.spec, self._reader)
75163

76164

77165
def wrap_spec(package):

‎Lib/importlib/_common.py

Copy file name to clipboardExpand all lines: Lib/importlib/_common.py
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from ._adapters import wrap_spec
1313

1414
Package = Union[types.ModuleType, str]
15+
Resource = Union[str, os.PathLike]
1516

1617

1718
def files(package):
@@ -93,7 +94,7 @@ def _tempfile(reader, suffix=''):
9394
finally:
9495
try:
9596
os.remove(raw_path)
96-
except FileNotFoundError:
97+
except (FileNotFoundError, PermissionError):
9798
pass
9899

99100

‎Lib/importlib/_itertools.py

Copy file name to clipboard
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from itertools import filterfalse
2+
3+
4+
def unique_everseen(iterable, key=None):
5+
"List unique elements, preserving order. Remember all elements ever seen."
6+
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
7+
# unique_everseen('ABBCcAD', str.lower) --> A B C D
8+
seen = set()
9+
seen_add = seen.add
10+
if key is None:
11+
for element in filterfalse(seen.__contains__, iterable):
12+
seen_add(element)
13+
yield element
14+
else:
15+
for element in iterable:
16+
k = key(element)
17+
if k not in seen:
18+
seen_add(k)
19+
yield element

‎Lib/importlib/_legacy.py

Copy file name to clipboard
+84Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import os
2+
import pathlib
3+
import types
4+
5+
from typing import Union, Iterable, ContextManager, BinaryIO, TextIO
6+
7+
from . import _common
8+
9+
Package = Union[types.ModuleType, str]
10+
Resource = Union[str, os.PathLike]
11+
12+
13+
def open_binary(package: Package, resource: Resource) -> BinaryIO:
14+
"""Return a file-like object opened for binary reading of the resource."""
15+
return (_common.files(package) / _common.normalize_path(resource)).open('rb')
16+
17+
18+
def read_binary(package: Package, resource: Resource) -> bytes:
19+
"""Return the binary contents of the resource."""
20+
return (_common.files(package) / _common.normalize_path(resource)).read_bytes()
21+
22+
23+
def open_text(
24+
package: Package,
25+
resource: Resource,
26+
encoding: str = 'utf-8',
27+
errors: str = 'strict',
28+
) -> TextIO:
29+
"""Return a file-like object opened for text reading of the resource."""
30+
return (_common.files(package) / _common.normalize_path(resource)).open(
31+
'r', encoding=encoding, errors=errors
32+
)
33+
34+
35+
def read_text(
36+
package: Package,
37+
resource: Resource,
38+
encoding: str = 'utf-8',
39+
errors: str = 'strict',
40+
) -> str:
41+
"""Return the decoded string of the resource.
42+
43+
The decoding-related arguments have the same semantics as those of
44+
bytes.decode().
45+
"""
46+
with open_text(package, resource, encoding, errors) as fp:
47+
return fp.read()
48+
49+
50+
def contents(package: Package) -> Iterable[str]:
51+
"""Return an iterable of entries in `package`.
52+
53+
Note that not all entries are resources. Specifically, directories are
54+
not considered resources. Use `is_resource()` on each entry returned here
55+
to check if it is a resource or not.
56+
"""
57+
return [path.name for path in _common.files(package).iterdir()]
58+
59+
60+
def is_resource(package: Package, name: str) -> bool:
61+
"""True if `name` is a resource inside `package`.
62+
63+
Directories are *not* resources.
64+
"""
65+
resource = _common.normalize_path(name)
66+
return any(
67+
traversable.name == resource and traversable.is_file()
68+
for traversable in _common.files(package).iterdir()
69+
)
70+
71+
72+
def path(
73+
package: Package,
74+
resource: Resource,
75+
) -> ContextManager[pathlib.Path]:
76+
"""A context manager providing a file path object to the resource.
77+
78+
If the resource does not already exist on its own on the file system,
79+
a temporary file will be created. If the file was created, the file
80+
will be deleted upon exiting the context manager (no exception is
81+
raised if the file was deleted prior to the context manager
82+
exiting).
83+
"""
84+
return _common.as_file(_common.files(package) / _common.normalize_path(resource))

‎Lib/importlib/readers.py

Copy file name to clipboardExpand all lines: Lib/importlib/readers.py
+7-8Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import collections
2-
import zipfile
2+
import operator
33
import pathlib
4+
import zipfile
5+
46
from . import abc
57

8+
from ._itertools import unique_everseen
9+
610

711
def remove_duplicates(items):
812
return iter(collections.OrderedDict.fromkeys(items))
@@ -63,13 +67,8 @@ def __init__(self, *paths):
6367
raise NotADirectoryError('MultiplexedPath only supports directories')
6468

6569
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
70+
files = (file for path in self._paths for file in path.iterdir())
71+
return unique_everseen(files, key=operator.attrgetter('name'))
7372

7473
def read_bytes(self):
7574
raise FileNotFoundError(f'{self} is not a file')

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.