Skip to content

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

Closed

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.

Activity

  1. brettcannon commented on Sep 2, 2025

    @brettcannon
    Member

    I am fully aware that reporting this might be seen as pedantry.

    It's still a bug, for thanks for filing! But as you suggest, it isn't a high priority issue.

  2. brettcannon commented on Sep 2, 2025

    @brettcannon
    Member

    I'm not fully familiar with the relationship between eg setuptools and pypa/packaging

    I believe setuptools uses 'packaging'.

  3. changed the title [-]SPDX license specification is not fully supported[/-] [+]SPDX license specification doesn't work with nested parentheses[/+] on Sep 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions