Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-flit-core for 
openSUSE:Factory checked in at 2025-05-07 19:14:34
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-flit-core (Old)
 and      /work/SRC/openSUSE:Factory/.python-flit-core.new.30101 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-flit-core"

Wed May  7 19:14:34 2025 rev:21 rq:1274041 version:3.12.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-flit-core/python-flit-core.changes        
2025-03-17 22:20:31.493827000 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-flit-core.new.30101/python-flit-core.changes 
    2025-05-07 19:14:36.110539634 +0200
@@ -1,0 +2,10 @@
+Fri May  2 14:17:24 UTC 2025 - Nico Krapp <nico.kr...@suse.com>
+
+- Update to 3.12.0
+  * Support for license expressions using the AND and OR operators.
+  * Recognise __version__: str = "0.1" annotated assignments when
+    finding the version number.
+  * Clear error message when referring to a license file in a parent
+    directory, which is not supported.
+
+-------------------------------------------------------------------

Old:
----
  flit_core-3.11.0.tar.gz

New:
----
  flit_core-3.12.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-flit-core.spec ++++++
--- /var/tmp/diff_new_pack.yvORZP/_old  2025-05-07 19:14:36.694563996 +0200
+++ /var/tmp/diff_new_pack.yvORZP/_new  2025-05-07 19:14:36.698564163 +0200
@@ -52,7 +52,7 @@
 %endif
 %{?sle15_python_module_pythons}
 Name:           %{pprefix}-flit-core%{?psuffix}
-Version:        3.11.0
+Version:        3.12.0
 Release:        0
 Summary:        Distribution-building parts of Flit
 License:        BSD-3-Clause AND MIT

++++++ flit_core-3.11.0.tar.gz -> flit_core-3.12.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.11.0/PKG-INFO 
new/flit_core-3.12.0/PKG-INFO
--- old/flit_core-3.11.0/PKG-INFO       1970-01-01 01:00:00.000000000 +0100
+++ new/flit_core-3.12.0/PKG-INFO       1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: flit_core
-Version: 3.11.0
+Version: 3.12.0
 Summary: Distribution-building parts of Flit. See flit package for more 
information
 Author-email: Thomas Kluyver & contributors <tho...@kluyver.me.uk>
 Requires-Python: >=3.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.11.0/flit_core/__init__.py 
new/flit_core-3.12.0/flit_core/__init__.py
--- old/flit_core-3.11.0/flit_core/__init__.py  2025-02-19 09:21:57.541935200 
+0100
+++ new/flit_core-3.12.0/flit_core/__init__.py  2025-03-25 09:03:05.503557200 
+0100
@@ -4,4 +4,4 @@
 All the convenient development features live in the main 'flit' package.
 """
 
-__version__ = '3.11.0'
+__version__ = '3.12.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.11.0/flit_core/common.py 
new/flit_core-3.12.0/flit_core/common.py
--- old/flit_core-3.11.0/flit_core/common.py    2025-02-19 09:21:57.542935100 
+0100
+++ new/flit_core-3.12.0/flit_core/common.py    2025-03-25 09:03:05.503557200 
+0100
@@ -148,22 +148,7 @@
         with target_path.open('rb') as f:
             node = ast.parse(f.read())
         for child in node.body:
-            if sys.version_info >= (3, 8):
-                target_type = ast.Constant
-            else:
-                target_type = ast.Str
-            # Only use the version from the given module if it's a simple
-            # string assignment to __version__
-            is_version_str = (
-                    isinstance(child, ast.Assign)
-                    and any(
-                        isinstance(target, ast.Name)
-                        and target.id == "__version__"
-                        for target in child.targets
-                    )
-                    and isinstance(child.value, target_type)
-            )
-            if is_version_str:
+            if is_version_str_assignment(child):
                 if sys.version_info >= (3, 8):
                     version = child.value.value
                 else:
@@ -172,6 +157,20 @@
     return ast.get_docstring(node), version
 
 
+def is_version_str_assignment(node):
+    """Check if *node* is a simple string assignment to __version__"""
+    if not isinstance(node, (ast.Assign, ast.AnnAssign)):
+        return False
+    constant_type = ast.Constant if sys.version_info >= (3, 8) else ast.Str
+    if not isinstance(node.value, constant_type):
+        return False
+    targets = (node.target,) if isinstance(node, ast.AnnAssign) else 
node.targets
+    for target in targets:
+        if isinstance(target, ast.Name) and target.id == "__version__":
+            return True
+    return False
+
+
 # To ensure we're actually loading the specified file, give it a unique name to
 # avoid any cached import. In normal use we'll only load one module per 
process,
 # so it should only matter for the tests, but we'll do it anyway.
@@ -379,7 +378,7 @@
             return [item.strip() for item in list_str.split(',')]
         else:
             return None
-    
+
     def _normalise_requires_dist(self, req):
         extras = self._extract_extras(req)
         if extras:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.11.0/flit_core/config.py 
new/flit_core-3.12.0/flit_core/config.py
--- old/flit_core-3.11.0/flit_core/config.py    2025-02-19 09:21:57.542935100 
+0100
+++ new/flit_core-3.12.0/flit_core/config.py    2025-03-25 09:03:05.504557400 
+0100
@@ -4,7 +4,6 @@
 import logging
 import os
 import os.path as osp
-from os.path import isabs
 from pathlib import Path
 import re
 
@@ -18,6 +17,7 @@
     except ImportError:
         import tomli as tomllib
 
+from ._spdx_data import licenses
 from .common import normalise_core_metadata_name
 from .versionno import normalise_version
 
@@ -92,7 +92,7 @@
 
 def prep_toml_config(d, path):
     """Validate config loaded from pyproject.toml and prepare common metadata
