Trouble with a packaging tutorial: Licence build issue
OS version
Ubuntu 20.04 LTS
Python version
3.12.8
Pip version
25.0
Guide link
https://packaging.python.org/en/latest/tutorials/packaging-projects/#generating-distribution-archives
Problem description
Trying to follow the guide as best as possible, but I run into an error when running python3 -m build for some backends but not others.
The error for setuptools is included below. Flit has a different error, also apparently related to the license and license-files fields.
Hatchling and PDM both build successfully.
If I comment out the license field only, I get the error that configuration error: `project` must not contain {'license-files'} properties and if I comment out both, then the contents of LICENSE appears to be included in the output. (adding 'example_package_[MY_UNAME]-0.0.1.dist-info/LICENSE' is not present if the LICENSE file is deleted).
My question is, why is the behaviour of each build system so different WRT including the license, and how do I know the correct way to specify the license for each individual build system? If I want to use setuptools specifically (it seems to be considered standard practice in my org) what is the "right" way to include a license?
Can the tutorial also be updated to reflect the fact that the body of the [project] section of pyproject.toml changes based on the build system used?
Error message
$python3 -m build
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- setuptools>=61.0
* Getting build dependencies for sdist...
configuration error: `project.license` must be valid exactly by one definition (2 matches found):
- keys:
'file': {type: string}
required: ['file']
- keys:
'text': {type: string}
required: ['text']
DESCRIPTION:
`Project license <https://peps.python.org/pep-0621/#license>`_.
GIVEN VALUE:
"MIT"
OFFENDING RULE: 'oneOf'
DEFINITION:
{
"oneOf": [
{
"properties": {
"file": {
"type": "string",
"$$description": [
"Relative path to the file (UTF-8) which contains the license for the",
"project."
]
}
},
"required": [
"file"
]
},
{
"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"
]
}
]
}
Traceback (most recent call last):
File "[REDACTED_FILE_LOCATION]/.conda/envs/PackagingTutoral/lib/python3.12/site-packages/pyproject_hooks/_in_process/_in_process.py", line 389, in <module>
main()
File "[REDACTED_FILE_LOCATION]/.conda/envs/PackagingTutoral/lib/python3.12/site-packages/pyproject_hooks/_in_process/_in_process.py", line 373, in main
json_out["return_val"] = hook(**hook_input["kwargs"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "[REDACTED_FILE_LOCATION]/.conda/envs/PackagingTutoral/lib/python3.12/site-packages/pyproject_hooks/_in_process/_in_process.py", line 317, in get_requires_for_build_sdist
return hook(config_settings)
^^^^^^^^^^^^^^^^^^^^^
File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/build_meta.py", line 337, in get_requires_for_build_sdist
return self._get_build_requires(config_settings, requirements=[])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/build_meta.py", line 304, in _get_build_requires
self.run_setup()
File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/build_meta.py", line 320, in run_setup
exec(code, locals())
File "<string>", line 1, in <module>
File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/__init__.py", line 117, in setup
return distutils.core.setup(**attrs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/_distutils/core.py", line 160, in setup
dist.parse_config_files()
File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/dist.py", line 652, in parse_config_files
pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)
File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/config/pyprojecttoml.py", line 72, in apply_configuration
config = read_configuration(filepath, True, ignore_option_errors, dist)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/build-env-_58y_gfp/lib/python3.12/site-packages/setuptools/config/pyprojecttoml.py", line 140, in read_configuration
validate(subset, filepath)
File "/tmp/build-env-_58y_gfp/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 (2 matches found):
- keys:
'file': {type: string}
required: ['file']
- keys:
'text': {type: string}
required: ['text']
ERROR Backend subprocess exited when trying to invoke get_requires_for_build_sdist
The new license-files field was recently standardized and isn't handled in a standard way across backends yet. The license field is optional, and I'd recommend not setting it for now. It's only not optional if you set license-files. The current standard is to specify the license in your Trove Classifiers, and all backends should find LICENSE.* and copy those into the SDist/wheels. In the future, once all backends implement PEP 639 (license-files and string-valued license field in METADATA 2.4) properly, then you can use those two fields instead. Things like Pip, PyPI, and twine are only just now started to support METADATA 2.4. Setuptools is still stuck on something like METADATA 2.2 IIRC.
It looks like someone was a bit forward-thinking and updated the tutorial for PEP 639. Flit and setuptools have not added support yet.
Hello, Please review the updated page: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license The diff of changes can be seen here: https://github.com/pypa/packaging.python.org/pull/1815/files
Hello, Please review the updated page: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license The diff of changes can be seen here: https://github.com/pypa/packaging.python.org/pull/1815/files
That page hasn't updated for me. I'd have suspected a caching issue but the PR was merged two days ago.
Huh, it's true. @webknjaz - could you take a look? The cron jobs of packaging.python.org appear to have run successfully from commits containing the change.
The current standard is to specify the license in your Trove Classifiers
@henryiii just a nit: it's not a standard but rather a convention.
@befeleme apparently, the latest build got stuck: https://app.readthedocs.org/projects/python-packaging-user-guide/builds/27216162/.
FTR, GHA cron runs don't publish the docs. GHA only runs as a convenient check for whether it builds. RTD has their own build system that runs on pull_request and push events (on merge essentially).
Clicked Rebuild. It should be fine now: https://app.readthedocs.org/projects/python-packaging-user-guide/builds/27241778/.
That page hasn't updated for me. I'd have suspected a caching issue but the PR was merged two days ago.
Verified that the updated page has new text.
Please note that Setuptools 77 adds support for the new standard.
Flit-core supports it now too, has for a little bit.
The current standard is to specify the license in your Trove Classifiers
setuptools actively fails the build and asks for the license to be removed from the classifiers: Please remove: License :: OSI Approved :: Apache Software License
File "C:\Users\Ark\AppData\Local\Temp\build-env-8fi_34mn\Lib\site-packages\setuptools\config\_apply_pyprojecttoml.py", line 61, in apply
dist._finalize_license_expression()
File "C:\Users\Ark\AppData\Local\Temp\build-env-8fi_34mn\Lib\site-packages\setuptools\dist.py", line 430, in _finalize_license_expression
raise InvalidConfigError(
setuptools.errors.InvalidConfigError: License classifiers have been superseded by license expressions (see https://peps.python.org/pep-0639/). Please remove:
License :: OSI Approved :: Apache Software License
ERROR Backend subprocess exited when trying to invoke get_requires_for_build_sdist
I believe this is only an error if you specify a new-style License expression (license = "..."), and doesn't trigger if you specify a classic license (license.text). If you specify an SPDX license using the new syntax you aren't supposed to put license classifiers too.
I hear the recommendation to remove the license entries until the dust settles. Unfortunately, the dust will not settle until 2029 (Ubuntu 24.04 + 5 years). I propose maintaining some backward compatibility with deprecation here rather than outright rejection of what has worked in the past.
I hear the recommendation to remove the license entries until the dust settles.
Actually for the common case I think most backends are in a good state now, and the new license field can be used in most situations.
Unfortunately, the dust will not settle until 2029 (Ubuntu 24.04 + 5 years).
The is a relatively new standard and therefore has to be implemented as a new feature in backends, if you are using a LTS distribution then you can not expect to receive and use new features or build tools that require those new features. You can either create your own virtual environment and use newer packages or use the packages Ubuntu provides for LTS. Trying to have it both ways, that is using LTS packages with new packages, won't always work, this is the trade off of LTS.
I propose maintaining some backward compatibility with deprecation here rather than outright rejection of what has worked in the past.
I don't think there's any plan to make the old license field incompatible.
First, millions of older distributions will continue to have it and need to be installable. Second, pip has not yet accepted the new license field, so it's highly unlikely the old license field will be made incompatible any time in the future.
That pip PR is to change the format of metadata for pip itself. It does not mean pip does not support the new PEP. To the best of my understanding, pip does not validate or use these metadata fields (apart from name and version), it only displays them in pip show.
Correct, as a frontend installer most metadata fields are not relevant for pip. The point I was making is if pip, a central tool to the Python packaging ecosystem, has erred on implementing the new field, it's unlikely that other tooling is going to ban the old convention, e.g. PyPI.
I would disagree with that point!
edit: meaning pip as a python project has maintainers with their preferences, compatibility policy, availability… that means they may or may not update their metadata to latest specs, but that is not a judgment call on their approval or support of the specs for the projects that people download with pip.
I would disagree with that point!
You think PyPI will implement something which means pip can't be uploaded to it over the enforcement of a PEP that there are known edge case issues with?