Skip to content

SPDX license specification doesn't work with nested parentheses #928

@Liam-DeVoe

Description

@Liam-DeVoe

To start: I am fully aware that reporting this might be seen as pedantry. However, given that pypa/packaging implements the PEP-level item PEP 639, I feel it's important for it to be fully compliant with that PEP, including edge cases.


PEP 639 states the following about the license field of pyproject.toml:

This PEP adopts the SPDX license expression syntax as documented in the SPDX specification, either Version 2.2 or a later compatible version.

SPDX specification v2.2.2 states that a license expression is (in part):

compound-expression = 1*1(simple-expression /
simple-expression "WITH" license-exception-id /
compound-expression "AND" compound-expression /
compound-expression "OR" compound-expression /
"(" compound-expression ")" )

Which means that MIT is a valid compound expression (as it is a simple expression), as is (MIT) (following the "(" compound-expression ")" rule), as is ((MIT)) (following the "(" compound-expression ")" rule twice), and so on.

However, canonicalize_license_expression errors on more than one nested of parentheses:

>>> packaging.metadata.licenses.canonicalize_license_expression("((MIT))")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/homebrew/lib/python3.12/site-packages/packaging/licenses/__init__.py", line 94, in canonicalize_license_expression
    raise InvalidLicenseExpression(message)
packaging.licenses.InvalidLicenseExpression: Invalid license expression: '((MIT))'

I'm not fully familiar with the relationship between eg setuptools and pypa/packaging, but I am guessing this or a related issue is the cause of an error when running pip install -e . on the following pyproject.toml:

[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

[project]
name = "test-package-name"
version = "0.1.0"
license = "((MIT))"
stacktrace
 ~/Desktop/testing-can-delete λ pip install -e .
Obtaining file:///Users/tybug/Desktop/testing-can-delete
  Installing build dependencies ... done
  Checking if build backend supports build_editable ... done
  Getting requirements to build editable ... error
  error: subprocess-exited-with-error
  
  × Getting requirements to build editable did not run successfully.
  │ exit code: 1
  ╰─> [112 lines of output]
      configuration error: `project.license` must be valid exactly by one definition (0 matches found):
      
          - {type: string, format: 'SPDX'}
          - type: table
            keys:
              'file': {type: string}
            required: ['file']
          - type: table
            keys:
              'text': {type: string}
            required: ['text']
      
      DESCRIPTION:
          `Project license <https://peps.python.org/pep-0621/#license>`_.
      
      GIVEN VALUE:
          "((MIT))"
      
      OFFENDING RULE: 'oneOf'
      
      DEFINITION:
          {
              "oneOf": [
                  {
                      "type": "string",
                      "description": "An SPDX license identifier",
                      "format": "SPDX"
                  },
                  {
                      "type": "object",
                      "properties": {
                          "file": {
                              "type": "string",
                              "$$description": [
                                  "Relative path to the file (UTF-8) which contains the license for the",
                                  "project."
                              ]
                          }
                      },
                      "required": [
                          "file"
                      ]
                  },
                  {
                      "type": "object",
                      "properties": {
                          "text": {
                              "type": "string",
                              "$$description": [
                                  "The license of the project whose meaning is that of the",
                                  "`License field from the core metadata",
                                  "<https://packaging.python.org/specifications/core-metadata/#license>`_."
                              ]
                          }
                      },
                      "required": [
                          "text"
                      ]
                  }
              ]
          }
      
      For more details about `format` see
      https://validate-pyproject.readthedocs.io/en/latest/api/validate_pyproject.formats.html
      
      Traceback (most recent call last):
        File "/opt/homebrew/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 389, in <module>
          main()
        File "/opt/homebrew/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 373, in main
          json_out["return_val"] = hook(**hook_input["kwargs"])
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/opt/homebrew/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 157, in get_requires_for_build_editable
          return hook(config_settings)
                 ^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/t1/415srbnx62b2h3f1kx00n66w0000gn/T/pip-build-env-raxmk0vi/overlay/lib/python3.12/site-packages/setuptools/build_meta.py", line 473, in get_requires_for_build_editable
          return self.get_requires_for_build_wheel(config_settings)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/t1/415srbnx62b2h3f1kx00n66w0000gn/T/pip-build-env-raxmk0vi/overlay/lib/python3.12/site-packages/setuptools/build_meta.py", line 331, in get_requires_for_build_wheel
          return self._get_build_requires(config_settings, requirements=[])
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/t1/415srbnx62b2h3f1kx00n66w0000gn/T/pip-build-env-raxmk0vi/overlay/lib/python3.12/site-packages/setuptools/build_meta.py", line 301, in _get_build_requires
          self.run_setup()
        File "/private/var/folders/t1/415srbnx62b2h3f1kx00n66w0000gn/T/pip-build-env-raxmk0vi/overlay/lib/python3.12/site-packages/setuptools/build_meta.py", line 317, in run_setup
          exec(code, locals())
        File "<string>", line 1, in <module>
        File "/private/var/folders/t1/415srbnx62b2h3f1kx00n66w0000gn/T/pip-build-env-raxmk0vi/overlay/lib/python3.12/site-packages/setuptools/__init__.py", line 115, in setup
          return distutils.core.setup(**attrs)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/t1/415srbnx62b2h3f1kx00n66w0000gn/T/pip-build-env-raxmk0vi/overlay/lib/python3.12/site-packages/setuptools/_distutils/core.py", line 160, in setup
          dist.parse_config_files()
        File "/private/var/folders/t1/415srbnx62b2h3f1kx00n66w0000gn/T/pip-build-env-raxmk0vi/overlay/lib/python3.12/site-packages/setuptools/dist.py", line 756, in parse_config_files
          pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)
        File "/private/var/folders/t1/415srbnx62b2h3f1kx00n66w0000gn/T/pip-build-env-raxmk0vi/overlay/lib/python3.12/site-packages/setuptools/config/pyprojecttoml.py", line 72, in apply_configuration
          config = read_configuration(filepath, True, ignore_option_errors, dist)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/t1/415srbnx62b2h3f1kx00n66w0000gn/T/pip-build-env-raxmk0vi/overlay/lib/python3.12/site-packages/setuptools/config/pyprojecttoml.py", line 140, in read_configuration
          validate(subset, filepath)
        File "/private/var/folders/t1/415srbnx62b2h3f1kx00n66w0000gn/T/pip-build-env-raxmk0vi/overlay/lib/python3.12/site-packages/setuptools/config/pyprojecttoml.py", line 61, in validate
          raise ValueError(f"{error}\n{summary}") from None
      ValueError: invalid pyproject.toml config: `project.license`.
      configuration error: `project.license` must be valid exactly by one definition (0 matches found):
      
          - {type: string, format: 'SPDX'}
          - type: table
            keys:
              'file': {type: string}
            required: ['file']
          - type: table
            keys:
              'text': {type: string}
            required: ['text']
      
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: subprocess-exited-with-error

× Getting requirements to build editable did not run successfully.
│ exit code: 1
╰─> See above for output.

note: This error originates from a subprocess, and is likely not a problem with pip.
 ~/Desktop/testing-can-delete λ 

I would likely be willing to submit a PR for this.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions