From 1aaddc1e9ada945cdbe6677fcf9944242b08fca2 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 10 Sep 2024 17:32:56 +0800 Subject: [PATCH 01/58] make setuptools inline literal --- docs/userguide/datafiles.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 4eca7e4303..48356563ba 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -8,7 +8,7 @@ are placed in a platform-specific location. However, the most common use case for data files distributed with a package is for use *by* the package, usually by including the data files **inside the package directory**. -Setuptools focuses on this most common type of data files and offers three ways +``Setuptools`` focuses on this most common type of data files and offers three ways of specifying which files should be included in your packages, as described in the following section. @@ -172,7 +172,7 @@ file, nor require to be added by a revision control system plugin. .. note:: If your glob patterns use paths, you *must* use a forward slash (``/``) as - the path separator, even if you are on Windows. Setuptools automatically + the path separator, even if you are on Windows. ``Setuptools`` automatically converts slashes to appropriate platform-specific separators at build time. .. important:: @@ -450,7 +450,7 @@ With :ref:`package-data`, the configuration might look like this: } ) -In other words, we allow Setuptools to scan for namespace packages in the ``src`` directory, +In other words, we allow ``Setuptools`` to scan for namespace packages in the ``src`` directory, which enables the ``data`` directory to be identified, and then, we separately specify data files for the root package ``mypkg``, and the namespace package ``data`` under the package ``mypkg``. From 70b5ec518dddef4a017972fa288b82fb30235095 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 10 Sep 2024 18:50:22 +0800 Subject: [PATCH 02/58] add clarify of data files --- docs/userguide/datafiles.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 48356563ba..000b0324ee 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -2,11 +2,12 @@ Data Files Support ==================== -Old packaging installation methods in the Python ecosystem -have traditionally allowed installation of "data files", which -are placed in a platform-specific location. However, the most common use case -for data files distributed with a package is for use *by* the package, usually -by including the data files **inside the package directory**. +Old packaging installation methods in the Python ecosystem have +traditionally allowed the inclusion of "data files" (i.e. files beyond +:ref:`the default set ` ), which are placed in a platform-specific +location. However, the most common use case for data files distributed +with a package is for use *by* the package, usually by including the +data files **inside the package directory**. ``Setuptools`` focuses on this most common type of data files and offers three ways of specifying which files should be included in your packages, as described in From 0a5667ea33c8182f4582b5015c90ee61665f7bfc Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 10 Sep 2024 19:10:02 +0800 Subject: [PATCH 03/58] add index to second level titles --- docs/userguide/datafiles.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 000b0324ee..5c9800b42e 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -3,7 +3,7 @@ Data Files Support ==================== Old packaging installation methods in the Python ecosystem have -traditionally allowed the inclusion of "data files" (i.e. files beyond +traditionally allowed the inclusion of "data files" (files beyond :ref:`the default set ` ), which are placed in a platform-specific location. However, the most common use case for data files distributed with a package is for use *by* the package, usually by including the @@ -20,8 +20,8 @@ Configuration Options .. _include-package-data: -include_package_data --------------------- +1. ``include_package_data`` +--------------------------- First, you can use the ``include_package_data`` keyword. For example, if the package tree looks like this:: @@ -102,8 +102,8 @@ your package, provided: .. _package-data: -package_data ------------- +2. ``package_data`` +------------------- By default, ``include_package_data`` considers **all** non ``.py`` files found inside the package directory (``src/mypkg`` in this case) as data files, and includes those that @@ -272,8 +272,8 @@ we specify that ``data1.rst`` from ``mypkg1`` alone should be captured as well. .. _exclude-package-data: -exclude_package_data --------------------- +3. ``exclude_package_data`` +--------------------------- Sometimes, the ``include_package_data`` or ``package_data`` options alone aren't sufficient to precisely define what files you want included. For example, From a377af03cf1b32e23e3a4b2120b35a1884b13a27 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 10 Sep 2024 19:16:05 +0800 Subject: [PATCH 04/58] fix case sensitivity in toml --- docs/userguide/datafiles.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 5c9800b42e..7f8476c1d2 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -94,7 +94,7 @@ your package, provided: .. note:: .. versionadded:: v61.0.0 - The default value for ``tool.setuptools.include-package-data`` is ``True`` + The default value for ``tool.setuptools.include-package-data`` is ``true`` when projects are configured via ``pyproject.toml``. This behaviour differs from ``setup.cfg`` and ``setup.py`` (where ``include_package_data=False`` by default), which was not changed From 1a4a1297d445eba3c0517b7d1523e0ba38696d4e Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 10 Sep 2024 19:31:41 +0800 Subject: [PATCH 05/58] adjust section positioning --- docs/userguide/datafiles.rst | 42 ++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 7f8476c1d2..aa329d53e8 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -24,6 +24,7 @@ Configuration Options --------------------------- First, you can use the ``include_package_data`` keyword. + For example, if the package tree looks like this:: project_root_directory @@ -36,7 +37,25 @@ For example, if the package tree looks like this:: ├── data1.txt └── data2.txt -and you supply this configuration: +When at least one of the following conditions are met: + +1. These files are included via the :ref:`MANIFEST.in ` file, + like so:: + + include src/mypkg/*.txt + include src/mypkg/*.rst + +2. They are being tracked by a revision control system such as Git, Mercurial + or SVN, **AND** you have configured an appropriate plugin such as + :pypi:`setuptools-scm` or :pypi:`setuptools-svn`. + (See the section below on :ref:`Adding Support for Revision + Control Systems` for information on how to configure such plugins.) + +then all the ``.txt`` and ``.rst`` files will be included into +the source distribution. + +To further include them into the ``wheels``, you can need to use the +``include_package_data`` keyword: .. tab:: pyproject.toml @@ -44,8 +63,8 @@ and you supply this configuration: [tool.setuptools] # ... - # By default, include-package-data is true in pyproject.toml, so you do - # NOT have to specify this line. + # By default, include-package-data is true in pyproject.toml, + # so you do NOT have to specify this line. include-package-data = true [tool.setuptools.packages.find] @@ -77,27 +96,12 @@ and you supply this configuration: include_package_data=True ) -then all the ``.txt`` and ``.rst`` files will be automatically installed with -your package, provided: - -1. These files are included via the :ref:`MANIFEST.in ` file, - like so:: - - include src/mypkg/*.txt - include src/mypkg/*.rst - -2. OR, they are being tracked by a revision control system such as Git, Mercurial - or SVN, and you have configured an appropriate plugin such as - :pypi:`setuptools-scm` or :pypi:`setuptools-svn`. - (See the section below on :ref:`Adding Support for Revision - Control Systems` for information on how to write such plugins.) - .. note:: .. versionadded:: v61.0.0 The default value for ``tool.setuptools.include-package-data`` is ``true`` when projects are configured via ``pyproject.toml``. This behaviour differs from ``setup.cfg`` and ``setup.py`` - (where ``include_package_data=False`` by default), which was not changed + (where ``include_package_data`` is ``False`` by default), which was not changed to ensure backwards compatibility with existing projects. .. _package-data: From d1e4b118269bfd78e6866de81fe7bc73905d3dd6 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 10 Sep 2024 19:43:27 +0800 Subject: [PATCH 06/58] fix typo --- docs/userguide/datafiles.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index aa329d53e8..99f2e123fa 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -37,7 +37,7 @@ For example, if the package tree looks like this:: ├── data1.txt └── data2.txt -When at least one of the following conditions are met: +When **at least one** of the following conditions are met: 1. These files are included via the :ref:`MANIFEST.in ` file, like so:: @@ -54,7 +54,7 @@ When at least one of the following conditions are met: then all the ``.txt`` and ``.rst`` files will be included into the source distribution. -To further include them into the ``wheels``, you can need to use the +To further include them into the ``wheels``, you can use the ``include_package_data`` keyword: .. tab:: pyproject.toml From a079e8b143f2a687851925b23e4e04af543a8c4e Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 10 Sep 2024 19:44:08 +0800 Subject: [PATCH 07/58] revision control system -> version control system --- docs/userguide/datafiles.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 99f2e123fa..5ee4bde136 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -45,7 +45,7 @@ When **at least one** of the following conditions are met: include src/mypkg/*.txt include src/mypkg/*.rst -2. They are being tracked by a revision control system such as Git, Mercurial +2. They are being tracked by a version control system such as Git, Mercurial or SVN, **AND** you have configured an appropriate plugin such as :pypi:`setuptools-scm` or :pypi:`setuptools-svn`. (See the section below on :ref:`Adding Support for Revision From 46fd8db91815234da4f6a90a648f8607f4f08e04 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Wed, 11 Sep 2024 09:51:14 +0800 Subject: [PATCH 08/58] revert version control -> revision control --- docs/userguide/datafiles.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 5ee4bde136..99f2e123fa 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -45,7 +45,7 @@ When **at least one** of the following conditions are met: include src/mypkg/*.txt include src/mypkg/*.rst -2. They are being tracked by a version control system such as Git, Mercurial +2. They are being tracked by a revision control system such as Git, Mercurial or SVN, **AND** you have configured an appropriate plugin such as :pypi:`setuptools-scm` or :pypi:`setuptools-svn`. (See the section below on :ref:`Adding Support for Revision From 3a8430008f102c9e7e781ec908ccbbf202fcd748 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Wed, 11 Sep 2024 09:54:18 +0800 Subject: [PATCH 09/58] clarify usage of setuptools vs Setuptools --- docs/userguide/datafiles.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 99f2e123fa..8e2ea0d6d1 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -9,7 +9,7 @@ location. However, the most common use case for data files distributed with a package is for use *by* the package, usually by including the data files **inside the package directory**. -``Setuptools`` focuses on this most common type of data files and offers three ways +Setuptools focuses on this most common type of data files and offers three ways of specifying which files should be included in your packages, as described in the following section. @@ -177,7 +177,7 @@ file, nor require to be added by a revision control system plugin. .. note:: If your glob patterns use paths, you *must* use a forward slash (``/``) as - the path separator, even if you are on Windows. ``Setuptools`` automatically + the path separator, even if you are on Windows. ``setuptools`` automatically converts slashes to appropriate platform-specific separators at build time. .. important:: @@ -455,7 +455,7 @@ With :ref:`package-data`, the configuration might look like this: } ) -In other words, we allow ``Setuptools`` to scan for namespace packages in the ``src`` directory, +In other words, we allow ``setuptools`` to scan for namespace packages in the ``src`` directory, which enables the ``data`` directory to be identified, and then, we separately specify data files for the root package ``mypkg``, and the namespace package ``data`` under the package ``mypkg``. From 32138db92eed5f6ce6180acabdc605db547acd24 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 12 Aug 2024 13:50:04 -0400 Subject: [PATCH 10/58] Update platformdirs to >= 4.2.2 --- newsfragments/4560.misc.rst | 1 + pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 newsfragments/4560.misc.rst diff --git a/newsfragments/4560.misc.rst b/newsfragments/4560.misc.rst new file mode 100644 index 0000000000..0878f09abd --- /dev/null +++ b/newsfragments/4560.misc.rst @@ -0,0 +1 @@ +Bumped declared ``platformdirs`` dependency to ``>= 4.2.2`` to help platforms lacking `ctypes` support install setuptools seamlessly -- by :user:`Avasam` diff --git a/pyproject.toml b/pyproject.toml index f623e16445..79dc7e8466 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ core = [ "wheel>=0.43.0", # pkg_resources - "platformdirs >= 2.6.2", + "platformdirs >= 4.2.2", # Made ctypes optional (see #4461) # for distutils "jaraco.collections", From bbd2167d06782ac9cb88b255a919429a7137593d Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 17 Sep 2024 11:07:07 +0800 Subject: [PATCH 11/58] apply @abravalheri 's suggestion as is first Co-authored-by: @abravalheri --- docs/userguide/datafiles.rst | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 8e2ea0d6d1..aaa8b9061f 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -2,12 +2,20 @@ Data Files Support ==================== -Old packaging installation methods in the Python ecosystem have -traditionally allowed the inclusion of "data files" (files beyond -:ref:`the default set ` ), which are placed in a platform-specific -location. However, the most common use case for data files distributed -with a package is for use *by* the package, usually by including the -data files **inside the package directory**. +In the Python ecosystem, the term "data files" is used in various complex scenarios +and can have nuanced meanings. +For the purposes of this documentation, we define "data files" as non-Python files +that are installed alongside Python modules and packages on the user's machine +when they install a :term:`distribution ` from PyPI +or via a ``.whl`` file. +These files are typically intended for use at runtime by the package itself or +to influence the behavior of other packages or systems. +They may also be referred to as "resource files." +Old packaging installation methods in the Python ecosystem +have traditionally allowed installation of "data files", which +are placed in a platform-specific location. However, the most common use case +for data files distributed with a package is for use *by* the package, usually +by including the data files **inside the package directory**. Setuptools focuses on this most common type of data files and offers three ways of specifying which files should be included in your packages, as described in From b07bde5eeddbc5480effc55a31d20e804018abe5 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 17 Sep 2024 11:13:01 +0800 Subject: [PATCH 12/58] remove source files for simplicity --- docs/userguide/datafiles.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index aaa8b9061f..57c336bb9d 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -8,9 +8,10 @@ For the purposes of this documentation, we define "data files" as non-Python fil that are installed alongside Python modules and packages on the user's machine when they install a :term:`distribution ` from PyPI or via a ``.whl`` file. + These files are typically intended for use at runtime by the package itself or to influence the behavior of other packages or systems. -They may also be referred to as "resource files." + Old packaging installation methods in the Python ecosystem have traditionally allowed installation of "data files", which are placed in a platform-specific location. However, the most common use case From e19f2d6beb55963ad8d4bb966fbda8ba1df8f1bf Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 17 Sep 2024 11:17:06 +0800 Subject: [PATCH 13/58] replace with sdist or wheel --- docs/userguide/datafiles.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 57c336bb9d..503c06b7db 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -3,13 +3,13 @@ Data Files Support ==================== In the Python ecosystem, the term "data files" is used in various complex scenarios -and can have nuanced meanings. -For the purposes of this documentation, we define "data files" as non-Python files -that are installed alongside Python modules and packages on the user's machine -when they install a :term:`distribution ` from PyPI -or via a ``.whl`` file. +and can have nuanced meanings. For the purposes of this documentation, +we define "data files" as non-Python files that are installed alongside Python +modules and packages on the user's machine when they install a +:term:`distribution ` from either source distribution +or via a binary distribution (``.whl`` file for example). -These files are typically intended for use at runtime by the package itself or +These files are typically intended for use at **runtime** by the package itself or to influence the behavior of other packages or systems. Old packaging installation methods in the Python ecosystem From cae1e68e09f6b3374df3d87f88ea1f4e6cede1bf Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 17 Sep 2024 12:27:03 +0800 Subject: [PATCH 14/58] add sketch --- docs/userguide/datafiles.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 503c06b7db..01d3ce36a7 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -351,6 +351,38 @@ Any files that match these patterns will be *excluded* from installation, even if they were listed in ``package_data`` or were included as a result of using ``include_package_data``. +.. _interplay_package_data_keywords: + +Interplay between these keywords +-------------------------------- + +TODO: +- better formatted logic expression +- check logic correctness (especially regarding parentheses) + +Meanwhile, to further clarify the interplay between these three keywords, +to include certain data file into the source distribution, the following +logic condition has two be met:: + + (m or p) and not (i or e) # TODO: why "not i"? + +In plain language, the file should be selected by either ``package-data`` +or ``MAINFEST.in``, AND should not be excluded by ``exclude-package-data``. # TODO: why "not i"? + +To include some data file into the ``.whl``:: + + (not e) and ((i and m) or p) + +In plain language, the file should not be excluded by ``exclude-package-data`` +(highest priority), and should be either: 1. selected by ``package-data``; +2. selected by ``MANIFEST.in`` AND use ``include-package-data = true``. + +Notation:: + + i - "include-package-data = true" is set + e - file selected by "exclude-package-data" + p - file selected by "package-data" + m - "MANIFEST.in" exists and includes file Summary ------- From c46d0600415abc183856cd3aea7f93ef9615b2d7 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 17 Sep 2024 14:03:27 +0800 Subject: [PATCH 15/58] update sketch --- docs/userguide/datafiles.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 01d3ce36a7..174c31016c 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -364,10 +364,9 @@ Meanwhile, to further clarify the interplay between these three keywords, to include certain data file into the source distribution, the following logic condition has two be met:: - (m or p) and not (i or e) # TODO: why "not i"? + m or (p and not (i or e)) # TODO: why not i? -In plain language, the file should be selected by either ``package-data`` -or ``MAINFEST.in``, AND should not be excluded by ``exclude-package-data``. # TODO: why "not i"? +In plain language, TODO: To include some data file into the ``.whl``:: @@ -382,7 +381,7 @@ Notation:: i - "include-package-data = true" is set e - file selected by "exclude-package-data" p - file selected by "package-data" - m - "MANIFEST.in" exists and includes file + m - file included in "MANIFEST.in" Summary ------- From f32b975efbb3d9eb331841b84bfa58deaf2ac0a7 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 17 Sep 2024 14:10:09 +0800 Subject: [PATCH 16/58] update legacy with latest setuptools --- docs/userguide/datafiles.rst | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 174c31016c..cda73faddb 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -356,24 +356,21 @@ even if they were listed in ``package_data`` or were included as a result of usi Interplay between these keywords -------------------------------- -TODO: -- better formatted logic expression -- check logic correctness (especially regarding parentheses) - Meanwhile, to further clarify the interplay between these three keywords, to include certain data file into the source distribution, the following logic condition has two be met:: - m or (p and not (i or e)) # TODO: why not i? + m or (p and not e) -In plain language, TODO: +In plain language, the file should be either: 1. included in ``MANIFEST.in``; +or 2. selected by ``package-data`` AND not excluded by ``exclude-package-data``. To include some data file into the ``.whl``:: (not e) and ((i and m) or p) In plain language, the file should not be excluded by ``exclude-package-data`` -(highest priority), and should be either: 1. selected by ``package-data``; +(highest priority), and should be either: 1. selected by ``package-data``; or 2. selected by ``MANIFEST.in`` AND use ``include-package-data = true``. Notation:: From 5481166422104ab8217d92d1c1e9b8620af507c2 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 17 Sep 2024 14:16:23 +0800 Subject: [PATCH 17/58] fix typo --- docs/userguide/datafiles.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index cda73faddb..2b4e66c1dc 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -358,7 +358,7 @@ Interplay between these keywords Meanwhile, to further clarify the interplay between these three keywords, to include certain data file into the source distribution, the following -logic condition has two be met:: +logic condition has to be met:: m or (p and not e) @@ -373,7 +373,7 @@ In plain language, the file should not be excluded by ``exclude-package-data`` (highest priority), and should be either: 1. selected by ``package-data``; or 2. selected by ``MANIFEST.in`` AND use ``include-package-data = true``. -Notation:: +**Notation**:: i - "include-package-data = true" is set e - file selected by "exclude-package-data" From 041e23def94a360b9af2cd650d9896cd1ca39b97 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 17 Sep 2024 14:26:54 +0800 Subject: [PATCH 18/58] add note for version difference --- docs/userguide/datafiles.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 2b4e66c1dc..ad9b082be4 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -380,6 +380,12 @@ In plain language, the file should not be excluded by ``exclude-package-data`` p - file selected by "package-data" m - file included in "MANIFEST.in" +.. note:: + Different versions of ``setuptools`` might behave differently. The above + description applies to versions after ``58.5.3`` (exclusive). For information + on the behavior of earlier versions and more details, please refer to the + `GitHub repository `_. + Summary ------- From e447010119709bba54a52c7a265848d7ee7dd102 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 17 Sep 2024 22:34:41 +0800 Subject: [PATCH 19/58] remove note on bug behaviour < 58.5.3 --- docs/userguide/datafiles.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index ad9b082be4..2b4e66c1dc 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -380,12 +380,6 @@ In plain language, the file should not be excluded by ``exclude-package-data`` p - file selected by "package-data" m - file included in "MANIFEST.in" -.. note:: - Different versions of ``setuptools`` might behave differently. The above - description applies to versions after ``58.5.3`` (exclusive). For information - on the behavior of earlier versions and more details, please refer to the - `GitHub repository `_. - Summary ------- From 5eab47faeaf8d1239139d446886da417a95def8e Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Tue, 17 Sep 2024 22:40:12 +0800 Subject: [PATCH 20/58] remove custom notation --- docs/userguide/datafiles.rst | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 2b4e66c1dc..280b9a0a35 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -360,25 +360,24 @@ Meanwhile, to further clarify the interplay between these three keywords, to include certain data file into the source distribution, the following logic condition has to be met:: - m or (p and not e) + MANIFEST.in or (package-data and not exclude-package-data) -In plain language, the file should be either: 1. included in ``MANIFEST.in``; -or 2. selected by ``package-data`` AND not excluded by ``exclude-package-data``. +In plain language, the file should be either: + +1. included in ``MANIFEST.in``; or + +2. selected by ``package-data`` AND not excluded by ``exclude-package-data``. To include some data file into the ``.whl``:: - (not e) and ((i and m) or p) + (not exclude-package-data) and ((include-package-data and MANIFEST.in) or package-data) -In plain language, the file should not be excluded by ``exclude-package-data`` -(highest priority), and should be either: 1. selected by ``package-data``; or -2. selected by ``MANIFEST.in`` AND use ``include-package-data = true``. +In other words, the file should not be excluded by ``exclude-package-data`` +(highest priority), AND should be either: -**Notation**:: +1. selected by ``package-data``; or - i - "include-package-data = true" is set - e - file selected by "exclude-package-data" - p - file selected by "package-data" - m - file included in "MANIFEST.in" +2. selected by ``MANIFEST.in`` AND use ``include-package-data = true``. Summary ------- From e74b501029ea3914cb4b8281bba0f93cba9169d3 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Fri, 13 Sep 2024 17:33:33 +0200 Subject: [PATCH 21/58] Implement the test for being able to pass dist-info dir --- setuptools/build_meta.py | 5 ++++- setuptools/tests/test_bdist_wheel.py | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index a6b85afc42..ecf434bbf3 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -417,9 +417,12 @@ def build_wheel( config_settings: _ConfigSettings = None, metadata_directory: StrPath | None = None, ): + cmd = ['bdist_wheel'] + if metadata_directory: + cmd.extend(['--dist-info-dir', metadata_directory]) with suppress_known_deprecation(): return self._build_with_temp_dir( - ['bdist_wheel'], + cmd, '.whl', wheel_directory, config_settings, diff --git a/setuptools/tests/test_bdist_wheel.py b/setuptools/tests/test_bdist_wheel.py index 8b64e90f72..a127cc1b2d 100644 --- a/setuptools/tests/test_bdist_wheel.py +++ b/setuptools/tests/test_bdist_wheel.py @@ -619,3 +619,25 @@ def _fake_import(name: str, *args, **kwargs): monkeypatch.delitem(sys.modules, "setuptools.command.bdist_wheel") import setuptools.command.bdist_wheel # noqa: F401 + + +def test_dist_info_provided(dummy_dist, monkeypatch, tmp_path): + monkeypatch.chdir(dummy_dist) + distinfo = tmp_path / "dummy_dist.dist-info" + + distinfo.mkdir() + (distinfo / "METADATA").write_text("name: helloworld", encoding="utf-8") + + # We don't control the metadata. According to PEP-517, "The hook MAY also + # create other files inside this directory, and a build frontend MUST + # preserve". + (distinfo / "FOO").write_text("bar", encoding="utf-8") + + bdist_wheel_cmd(bdist_dir=str(tmp_path), universal=True, dist_info_dir=str(distinfo)).run() + expected = { + "dummy_dist-1.0.dist-info/FOO", + "dummy_dist-1.0.dist-info/RECORD", + } + with ZipFile("dist/dummy_dist-1.0-py2.py3-none-any.whl") as wf: + # Check that all expected files are there. + assert set(wf.namelist()).intersection(expected) == expected From bc82d73b12b8260b9b2a3736cf7f3c603e379e93 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Fri, 13 Sep 2024 17:37:56 +0200 Subject: [PATCH 22/58] Implement the desired functionality --- setuptools/command/bdist_wheel.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index fa97976fef..59c162dd9e 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -231,6 +231,13 @@ class bdist_wheel(Command): None, "Python tag (cp32|cp33|cpNN) for abi3 wheel tag [default: false]", ), + ( + "dist-info-dir=", + None, + "directory where a pre-generated dist-info can be found (e.g. as a " + "result of calling the PEP517 'prepare_metadata_for_build_wheel' " + "method)", + ), ] boolean_options = ["keep-temp", "skip-build", "relative", "universal"] @@ -243,6 +250,7 @@ def initialize_options(self) -> None: self.format = "zip" self.keep_temp = False self.dist_dir: str | None = None + self.dist_info_dir = None self.egginfo_dir: str | None = None self.root_is_pure: bool | None = None self.skip_build = False @@ -261,8 +269,9 @@ def finalize_options(self) -> None: bdist_base = self.get_finalized_command("bdist").bdist_base self.bdist_dir = os.path.join(bdist_base, "wheel") - egg_info = cast(egg_info_cls, self.distribution.get_command_obj("egg_info")) - egg_info.ensure_finalized() # needed for correct `wheel_dist_name` + if self.dist_info_dir is None: + egg_info = self.distribution.get_command_obj("egg_info") + egg_info.ensure_finalized() # needed for correct `wheel_dist_name` self.data_dir = self.wheel_dist_name + ".data" self.plat_name_supplied = bool(self.plat_name) @@ -447,7 +456,15 @@ def run(self): f"{safer_version(self.distribution.get_version())}.dist-info" ) distinfo_dir = os.path.join(self.bdist_dir, distinfo_dirname) - self.egg2dist(self.egginfo_dir, distinfo_dir) + if self.dist_info_dir: + # Use the given dist-info directly. + shutil.copytree(self.dist_info_dir, distinfo_dir) + # Egg info is still generated, so remove it now to avoid it getting + # copied into the wheel. + shutil.rmtree(self.egginfo_dir) + else: + # Convert the generated egg-info into dist-info. + self.egg2dist(self.egginfo_dir, distinfo_dir) self.write_wheelfile(distinfo_dir) From 283ce3b9eb8720a50948f4ee938b603d2f1e97d9 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Fri, 13 Sep 2024 17:46:44 +0200 Subject: [PATCH 23/58] Add a news fragment about the work --- newsfragments/1825.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/1825.bugfix.rst diff --git a/newsfragments/1825.bugfix.rst b/newsfragments/1825.bugfix.rst new file mode 100644 index 0000000000..1fd1e95a4f --- /dev/null +++ b/newsfragments/1825.bugfix.rst @@ -0,0 +1 @@ +Enabled passing dist-info-dir to the bdist_wheel, and to the PEP-517 build backend. Therefore, metadata can now be injected prior to wheel building when following PEP-517 -- by :user:`pelson` From bd615ee524a3c1b68b06347958c7d92f83e8a259 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 16 Sep 2024 06:02:37 +0200 Subject: [PATCH 24/58] Test that there is no egg-info files accidentally leaking into the wheel --- newsfragments/1825.bugfix.rst | 2 +- setuptools/command/bdist_wheel.py | 1 - setuptools/tests/test_bdist_wheel.py | 11 +++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/newsfragments/1825.bugfix.rst b/newsfragments/1825.bugfix.rst index 1fd1e95a4f..99e8fdf22a 100644 --- a/newsfragments/1825.bugfix.rst +++ b/newsfragments/1825.bugfix.rst @@ -1 +1 @@ -Enabled passing dist-info-dir to the bdist_wheel, and to the PEP-517 build backend. Therefore, metadata can now be injected prior to wheel building when following PEP-517 -- by :user:`pelson` +Allow passing dist-info-dir to the bdist_wheel command, as well as to the PEP-517 build backend. Metadata can therefore now be injected prior to wheel building when following PEP-517 -- by :user:`pelson` diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index 59c162dd9e..a770de2e54 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -27,7 +27,6 @@ from .. import Command, __version__ from ..warnings import SetuptoolsDeprecationWarning -from .egg_info import egg_info as egg_info_cls from distutils import log diff --git a/setuptools/tests/test_bdist_wheel.py b/setuptools/tests/test_bdist_wheel.py index a127cc1b2d..47200d0a26 100644 --- a/setuptools/tests/test_bdist_wheel.py +++ b/setuptools/tests/test_bdist_wheel.py @@ -633,11 +633,14 @@ def test_dist_info_provided(dummy_dist, monkeypatch, tmp_path): # preserve". (distinfo / "FOO").write_text("bar", encoding="utf-8") - bdist_wheel_cmd(bdist_dir=str(tmp_path), universal=True, dist_info_dir=str(distinfo)).run() + bdist_wheel_cmd(bdist_dir=str(tmp_path), dist_info_dir=str(distinfo)).run() expected = { "dummy_dist-1.0.dist-info/FOO", "dummy_dist-1.0.dist-info/RECORD", } - with ZipFile("dist/dummy_dist-1.0-py2.py3-none-any.whl") as wf: - # Check that all expected files are there. - assert set(wf.namelist()).intersection(expected) == expected + with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf: + files_found = set(wf.namelist()) + # Check that all expected files are there. + assert expected - files_found == set() + # Make sure there is no accidental egg-info bleeding into the wheel. + assert not [path for path in files_found if 'egg-info' in str(path)] From eb81747c129f033ede333e97589ff4f8b600b6e0 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 25 Sep 2024 13:52:22 +0200 Subject: [PATCH 25/58] Restore the egg-info-cls casting --- setuptools/command/bdist_wheel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index a770de2e54..83abe6e0c3 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -27,6 +27,7 @@ from .. import Command, __version__ from ..warnings import SetuptoolsDeprecationWarning +from .egg_info import egg_info as egg_info_cls from distutils import log @@ -269,7 +270,7 @@ def finalize_options(self) -> None: self.bdist_dir = os.path.join(bdist_base, "wheel") if self.dist_info_dir is None: - egg_info = self.distribution.get_command_obj("egg_info") + egg_info = cast(egg_info_cls, self.distribution.get_command_obj("egg_info")) egg_info.ensure_finalized() # needed for correct `wheel_dist_name` self.data_dir = self.wheel_dist_name + ".data" From 2f3b273e534965d33fce9c5576ad89c4afbd8746 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 26 Sep 2024 07:55:43 +0200 Subject: [PATCH 26/58] Update newsfragments/1825.bugfix.rst Co-authored-by: Anderson Bravalheri --- newsfragments/1825.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newsfragments/1825.bugfix.rst b/newsfragments/1825.bugfix.rst index 99e8fdf22a..226df0a936 100644 --- a/newsfragments/1825.bugfix.rst +++ b/newsfragments/1825.bugfix.rst @@ -1 +1 @@ -Allow passing dist-info-dir to the bdist_wheel command, as well as to the PEP-517 build backend. Metadata can therefore now be injected prior to wheel building when following PEP-517 -- by :user:`pelson` +Re-use pre-existing ``.dist-info`` dir when creating wheels via the build backend APIs (PEP 517) and the `metadata_directory` argument is passed -- by :user:`pelson`. From cf299fcb838f388a210569df7f5419fe762f26c7 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 26 Sep 2024 13:05:05 +0200 Subject: [PATCH 27/58] Update setuptools/command/bdist_wheel.py Co-authored-by: Anderson Bravalheri --- setuptools/command/bdist_wheel.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index 83abe6e0c3..aeade98f6f 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -458,6 +458,7 @@ def run(self): distinfo_dir = os.path.join(self.bdist_dir, distinfo_dirname) if self.dist_info_dir: # Use the given dist-info directly. + log.debug(f"reusing {self.dist_info_dir}") shutil.copytree(self.dist_info_dir, distinfo_dir) # Egg info is still generated, so remove it now to avoid it getting # copied into the wheel. From fc08e7ef01fea2033e7f1c33e51704574f392ca0 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Thu, 26 Sep 2024 14:28:56 +0100 Subject: [PATCH 28/58] Fix RST in newsfragment --- newsfragments/1825.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newsfragments/1825.bugfix.rst b/newsfragments/1825.bugfix.rst index 226df0a936..ff55d18725 100644 --- a/newsfragments/1825.bugfix.rst +++ b/newsfragments/1825.bugfix.rst @@ -1 +1 @@ -Re-use pre-existing ``.dist-info`` dir when creating wheels via the build backend APIs (PEP 517) and the `metadata_directory` argument is passed -- by :user:`pelson`. +Re-use pre-existing ``.dist-info`` dir when creating wheels via the build backend APIs (PEP 517) and the ``metadata_directory`` argument is passed -- by :user:`pelson`. From 1595318c54a0018cc8200bc3f07f0786da74ef3f Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Thu, 26 Sep 2024 14:59:12 +0100 Subject: [PATCH 29/58] Update docs/userguide/datafiles.rst --- docs/userguide/datafiles.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/userguide/datafiles.rst b/docs/userguide/datafiles.rst index 280b9a0a35..72a658ee9c 100644 --- a/docs/userguide/datafiles.rst +++ b/docs/userguide/datafiles.rst @@ -6,8 +6,7 @@ In the Python ecosystem, the term "data files" is used in various complex scenar and can have nuanced meanings. For the purposes of this documentation, we define "data files" as non-Python files that are installed alongside Python modules and packages on the user's machine when they install a -:term:`distribution ` from either source distribution -or via a binary distribution (``.whl`` file for example). +:term:`distribution ` via :term:`wheel `. These files are typically intended for use at **runtime** by the package itself or to influence the behavior of other packages or systems. From c328b85dcbe4eda92ff1afc6aef2922e94262193 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Thu, 26 Sep 2024 15:36:06 +0100 Subject: [PATCH 30/58] Remove trailling / from interspjinx_mapping to avoid verbose logs --- docs/conf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 4ea38e7490..20c2a8f099 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -191,7 +191,7 @@ # Allow linking objects on other Sphinx sites seamlessly: intersphinx_mapping.update( # python=('https://docs.python.org/3', None), - python=('https://docs.python.org/3.11/', None), + python=('https://docs.python.org/3.11', None), # ^-- Python 3.11 is required because it still contains `distutils`. # Just leaving it as `3` would imply 3.12+, but that causes an # error with the cross references to distutils functions. @@ -237,9 +237,9 @@ intersphinx_mapping.update({ 'pip': ('https://pip.pypa.io/en/latest', None), 'build': ('https://build.pypa.io/en/latest', None), - 'PyPUG': ('https://packaging.python.org/en/latest/', None), - 'packaging': ('https://packaging.pypa.io/en/latest/', None), - 'twine': ('https://twine.readthedocs.io/en/stable/', None), + 'PyPUG': ('https://packaging.python.org/en/latest', None), + 'packaging': ('https://packaging.pypa.io/en/latest', None), + 'twine': ('https://twine.readthedocs.io/en/stable', None), 'importlib-resources': ( 'https://importlib-resources.readthedocs.io/en/latest', None, From 258edabeb2dcc5dc3e62e9cf98926390e452340d Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Mon, 7 Oct 2024 15:40:23 +0100 Subject: [PATCH 31/58] Post-pone deprecation for now It seems that there are many projects that haven't adapted yet, so we should probably avoid disruption. Evidence: https://github.com/search?q=%2Fauthor-email%7Cmaintainer-email%7Clong-description%7Clicense-file%2F+path%3Asetup.cfg&type=code --- setuptools/dist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setuptools/dist.py b/setuptools/dist.py index 68f877decd..7c516fefb8 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -504,7 +504,7 @@ def warn_dash_deprecation(self, opt, section): versions. Please use the underscore name {underscore_opt!r} instead. """, see_docs="userguide/declarative_config.html", - due_date=(2024, 9, 26), + due_date=(2025, 3, 3), # Warning initially introduced in 3 Mar 2021 ) return underscore_opt @@ -529,7 +529,7 @@ def make_option_lowercase(self, opt, section): future versions. Please use lowercase {lowercase_opt!r} instead. """, see_docs="userguide/declarative_config.html", - due_date=(2024, 9, 26), + due_date=(2025, 3, 3), # Warning initially introduced in 6 Mar 2021 ) return lowercase_opt From 35d1139126b9a5021e1d7fd819f5dbbec9ce950c Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 11:35:26 +0100 Subject: [PATCH 32/58] For now avoid problems with pyproject-hooks --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 49206410ce..6cb4d9d17c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,8 +56,8 @@ test = [ "pytest-home >= 0.5", "pytest-subprocess", - # workaround for pypa/setuptools#4333 - "pyproject-hooks!=1.1", + # workaround for pypa/pyproject-hooks#206 + "pyproject-hooks!=1.2", # TODO: fix problem with egg-info, see #4670 "jaraco.test", ] From a0a0e329161868703e3ef4cec86e72d748e5b9bf Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 11:48:15 +0100 Subject: [PATCH 33/58] Fix version specifier in workaround for pyproject-hooks --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6cb4d9d17c..b9b6d441db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ test = [ "pytest-subprocess", # workaround for pypa/pyproject-hooks#206 - "pyproject-hooks!=1.2", # TODO: fix problem with egg-info, see #4670 + "pyproject-hooks<1.1", # TODO: fix problem with egg-info, see #4670 "jaraco.test", ] From e2f467964ddce1c436652c99d8c6ffe493583edc Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 11:52:19 +0100 Subject: [PATCH 34/58] Add missing workaround in github actions --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f90a4607b1..0c765a6bdd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -100,7 +100,7 @@ jobs: run: | rm -rf dist # workaround for pypa/setuptools#4333 - pipx run --pip-args 'pyproject-hooks!=1.1' build + pipx run --pip-args 'pyproject-hooks<1.1' build echo "PRE_BUILT_SETUPTOOLS_SDIST=$(ls dist/*.tar.gz)" >> $GITHUB_ENV echo "PRE_BUILT_SETUPTOOLS_WHEEL=$(ls dist/*.whl)" >> $GITHUB_ENV rm -rf setuptools.egg-info # Avoid interfering with the other tests From b5e40f31208ad23288172bd2b7c2a556244c858f Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 11:46:22 +0100 Subject: [PATCH 35/58] Avoid iterating over entry-points while an empty .egg-info exists in sys.path --- setuptools/command/egg_info.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 280eb5e807..f4d3a2a57e 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -293,13 +293,17 @@ def delete_file(self, filename): os.unlink(filename) def run(self): + # Pre-load to avoid iterating over entry-points while an empty .egg-info + # exists in sys.path. See pypa/pyproject-hooks#206 + writers = list(metadata.entry_points(group='egg_info.writers')) + self.mkpath(self.egg_info) try: os.utime(self.egg_info, None) except OSError as e: msg = f"Cannot update time stamp of directory '{self.egg_info}'" raise distutils.errors.DistutilsFileError(msg) from e - for ep in metadata.entry_points(group='egg_info.writers'): + for ep in writers: writer = ep.load() writer(self, ep.name, os.path.join(self.egg_info, ep.name)) From fa66840443ae2b3bb0a3721879b55c57816fb7ba Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 11:49:38 +0100 Subject: [PATCH 36/58] Remove workaround --- .github/workflows/main.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0c765a6bdd..f90a4607b1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -100,7 +100,7 @@ jobs: run: | rm -rf dist # workaround for pypa/setuptools#4333 - pipx run --pip-args 'pyproject-hooks<1.1' build + pipx run --pip-args 'pyproject-hooks!=1.1' build echo "PRE_BUILT_SETUPTOOLS_SDIST=$(ls dist/*.tar.gz)" >> $GITHUB_ENV echo "PRE_BUILT_SETUPTOOLS_WHEEL=$(ls dist/*.whl)" >> $GITHUB_ENV rm -rf setuptools.egg-info # Avoid interfering with the other tests diff --git a/pyproject.toml b/pyproject.toml index b9b6d441db..7c6cf36cbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ test = [ "pytest-subprocess", # workaround for pypa/pyproject-hooks#206 - "pyproject-hooks<1.1", # TODO: fix problem with egg-info, see #4670 + "pyproject-hooks!=1.1", "jaraco.test", ] From 28677ae4e4d1d6f5a4267be6df5bdbb024d89d20 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 16:42:40 +0100 Subject: [PATCH 37/58] Add news fragment --- newsfragments/4680.bugfix.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 newsfragments/4680.bugfix.rst diff --git a/newsfragments/4680.bugfix.rst b/newsfragments/4680.bugfix.rst new file mode 100644 index 0000000000..7f5fd0aade --- /dev/null +++ b/newsfragments/4680.bugfix.rst @@ -0,0 +1,4 @@ +Changed ``egg_info`` command to avoid adding an empty ``.egg-info`` while +iterating over entry-points is available in ``sys.path``. +This avoids triggering integration problems with ``importlib.metadata``/``importlib_metadata`` +(reference: pypa/pyproject-hooks#206). From fcb04c3e0ac1a958541cae2ec63fdcd80e88c2ef Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 17:04:36 +0100 Subject: [PATCH 38/58] Fix newsfragment phrasing --- newsfragments/4680.bugfix.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/newsfragments/4680.bugfix.rst b/newsfragments/4680.bugfix.rst index 7f5fd0aade..71cb0d4322 100644 --- a/newsfragments/4680.bugfix.rst +++ b/newsfragments/4680.bugfix.rst @@ -1,4 +1,4 @@ -Changed ``egg_info`` command to avoid adding an empty ``.egg-info`` while -iterating over entry-points is available in ``sys.path``. +Changed ``egg_info`` command to avoid adding an empty ``.egg-info`` directory +while iterating over entry-points. This avoids triggering integration problems with ``importlib.metadata``/``importlib_metadata`` (reference: pypa/pyproject-hooks#206). From b828db4242966382f47fe521865ac8b1f12ace4b Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 17:04:45 +0100 Subject: [PATCH 39/58] =?UTF-8?q?Bump=20version:=2075.1.0=20=E2=86=92=2075?= =?UTF-8?q?.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- NEWS.rst | 13 +++++++++++++ newsfragments/1825.bugfix.rst | 1 - newsfragments/4680.bugfix.rst | 4 ---- pyproject.toml | 2 +- 5 files changed, 15 insertions(+), 7 deletions(-) delete mode 100644 newsfragments/1825.bugfix.rst delete mode 100644 newsfragments/4680.bugfix.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9166a09130..5247cca130 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 75.1.0 +current_version = 75.1.1 commit = True tag = True diff --git a/NEWS.rst b/NEWS.rst index 313d6dfdc1..75ef319dd5 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,16 @@ +v75.1.1 +======= + +Bugfixes +-------- + +- Re-use pre-existing ``.dist-info`` dir when creating wheels via the build backend APIs (PEP 517) and the ``metadata_directory`` argument is passed -- by :user:`pelson`. (#1825) +- Changed ``egg_info`` command to avoid adding an empty ``.egg-info`` directory + while iterating over entry-points. + This avoids triggering integration problems with ``importlib.metadata``/``importlib_metadata`` + (reference: pypa/pyproject-hooks#206). (#4680) + + v75.1.0 ======= diff --git a/newsfragments/1825.bugfix.rst b/newsfragments/1825.bugfix.rst deleted file mode 100644 index ff55d18725..0000000000 --- a/newsfragments/1825.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Re-use pre-existing ``.dist-info`` dir when creating wheels via the build backend APIs (PEP 517) and the ``metadata_directory`` argument is passed -- by :user:`pelson`. diff --git a/newsfragments/4680.bugfix.rst b/newsfragments/4680.bugfix.rst deleted file mode 100644 index 71cb0d4322..0000000000 --- a/newsfragments/4680.bugfix.rst +++ /dev/null @@ -1,4 +0,0 @@ -Changed ``egg_info`` command to avoid adding an empty ``.egg-info`` directory -while iterating over entry-points. -This avoids triggering integration problems with ``importlib.metadata``/``importlib_metadata`` -(reference: pypa/pyproject-hooks#206). diff --git a/pyproject.toml b/pyproject.toml index 7c6cf36cbe..c27a988afd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ backend-path = ["."] [project] name = "setuptools" -version = "75.1.0" +version = "75.1.1" authors = [ { name = "Python Packaging Authority", email = "distutils-sig@python.org" }, ] From d457d0e87889aefe2093cd79ab4d1ee35d3101e7 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 20 Aug 2024 12:57:29 -0400 Subject: [PATCH 40/58] Type sequence checks in setuptools/dist.py --- newsfragments/4578.bugfix.rst | 1 + newsfragments/4578.feature.rst | 1 + setuptools/dist.py | 48 ++++++++++++++++++++++++---------- setuptools/tests/test_dist.py | 8 +++--- 4 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 newsfragments/4578.bugfix.rst create mode 100644 newsfragments/4578.feature.rst diff --git a/newsfragments/4578.bugfix.rst b/newsfragments/4578.bugfix.rst new file mode 100644 index 0000000000..e9bde46269 --- /dev/null +++ b/newsfragments/4578.bugfix.rst @@ -0,0 +1 @@ +Fix a `TypeError` when a ``Distribution``'s old included attribute was a `tuple` -- by :user:`Avasam` diff --git a/newsfragments/4578.feature.rst b/newsfragments/4578.feature.rst new file mode 100644 index 0000000000..48f57edce3 --- /dev/null +++ b/newsfragments/4578.feature.rst @@ -0,0 +1 @@ +Made errors when parsing ``Distribution`` data more explicit about the expected type (``tuple[str, ...] | list[str]``) -- by :user:`Avasam` diff --git a/setuptools/dist.py b/setuptools/dist.py index 7c516fefb8..bb9a2a9951 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -8,7 +8,7 @@ import sys from glob import iglob from pathlib import Path -from typing import TYPE_CHECKING, MutableMapping +from typing import TYPE_CHECKING, List, MutableMapping, NoReturn, Tuple, Union, overload from more_itertools import partition, unique_everseen from packaging.markers import InvalidMarker, Marker @@ -21,6 +21,7 @@ command as _, # noqa: F401 # imported for side-effects ) from ._importlib import metadata +from ._reqs import _StrOrIter from .config import pyprojecttoml, setupcfg from .discovery import ConfigDiscovery from .monkey import get_unpatched @@ -36,9 +37,22 @@ from distutils.fancy_getopt import translate_longopt from distutils.util import strtobool +if TYPE_CHECKING: + from typing_extensions import TypeAlias + __all__ = ['Distribution'] sequence = tuple, list +""" +Supported iterable types that are known to be: +- ordered (which `set` isn't) +- not match a str (which `Sequence[str]` does) +- not imply a nested type (like `dict`) +for use with `isinstance`. +""" +_Sequence: TypeAlias = Union[Tuple[str, ...], List[str]] +# This is how stringifying _Sequence would look in Python 3.10 +_requence_type_repr = "tuple[str, ...] | list[str]" def check_importable(dist, attr, value): @@ -51,7 +65,7 @@ def check_importable(dist, attr, value): ) from e -def assert_string_list(dist, attr, value): +def assert_string_list(dist, attr: str, value: _Sequence) -> None: """Verify that value is a string list""" try: # verify that value is a list or tuple to exclude unordered @@ -61,7 +75,7 @@ def assert_string_list(dist, attr, value): assert ''.join(value) != value except (TypeError, ValueError, AttributeError, AssertionError) as e: raise DistutilsSetupError( - "%r must be a list of strings (got %r)" % (attr, value) + f"{attr!r} must be of type <{_requence_type_repr}> (got {value!r})" ) from e @@ -138,7 +152,11 @@ def invalid_unless_false(dist, attr, value): raise DistutilsSetupError(f"{attr} is invalid.") -def check_requirements(dist, attr, value): +@overload +def check_requirements(dist, attr: str, value: set | dict) -> NoReturn: ... +@overload +def check_requirements(dist, attr: str, value: _StrOrIter) -> None: ... +def check_requirements(dist, attr: str, value: _StrOrIter) -> None: """Verify that install_requires is a valid requirements list""" try: list(_reqs.parse(value)) @@ -146,10 +164,10 @@ def check_requirements(dist, attr, value): raise TypeError("Unordered types are not allowed") except (TypeError, ValueError) as error: tmpl = ( - "{attr!r} must be a string or list of strings " - "containing valid project/version requirement specifiers; {error}" + f"{attr!r} must be a string or iterable of strings " + f"containing valid project/version requirement specifiers; {error}" ) - raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error + raise DistutilsSetupError(tmpl) from error def check_specifier(dist, attr, value): @@ -767,11 +785,11 @@ def has_contents_for(self, package): return False - def _exclude_misc(self, name, value): + def _exclude_misc(self, name: str, value: _Sequence) -> None: """Handle 'exclude()' for list/tuple attrs without a special handler""" if not isinstance(value, sequence): raise DistutilsSetupError( - "%s: setting must be a list or tuple (%r)" % (name, value) + f"{name}: setting must be of type <{_requence_type_repr}> (got {value!r})" ) try: old = getattr(self, name) @@ -784,11 +802,13 @@ def _exclude_misc(self, name, value): elif old: setattr(self, name, [item for item in old if item not in value]) - def _include_misc(self, name, value): + def _include_misc(self, name: str, value: _Sequence) -> None: """Handle 'include()' for list/tuple attrs without a special handler""" if not isinstance(value, sequence): - raise DistutilsSetupError("%s: setting must be a list (%r)" % (name, value)) + raise DistutilsSetupError( + f"{name}: setting must be of type <{_requence_type_repr}> (got {value!r})" + ) try: old = getattr(self, name) except AttributeError as e: @@ -801,7 +821,7 @@ def _include_misc(self, name, value): ) else: new = [item for item in value if item not in old] - setattr(self, name, old + new) + setattr(self, name, list(old) + new) def exclude(self, **attrs): """Remove items from distribution that are named in keyword arguments @@ -826,10 +846,10 @@ def exclude(self, **attrs): else: self._exclude_misc(k, v) - def _exclude_packages(self, packages): + def _exclude_packages(self, packages: _Sequence) -> None: if not isinstance(packages, sequence): raise DistutilsSetupError( - "packages: setting must be a list or tuple (%r)" % (packages,) + f"packages: setting must be of type <{_requence_type_repr}> (got {packages!r})" ) list(map(self.exclude_package, packages)) diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index 597785b519..fde0de99ac 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -118,8 +118,8 @@ def test_provides_extras_deterministic_order(): 'hello': '*.msg', }, ( - "\"values of 'package_data' dict\" " - "must be a list of strings (got '*.msg')" + "\"values of 'package_data' dict\" must be of type " + " (got '*.msg')" ), ), # Invalid value type (generators are single use) @@ -128,8 +128,8 @@ def test_provides_extras_deterministic_order(): 'hello': (x for x in "generator"), }, ( - "\"values of 'package_data' dict\" must be a list of strings " - "(got " + " (got Date: Tue, 27 Aug 2024 11:32:59 +0100 Subject: [PATCH 41/58] Use variable msg instead of tmpl in setuptools/dist --- setuptools/dist.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/setuptools/dist.py b/setuptools/dist.py index bb9a2a9951..b5d78aa37d 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -140,8 +140,7 @@ def _check_marker(marker): def assert_bool(dist, attr, value): """Verify that value is True, False, 0, or 1""" if bool(value) != value: - tmpl = "{attr!r} must be a boolean value (got {value!r})" - raise DistutilsSetupError(tmpl.format(attr=attr, value=value)) + raise DistutilsSetupError(f"{attr!r} must be a boolean value (got {value!r})") def invalid_unless_false(dist, attr, value): @@ -163,11 +162,11 @@ def check_requirements(dist, attr: str, value: _StrOrIter) -> None: if isinstance(value, (dict, set)): raise TypeError("Unordered types are not allowed") except (TypeError, ValueError) as error: - tmpl = ( + msg = ( f"{attr!r} must be a string or iterable of strings " f"containing valid project/version requirement specifiers; {error}" ) - raise DistutilsSetupError(tmpl) from error + raise DistutilsSetupError(msg) from error def check_specifier(dist, attr, value): @@ -175,8 +174,8 @@ def check_specifier(dist, attr, value): try: SpecifierSet(value) except (InvalidSpecifier, AttributeError) as error: - tmpl = "{attr!r} must be a string containing valid version specifiers; {error}" - raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error + msg = f"{attr!r} must be a string containing valid version specifiers; {error}" + raise DistutilsSetupError(msg) from error def check_entry_points(dist, attr, value): From 000a413e2af9c271166cebe6909ad664907887f1 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 27 Aug 2024 11:49:33 +0100 Subject: [PATCH 42/58] Deprecate public access to setuptools.dist.sequence --- setuptools/dist.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/setuptools/dist.py b/setuptools/dist.py index b5d78aa37d..f22e3eea54 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -8,7 +8,16 @@ import sys from glob import iglob from pathlib import Path -from typing import TYPE_CHECKING, List, MutableMapping, NoReturn, Tuple, Union, overload +from typing import ( + TYPE_CHECKING, + Any, + List, + MutableMapping, + NoReturn, + Tuple, + Union, + overload, +) from more_itertools import partition, unique_everseen from packaging.markers import InvalidMarker, Marker @@ -42,8 +51,10 @@ __all__ = ['Distribution'] -sequence = tuple, list +_sequence = tuple, list """ +:meta private: + Supported iterable types that are known to be: - ordered (which `set` isn't) - not match a str (which `Sequence[str]` does) @@ -55,6 +66,17 @@ _requence_type_repr = "tuple[str, ...] | list[str]" +def __getattr__(name: str) -> Any: # pragma: no cover + if name == "sequence": + SetuptoolsDeprecationWarning.emit( + "`setuptools.dist.sequence` is an internal implementation detail.", + "Please define your own `sequence = tuple, list` instead.", + due_date=(2025, 8, 28), # Originally added on 2024-08-27 + ) + return _sequence + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + def check_importable(dist, attr, value): try: ep = metadata.EntryPoint(value=value, name=None, group=None) @@ -70,7 +92,7 @@ def assert_string_list(dist, attr: str, value: _Sequence) -> None: try: # verify that value is a list or tuple to exclude unordered # or single-use iterables - assert isinstance(value, sequence) + assert isinstance(value, _sequence) # verify that elements of value are strings assert ''.join(value) != value except (TypeError, ValueError, AttributeError, AssertionError) as e: @@ -786,7 +808,7 @@ def has_contents_for(self, package): def _exclude_misc(self, name: str, value: _Sequence) -> None: """Handle 'exclude()' for list/tuple attrs without a special handler""" - if not isinstance(value, sequence): + if not isinstance(value, _sequence): raise DistutilsSetupError( f"{name}: setting must be of type <{_requence_type_repr}> (got {value!r})" ) @@ -794,7 +816,7 @@ def _exclude_misc(self, name: str, value: _Sequence) -> None: old = getattr(self, name) except AttributeError as e: raise DistutilsSetupError("%s: No such distribution setting" % name) from e - if old is not None and not isinstance(old, sequence): + if old is not None and not isinstance(old, _sequence): raise DistutilsSetupError( name + ": this setting cannot be changed via include/exclude" ) @@ -804,7 +826,7 @@ def _exclude_misc(self, name: str, value: _Sequence) -> None: def _include_misc(self, name: str, value: _Sequence) -> None: """Handle 'include()' for list/tuple attrs without a special handler""" - if not isinstance(value, sequence): + if not isinstance(value, _sequence): raise DistutilsSetupError( f"{name}: setting must be of type <{_requence_type_repr}> (got {value!r})" ) @@ -814,7 +836,7 @@ def _include_misc(self, name: str, value: _Sequence) -> None: raise DistutilsSetupError("%s: No such distribution setting" % name) from e if old is None: setattr(self, name, value) - elif not isinstance(old, sequence): + elif not isinstance(old, _sequence): raise DistutilsSetupError( name + ": this setting cannot be changed via include/exclude" ) @@ -846,7 +868,7 @@ def exclude(self, **attrs): self._exclude_misc(k, v) def _exclude_packages(self, packages: _Sequence) -> None: - if not isinstance(packages, sequence): + if not isinstance(packages, _sequence): raise DistutilsSetupError( f"packages: setting must be of type <{_requence_type_repr}> (got {packages!r})" ) From 96be735ca2e77b7db876133dfda0b4df3ced4ac0 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 18:12:02 +0100 Subject: [PATCH 43/58] Workaround for bdist_wheel.dist_info_dir problems --- setuptools/build_meta.py | 43 ++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index ecf434bbf3..3231105e69 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -417,17 +417,27 @@ def build_wheel( config_settings: _ConfigSettings = None, metadata_directory: StrPath | None = None, ): - cmd = ['bdist_wheel'] - if metadata_directory: - cmd.extend(['--dist-info-dir', metadata_directory]) - with suppress_known_deprecation(): - return self._build_with_temp_dir( - cmd, - '.whl', - wheel_directory, - config_settings, - self._arbitrary_args(config_settings), - ) + def _build(cmd: list[str]): + with suppress_known_deprecation(): + return self._build_with_temp_dir( + cmd, + '.whl', + wheel_directory, + config_settings, + self._arbitrary_args(config_settings), + ) + + if metadata_directory is None: + return _build(['bdist_wheel']) + + try: + return _build(['bdist_wheel', '--dist-info-dir', metadata_directory]) + except SystemExit as ex: + # pypa/setuptools#4683 + if "--dist-info-dir" not in str(ex): + raise + _IncompatibleBdistWheel.emit() + return _build(['bdist_wheel']) def build_sdist( self, sdist_directory: StrPath, config_settings: _ConfigSettings = None @@ -514,6 +524,17 @@ def run_setup(self, setup_script='setup.py'): sys.argv[0] = sys_argv_0 +class _IncompatibleBdistWheel(SetuptoolsDeprecationWarning): + _SUMMARY = "wheel.bdist_wheel is deprecated, please import it from setuptools" + _DETAILS = """ + Ensure that any custom bdist_wheel implementation is a subclass of + setuptools.command.bdist_wheel.bdist_wheel. + """ + _DUE_DATE = (2025, 10, 15) + # Initially introduced in 2024/10/15, but maybe too disruptive to be enforced? + _SEE_URL = "https://github.com/pypa/wheel/pull/631" + + # The primary backend _BACKEND = _BuildMetaBackend() From a663287c9c5f0bfc5e05addfb3a15fea7fc716c3 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 20:31:58 +0100 Subject: [PATCH 44/58] Add pragma for edge-case code path --- setuptools/build_meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index 3231105e69..19d0e1688e 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -432,7 +432,7 @@ def _build(cmd: list[str]): try: return _build(['bdist_wheel', '--dist-info-dir', metadata_directory]) - except SystemExit as ex: + except SystemExit as ex: # pragma: nocover # pypa/setuptools#4683 if "--dist-info-dir" not in str(ex): raise From 50b732a4006f3b84315d4473f7c203e4fe13aed9 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 20:34:44 +0100 Subject: [PATCH 45/58] Check for more specific error message --- setuptools/build_meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index 19d0e1688e..e730a27f25 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -434,7 +434,7 @@ def _build(cmd: list[str]): return _build(['bdist_wheel', '--dist-info-dir', metadata_directory]) except SystemExit as ex: # pragma: nocover # pypa/setuptools#4683 - if "--dist-info-dir" not in str(ex): + if "--dist-info-dir not recognized" not in str(ex): raise _IncompatibleBdistWheel.emit() return _build(['bdist_wheel']) From 0534fde847e0bd0c2214d6821c042c0eb5c0ffc3 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 15 Oct 2024 20:34:54 +0100 Subject: [PATCH 46/58] Add news fragment --- newsfragments/4684.bugfix.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 newsfragments/4684.bugfix.rst diff --git a/newsfragments/4684.bugfix.rst b/newsfragments/4684.bugfix.rst new file mode 100644 index 0000000000..40f554cccc --- /dev/null +++ b/newsfragments/4684.bugfix.rst @@ -0,0 +1,2 @@ +Add workaround for ``bdist_wheel --dist-info-dir`` errors +when customisation does not inherit from setuptools. From 61a5a03fbf8acc59e6e12144011aa06b85162bda Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Wed, 16 Oct 2024 11:04:49 +0100 Subject: [PATCH 47/58] =?UTF-8?q?Bump=20version:=2075.1.1=20=E2=86=92=2075?= =?UTF-8?q?.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- NEWS.rst | 17 +++++++++++++++++ newsfragments/4578.bugfix.rst | 1 - newsfragments/4578.feature.rst | 1 - newsfragments/4684.bugfix.rst | 2 -- pyproject.toml | 2 +- 6 files changed, 19 insertions(+), 6 deletions(-) delete mode 100644 newsfragments/4578.bugfix.rst delete mode 100644 newsfragments/4578.feature.rst delete mode 100644 newsfragments/4684.bugfix.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 5247cca130..91921ce92d 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 75.1.1 +current_version = 75.2.0 commit = True tag = True diff --git a/NEWS.rst b/NEWS.rst index 75ef319dd5..e79b45a623 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,20 @@ +v75.2.0 +======= + +Features +-------- + +- Made errors when parsing ``Distribution`` data more explicit about the expected type (``tuple[str, ...] | list[str]``) -- by :user:`Avasam` (#4578) + + +Bugfixes +-------- + +- Fix a `TypeError` when a ``Distribution``'s old included attribute was a `tuple` -- by :user:`Avasam` (#4578) +- Add workaround for ``bdist_wheel --dist-info-dir`` errors + when customisation does not inherit from setuptools. (#4684) + + v75.1.1 ======= diff --git a/newsfragments/4578.bugfix.rst b/newsfragments/4578.bugfix.rst deleted file mode 100644 index e9bde46269..0000000000 --- a/newsfragments/4578.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix a `TypeError` when a ``Distribution``'s old included attribute was a `tuple` -- by :user:`Avasam` diff --git a/newsfragments/4578.feature.rst b/newsfragments/4578.feature.rst deleted file mode 100644 index 48f57edce3..0000000000 --- a/newsfragments/4578.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Made errors when parsing ``Distribution`` data more explicit about the expected type (``tuple[str, ...] | list[str]``) -- by :user:`Avasam` diff --git a/newsfragments/4684.bugfix.rst b/newsfragments/4684.bugfix.rst deleted file mode 100644 index 40f554cccc..0000000000 --- a/newsfragments/4684.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add workaround for ``bdist_wheel --dist-info-dir`` errors -when customisation does not inherit from setuptools. diff --git a/pyproject.toml b/pyproject.toml index c27a988afd..bfa4d154a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ backend-path = ["."] [project] name = "setuptools" -version = "75.1.1" +version = "75.2.0" authors = [ { name = "Python Packaging Authority", email = "distutils-sig@python.org" }, ] From ecde60bfa50de155aa88c3410bd00b7dbaa0afd4 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 27 Aug 2024 10:54:32 -0400 Subject: [PATCH 48/58] Update mypy.ini from skeleton --- mypy.ini | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/mypy.ini b/mypy.ini index 7de7e5a508..a9d0fdd7df 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,17 +1,27 @@ [mypy] -# CI should test for all versions, local development gets hints for oldest supported -# But our testing setup doesn't allow passing CLI arguments, so local devs have to set this manually. -# python_version = 3.8 +## upstream + +# Is the project well-typed? strict = False + +# Early opt-in even when strict = False warn_unused_ignores = True warn_redundant_casts = True -# required to support namespace packages: https://github.com/python/mypy/issues/14057 +enable_error_code = ignore-without-code + +# Support namespace packages per https://github.com/python/mypy/issues/14057 explicit_package_bases = True disable_error_code = # Disable due to many false positives overload-overlap, +## local + +# CI should test for all versions, local development gets hints for oldest supported +# But our testing setup doesn't allow passing CLI arguments, so local devs have to set this manually. +# python_version = 3.8 + exclude = (?x)( # Avoid scanning Python files in generated folders ^build/ From 3403ffd553a8781f07cadd94ecc1680d8e2003c2 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 27 Aug 2024 11:59:12 -0400 Subject: [PATCH 49/58] Re-enable mypy & Resolve all [ignore-without-code] --- mypy.ini | 2 +- pkg_resources/__init__.py | 2 +- pyproject.toml | 4 ---- setuptools/build_meta.py | 2 +- setuptools/config/expand.py | 13 ++++++++----- setuptools/config/pyprojecttoml.py | 4 ++-- setuptools/msvc.py | 2 +- setuptools/tests/test_build_ext.py | 2 +- setuptools/tests/test_editable_install.py | 14 +++++++++----- 9 files changed, 24 insertions(+), 21 deletions(-) diff --git a/mypy.ini b/mypy.ini index a9d0fdd7df..26692755ea 100644 --- a/mypy.ini +++ b/mypy.ini @@ -64,6 +64,6 @@ ignore_missing_imports = True # Even when excluding a module, import issues can show up due to following import # https://github.com/python/mypy/issues/11936#issuecomment-1466764006 -[mypy-setuptools.config._validate_pyproject.*,setuptools._distutils.*] +[mypy-setuptools.config._validate_pyproject.*,setuptools._vendor.*,setuptools._distutils.*] follow_imports = silent # silent => ignore errors when following imports diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 4e9b83d83d..f1f0ef2535 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -2777,7 +2777,7 @@ def load( if require: # We could pass `env` and `installer` directly, # but keeping `*args` and `**kwargs` for backwards compatibility - self.require(*args, **kwargs) # type: ignore + self.require(*args, **kwargs) # type: ignore[arg-type] return self.resolve() def resolve(self) -> _ResolvedEntryPoint: diff --git a/pyproject.toml b/pyproject.toml index bfa4d154a7..eb6ec11041 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -216,7 +216,3 @@ formats = "zip" [tool.setuptools_scm] - - -[tool.pytest-enabler.mypy] -# Disabled due to jaraco/skeleton#143 diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index e730a27f25..a3c83c7002 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -431,7 +431,7 @@ def _build(cmd: list[str]): return _build(['bdist_wheel']) try: - return _build(['bdist_wheel', '--dist-info-dir', metadata_directory]) + return _build(['bdist_wheel', '--dist-info-dir', str(metadata_directory)]) except SystemExit as ex: # pragma: nocover # pypa/setuptools#4683 if "--dist-info-dir not recognized" not in str(ex): diff --git a/setuptools/config/expand.py b/setuptools/config/expand.py index e11bcf9b42..8f2040fefa 100644 --- a/setuptools/config/expand.py +++ b/setuptools/config/expand.py @@ -203,7 +203,8 @@ def _load_spec(spec: ModuleSpec, module_name: str) -> ModuleType: return sys.modules[name] module = importlib.util.module_from_spec(spec) sys.modules[name] = module # cache (it also ensures `==` works on loaded items) - spec.loader.exec_module(module) # type: ignore + assert spec.loader is not None + spec.loader.exec_module(module) return module @@ -285,10 +286,11 @@ def find_packages( from setuptools.discovery import construct_package_dir - if namespaces: - from setuptools.discovery import PEP420PackageFinder as PackageFinder + # check "not namespaces" first due to python/mypy#6232 + if not namespaces: + from setuptools.discovery import PackageFinder else: - from setuptools.discovery import PackageFinder # type: ignore + from setuptools.discovery import PEP420PackageFinder as PackageFinder root_dir = root_dir or os.curdir where = kwargs.pop('where', ['.']) @@ -359,7 +361,8 @@ def entry_points(text: str, text_source="entry-points") -> dict[str, dict]: entry-point names, and the second level values are references to objects (that correspond to the entry-point value). """ - parser = ConfigParser(default_section=None, delimiters=("=",)) # type: ignore + # Using undocumented behaviour, see python/typeshed#12700 + parser = ConfigParser(default_section=None, delimiters=("=",)) # type: ignore[call-overload] parser.optionxform = str # case sensitive parser.read_string(text, text_source) groups = {k: dict(v.items()) for k, v in parser.items()} diff --git a/setuptools/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py index 5d95e18b83..e0040cefbd 100644 --- a/setuptools/config/pyprojecttoml.py +++ b/setuptools/config/pyprojecttoml.py @@ -44,8 +44,8 @@ def validate(config: dict, filepath: StrPath) -> bool: trove_classifier = validator.FORMAT_FUNCTIONS.get("trove-classifier") if hasattr(trove_classifier, "_disable_download"): - # Improve reproducibility by default. See issue 31 for validate-pyproject. - trove_classifier._disable_download() # type: ignore + # Improve reproducibility by default. See abravalheri/validate-pyproject#31 + trove_classifier._disable_download() # type: ignore[union-attr] try: return validator.validate(config) diff --git a/setuptools/msvc.py b/setuptools/msvc.py index de4b05f928..7ee685e023 100644 --- a/setuptools/msvc.py +++ b/setuptools/msvc.py @@ -1418,7 +1418,7 @@ def VCRuntimeRedist(self) -> str | None: os.path.join(prefix, arch_subdir, crt_dir, vcruntime) for (prefix, crt_dir) in itertools.product(prefixes, crt_dirs) ) - return next(filter(os.path.isfile, candidate_paths), None) + return next(filter(os.path.isfile, candidate_paths), None) # type: ignore[arg-type] #python/mypy#12682 def return_env(self, exists=True): """ diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py index dab8b41cc9..f3e4ccd364 100644 --- a/setuptools/tests/test_build_ext.py +++ b/setuptools/tests/test_build_ext.py @@ -183,7 +183,7 @@ def get_build_ext_cmd(self, optional: bool, **opts): "eggs.c": "#include missingheader.h\n", ".build": {"lib": {}, "tmp": {}}, } - path.build(files) # type: ignore[arg-type] # jaraco/path#232 + path.build(files) # jaraco/path#232 extension = Extension('spam.eggs', ['eggs.c'], optional=optional) dist = Distribution(dict(ext_modules=[extension])) dist.script_name = 'setup.py' diff --git a/setuptools/tests/test_editable_install.py b/setuptools/tests/test_editable_install.py index 287367ac18..bdbaa3c7e7 100644 --- a/setuptools/tests/test_editable_install.py +++ b/setuptools/tests/test_editable_install.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import platform import stat @@ -8,6 +10,7 @@ from importlib.machinery import EXTENSION_SUFFIXES from pathlib import Path from textwrap import dedent +from typing import Any from unittest.mock import Mock from uuid import uuid4 @@ -840,7 +843,8 @@ class TestOverallBehaviour: version = "3.14159" """ - FLAT_LAYOUT = { + # Any: Would need a TypedDict. Keep it simple for tests + FLAT_LAYOUT: dict[str, Any] = { "pyproject.toml": dedent(PYPROJECT), "MANIFEST.in": EXAMPLE["MANIFEST.in"], "otherfile.py": "", @@ -878,9 +882,9 @@ class TestOverallBehaviour: "otherfile.py": "", "mypkg": { "__init__.py": "", - "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"], # type: ignore + "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"], }, - "other": FLAT_LAYOUT["mypkg"]["subpackage"], # type: ignore + "other": FLAT_LAYOUT["mypkg"]["subpackage"], }, "namespace": { "pyproject.toml": dedent(PYPROJECT), @@ -888,8 +892,8 @@ class TestOverallBehaviour: "otherfile.py": "", "src": { "mypkg": { - "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"], # type: ignore - "subpackage": FLAT_LAYOUT["mypkg"]["subpackage"], # type: ignore + "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"], + "subpackage": FLAT_LAYOUT["mypkg"]["subpackage"], }, }, }, From 1429bf595df92ce2cf901a54d4e17d5d8e44f4f4 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 27 Aug 2024 12:33:34 -0400 Subject: [PATCH 50/58] Fix ConfigHandler generic --- setuptools/config/setupcfg.py | 17 ++++++++++------- setuptools/tests/config/test_setupcfg.py | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py index 54469f74a3..4fee109e26 100644 --- a/setuptools/config/setupcfg.py +++ b/setuptools/config/setupcfg.py @@ -27,7 +27,6 @@ List, Tuple, TypeVar, - Union, cast, ) @@ -53,7 +52,7 @@ while the second element of the tuple is the option value itself """ AllCommandOptions = Dict["str", SingleCommandOptions] # cmd name => its options -Target = TypeVar("Target", bound=Union["Distribution", "DistributionMetadata"]) +Target = TypeVar("Target", "Distribution", "DistributionMetadata") def read_configuration( @@ -96,7 +95,7 @@ def _apply( filepath: StrPath, other_files: Iterable[StrPath] = (), ignore_option_errors: bool = False, -) -> tuple[ConfigHandler, ...]: +) -> tuple[ConfigMetadataHandler, ConfigOptionsHandler]: """Read configuration from ``filepath`` and applies to the ``dist`` object.""" from setuptools.dist import _Distribution @@ -122,7 +121,7 @@ def _apply( return handlers -def _get_option(target_obj: Target, key: str): +def _get_option(target_obj: Distribution | DistributionMetadata, key: str): """ Given a target object and option key, get that option from the target object, either through a get_{key} method or @@ -134,10 +133,14 @@ def _get_option(target_obj: Target, key: str): return getter() -def configuration_to_dict(handlers: tuple[ConfigHandler, ...]) -> dict: +def configuration_to_dict( + handlers: Iterable[ + ConfigHandler[Distribution] | ConfigHandler[DistributionMetadata] + ], +) -> dict: """Returns configuration data gathered by given handlers as a dict. - :param list[ConfigHandler] handlers: Handlers list, + :param Iterable[ConfigHandler] handlers: Handlers list, usually from parse_configuration() :rtype: dict @@ -254,7 +257,7 @@ def __init__( ensure_discovered: expand.EnsurePackagesDiscovered, ): self.ignore_option_errors = ignore_option_errors - self.target_obj = target_obj + self.target_obj: Target = target_obj self.sections = dict(self._section_options(options)) self.set_options: list[str] = [] self.ensure_discovered = ensure_discovered diff --git a/setuptools/tests/config/test_setupcfg.py b/setuptools/tests/config/test_setupcfg.py index 4f0a7349f5..8d95798123 100644 --- a/setuptools/tests/config/test_setupcfg.py +++ b/setuptools/tests/config/test_setupcfg.py @@ -7,7 +7,7 @@ import pytest from packaging.requirements import InvalidRequirement -from setuptools.config.setupcfg import ConfigHandler, read_configuration +from setuptools.config.setupcfg import ConfigHandler, Target, read_configuration from setuptools.dist import Distribution, _Distribution from setuptools.warnings import SetuptoolsDeprecationWarning @@ -16,7 +16,7 @@ from distutils.errors import DistutilsFileError, DistutilsOptionError -class ErrConfigHandler(ConfigHandler): +class ErrConfigHandler(ConfigHandler[Target]): """Erroneous handler. Fails to implement required methods.""" section_prefix = "**err**" From 2072d9876f813dde19ea856751b076265c1d0305 Mon Sep 17 00:00:00 2001 From: Avasam Date: Thu, 17 Oct 2024 12:20:14 -0400 Subject: [PATCH 51/58] Using `dict` as an `OrderedDict` and allowed using `dict` as an ordered type in `setuptools.dist.check_requirements` --- newsfragments/4575.feature.rst | 1 + setuptools/command/_requirestxt.py | 3 +-- setuptools/command/egg_info.py | 5 +---- setuptools/dist.py | 29 +++++++++++++------------- setuptools/tests/test_core_metadata.py | 1 - setuptools/tests/test_dist.py | 10 ++------- 6 files changed, 20 insertions(+), 29 deletions(-) create mode 100644 newsfragments/4575.feature.rst diff --git a/newsfragments/4575.feature.rst b/newsfragments/4575.feature.rst new file mode 100644 index 0000000000..64ab49830f --- /dev/null +++ b/newsfragments/4575.feature.rst @@ -0,0 +1 @@ +Allowed using `dict` as an ordered type in ``setuptools.dist.check_requirements`` -- by :user:`Avasam` diff --git a/setuptools/command/_requirestxt.py b/setuptools/command/_requirestxt.py index b87476d6f4..d426f5dffb 100644 --- a/setuptools/command/_requirestxt.py +++ b/setuptools/command/_requirestxt.py @@ -18,12 +18,11 @@ from packaging.requirements import Requirement from .. import _reqs +from .._reqs import _StrOrIter # dict can work as an ordered set _T = TypeVar("_T") _Ordered = Dict[_T, None] -_ordered = dict -_StrOrIter = _reqs._StrOrIter def _prepare( diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index f4d3a2a57e..bc6c677878 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -2,7 +2,6 @@ Create a distribution's .egg-info directory and contents""" -import collections import functools import os import re @@ -211,11 +210,9 @@ def save_version_info(self, filename): build tag. Install build keys in a deterministic order to avoid arbitrary reordering on subsequent builds. """ - egg_info = collections.OrderedDict() # follow the order these keys would have been added # when PYTHONHASHSEED=0 - egg_info['tag_build'] = self.tags() - egg_info['tag_date'] = 0 + egg_info = dict(tag_build=self.tags(), tag_date=0) edit_config(filename, dict(egg_info=egg_info)) def finalize_options(self): diff --git a/setuptools/dist.py b/setuptools/dist.py index f22e3eea54..d6b8e08214 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -11,12 +11,12 @@ from typing import ( TYPE_CHECKING, Any, + Dict, List, MutableMapping, - NoReturn, + Sequence, Tuple, Union, - overload, ) from more_itertools import partition, unique_everseen @@ -30,7 +30,6 @@ command as _, # noqa: F401 # imported for side-effects ) from ._importlib import metadata -from ._reqs import _StrOrIter from .config import pyprojecttoml, setupcfg from .discovery import ConfigDiscovery from .monkey import get_unpatched @@ -63,7 +62,13 @@ """ _Sequence: TypeAlias = Union[Tuple[str, ...], List[str]] # This is how stringifying _Sequence would look in Python 3.10 -_requence_type_repr = "tuple[str, ...] | list[str]" +_sequence_type_repr = "tuple[str, ...] | list[str]" +_OrderedStrSequence: TypeAlias = Union[str, Dict[str, Any], Sequence[str]] +""" +:meta private: +Avoid single-use iterable. Disallow sets. +A poor approximation of an OrderedSequence (dict doesn't match a Sequence). +""" def __getattr__(name: str) -> Any: # pragma: no cover @@ -97,7 +102,7 @@ def assert_string_list(dist, attr: str, value: _Sequence) -> None: assert ''.join(value) != value except (TypeError, ValueError, AttributeError, AssertionError) as e: raise DistutilsSetupError( - f"{attr!r} must be of type <{_requence_type_repr}> (got {value!r})" + f"{attr!r} must be of type <{_sequence_type_repr}> (got {value!r})" ) from e @@ -173,15 +178,11 @@ def invalid_unless_false(dist, attr, value): raise DistutilsSetupError(f"{attr} is invalid.") -@overload -def check_requirements(dist, attr: str, value: set | dict) -> NoReturn: ... -@overload -def check_requirements(dist, attr: str, value: _StrOrIter) -> None: ... -def check_requirements(dist, attr: str, value: _StrOrIter) -> None: +def check_requirements(dist, attr: str, value: _OrderedStrSequence) -> None: """Verify that install_requires is a valid requirements list""" try: list(_reqs.parse(value)) - if isinstance(value, (dict, set)): + if isinstance(value, set): raise TypeError("Unordered types are not allowed") except (TypeError, ValueError) as error: msg = ( @@ -810,7 +811,7 @@ def _exclude_misc(self, name: str, value: _Sequence) -> None: """Handle 'exclude()' for list/tuple attrs without a special handler""" if not isinstance(value, _sequence): raise DistutilsSetupError( - f"{name}: setting must be of type <{_requence_type_repr}> (got {value!r})" + f"{name}: setting must be of type <{_sequence_type_repr}> (got {value!r})" ) try: old = getattr(self, name) @@ -828,7 +829,7 @@ def _include_misc(self, name: str, value: _Sequence) -> None: if not isinstance(value, _sequence): raise DistutilsSetupError( - f"{name}: setting must be of type <{_requence_type_repr}> (got {value!r})" + f"{name}: setting must be of type <{_sequence_type_repr}> (got {value!r})" ) try: old = getattr(self, name) @@ -870,7 +871,7 @@ def exclude(self, **attrs): def _exclude_packages(self, packages: _Sequence) -> None: if not isinstance(packages, _sequence): raise DistutilsSetupError( - f"packages: setting must be of type <{_requence_type_repr}> (got {packages!r})" + f"packages: setting must be of type <{_sequence_type_repr}> (got {packages!r})" ) list(map(self.exclude_package, packages)) diff --git a/setuptools/tests/test_core_metadata.py b/setuptools/tests/test_core_metadata.py index 34828ac750..51d4a10810 100644 --- a/setuptools/tests/test_core_metadata.py +++ b/setuptools/tests/test_core_metadata.py @@ -310,7 +310,6 @@ def test_parity_with_metadata_from_pypa_wheel(tmp_path): python_requires=">=3.8", install_requires=""" packaging==23.2 - ordered-set==3.1.1 more-itertools==8.8.0; extra == "other" jaraco.text==3.7.0 importlib-resources==5.10.2; python_version<"3.8" diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index fde0de99ac..1bc4923032 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -1,4 +1,3 @@ -import collections import os import re import urllib.parse @@ -72,15 +71,10 @@ def sdist_with_index(distname, version): def test_provides_extras_deterministic_order(): - extras = collections.OrderedDict() - extras['a'] = ['foo'] - extras['b'] = ['bar'] - attrs = dict(extras_require=extras) + attrs = dict(extras_require=dict(a=['foo'], b=['bar'])) dist = Distribution(attrs) assert list(dist.metadata.provides_extras) == ['a', 'b'] - attrs['extras_require'] = collections.OrderedDict( - reversed(list(attrs['extras_require'].items())) - ) + attrs['extras_require'] = dict(reversed(attrs['extras_require'].items())) dist = Distribution(attrs) assert list(dist.metadata.provides_extras) == ['b', 'a'] From 206c4b8913b0cb792e1077673b9db7f3c73e8ac5 Mon Sep 17 00:00:00 2001 From: Avasam Date: Thu, 17 Oct 2024 12:57:21 -0400 Subject: [PATCH 52/58] Bump mypy to 1.12 and pyright to 1.1.385 --- .github/workflows/pyright.yml | 3 ++- mypy.ini | 3 ++- pkg_resources/__init__.py | 2 +- pyproject.toml | 2 +- setuptools/_path.py | 3 ++- setuptools/command/__init__.py | 10 +++++++++- setuptools/command/sdist.py | 3 ++- 7 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pyright.yml b/.github/workflows/pyright.yml index 38fb910d85..17a1e2dbbe 100644 --- a/.github/workflows/pyright.yml +++ b/.github/workflows/pyright.yml @@ -26,7 +26,7 @@ env: # For help with static-typing issues, or pyright update, ping @Avasam # # An exact version from https://github.com/microsoft/pyright/releases or "latest" - PYRIGHT_VERSION: "1.1.377" + PYRIGHT_VERSION: "1.1.385" # Environment variable to support color support (jaraco/skeleton#66) FORCE_COLOR: 1 @@ -73,4 +73,5 @@ jobs: uses: jakebailey/pyright-action@v2 with: version: ${{ env.PYRIGHT_VERSION }} + python-version: ${{ matrix.python }} extra-args: --threads diff --git a/mypy.ini b/mypy.ini index 26692755ea..2dc8aab56f 100644 --- a/mypy.ini +++ b/mypy.ini @@ -52,6 +52,7 @@ disable_error_code = import-not-found # for setuptools to import `_distutils` directly # - or non-stdlib distutils typings are exposed # - The following are not marked as py.typed: +# - jaraco: Since mypy 1.12, the root name of the untyped namespace package gets called-out too # - jaraco.develop: https://github.com/jaraco/jaraco.develop/issues/22 # - jaraco.envs: https://github.com/jaraco/jaraco.envs/issues/7 # - jaraco.packaging: https://github.com/jaraco/jaraco.packaging/issues/20 @@ -59,7 +60,7 @@ disable_error_code = import-not-found # - jaraco.test: https://github.com/jaraco/jaraco.test/issues/7 # - jaraco.text: https://github.com/jaraco/jaraco.text/issues/17 # - wheel: does not intend on exposing a programmatic API https://github.com/pypa/wheel/pull/610#issuecomment-2081687671 -[mypy-distutils.*,jaraco.develop,jaraco.envs,jaraco.packaging.*,jaraco.path,jaraco.test.*,jaraco.text,wheel.*] +[mypy-distutils.*,jaraco,jaraco.develop,jaraco.envs,jaraco.packaging.*,jaraco.path,jaraco.test.*,jaraco.text,wheel.*] ignore_missing_imports = True # Even when excluding a module, import issues can show up due to following import diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index f1f0ef2535..47824ab66e 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -2648,7 +2648,7 @@ def _cygwin_patch(filename: StrOrBytesPath): # pragma: nocover would probably better, in Cygwin even more so, except that this seems to be by design... """ - return os.path.abspath(filename) if sys.platform == 'cygwin' else filename + return os.path.abspath(filename) if sys.platform == 'cygwin' else filename # type: ignore[type-var] # python/mypy#17952 if TYPE_CHECKING: diff --git a/pyproject.toml b/pyproject.toml index eb6ec11041..72fc6df93a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,7 +136,7 @@ type = [ # pin mypy version so a new version doesn't suddenly cause the CI to fail, # until types-setuptools is removed from typeshed. # For help with static-typing issues, or mypy update, ping @Avasam - "mypy==1.11.*", + "mypy==1.12.*", # Typing fixes in version newer than we require at runtime "importlib_metadata>=7.0.2; python_version < '3.10'", # Imported unconditionally in tools/finalize.py diff --git a/setuptools/_path.py b/setuptools/_path.py index dd4a9db8cb..c7bef83365 100644 --- a/setuptools/_path.py +++ b/setuptools/_path.py @@ -11,9 +11,10 @@ from more_itertools import unique_everseen -if sys.version_info >= (3, 9): +if TYPE_CHECKING: StrPath: TypeAlias = Union[str, os.PathLike[str]] # Same as _typeshed.StrPath else: + # Python 3.8 support StrPath: TypeAlias = Union[str, os.PathLike] diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py index bf011e896d..50e6c2f54f 100644 --- a/setuptools/command/__init__.py +++ b/setuptools/command/__init__.py @@ -1,12 +1,20 @@ +# mypy: disable_error_code=call-overload +# pyright: reportCallIssue=false, reportArgumentType=false +# Can't disable on the exact line because distutils doesn't exists on Python 3.12 +# and type-checkers aren't aware of distutils_hack, +# causing distutils.command.bdist.bdist.format_commands to be Any. + import sys from distutils.command.bdist import bdist if 'egg' not in bdist.format_commands: try: + # format_commands is a dict in vendored distutils + # It used to be a list in older (stdlib) distutils + # We support both for backwards compatibility bdist.format_commands['egg'] = ('bdist_egg', "Python .egg file") except TypeError: - # For backward compatibility with older distutils (stdlib) bdist.format_command['egg'] = ('bdist_egg', "Python .egg file") bdist.format_commands.append('egg') diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index fa9a2c4d81..65ce735dde 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -4,6 +4,7 @@ import os import re from itertools import chain +from typing import ClassVar from .._importlib import metadata from ..dist import Distribution @@ -49,7 +50,7 @@ class sdist(orig.sdist): ] distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution - negative_opt: dict[str, str] = {} + negative_opt: ClassVar[dict[str, str]] = {} README_EXTENSIONS = ['', '.rst', '.txt', '.md'] READMES = tuple('README{0}'.format(ext) for ext in README_EXTENSIONS) From e6ba3ad63812c4160a4b0d585316329ceb750387 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Mon, 21 Oct 2024 15:11:21 +0100 Subject: [PATCH 53/58] Remove unused type ignore --- pkg_resources/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 47824ab66e..f1f0ef2535 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -2648,7 +2648,7 @@ def _cygwin_patch(filename: StrOrBytesPath): # pragma: nocover would probably better, in Cygwin even more so, except that this seems to be by design... """ - return os.path.abspath(filename) if sys.platform == 'cygwin' else filename # type: ignore[type-var] # python/mypy#17952 + return os.path.abspath(filename) if sys.platform == 'cygwin' else filename if TYPE_CHECKING: From 9cefa0af0cbfda14d8852ad8d55ca2ca29a99983 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 12 Aug 2024 13:02:44 -0400 Subject: [PATCH 54/58] Disable and revert unpacked-list-comprehension (UP027) --- ruff.toml | 5 +++-- setuptools/package_index.py | 5 +---- setuptools/tests/test_egg_info.py | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ruff.toml b/ruff.toml index b55b4e8067..53d644f6a5 100644 --- a/ruff.toml +++ b/ruff.toml @@ -12,18 +12,19 @@ extend-select = [ # local "ANN2", # missing-return-type-* - "FA", # flake8-future-annotations "F404", # late-future-import + "FA", # flake8-future-annotations "I", # isort "PYI", # flake8-pyi + "TRY", # tryceratops "UP", # pyupgrade - "TRY", "YTT", # flake8-2020 ] ignore = [ "TRY003", # raise-vanilla-args, avoid multitude of exception classes "TRY301", # raise-within-try, it's handy "UP015", # redundant-open-modes, explicit is preferred + "UP027", # unpacked-list-comprehension, is actually slower for cases relevant to unpacking, set for deprecation: https://github.com/astral-sh/ruff/issues/12754 "UP030", # temporarily disabled "UP031", # temporarily disabled "UP032", # temporarily disabled diff --git a/setuptools/package_index.py b/setuptools/package_index.py index 9e01d5e082..9b3769fac9 100644 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -561,10 +561,7 @@ def not_found_in_index(self, requirement): if self[requirement.key]: # we've seen at least one distro meth, msg = self.info, "Couldn't retrieve index page for %r" else: # no distros seen for this name, might be misspelled - meth, msg = ( - self.warn, - "Couldn't find index page for %r (maybe misspelled?)", - ) + meth, msg = self.warn, "Couldn't find index page for %r (maybe misspelled?)" meth(msg, requirement.unsafe_name) self.scan_all() diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index 6e8d0c68c3..12d6b30a8b 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -283,9 +283,9 @@ def parametrize(*test_list, **format_dict): else: install_cmd_kwargs = {} name = name_kwargs[0].strip() - setup_py_requires, setup_cfg_requires, expected_requires = ( + setup_py_requires, setup_cfg_requires, expected_requires = [ DALS(a).format(**format_dict) for a in test_params - ) + ] for id_, requires, use_cfg in ( (name, setup_py_requires, False), (name + '_in_setup_cfg', setup_cfg_requires, True), From 5bc3ebfe66a218261ca2884766a1e6c906ff70c4 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Mon, 21 Oct 2024 12:53:41 +0100 Subject: [PATCH 55/58] Fix possible invalid dynamic behaviour of optional-dependencies --- setuptools/config/_apply_pyprojecttoml.py | 6 ++-- .../config/test_pyprojecttoml_dynamic_deps.py | 32 ++++++++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py index 23179f3548..16fe753b58 100644 --- a/setuptools/config/_apply_pyprojecttoml.py +++ b/setuptools/config/_apply_pyprojecttoml.py @@ -217,8 +217,10 @@ def _dependencies(dist: Distribution, val: list, _root_dir): def _optional_dependencies(dist: Distribution, val: dict, _root_dir): - existing = getattr(dist, "extras_require", None) or {} - dist.extras_require = {**existing, **val} + if getattr(dist, "extras_require", None): + msg = "`extras_require` overwritten in `pyproject.toml` (optional-dependencies)" + SetuptoolsWarning.emit(msg) + dist.extras_require = val def _ext_modules(dist: Distribution, val: list[dict]) -> list[Extension]: diff --git a/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py b/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py index 37e5234a45..e42f28ffaa 100644 --- a/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py +++ b/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py @@ -5,6 +5,7 @@ from setuptools.config.pyprojecttoml import apply_configuration from setuptools.dist import Distribution +from setuptools.warnings import SetuptoolsWarning def test_dynamic_dependencies(tmp_path): @@ -77,23 +78,32 @@ def test_mixed_dynamic_optional_dependencies(tmp_path): [tool.setuptools.dynamic.optional-dependencies.images] file = ["requirements-images.txt"] - - [build-system] - requires = ["setuptools", "wheel"] - build-backend = "setuptools.build_meta" """ ), } path.build(files, prefix=tmp_path) - - # Test that the mix-and-match doesn't currently validate. pyproject = tmp_path / "pyproject.toml" with pytest.raises(ValueError, match="project.optional-dependencies"): apply_configuration(Distribution(), pyproject) - # Explicitly disable the validation and try again, to see that the mix-and-match - # result would be correct. - dist = Distribution() - dist = apply_configuration(dist, pyproject, ignore_option_errors=True) - assert dist.extras_require == {"docs": ["sphinx"], "images": ["pillow~=42.0"]} + +def test_mixed_extras_require_optional_dependencies(tmp_path): + files = { + "pyproject.toml": cleandoc( + """ + [project] + name = "myproj" + version = "1.0" + optional-dependencies.docs = ["sphinx"] + """ + ), + } + + path.build(files, prefix=tmp_path) + pyproject = tmp_path / "pyproject.toml" + + with pytest.warns(SetuptoolsWarning, match=".extras_require. overwritten"): + dist = Distribution({"extras_require": {"hello": ["world"]}}) + dist = apply_configuration(dist, pyproject) + assert dist.extras_require == {"docs": ["sphinx"]} From 446e58f7a147015e0946e76083d86db8e30e7ffb Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Mon, 21 Oct 2024 11:30:28 +0100 Subject: [PATCH 56/58] Addopt modern syntax sugar in setupcfg.py --- setuptools/config/setupcfg.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py index 4fee109e26..5b4e1e8d95 100644 --- a/setuptools/config/setupcfg.py +++ b/setuptools/config/setupcfg.py @@ -304,7 +304,7 @@ def __setitem__(self, option_name, value) -> None: return simple_setter = functools.partial(target_obj.__setattr__, option_name) - setter = getattr(target_obj, 'set_%s' % option_name, simple_setter) + setter = getattr(target_obj, f"set_{option_name}", simple_setter) setter(parsed) self.set_options.append(option_name) @@ -372,8 +372,8 @@ def parser(value): exclude_directive = 'file:' if value.startswith(exclude_directive): raise ValueError( - 'Only strings are accepted for the {0} field, ' - 'files are not accepted'.format(key) + f'Only strings are accepted for the {key} field, ' + 'files are not accepted' ) return value @@ -491,12 +491,12 @@ def parse(self) -> None: for section_name, section_options in self.sections.items(): method_postfix = '' if section_name: # [section.option] variant - method_postfix = '_%s' % section_name + method_postfix = f"_{section_name}" section_parser_method: Callable | None = getattr( self, # Dots in section names are translated into dunderscores. - ('parse_section%s' % method_postfix).replace('.', '__'), + f'parse_section{method_postfix}'.replace('.', '__'), None, ) @@ -701,10 +701,7 @@ def parse_section_packages__find(self, section_options): section_data = self._parse_section_to_dict(section_options, self._parse_list) valid_keys = ['where', 'include', 'exclude'] - - find_kwargs = dict([ - (k, v) for k, v in section_data.items() if k in valid_keys and v - ]) + find_kwargs = {k: v for k, v in section_data.items() if k in valid_keys and v} where = find_kwargs.get('where') if where is not None: From 2c99d1e350512c7020c516ccea0d64848399b5d3 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Mon, 21 Oct 2024 16:17:01 +0100 Subject: [PATCH 57/58] Add news fragment --- newsfragments/4696.bugfix.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 newsfragments/4696.bugfix.rst diff --git a/newsfragments/4696.bugfix.rst b/newsfragments/4696.bugfix.rst new file mode 100644 index 0000000000..77ebf87c48 --- /dev/null +++ b/newsfragments/4696.bugfix.rst @@ -0,0 +1,4 @@ +Fix clashes for ``optional-dependencies`` in ``pyproject.toml`` and +``extra_requires`` in ``setup.cfg/setup.py``. +As per PEP 621, ``optional-dependencies`` has to be honoured and dynamic +behaviour is not allowed. From 99c75c945ac7afd3d37ca918125a157492e959da Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 21 Oct 2024 12:24:22 -0400 Subject: [PATCH 58/58] Ensured all methods in `setuptools.modified` raise a consistant `distutils.error.DistutilsError` type (#4567) * Ensured all methods in `setuptools.modified` raise a consistant `distutils.error.DistutilsError` type * Update tests to reflect runtime behaviour with SETUPTOOLS_USE_DISTUTILS=stdlib * Update newsfragments/4567.bugfix.rst Co-authored-by: Anderson Bravalheri * Attempt to fix setuptools/tests/test_distutils_adoption.py * can't use setuptoolsrelative import in test * Fix formatting error --------- Co-authored-by: Anderson Bravalheri Co-authored-by: Anderson Bravalheri --- newsfragments/4567.bugfix.rst | 4 ++ setuptools/command/build_clib.py | 9 +--- setuptools/modified.py | 22 +++++++--- setuptools/tests/test_distutils_adoption.py | 47 +++++++++++++++++++-- 4 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 newsfragments/4567.bugfix.rst diff --git a/newsfragments/4567.bugfix.rst b/newsfragments/4567.bugfix.rst new file mode 100644 index 0000000000..7d7bb282e1 --- /dev/null +++ b/newsfragments/4567.bugfix.rst @@ -0,0 +1,4 @@ +Ensured methods in ``setuptools.modified`` preferably raise a consistent +``distutils.errors.DistutilsError`` type +(except in the deprecated use case of ``SETUPTOOLS_USE_DISTUTILS=stdlib``) +-- by :user:`Avasam` diff --git a/setuptools/command/build_clib.py b/setuptools/command/build_clib.py index d532762ebe..eab08e70f2 100644 --- a/setuptools/command/build_clib.py +++ b/setuptools/command/build_clib.py @@ -1,17 +1,10 @@ from ..dist import Distribution +from ..modified import newer_pairwise_group import distutils.command.build_clib as orig from distutils import log from distutils.errors import DistutilsSetupError -try: - from distutils._modified import ( # pyright: ignore[reportMissingImports] - newer_pairwise_group, - ) -except ImportError: - # fallback for SETUPTOOLS_USE_DISTUTILS=stdlib - from .._distutils._modified import newer_pairwise_group - class build_clib(orig.build_clib): """ diff --git a/setuptools/modified.py b/setuptools/modified.py index 245a61580b..6ba02fab68 100644 --- a/setuptools/modified.py +++ b/setuptools/modified.py @@ -1,8 +1,18 @@ -from ._distutils._modified import ( - newer, - newer_group, - newer_pairwise, - newer_pairwise_group, -) +try: + # Ensure a DistutilsError raised by these methods is the same as distutils.errors.DistutilsError + from distutils._modified import ( + newer, + newer_group, + newer_pairwise, + newer_pairwise_group, + ) +except ImportError: + # fallback for SETUPTOOLS_USE_DISTUTILS=stdlib, because _modified never existed in stdlib + from ._distutils._modified import ( + newer, + newer_group, + newer_pairwise, + newer_pairwise_group, + ) __all__ = ['newer', 'newer_pairwise', 'newer_group', 'newer_pairwise_group'] diff --git a/setuptools/tests/test_distutils_adoption.py b/setuptools/tests/test_distutils_adoption.py index 0b020ba9fc..aabfdd283a 100644 --- a/setuptools/tests/test_distutils_adoption.py +++ b/setuptools/tests/test_distutils_adoption.py @@ -114,6 +114,7 @@ def test_distutils_has_origin(): """ +@pytest.mark.usefixtures("tmpdir_cwd") @pytest.mark.parametrize( "distutils_version, imported_module", [ @@ -125,9 +126,7 @@ def test_distutils_has_origin(): ("local", "archive_util"), ], ) -def test_modules_are_not_duplicated_on_import( - distutils_version, imported_module, tmpdir_cwd, venv -): +def test_modules_are_not_duplicated_on_import(distutils_version, imported_module, venv): env = dict(SETUPTOOLS_USE_DISTUTILS=distutils_version) script = ENSURE_IMPORTS_ARE_NOT_DUPLICATED.format(imported_module=imported_module) cmd = ['python', '-c', script] @@ -145,6 +144,7 @@ def test_modules_are_not_duplicated_on_import( """ +@pytest.mark.usefixtures("tmpdir_cwd") @pytest.mark.parametrize( "distutils_version", [ @@ -152,8 +152,47 @@ def test_modules_are_not_duplicated_on_import( pytest.param("stdlib", marks=skip_without_stdlib_distutils), ], ) -def test_log_module_is_not_duplicated_on_import(distutils_version, tmpdir_cwd, venv): +def test_log_module_is_not_duplicated_on_import(distutils_version, venv): env = dict(SETUPTOOLS_USE_DISTUTILS=distutils_version) cmd = ['python', '-c', ENSURE_LOG_IMPORT_IS_NOT_DUPLICATED] output = venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS).strip() assert output == "success" + + +ENSURE_CONSISTENT_ERROR_FROM_MODIFIED_PY = r""" +from setuptools.modified import newer +from {imported_module}.errors import DistutilsError + +# Can't use pytest.raises in this context +try: + newer("", "") +except DistutilsError: + print("success") +else: + raise AssertionError("Expected to raise") +""" + + +@pytest.mark.usefixtures("tmpdir_cwd") +@pytest.mark.parametrize( + "distutils_version, imported_module", + [ + ("local", "distutils"), + # Unfortunately we still get ._distutils.errors.DistutilsError with SETUPTOOLS_USE_DISTUTILS=stdlib + # But that's a deprecated use-case we don't mind not fully supporting in newer code + pytest.param( + "stdlib", "setuptools._distutils", marks=skip_without_stdlib_distutils + ), + ], +) +def test_consistent_error_from_modified_py(distutils_version, imported_module, venv): + env = dict(SETUPTOOLS_USE_DISTUTILS=distutils_version) + cmd = [ + 'python', + '-c', + ENSURE_CONSISTENT_ERROR_FROM_MODIFIED_PY.format( + imported_module=imported_module + ), + ] + output = venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS).strip() + assert output == "success"