Skip to content

Commit c932b0b

Browse files
committed
flit_core: refactor path handling to use pathlib
1 parent 6982fae commit c932b0b

File tree

6 files changed

+99
-87
lines changed

6 files changed

+99
-87
lines changed

flit_core/flit_core/buildapi.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
"""PEP-517 compliant buildsystem API"""
22
import logging
3-
import io
4-
import os
5-
import os.path as osp
63
from pathlib import Path
74

85
from .common import (
@@ -48,36 +45,36 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
4845
module = Module(ini_info.module, Path.cwd())
4946
metadata = make_metadata(module, ini_info)
5047

51-
dist_info = osp.join(metadata_directory,
52-
dist_info_name(metadata.name, metadata.version))
53-
os.mkdir(dist_info)
48+
dist_info = Path(metadata_directory).joinpath(
49+
dist_info_name(metadata.name, metadata.version))
50+
dist_info.mkdir()
5451

55-
with io.open(osp.join(dist_info, 'WHEEL'), 'w', encoding='utf-8') as f:
52+
with dist_info.joinpath('WHEEL').open('w', encoding='utf-8') as f:
5653
_write_wheel_file(f, supports_py2=metadata.supports_py2)
5754

58-
with io.open(osp.join(dist_info, 'METADATA'), 'w', encoding='utf-8') as f:
55+
with dist_info.joinpath('METADATA').open('w', encoding='utf-8') as f:
5956
metadata.write_metadata_file(f)
6057

6158
if ini_info.entrypoints:
62-
with io.open(osp.join(dist_info, 'entry_points.txt'), 'w', encoding='utf-8') as f:
59+
with dist_info.joinpath('entry_points.txt').open('w', encoding='utf-8') as f:
6360
write_entry_points(ini_info.entrypoints, f)
6461

65-
return osp.basename(dist_info)
62+
return Path(dist_info).name
6663

6764
# Metadata for editable are the same as for a wheel
6865
prepare_metadata_for_build_editable = prepare_metadata_for_build_wheel
6966

7067
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
7168
"""Builds a wheel, places it in wheel_directory"""
72-
info = make_wheel_in(pyproj_toml, Path(wheel_directory))
69+
info = make_wheel_in(pyproj_toml, wheel_directory)
7370
return info.file.name
7471

7572
def build_editable(wheel_directory, config_settings=None, metadata_directory=None):
7673
"""Builds an "editable" wheel, places it in wheel_directory"""
77-
info = make_wheel_in(pyproj_toml, Path(wheel_directory), editable=True)
74+
info = make_wheel_in(pyproj_toml, wheel_directory, editable=True)
7875
return info.file.name
7976

8077
def build_sdist(sdist_directory, config_settings=None):
8178
"""Builds an sdist, places it in sdist_directory"""
82-
path = SdistBuilder.from_ini_path(pyproj_toml).build(Path(sdist_directory))
79+
path = SdistBuilder.from_ini_path(pyproj_toml).build(sdist_directory)
8380
return path.name

flit_core/flit_core/common.py

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ def __init__(self, name, directory=Path()):
2121
self.name = name
2222

2323
# It must exist either as a .py file or a directory, but not both
24-
name_as_path = name.replace('.', os.sep)
25-
pkg_dir = directory / name_as_path
26-
py_file = directory / (name_as_path+'.py')
27-
src_pkg_dir = directory / 'src' / name_as_path
28-
src_py_file = directory / 'src' / (name_as_path+'.py')
24+
name_as_path = Path(name.replace('.', os.sep))
25+
pkg_dir = directory.joinpath(name_as_path)
26+
py_file = directory.joinpath(name_as_path).with_suffix('.py')
27+
src_pkg_dir = directory.joinpath('src').joinpath(name_as_path)
28+
src_py_file = directory.joinpath('src').joinpath(name_as_path).with_suffix('.py')
2929

3030
existing = set()
3131
if pkg_dir.is_dir():
@@ -57,7 +57,7 @@ def __init__(self, name, directory=Path()):
5757
elif not existing:
5858
raise ValueError("No file/folder found for module {}".format(name))
5959

60-
self.source_dir = directory / self.prefix
60+
self.source_dir = directory.joinpath(self.prefix)
6161

6262
if '.' in name:
6363
self.namespace_package_name = name.rpartition('.')[0]
@@ -66,7 +66,7 @@ def __init__(self, name, directory=Path()):
6666
@property
6767
def file(self):
6868
if self.is_package:
69-
return self.path / '__init__.py'
69+
return self.path.joinpath('__init__.py')
7070
else:
7171
return self.path
7272

@@ -76,24 +76,20 @@ def iter_files(self):
7676
Yields absolute paths - caller may want to make them relative.
7777
Excludes any __pycache__ and *.pyc files.
7878
"""
79-
def _include(path):
80-
name = os.path.basename(path)
81-
if (name == '__pycache__') or name.endswith('.pyc'):
82-
return False
83-
return True
79+
def _walk(path):
80+
files = []
81+
for path in path.iterdir():
82+
if path.is_file() and path.suffix != '.pyc':
83+
files.append(path)
84+
if path.is_dir() and path.name != '__pycache__':
85+
files.extend(_walk(path))
86+
return files
8487

8588
if self.is_package:
8689
# Ensure we sort all files and directories so the order is stable
87-
for dirpath, dirs, files in os.walk(str(self.path)):
88-
for file in sorted(files):
89-
full_path = os.path.join(dirpath, file)
90-
if _include(full_path):
91-
yield full_path
92-
93-
dirs[:] = [d for d in sorted(dirs) if _include(d)]
94-
90+
return sorted(_walk(self.path))
9591
else:
96-
yield str(self.path)
92+
return [self.path]
9793

9894
class ProblemInModule(ValueError): pass
9995
class NoDocstringError(ProblemInModule): pass

flit_core/flit_core/config.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import errno
44
import logging
55
import os
6-
import os.path as osp
7-
from pathlib import Path
6+
from pathlib import Path, PurePosixPath
87
import re
98

109
from .vendor import tomli
@@ -177,6 +176,37 @@ def _flatten(d, prefix):
177176
res.update(_flatten(v, k))
178177
return res
179178

179+
def posix_normpath(path):
180+
"""Normalize path, eliminating double slashes, etc."""
181+
path = str(path)
182+
sep = '/'
183+
empty = ''
184+
dot = '.'
185+
dotdot = '..'
186+
if path == empty:
187+
return dot
188+
initial_slashes = path.startswith(sep)
189+
# POSIX allows one or two initial slashes, but treats three or more
190+
# as single slash.
191+
# (see http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13)
192+
if (initial_slashes and
193+
path.startswith(sep*2) and not path.startswith(sep*3)):
194+
initial_slashes = 2
195+
comps = path.split(sep)
196+
new_comps = []
197+
for comp in comps:
198+
if comp in (empty, dot):
199+
continue
200+
if (comp != dotdot or (not initial_slashes and not new_comps) or
201+
(new_comps and new_comps[-1] == dotdot)):
202+
new_comps.append(comp)
203+
elif new_comps:
204+
new_comps.pop()
205+
comps = new_comps
206+
path = sep.join(comps)
207+
if initial_slashes:
208+
path = sep*initial_slashes + path
209+
return PurePosixPath(path or dot)
180210

181211
def _check_glob_patterns(pats, clude):
182212
"""Check and normalise glob patterns for sdist include/exclude"""
@@ -201,18 +231,18 @@ def _check_glob_patterns(pats, clude):
201231
.format(clude, p)
202232
)
203233

204-
normp = osp.normpath(p)
234+
normp = posix_normpath(PurePosixPath(p))
205235

206-
if osp.isabs(normp):
236+
if normp.is_absolute():
207237
raise ConfigError(
208238
'{} pattern {!r} is an absolute path'.format(clude, p)
209239
)
210-
if osp.normpath(p).startswith('..' + os.sep):
240+
if PurePosixPath(normp.parts[0]) == PurePosixPath('..'):
211241
raise ConfigError(
212242
'{} pattern {!r} points out of the directory containing pyproject.toml'
213243
.format(clude, p)
214244
)
215-
normed.append(normp)
245+
normed.append(str(normp))
216246

217247
return normed
218248

@@ -243,10 +273,10 @@ def add_scripts(self, scripts_dict):
243273

244274

245275
def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True):
246-
if osp.isabs(rel_path):
276+
if PurePosixPath(rel_path).is_absolute():
247277
raise ConfigError("Readme path must be relative")
248278

249-
desc_path = proj_dir / rel_path
279+
desc_path = proj_dir.joinpath(rel_path)
250280
try:
251281
with desc_path.open('r', encoding='utf-8') as f:
252282
raw_desc = f.read()

flit_core/flit_core/sdist.py

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
from collections import defaultdict
22
from copy import copy
3-
from glob import glob
43
from gzip import GzipFile
54
import io
65
import logging
76
import os
8-
import os.path as osp
97
from pathlib import Path
108
from posixpath import join as pjoin
119
import tarfile
@@ -43,9 +41,9 @@ def __init__(self, patterns, basedir):
4341
self.files = set()
4442

4543
for pattern in patterns:
46-
for path in sorted(glob(osp.join(basedir, pattern))):
47-
rel = osp.relpath(path, basedir)
48-
if osp.isdir(path):
44+
for path in self.basedir.glob(pattern):
45+
rel = path.relative_to(basedir)
46+
if rel.is_dir():
4947
self.dirs.add(rel)
5048
else:
5149
self.files.add(rel)
@@ -54,14 +52,14 @@ def match_file(self, rel_path):
5452
if rel_path in self.files:
5553
return True
5654

57-
return any(rel_path.startswith(d + os.sep) for d in self.dirs)
55+
return any(d in rel_path.parents for d in self.dirs)
5856

5957
def match_dir(self, rel_path):
6058
if rel_path in self.dirs:
6159
return True
6260

6361
# Check if it's a subdirectory of any directory in the list
64-
return any(rel_path.startswith(d + os.sep) for d in self.dirs)
62+
return any(d in rel_path.parents for d in self.dirs)
6563

6664

6765
class SdistBuilder:
@@ -75,12 +73,12 @@ def __init__(self, module, metadata, cfgdir, reqs_by_extra, entrypoints,
7573
extra_files, include_patterns=(), exclude_patterns=()):
7674
self.module = module
7775
self.metadata = metadata
78-
self.cfgdir = cfgdir
76+
self.cfgdir = Path(cfgdir)
7977
self.reqs_by_extra = reqs_by_extra
8078
self.entrypoints = entrypoints
81-
self.extra_files = extra_files
82-
self.includes = FilePatterns(include_patterns, str(cfgdir))
83-
self.excludes = FilePatterns(exclude_patterns, str(cfgdir))
79+
self.extra_files = [Path(p) for p in extra_files]
80+
self.includes = FilePatterns(include_patterns, cfgdir)
81+
self.excludes = FilePatterns(exclude_patterns, cfgdir)
8482

8583
@classmethod
8684
def from_ini_path(cls, ini_path: Path):
@@ -112,39 +110,30 @@ def select_files(self):
112110
This is overridden in flit itself to use information from a VCS to
113111
include tests, docs, etc. for a 'gold standard' sdist.
114112
"""
115-
cfgdir_s = str(self.cfgdir)
116113
return [
117-
osp.relpath(p, cfgdir_s) for p in self.module.iter_files()
114+
p.relative_to(self.cfgdir) for p in self.module.iter_files()
118115
] + self.extra_files
119116

120117
def apply_includes_excludes(self, files):
121-
cfgdir_s = str(self.cfgdir)
122-
files = {f for f in files if not self.excludes.match_file(f)}
118+
files = {Path(f) for f in files if not self.excludes.match_file(Path(f))}
123119

124120
for f_rel in self.includes.files:
125121
if not self.excludes.match_file(f_rel):
126122
files.add(f_rel)
127123

128124
for rel_d in self.includes.dirs:
129-
for dirpath, dirs, dfiles in os.walk(osp.join(cfgdir_s, rel_d)):
130-
for file in dfiles:
131-
f_abs = osp.join(dirpath, file)
132-
f_rel = osp.relpath(f_abs, cfgdir_s)
133-
if not self.excludes.match_file(f_rel):
134-
files.add(f_rel)
135-
136-
# Filter subdirectories before os.walk scans them
137-
dirs[:] = [d for d in dirs if not self.excludes.match_dir(
138-
osp.relpath(osp.join(dirpath, d), cfgdir_s)
139-
)]
125+
for abs_path in self.cfgdir.joinpath(rel_d).glob('**/*'):
126+
path = abs_path.relative_to(self.cfgdir)
127+
if not self.excludes.match_file(path):
128+
files.add(path)
140129

141130
crucial_files = set(
142-
self.extra_files + [str(self.module.file.relative_to(self.cfgdir))]
131+
self.extra_files + [self.module.file.relative_to(self.cfgdir)]
143132
)
144133
missing_crucial = crucial_files - files
145134
if missing_crucial:
146135
raise Exception("Crucial files were excluded from the sdist: {}"
147-
.format(", ".join(missing_crucial)))
136+
.format(", ".join(str(m) for m in missing_crucial)))
148137