-    
+
     Returns a LoadedConfig object.
     """
     dtool = d.get('tool', {}).get('flit', {})
@@ -310,11 +310,11 @@
 
 def _prep_metadata(md_sect, path):
     """Process & verify the metadata from a config file
-    
+
     - Pull out the module name we're packaging.
     - Read description-file and check that it's valid rst
     - Convert dashes in key names to underscores
-      (e.g. home-page in config -> home_page in metadata) 
+      (e.g. home-page in config -> home_page in metadata)
     """
     if not set(md_sect).issuperset(metadata_required_fields):
         missing = metadata_required_fields - set(md_sect)
@@ -473,7 +473,7 @@
             )
         try:
             files = [
-                str(file.relative_to(project_dir)).replace(osp.sep, "/")
+                file.relative_to(project_dir).as_posix()
                 for file in project_dir.glob(pattern)
                 if file.is_file()
             ]
@@ -587,7 +587,8 @@
     if 'license' in proj:
         _check_types(proj, 'license', (str, dict))
         if isinstance(proj['license'], str):
-            md_dict['license_expression'] = 
normalize_license_expr(proj['license'])
+            licence_expr = proj['license']
+            md_dict['license_expression'] = 
normalise_compound_license_expr(licence_expr)
         else:
             license_tbl = proj['license']
             unrec_keys = set(license_tbl.keys()) - {'text', 'file'}
@@ -603,14 +604,20 @@
                     raise ConfigError(
                         "[project.license] should specify file or text, not 
both"
                     )
-                license_f = license_tbl['file']
+                license_f = osp.normpath(license_tbl['file'])
                 if isabs_ish(license_f):
                     raise ConfigError(
-                        f"License file path ({license_f}) cannot be an 
absolute path"
+                        f"License file path ({license_tbl['file']}) cannot be 
an absolute path"
                     )
-                if not (path.parent / license_f).is_file():
-                    raise ConfigError(f"License file {license_f} does not 
exist")
-                license_files.add(license_tbl['file'])
+                if license_f.startswith('..' + os.sep):
+                    raise ConfigError(
+                        f"License file path ({license_tbl['file']}) cannot 
contain '..'"
+                    )
+                license_p = path.parent / license_f
+                if not license_p.is_file():
+                    raise ConfigError(f"License file {license_tbl['file']} 
does not exist")
+                license_f = license_p.relative_to(path.parent).as_posix()
+                license_files.add(license_f)
             elif 'text' in license_tbl:
                 pass
             else:
@@ -816,34 +823,97 @@
     return os.path.isabs(path) or path.startswith(('/', '\\'))
 
 
-def normalize_license_expr(s: str):
-    """Validate & normalise an SPDX license expression
+def normalise_compound_license_expr(s: str) -> str:
+    """Validate and normalise a compund SPDX license expression.
+
+    Per the specification, licence expression operators (AND, OR and WITH)
+    are matched case-sensitively. The WITH operator is not currently supported.
 
-    For now this only handles simple expressions (referring to 1 license)
+    Spec: https://spdx.github.io/spdx-spec/v2.2.2/SPDX-license-expressions/
+    """
+    invalid_msg = "'{s}' is not a valid SPDX license expression: {reason}"
+    if not s or s.isspace():
+        raise ConfigError(f"The SPDX license expression must not be empty")
+
+    stack = 0
+    parts = []
+    try:
+        for part in filter(None, re.split(r' +|([()])', s)):
+            if part.upper() == 'WITH':
+                # provide a sensible error message for the WITH operator
+                raise ConfigError(f"The SPDX 'WITH' operator is not yet 
supported!")
+            elif part in {'AND', 'OR'}:
+                if not parts or parts[-1] in {' AND ', ' OR ', ' WITH ', '('}:
+                    reason = f"a license ID is missing before '{part}'"
+                    raise ConfigError(invalid_msg.format(s=s, reason=reason))
+                parts.append(f' {part} ')
+            elif part.lower() in {'and', 'or', 'with'}:
+                # provide a sensible error message for non-uppercase operators
+                reason = f"operators must be uppercase, not '{part}'"
+                raise ConfigError(invalid_msg.format(s=s, reason=reason))
+            elif part == '(':
+                if parts and parts[-1] not in {' AND ', ' OR ', '('}:
+                    reason = f"'(' must follow either AND, OR, or '('"
+                    raise ConfigError(invalid_msg.format(s=s, reason=reason))
+                stack += 1
+                parts.append(part)
+            elif part == ')':
+                if not parts or parts[-1] in {' AND ', ' OR ', ' WITH ', '('}:
+                    reason = f"a license ID is missing before '{part}'"
+                    raise ConfigError(invalid_msg.format(s=s, reason=reason))
+                stack -= 1
+                if stack < 0:
+                    reason = 'unbalanced brackets'
+                    raise ConfigError(invalid_msg.format(s=s, reason=reason))
+                parts.append(part)
+            else:
+                if parts and parts[-1] not in {' AND ', ' OR ', '('}:
+                    reason = f"a license ID must follow either AND, OR, or '('"
+                    raise ConfigError(invalid_msg.format(s=s, reason=reason))
+                simple_expr = normalise_simple_license_expr(part)
+                parts.append(simple_expr)
+
+        if stack != 0:
+            reason = 'unbalanced brackets'
+            raise ConfigError(invalid_msg.format(s=s, reason=reason))
+        if parts[-1] in {' AND ', ' OR ', ' WITH '}:
+            last_part = parts[-1].strip()
+            reason = f"a license ID or expression should follow '{last_part}'"
+            raise ConfigError(invalid_msg.format(s=s, reason=reason))
+    except ConfigError:
+        if os.environ.get('FLIT_ALLOW_INVALID'):
+            log.warning(f"Invalid license ID {s!r} allowed by 
FLIT_ALLOW_INVALID")
+            return s
+        raise
+
+    return ''.join(parts)
+
+
+def normalise_simple_license_expr(s: str) -> str:
+    """Normalise a simple SPDX license expression.
+
+    
https://spdx.github.io/spdx-spec/v2.2.2/SPDX-license-expressions/#d3-simple-license-expressions
     """
-    from ._spdx_data import licenses
     ls = s.lower()
     if ls.startswith('licenseref-'):
-        ref = s.partition('-')[2]
-        if re.match(r'([a-zA-Z0-9\-.])+$', ref):
+        ref = s[11:]
+        if re.fullmatch(r'[a-zA-Z0-9\-.]+', ref):
             # Normalise case of LicenseRef, leave the rest alone
-            return "LicenseRef-" + ref
+            return f"LicenseRef-{ref}"
         raise ConfigError(
             "LicenseRef- license expression can only contain ASCII letters "
             "& digits, - and ."
         )
 
-    or_later = s.endswith('+')
+    or_later = ls.endswith('+')
     if or_later:
         ls = ls[:-1]
 
     try:
-        info = licenses[ls]
+        normalised_id = licenses[ls]['id']
     except KeyError:
-        if os.environ.get('FLIT_ALLOW_INVALID'):
-            log.warning("Invalid license ID {!r} allowed by FLIT_ALLOW_INVALID"
-                        .format(s))
-            return s
         raise ConfigError(f"{s!r} is not a recognised SPDX license ID")
 
-    return info['id'] + ('+' if or_later else '')
+    if or_later:
+        return f'{normalised_id}+'
+    return normalised_id
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.11.0/flit_core/vendor/tomli-1.2.3.dist-info/METADATA 
new/flit_core-3.12.0/flit_core/vendor/tomli-1.2.3.dist-info/METADATA
--- old/flit_core-3.11.0/flit_core/vendor/tomli-1.2.3.dist-info/METADATA        
2025-02-19 09:21:57.542935100 +0100
+++ new/flit_core-3.12.0/flit_core/vendor/tomli-1.2.3.dist-info/METADATA        
2025-03-25 09:03:05.504557400 +0100
@@ -205,4 +205,3 @@
 The parsers are ordered from fastest to slowest, using the fastest parser as 
baseline.
 Tomli performed the best out of all pure Python TOML parsers,
 losing only to pytomlpp (wraps C++) and rtoml (wraps Rust).
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.11.0/flit_core/versionno.py 
new/flit_core-3.12.0/flit_core/versionno.py
--- old/flit_core-3.11.0/flit_core/versionno.py 2025-02-19 09:21:57.542935100 
+0100
+++ new/flit_core-3.12.0/flit_core/versionno.py 2025-03-25 09:03:05.504557400 
+0100
@@ -124,4 +124,3 @@
         log.warning("Version number normalised: {!r} -> {!r} (see PEP 440)"
                     .format(orig_version, version))
     return version
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.11.0/tests_core/samples/annotated_version/module1.py 
new/flit_core-3.12.0/tests_core/samples/annotated_version/module1.py
--- old/flit_core-3.11.0/tests_core/samples/annotated_version/module1.py        
1970-01-01 01:00:00.000000000 +0100
+++ new/flit_core-3.12.0/tests_core/samples/annotated_version/module1.py        
2025-03-25 09:03:05.505557300 +0100
@@ -0,0 +1,4 @@
+
+"""This module has a __version__ that has a type annotation"""
+
+__version__: str = '0.1'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.11.0/tests_core/samples/annotated_version/pyproject.toml 
new/flit_core-3.12.0/tests_core/samples/annotated_version/pyproject.toml
--- old/flit_core-3.11.0/tests_core/samples/annotated_version/pyproject.toml    
1970-01-01 01:00:00.000000000 +0100
+++ new/flit_core-3.12.0/tests_core/samples/annotated_version/pyproject.toml    
2025-03-25 09:03:05.505557300 +0100
@@ -0,0 +1,12 @@
+[build-system]
+requires = ["flit_core >=2,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.flit.metadata]
+module = "module1"
+author = "Sir Robin"
+author-email = "ro...@camelot.uk"
+home-page = "http://github.com/sirrobin/module1";
+requires = [
+    "numpy >=1.16.0",
+]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.11.0/tests_core/samples/imported_version/package1/__init__.py 
new/flit_core-3.12.0/tests_core/samples/imported_version/package1/__init__.py
--- 
old/flit_core-3.11.0/tests_core/samples/imported_version/package1/__init__.py   
    2025-02-19 09:21:57.543935000 +0100
+++ 
new/flit_core-3.12.0/tests_core/samples/imported_version/package1/__init__.py   
    2025-03-25 09:03:05.505557300 +0100
@@ -2,4 +2,4 @@
 
 from ._version import __version__
 
-import a_package_that_doesnt_exist
\ No newline at end of file
+import a_package_that_doesnt_exist
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.11.0/tests_core/samples/invalid_version1.py 
new/flit_core-3.12.0/tests_core/samples/invalid_version1.py
--- old/flit_core-3.11.0/tests_core/samples/invalid_version1.py 2025-02-19 
09:21:57.543935000 +0100
+++ new/flit_core-3.12.0/tests_core/samples/invalid_version1.py 2025-03-25 
09:03:05.505557300 +0100
@@ -1,3 +1,3 @@
 """Sample module with invalid __version__ string"""
 
-__version__ = "not starting with a number"
\ No newline at end of file
+__version__ = "not starting with a number"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.11.0/tests_core/samples/ns1-pkg/ns1/pkg/__init__.py 
new/flit_core-3.12.0/tests_core/samples/ns1-pkg/ns1/pkg/__init__.py
--- old/flit_core-3.11.0/tests_core/samples/ns1-pkg/ns1/pkg/__init__.py 
2025-02-19 09:21:57.544935200 +0100
+++ new/flit_core-3.12.0/tests_core/samples/ns1-pkg/ns1/pkg/__init__.py 
2025-03-25 09:03:05.506557200 +0100
@@ -5,4 +5,3 @@
 """
 
 __version__ = '0.1'
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.11.0/tests_core/test_common.py 
new/flit_core-3.12.0/tests_core/test_common.py
--- old/flit_core-3.11.0/tests_core/test_common.py      2025-02-19 
09:21:57.545935200 +0100
+++ new/flit_core-3.12.0/tests_core/test_common.py      2025-03-25 
09:03:05.508557300 +0100
@@ -75,6 +75,11 @@
                                 'version': '0.1'}
                          )
 
+        info = get_info_from_module(Module('module1', samples_dir / 
'annotated_version'))
+        self.assertEqual(info, {'summary': 'This module has a __version__ that 
has a type annotation',
+                                'version': '0.1'}
+                         )
+
         info = get_info_from_module(Module('module1', samples_dir / 
'constructed_version'))
         self.assertEqual(info, {'summary': 'This module has a __version__ that 
requires runtime interpretation',
                                 'version': '1.2.3'}
@@ -210,8 +215,12 @@
     ('value', 'expected_license', 'expected_license_expression'),
     [
         ({'license': 'MIT'}, 'MIT', None),
+        ({'license': 'MIT OR Apache-2.0'}, 'MIT OR Apache-2.0', None),
+        ({'license': 'MIT AND Apache-2.0'}, 'MIT AND Apache-2.0', None),
         ({'license_expression': 'MIT'}, None, 'MIT'),
         ({'license_expression': 'Apache-2.0'}, None, 'Apache-2.0'),
+        ({'license_expression': 'MIT OR Apache-2.0'}, None, 'MIT OR 
Apache-2.0'),
+        ({'license_expression': 'MIT AND Apache-2.0'}, None, 'MIT AND 
Apache-2.0'),
     ],
 )
 def test_metadata_license(value, expected_license, 
expected_license_expression):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.11.0/tests_core/test_config.py 
new/flit_core-3.12.0/tests_core/test_config.py
--- old/flit_core-3.11.0/tests_core/test_config.py      2025-02-19 
09:21:57.545935200 +0100
+++ new/flit_core-3.12.0/tests_core/test_config.py      2025-03-25 
09:03:05.508557300 +0100
@@ -1,4 +1,5 @@
 import logging
+import re
 import sys
 from pathlib import Path
 import pytest
@@ -139,6 +140,14 @@
     ({'version': 1}, r'\bstr\b'),
     ({'license': {'fromage': 2}}, '[Uu]nrecognised'),
     ({'license': {'file': 'LICENSE', 'text': 'xyz'}}, 'both'),
+    (
+        {'license': {'file': '/LICENSE'}},
+        re.escape("License file path (/LICENSE) cannot be an absolute path"),
+    ),
+    (
+        {'license': {'file': '../LICENSE'}},
+        re.escape("License file path (../LICENSE) cannot contain '..'"),
+    ),
     ({'license': {}}, 'required'),
     ({'license': 1}, "license field should be <class 'str'> or <class 'dict'>, 
not <class 'int'>"),
     # ({'license': "MIT License"}, "Invalid license expression: 'MIT 
License'"),  # TODO
@@ -213,8 +222,17 @@
     ("mit",  "MIT"),
     ("apache-2.0", "Apache-2.0"),
     ("APACHE-2.0+", "Apache-2.0+"),
-    # TODO: compound expressions
-    #("mit and (apache-2.0 or bsd-2-clause)", "MIT AND (Apache-2.0 OR 
BSD-2-Clause)"),
+    ("mit AND (apache-2.0 OR bsd-2-clause)", "MIT AND (Apache-2.0 OR 
BSD-2-Clause)"),
+    ("(mit)", "(MIT)"),
+    ("MIT OR Apache-2.0", "MIT OR Apache-2.0"),
+    ("MIT AND Apache-2.0", "MIT AND Apache-2.0"),
+    ("MIT AND Apache-2.0+ OR 0BSD", "MIT AND Apache-2.0+ OR 0BSD"),
+    ("MIT AND (Apache-2.0+ OR (0BSD))", "MIT AND (Apache-2.0+ OR (0BSD))"),
+    ("MIT OR(mit)", "MIT OR (MIT)"),
+    ("(mit)AND mit", "(MIT) AND MIT"),
+    ("MIT OR (MIT OR ( MIT )) AND ((MIT) AND MIT) OR MIT", "MIT OR (MIT OR 
(MIT)) AND ((MIT) AND MIT) OR MIT"),
+    ("LICENSEREF-Public-Domain OR cc0-1.0 OR unlicense", 
"LicenseRef-Public-Domain OR CC0-1.0 OR Unlicense"),
+    ("mit  AND  ( apache-2.0+  OR  mpl-2.0+ )", "MIT AND (Apache-2.0+ OR 
MPL-2.0+)"),
     # LicenseRef expressions: only the LicenseRef is normalised
     ("LiceNseref-Public-DoMain", "LicenseRef-Public-DoMain"),
 ])
@@ -226,19 +244,143 @@
     assert 'license' not in info.metadata
     assert info.metadata['license_expression'] == license_expression
 
-def test_license_expr_error():
+@pytest.mark.parametrize('invalid_expr', [
+    "LicenseRef-foo_bar",
+    "LicenseRef-foo~bar",
+    "LicenseRef-foo:bar",
+    "LicenseRef-foo[bar]",
+    "LicenseRef-foo-bar+",
+])
+def test_license_expr_error_licenseref(invalid_expr: str):
     proj = {
         'name': 'module1', 'version': '1.0', 'description': 'x',
-        'license': 'LicenseRef-foo_bar',  # Underscore not allowed
+        'license': invalid_expr,
     }
     with pytest.raises(config.ConfigError, match="can only contain"):
         config.read_pep621_metadata(proj, samples_dir / 'pep621' / 
'pyproject.toml')
 
-    proj['license'] = "BSD-33-Clause"  # Not a real license
+
+@pytest.mark.parametrize('invalid_expr', [
+    # Not a real licence
+    "BSD-33-Clause",
+    "MIT OR BSD-33-Clause",
+    "MIT OR (MIT AND BSD-33-Clause)",
+])
+def test_license_expr_error_not_recognised(invalid_expr: str):
+    proj = {
+        'name': 'module1', 'version': '1.0', 'description': 'x',
+        'license': invalid_expr,
+    }
     with pytest.raises(config.ConfigError, match="recognised"):
         config.read_pep621_metadata(proj, samples_dir / 'pep621' / 
'pyproject.toml')
 
 
+@pytest.mark.parametrize('invalid_expr', [
+    # No operator
+    "MIT MIT",
+    "MIT OR (MIT MIT)",
+    # Only operator
+    "AND",
+    "OR",
+    "AND AND AND",
+    "OR OR OR",
+    "OR AND OR",
+    "AND OR OR AND OR OR AND",
+    # Too many operators
+    "MIT AND AND MIT",
+    "MIT OR OR OR MIT",
+    "MIT AND OR MIT",
+    # Mixed case operator
+    "MIT aND MIT",
+    "MIT oR MIT",
+    "MIT AND MIT oR MIT",
+    # Missing operand
+    "MIT AND",
+    "AND MIT",
+    "MIT OR",
+    "OR MIT",
+    "MIT (AND MIT)",
+    "(MIT OR) MIT",
+    # Unbalanced brackets
+    ")(",
+    "(",
+    ")",
+    "MIT OR ()",
+    ") AND MIT",
+    "MIT OR (",
+    "MIT OR (MIT))",
+    # Only brackets
+    "()",
+    "()()",
+    "()(())",
+    "(  )",
+    "  (  )",
+    "(  )  ",
+    "  (  )  ",
+])
+def test_license_expr_error(invalid_expr: str):
+    proj = {
+        'name': 'module1', 'version': '1.0', 'description': 'x',
+        'license': invalid_expr,
+    }
+    with pytest.raises(config.ConfigError, match="is not a valid"):
+        config.read_pep621_metadata(proj, samples_dir / 'pep621' / 
'pyproject.toml')
+
+
+@pytest.mark.parametrize('invalid_expr', [
+    "",
+    " ",
+    "\t",
+    "\r",
+    "\n",
+    "\f",
+    " \t \n \r \f ",
+])
+def test_license_expr_error_empty(invalid_expr: str):
+    proj = {
+        'name': 'module1', 'version': '1.0', 'description': 'x',
+        'license': invalid_expr,
+    }
+    with pytest.raises(config.ConfigError, match="must not be empty"):
+        config.read_pep621_metadata(proj, samples_dir / 'pep621' / 
'pyproject.toml')
+
+
+@pytest.mark.parametrize('invalid_expr', [
+    "mit or mit",
+    "or",
+    "and",
+    "MIT and MIT",
+    "MIT AND MIT or MIT",
+    "MIT AND (MIT or MIT)",
+])
+def test_license_expr_error_lowercase(invalid_expr: str):
+    proj = {
+        'name': 'module1', 'version': '1.0', 'description': 'x',
+        'license': invalid_expr,
+    }
+    with pytest.raises(config.ConfigError, match="must be uppercase"):
+        config.read_pep621_metadata(proj, samples_dir / 'pep621' / 
'pyproject.toml')
+
+
+@pytest.mark.parametrize('invalid_expr', [
+    "WITH",
+    "with",
+    "WiTh",
+    "wiTH",
+    "MIT WITH MIT-Exception",
+    "(MIT WITH MIT-Exception)",
+    "MIT OR MIT WITH MIT-Exception",
+    "MIT WITH MIT-Exception OR (MIT AND MIT)",
+])
+def test_license_expr_error_unsupported_with(invalid_expr: str):
+    proj = {
+        'name': 'module1', 'version': '1.0', 'description': 'x',
+        'license': invalid_expr,
+    }
+    with pytest.raises(config.ConfigError, match="not yet supported"):
+        config.read_pep621_metadata(proj, samples_dir / 'pep621' / 
'pyproject.toml')
+
+
 def test_license_file_defaults_with_old_metadata():
     metadata = {'module': 'mymod', 'author': ''}
     info = config._prep_metadata(metadata, samples_dir / 
'pep621_license_files' / 'pyproject.toml')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.11.0/tests_core/test_wheel.py 
new/flit_core-3.12.0/tests_core/test_wheel.py
--- old/flit_core-3.11.0/tests_core/test_wheel.py       2025-02-19 
09:21:57.546935000 +0100
+++ new/flit_core-3.12.0/tests_core/test_wheel.py       2025-03-25 
09:03:05.508557300 +0100
@@ -39,7 +39,7 @@
     with ZipFile(wheels[0], 'r') as zf:
         assert 'module1a.py' in zf.namelist()
 
-        
+
 def test_data_dir(tmp_path):
     info = make_wheel_in(samples_dir / 'with_data_dir' / 'pyproject.toml', 
tmp_path)
     assert_isfile(info.file)

Reply via email to