Skip to content

Commit

Permalink
Add more robust license file installation
Browse files Browse the repository at this point in the history
- Expand default license file inclusion globs. This uses the same
  patterns as setuptools/wheel with the addition of 'LICENSES/*.txt' for
  REUSE.
- Ensure matching license files are always included in sdists.
- Warn users when no license files are detected.
- Add unit tests
  • Loading branch information
gotmax23 committed Jan 12, 2023
1 parent 97e985f commit bf8ca62
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 9 deletions.
20 changes: 18 additions & 2 deletions flit_core/flit_core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

log = logging.getLogger(__name__)

# These are the same patterns used by setuptools/wheel
# besides `LICENSES/*.txt` which is from the REUSE specification
LICENSE_PATTERNS = ('AUTHORS*', 'COPYING*', 'LICEN[CS]E*', 'LICENSES/*.txt')

class ConfigError(ValueError):
pass
Expand Down Expand Up @@ -257,6 +260,7 @@ def __init__(self):
self.sdist_exclude_patterns = []
self.dynamic_metadata = []
self.data_directory = None
self.license_files = []

def add_scripts(self, scripts_dict):
if scripts_dict:
Expand Down Expand Up @@ -401,6 +405,8 @@ def _prep_metadata(md_sect, path):
# For internal use, record the main requirements as a '.none' extra.
res.reqs_by_extra['.none'] = reqs_noextra

res.license_files = find_licenses(path.parent if path else Path("."))

return res

def _expand_requires_extra(re):
Expand Down Expand Up @@ -506,7 +512,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig:
"Unrecognised keys in [project.license]: {}".format(unrec_keys)
)

# TODO: Do something with license info.
# TODO: Include license info in the metadata.
# The 'License' field in packaging metadata is a brief description of
# a license, not the full text or a file path. PEP 639 will improve on
# how licenses are recorded.
Expand All @@ -515,13 +521,15 @@ def read_pep621_metadata(proj, path) -> LoadedConfig:
raise ConfigError(
"[project.license] should specify file or text, not both"
)
lc.referenced_files.append(license_tbl['file'])
lc.license_files.append(Path(license_tbl['file']))
elif 'text' in license_tbl:
pass
else:
raise ConfigError(
"file or text field required in [project.license] table"
)
if not lc.license_files:
lc.license_files = find_licenses(path.parent)

if 'authors' in proj:
_check_type(proj, 'authors', list)
Expand Down Expand Up @@ -658,3 +666,11 @@ def pep621_people(people, group_name='author') -> dict:
if emails:
res[group_name + '_email'] = ", ".join(emails)
return res

def find_licenses(path):
found = []
for pattern in LICENSE_PATTERNS:
found.extend(file for file in path.glob(pattern) if file.is_file())
if not found:
log.warning("No licenses were found in %s. Add a license!", LICENSE_PATTERNS)
return found
3 changes: 2 additions & 1 deletion flit_core/flit_core/sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ def from_ini_path(cls, ini_path: Path):
srcdir = ini_path.parent
module = common.Module(ini_info.module, srcdir)
metadata = common.make_metadata(module, ini_info)
extra_files = [ini_path.name] + ini_info.referenced_files
license_files = list(map(str, ini_info.license_files))
extra_files = [ini_path.name] + ini_info.referenced_files + license_files
return cls(
module, metadata, srcdir, ini_info.reqs_by_extra,
ini_info.entrypoints, extra_files, ini_info.data_directory,
Expand Down
17 changes: 17 additions & 0 deletions flit_core/flit_core/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,20 @@ def test_bad_pep621_readme(readme, err_match):
}
with pytest.raises(config.ConfigError, match=err_match):
config.read_pep621_metadata(proj, samples_dir / 'pep621')

def test_license_file_auto_detect(tmp_path):
proj = {'name': 'module1', 'version': '1.0', 'description': 'x'}
tmp_path.joinpath('module1').mkdir()
for file in ('LICENSE', 'COPYING.md', 'module1/__init__.py'):
tmp_path.joinpath(file).touch()
parsed_config = config.read_pep621_metadata(proj, tmp_path / "pyproject.toml")
assert parsed_config.license_files == [tmp_path / 'COPYING.md', tmp_path / 'LICENSE']

def test_missing_license_warning(tmp_path, caplog):
proj = {'name': 'module1', 'version': '1.0', 'description': 'x'}
tmp_path.joinpath('module1').mkdir()
for file in ('module1/__init__.py',):
tmp_path.joinpath(file).touch()
config.read_pep621_metadata(proj, tmp_path / 'pyproject.toml')
message = f"No licenses were found in {config.LICENSE_PATTERNS}. Add a license!"
assert message in caplog.messages
13 changes: 7 additions & 6 deletions flit_core/flit_core/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ def zip_timestamp_from_env() -> Optional[tuple]:

class WheelBuilder:
def __init__(
self, directory, module, metadata, entrypoints, target_fp, data_directory
self, directory, module, metadata, entrypoints, target_fp, data_directory,
license_files
):
"""Build a wheel from a module/package
"""
Expand All @@ -69,6 +70,7 @@ def __init__(
self.metadata = metadata
self.entrypoints = entrypoints
self.data_directory = data_directory
self.license_files = license_files

self.records = []
self.source_time_stamp = zip_timestamp_from_env()
Expand All @@ -86,7 +88,8 @@ def from_ini_path(cls, ini_path, target_fp):
module = common.Module(ini_info.module, directory)
metadata = common.make_metadata(module, ini_info)
return cls(
directory, module, metadata, entrypoints, target_fp, ini_info.data_directory
directory, module, metadata, entrypoints, target_fp, ini_info.data_directory,
ini_info.license_files
)

@property
Expand Down Expand Up @@ -181,10 +184,8 @@ def write_metadata(self):
with self._write_to_zip(self.dist_info + '/entry_points.txt') as f:
common.write_entry_points(self.entrypoints, f)

for base in ('COPYING', 'LICENSE'):
for path in sorted(self.directory.glob(base + '*')):
if path.is_file():
self._add_file(path, '%s/%s' % (self.dist_info, path.name))
for path in self.license_files:
self._add_file(path, '%s/%s' % (self.dist_info, path.name))

with self._write_to_zip(self.dist_info + '/WHEEL') as f:
_write_wheel_file(f, supports_py2=self.metadata.supports_py2)
Expand Down

0 comments on commit bf8ca62

Please sign in to comment.