Skip to content

Commit 5811d73

Browse files
authored
Merge pull request #446 from python/feature/jaraco-path-build
Use jaraco.path for generating the record and files
2 parents 64ae8f6 + 142d0dd commit 5811d73

File tree

3 files changed

+141
-18
lines changed

3 files changed

+141
-18
lines changed

tests/_path.py

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# from jaraco.path 3.5
2+
3+
import functools
4+
import pathlib
5+
from typing import Dict, Union
6+
7+
try:
8+
from typing import Protocol, runtime_checkable
9+
except ImportError: # pragma: no cover
10+
# Python 3.7
11+
from typing_extensions import Protocol, runtime_checkable # type: ignore
12+
13+
14+
FilesSpec = Dict[str, Union[str, bytes, 'FilesSpec']] # type: ignore
15+
16+
17+
@runtime_checkable
18+
class TreeMaker(Protocol):
19+
def __truediv__(self, *args, **kwargs):
20+
... # pragma: no cover
21+
22+
def mkdir(self, **kwargs):
23+
... # pragma: no cover
24+
25+
def write_text(self, content, **kwargs):
26+
... # pragma: no cover
27+
28+
def write_bytes(self, content):
29+
... # pragma: no cover
30+
31+
32+
def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker:
33+
return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore
34+
35+
36+
def build(
37+
spec: FilesSpec,
38+
prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore
39+
):
40+
"""
41+
Build a set of files/directories, as described by the spec.
42+
43+
Each key represents a pathname, and the value represents
44+
the content. Content may be a nested directory.
45+
46+
>>> spec = {
47+
... 'README.txt': "A README file",
48+
... "foo": {
49+
... "__init__.py": "",
50+
... "bar": {
51+
... "__init__.py": "",
52+
... },
53+
... "baz.py": "# Some code",
54+
... }
55+
... }
56+
>>> target = getfixture('tmp_path')
57+
>>> build(spec, target)
58+
>>> target.joinpath('foo/baz.py').read_text(encoding='utf-8')
59+
'# Some code'
60+
"""
61+
for name, contents in spec.items():
62+
create(contents, _ensure_tree_maker(prefix) / name)
63+
64+
65+
@functools.singledispatch
66+
def create(content: Union[str, bytes, FilesSpec], path):
67+
path.mkdir(exist_ok=True)
68+
build(content, prefix=path) # type: ignore
69+
70+
71+
@create.register
72+
def _(content: bytes, path):
73+
path.write_bytes(content)
74+
75+
76+
@create.register
77+
def _(content: str, path):
78+
path.write_text(content, encoding='utf-8')
79+
80+
81+
class Recording:
82+
"""
83+
A TreeMaker object that records everything that would be written.
84+
85+
>>> r = Recording()
86+
>>> build({'foo': {'foo1.txt': 'yes'}, 'bar.txt': 'abc'}, r)
87+
>>> r.record
88+
['foo/foo1.txt', 'bar.txt']
89+
"""
90+
91+
def __init__(self, loc=pathlib.PurePosixPath(), record=None):
92+
self.loc = loc
93+
self.record = record if record is not None else []
94+
95+
def __truediv__(self, other):
96+
return Recording(self.loc / other, self.record)
97+
98+
def write_text(self, content, **kwargs):
99+
self.record.append(str(self.loc))
100+
101+
write_bytes = write_text
102+
103+
def mkdir(self, **kwargs):
104+
return

tests/fixtures.py

+13
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
from .py39compat import FS_NONASCII
1212
from typing import Dict, Union
1313

14+
from . import _path
15+
16+
1417
try:
1518
from importlib import resources # type: ignore
1619

@@ -266,6 +269,16 @@ def build_files(file_defs, prefix=pathlib.Path()):
266269
f.write(DALS(contents))
267270

268271

272+
def build_record(file_defs):
273+
return ''.join(f'{name},,\n' for name in record_names(file_defs))
274+
275+
276+
def record_names(file_defs):
277+
recording = _path.Recording()
278+
_path.build(file_defs, recording)
279+
return recording.record
280+
281+
269282
class FileBuilder:
270283
def unicode_filename(self):
271284
return FS_NONASCII or self.skip("File system does not support non-ascii.")

tests/test_main.py

+24-18
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import unittest
44
import importlib
55
import importlib_metadata
6+
import itertools
67
import pyfakefs.fake_filesystem_unittest as ffs
78

89
from . import fixtures
@@ -328,25 +329,30 @@ def test_packages_distributions_all_module_types(self):
328329
Test top-level modules detected on a package without 'top-level.txt'.
329330
"""
330331
suffixes = importlib.machinery.all_suffixes()
331-
fixtures.build_files(
332-
{
333-
'all_distributions-1.0.0.dist-info': {
334-
'METADATA': """
335-
Name: all_distributions
336-
Version: 1.0.0
337-
""",
338-
'RECORD': 'all_distributions-1.0.0.dist-info/METADATA\n'
339-
+ ''.join(
340-
f'importable-name {i}{suffix},,\n'
341-
f'in_namespace_{i}/mod{suffix},,\n'
342-
f'in_package_{i}/__init__.py,,\n'
343-
f'in_package_{i}/mod{suffix},,\n'
344-
for i, suffix in enumerate(suffixes)
345-
),
346-
},
347-
},
348-
prefix=self.site_dir,
332+
metadata = dict(
333+
METADATA="""
334+
Name: all_distributions
335+
Version: 1.0.0
336+
""",
349337
)
338+
files = {
339+
'all_distributions-1.0.0.dist-info': metadata,
340+
}
341+
for i, suffix in enumerate(suffixes):
342+
files.update(
343+
{
344+
f'importable-name {i}{suffix}': '',
345+
f'in_namespace_{i}': {
346+
f'mod{suffix}': '',
347+
},
348+
f'in_package_{i}': {
349+
'__init__.py': '',
350+
f'mod{suffix}': '',
351+
},
352+
}
353+
)
354+
metadata.update(RECORD=fixtures.build_record(files))
355+
fixtures.build_files(files, prefix=self.site_dir)
350356

351357
distributions = packages_distributions()
352358

0 commit comments

Comments
 (0)