149138
return sorted(files)
150139

@@ -157,26 +146,27 @@ def dir_name(self):
157146
return '{}-{}'.format(self.metadata.name, self.metadata.version)
158147

159148
def build(self, target_dir, gen_setup_py=True):
160-
os.makedirs(str(target_dir), exist_ok=True)
161-
target = target_dir / '{}-{}.tar.gz'.format(
149+
target_dir = Path(target_dir)
150+
target_dir.mkdir(exist_ok=True)
151+
target = target_dir.joinpath('{}-{}.tar.gz'.format(
162152
self.metadata.name, self.metadata.version
163-
)
153+
))
164154
source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH', '')
165155
mtime = int(source_date_epoch) if source_date_epoch else None
166-
gz = GzipFile(str(target), mode='wb', mtime=mtime)
167-
tf = tarfile.TarFile(str(target), mode='w', fileobj=gz,
156+
gz = GzipFile(target, mode='wb', mtime=mtime)
157+
tf = tarfile.TarFile(target, mode='w', fileobj=gz,
168158
format=tarfile.PAX_FORMAT)
169159

170160
try:
171161
files_to_add = self.apply_includes_excludes(self.select_files())
172162

173163
for relpath in files_to_add:
174-
path = str(self.cfgdir / relpath)
175-
ti = tf.gettarinfo(path, arcname=pjoin(self.dir_name, relpath))
164+
path = self.cfgdir.joinpath(relpath)
165+
ti = tf.gettarinfo(str(path), arcname=pjoin(self.dir_name, relpath))
176166
ti = clean_tarinfo(ti, mtime)
177167

178168
if ti.isreg():
179-
with open(path, 'rb') as f:
169+
with path.open('rb') as f:
180170
tf.addfile(ti, f)
181171
else:
182172
tf.addfile(ti) # Symlinks & ?

flit_core/flit_core/tests/test_sdist.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from io import BytesIO
2-
import os.path as osp
32
from pathlib import Path
43
import tarfile
54
from testpath import assert_isfile
@@ -46,6 +45,6 @@ def test_include_exclude():
4645
)
4746
files = builder.apply_includes_excludes(builder.select_files())
4847

49-
assert osp.join('doc', 'test.rst') in files
50-
assert osp.join('doc', 'test.txt') not in files
51-
assert osp.join('doc', 'subdir', 'test.txt') in files
48+
assert Path('doc').joinpath('test.rst') in files
49+
assert Path('doc').joinpath('test.txt') not in files
50+
assert Path('doc').joinpath('subdir').joinpath('test.txt') in files

0 commit comments

Comments
 (0)