99import io
1010import re
1111import tarfile
12+ import warnings
1213from inspect import cleandoc
1314from pathlib import Path
1415from unittest .mock import Mock
2425from setuptools .config ._apply_pyprojecttoml import _MissingDynamic , _some_attrgetter
2526from setuptools .dist import Distribution
2627from setuptools .errors import RemovedConfigError
28+ from setuptools .warnings import SetuptoolsDeprecationWarning
2729
2830from .downloads import retrieve_file , urls_from_file
2931
@@ -156,6 +158,32 @@ def main_gui(): pass
156158def main_tomatoes(): pass
157159"""
158160
161+ PEP639_LICENSE_TEXT = """\
162+ [project]
163+ name = "spam"
164+ version = "2020.0.0"
165+ authors = [
166+ 167+ {name = "Tzu-Ping Chung"}
168+ ]
169+ license = {text = "MIT"}
170+ """
171+
172+ PEP639_LICENSE_EXPRESSION = """\
173+ [project]
174+ name = "spam"
175+ version = "2020.0.0"
176+ authors = [
177+ 178+ {name = "Tzu-Ping Chung"}
179+ ]
180+ license = "mit or apache-2.0" # should be normalized in metadata
181+ classifiers = [
182+ "Development Status :: 5 - Production/Stable",
183+ "Programming Language :: Python",
184+ ]
185+ """
186+
159187
160188def _pep621_example_project (
161189 tmp_path ,
@@ -251,10 +279,70 @@ def test_utf8_maintainer_in_metadata( # issue-3663
251279 assert f"Maintainer-email: { expected_maintainers_meta_value } " in content
252280
253281
254- class TestLicenseFiles :
255- # TODO: After PEP 639 is accepted, we have to move the license-files
256- # to the `project` table instead of `tool.setuptools`
282+ @pytest .mark .parametrize (
283+ ('pyproject_text' , 'license' , 'license_expression' , 'content_str' ),
284+ (
285+ pytest .param (
286+ PEP639_LICENSE_TEXT ,
287+ 'MIT' ,
288+ None ,
289+ 'License: MIT' ,
290+ id = 'license-text' ,
291+ ),
292+ pytest .param (
293+ PEP639_LICENSE_EXPRESSION ,
294+ None ,
295+ 'MIT OR Apache-2.0' ,
296+ 'License: MIT OR Apache-2.0' , # TODO Metadata version '2.4'
297+ id = 'license-expression' ,
298+ ),
299+ ),
300+ )
301+ def test_license_in_metadata (
302+ license ,
303+ license_expression ,
304+ content_str ,
305+ pyproject_text ,
306+ tmp_path ,
307+ ):
308+ pyproject = _pep621_example_project (
309+ tmp_path ,
310+ "README" ,
311+ pyproject_text = pyproject_text ,
312+ )
313+ dist = pyprojecttoml .apply_configuration (makedist (tmp_path ), pyproject )
314+ assert dist .metadata .license == license
315+ assert dist .metadata .license_expression == license_expression
316+ pkg_file = tmp_path / "PKG-FILE"
317+ with open (pkg_file , "w" , encoding = "utf-8" ) as fh :
318+ dist .metadata .write_pkg_file (fh )
319+ content = pkg_file .read_text (encoding = "utf-8" )
320+ assert content_str in content
321+
322+
323+ def test_license_expression_with_bad_classifier (tmp_path ):
324+ text = PEP639_LICENSE_EXPRESSION .rsplit ("\n " , 2 )[0 ]
325+ pyproject = _pep621_example_project (
326+ tmp_path ,
327+ "README" ,
328+ f"{ text } \n \" License :: OSI Approved :: MIT License\" \n ]" ,
329+ )
330+ msg = "License classifier are deprecated(?:.|\n )*'License :: OSI Approved :: MIT License'"
331+ with pytest .raises (SetuptoolsDeprecationWarning , match = msg ):
332+ pyprojecttoml .apply_configuration (makedist (tmp_path ), pyproject )
257333
334+ with warnings .catch_warnings ():
335+ warnings .simplefilter ("ignore" , SetuptoolsDeprecationWarning )
336+ dist = pyprojecttoml .apply_configuration (makedist (tmp_path ), pyproject )
337+ # Check license classifier is still included
338+ assert dist .metadata .get_classifiers () == [
339+ "Development Status :: 5 - Production/Stable" ,
340+ "Programming Language :: Python" ,
341+ "License :: OSI Approved :: MIT License" ,
342+ ]
343+
344+
345+ class TestLicenseFiles :
258346 def base_pyproject (self , tmp_path , additional_text ):
259347 pyproject = _pep621_example_project (tmp_path , "README" )
260348 text = pyproject .read_text (encoding = "utf-8" )
@@ -267,6 +355,24 @@ def base_pyproject(self, tmp_path, additional_text):
267355 pyproject .write_text (text , encoding = "utf-8" )
268356 return pyproject
269357
358+ def base_pyproject_license_pep639 (self , tmp_path ):
359+ pyproject = _pep621_example_project (tmp_path , "README" )
360+ text = pyproject .read_text (encoding = "utf-8" )
361+
362+ # Sanity-check
363+ assert 'license = {file = "LICENSE.txt"}' in text
364+ assert 'license-files' not in text
365+ assert "[tool.setuptools]" not in text
366+
367+ text = re .sub (
368+ r"(license = {file = \"LICENSE.txt\"})\n" ,
369+ ("license = \" licenseref-Proprietary\" \n license-files = [\" _FILE*\" ]\n " ),
370+ text ,
371+ count = 1 ,
372+ )
373+ pyproject .write_text (text , encoding = "utf-8" )
374+ return pyproject
375+
270376 def test_both_license_and_license_files_defined (self , tmp_path ):
271377 setuptools_config = '[tool.setuptools]\n license-files = ["_FILE*"]'
272378 pyproject = self .base_pyproject (tmp_path , setuptools_config )
@@ -283,6 +389,18 @@ def test_both_license_and_license_files_defined(self, tmp_path):
283389 assert set (dist .metadata .license_files ) == {"_FILE.rst" , "_FILE.txt" }
284390 assert dist .metadata .license == "LicenseRef-Proprietary\n "
285391
392+ def test_both_license_and_license_files_defined_pep639 (self , tmp_path ):
393+ # Set license and license-files
394+ pyproject = self .base_pyproject_license_pep639 (tmp_path )
395+
396+ (tmp_path / "_FILE.txt" ).touch ()
397+ (tmp_path / "_FILE.rst" ).touch ()
398+
399+ dist = pyprojecttoml .apply_configuration (makedist (tmp_path ), pyproject )
400+ assert set (dist .metadata .license_files ) == {"_FILE.rst" , "_FILE.txt" }
401+ assert dist .metadata .license is None
402+ assert dist .metadata .license_expression == "LicenseRef-Proprietary"
403+
286404 def test_default_patterns (self , tmp_path ):
287405 setuptools_config = '[tool.setuptools]\n zip-safe = false'
288406 # ^ used just to trigger section validation
0 commit comments