1818 except ImportError :
1919 import tomli as tomllib
2020
21+ try :
22+ from .vendor .packaging import licenses
23+ # Some downstream distributors remove the vendored packaging.
24+ # When that is removed, import packaging from the regular location.
25+ except ImportError :
26+ from packaging import licenses
27+
2128from .common import normalise_core_metadata_name
2229from .versionno import normalise_version
2330
@@ -445,6 +452,14 @@ def _check_type(d, field_name, cls):
445452 "{} field should be {}, not {}" .format (field_name , cls , type (d [field_name ]))
446453 )
447454
455+ def _check_types (d , field_name , cls_list ) -> None :
456+ if not isinstance (d [field_name ], cls_list ):
457+ raise ConfigError (
458+ "{} field should be {}, not {}" .format (
459+ field_name , ' or ' .join (map (str , cls_list )), type (d [field_name ])
460+ )
461+ )
462+
448463def _check_list_of_str (d , field_name ):
449464 if not isinstance (d [field_name ], list ) or not all (
450465 isinstance (e , str ) for e in d [field_name ]
@@ -526,30 +541,42 @@ def read_pep621_metadata(proj, path) -> LoadedConfig:
526541 md_dict ['requires_python' ] = proj ['requires-python' ]
527542
528543 if 'license' in proj :
529- _check_type (proj , 'license' , dict )
530- license_tbl = proj ['license' ]
531- unrec_keys = set (license_tbl .keys ()) - {'text' , 'file' }
532- if unrec_keys :
533- raise ConfigError (
534- "Unrecognised keys in [project.license]: {}" .format (unrec_keys )
535- )
544+ _check_types (proj , 'license' , (str , dict ))
545+ if isinstance (proj ['license' ], str ):
546+ license_expr = proj ['license' ]
547+ try :
548+ license_expr = licenses .canonicalize_license_expression (license_expr )
549+ except licenses .InvalidLicenseExpression as ex :
550+ raise ConfigError (ex .args [0 ])
551+ md_dict ['license_expression' ] = license_expr
552+ else :
553+ license_tbl = proj ['license' ]
554+ unrec_keys = set (license_tbl .keys ()) - {'text' , 'file' }
555+ if unrec_keys :
556+ raise ConfigError (
557+ "Unrecognised keys in [project.license]: {}" .format (unrec_keys )
558+ )
536559
537- # TODO: Do something with license info.
538- # The 'License' field in packaging metadata is a brief description of
539- # a license, not the full text or a file path. PEP 639 will improve on
540- # how licenses are recorded.
541- if 'file' in license_tbl :
542- if 'text' in license_tbl :
560+ # The 'License' field in packaging metadata is a brief description of
561+ # a license, not the full text or a file path.
562+ if 'file' in license_tbl :
563+ if 'text' in license_tbl :
564+ raise ConfigError (
565+ "[project.license] should specify file or text, not both"
566+ )
567+ lc .referenced_files .append (license_tbl ['file' ])
568+ elif 'text' in license_tbl :
569+ license = license_tbl ['text' ]
570+ try :
571+ # Normalize license if it's a valid SPDX expression
572+ license = licenses .canonicalize_license_expression (license )
573+ except licenses .InvalidLicenseExpression :
574+ pass
575+ md_dict ['license' ] = license
576+ else :
543577 raise ConfigError (
544- "[project.license] should specify file or text, not both "
578+ "file or text field required in [project.license] table "
545579 )
546- lc .referenced_files .append (license_tbl ['file' ])
547- elif 'text' in license_tbl :
548- pass
549- else :
550- raise ConfigError (
551- "file or text field required in [project.license] table"
552- )
553580
554581 if 'authors' in proj :
555582 _check_type (proj , 'authors' , list )
@@ -565,6 +592,16 @@ def read_pep621_metadata(proj, path) -> LoadedConfig:
565592
566593 if 'classifiers' in proj :
567594 _check_list_of_str (proj , 'classifiers' )
595+ classifiers = proj ['classifiers' ]
596+ license_expr = md_dict .get ('license_expression' , None )
597+ if license_expr :
598+ for cl in classifiers :
599+ if not cl .startswith ('License :: ' ):
600+ continue
601+ raise ConfigError (
602+ "License classifier are deprecated in favor of the license expression. "
603+ "Remove the '{}' classifier" .format (cl )
604+ )
568605 md_dict ['classifiers' ] = proj ['classifiers' ]
569606
570607 if 'urls' in proj :
0 commit comments