pypa / setuptools

Official project repository for the Setuptools build system
https://pypi.org/project/setuptools/
MIT License
2.47k stars 1.18k forks source link

[BUG] Dynamic version attr with leading dot broken in 70.0.0 #4413

Closed MetRonnie closed 3 months ago

MetRonnie commented 3 months ago

setuptools version

70.0.0

Python version

3.12

OS

Windows, Ubuntu

Additional environment information

No response

Description

The dot in attr now causes an error

[tool.setuptools.dynamic]
version = {attr = ".__version__"}

Expected behavior

TIL the dot is not necessary, but this used to work in previous versions.

How to Reproduce

Write these in a directory:

# pyproject.toml
[project]
name = "test-project"
description = "This is a test project"
dynamic = ["version"]

[tool.setuptools]
platforms = ["any"]

[tool.setuptools.dynamic]
version = {attr = ".__version__"}

[tool.setuptools.packages.find]
include = ["*"]
# __init__.py

__version__ = "2.0.2"

Run in that directory:

python -c 'from setuptools import setup; setup()' --version

Output

> python -c 'from setuptools import setup; setup()' --version

configuration error: `tool.setuptools.dynamic.version` must be valid exactly by one definition (0 matches found):

    - type: table
      additional keys: False
      keys:
        'attr': {type: string, format: 'python-qualified-identifier'}
      required: ['attr']
    - type: table
      additional keys: False
      keys:
        'file':
          exactly one of the following:
            - {type: string}
            - type: array
              items: {type: string}
      required: ['file']

DESCRIPTION:
    A version dynamically loaded via either the ``attr:`` or ``file:``
    directives. Please make sure the given file or attribute respects
    :pep:`440`. Also ensure to set ``project.dynamic`` accordingly.

GIVEN VALUE:
    {
        "attr": ".FULL_VERSION"
    }

OFFENDING RULE: 'oneOf'

DEFINITION:
    {
        "oneOf": [
            {
                "title": "'attr:' directive",
                "$id": "#/definitions/attr-directive",
                "$$description": [
                    "Value is read from a module attribute. Supports callables and iterables;",
                    "unsupported types are cast via ``str()``"
                ],
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "attr": {
                        "type": "string",
                        "format": "python-qualified-identifier"
                    }
                },
                "required": [
                    "attr"
                ]
            },
            {
                "$id": "#/definitions/file-directive",
                "title": "'file:' directive",
                "description": "Value is read from a file (or list of files and then concatenated)",
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "file": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                }
                            }
                        ]
                    }
                },
                "required": [
                    "file"
                ]
            }
        ]
    }

For more details about `format` see
https://validate-pyproject.readthedocs.io/en/latest/api/validate_pyproject.formats.html

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "~\AppData\Roaming\Python\Python312\site-packages\setuptools\__init__.py", line 103, in setup
    return distutils.core.setup(**attrs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "~\AppData\Roaming\Python\Python312\site-packages\setuptools\_distutils\core.py", line 158, in setup
    dist.parse_config_files()
  File "~\AppData\Roaming\Python\Python312\site-packages\setuptools\dist.py", line 632, in parse_config_files
    pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)
  File "~\AppData\Roaming\Python\Python312\site-packages\setuptools\config\pyprojecttoml.py", line 68, in apply_configuration
    config = read_configuration(filepath, True, ignore_option_errors, dist)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "~\AppData\Roaming\Python\Python312\site-packages\setuptools\config\pyprojecttoml.py", line 133, in read_configuration
    validate(subset, filepath)
  File "~\AppData\Roaming\Python\Python312\site-packages\setuptools\config\pyprojecttoml.py", line 57, in validate
    raise ValueError(f"{error}\n{summary}") from None
ValueError: invalid pyproject.toml config: `tool.setuptools.dynamic.version`.
configuration error: `tool.setuptools.dynamic.version` must be valid exactly by one definition (0 matches found):

    - type: table
      additional keys: False
      keys:
        'attr': {type: string, format: 'python-qualified-identifier'}
      required: ['attr']
    - type: table
      additional keys: False
      keys:
        'file':
          exactly one of the following:
            - {type: string}
            - type: array
              items: {type: string}
      required: ['file']
abravalheri commented 3 months ago

Hi @MetRonnie thank you very much for reporting this.

Please consider it an error on the previous versions that this used to work, and that v70 fixed by providing better validation and a more complete error message.

For attr you need to provide a module name and an attribute, for example mymodule.__version__.

MetRonnie commented 3 months ago

It still works without the dot, if the current directory is the one containing pyproject.toml and __init__.py. E.g.

version = {attr = "__version__"}
abravalheri commented 3 months ago

Hi @MetRonnie thank you very much for reporting this.

Regarding {attr = "__version__"}, by reading the code, it seems to be intentional that module name falls back to __init__. Not sure if we should add an extra check there to make it also fail... (Sounds like a very edge case, there are other more important problems that with a "root" __init__.py).

Any way unless you have a good customisation of package-dir, I don't think that nesting pyproject.toml inside of a package directory with __init__.py makes much sense... Otherwise you will end up with an __init__.py file on the root of your site-packages directory and I am sure that it can cause some problems.

MetRonnie commented 3 months ago

Ok, this is only affecting a test repository so no worries if there are good reasons to not support this