From 5118af40e127ff88124c2f81304a456476cd1826 Mon Sep 17 00:00:00 2001 From: Karl Knechtel Date: Tue, 30 Jul 2024 19:35:10 -0400 Subject: [PATCH 1/5] Check for error importing setuptools.command.test In Setuptools 72.0.0, `setuptools.command.test` was temporarily removed. This version of Setuptools was yanked, and 72.1.0 restored the functionality (it appears that the removal didn't follow the standard process, but this functionality has been deprecated for many years). The functionality will be removed in a future version (probably very soon) and that would break this `setup.py` as a result. This commit, as a first step, handles the `ImportError` that results when `setuptools.command.test` is removed, and only attempts to define the `PyTest` command (the only thing depending on it) when it's available. For more details, please see: https://github.com/pypa/setuptools/issues/4519 Although many projects were affected by the change, this one was the unlucky "winner" of a popular Stack Overflow Q&A drawing attention to the general issue: https://stackoverflow.com/questions/78806100 --- setup.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/setup.py b/setup.py index 266d812..2956766 100644 --- a/setup.py +++ b/setup.py @@ -3,19 +3,25 @@ import sys from setuptools import setup -from setuptools.command.test import test as TestCommand +try: + from setuptools.command.test import test as TestCommand +except ImportError: + # setuptools.command.test has been removed. + # Since we can't define `PyTest`, there are no `cmdclass`es to use. + CMDCLASS = {} +else: + class PyTest(TestCommand): + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = ['--strict-markers', '--verbose', '--tb=long'] + self.test_suite = True - -class PyTest(TestCommand): - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = ['--strict-markers', '--verbose', '--tb=long'] - self.test_suite = True - - def run_tests(self): - import pytest - errcode = pytest.main(self.test_args) - sys.exit(errcode) + def run_tests(self): + import pytest + errcode = pytest.main(self.test_args) + sys.exit(errcode) + # Use `PyTest` as a `cmdclass`. + CMDCLASS = {'test': PyTest} setup( @@ -43,9 +49,7 @@ def run_tests(self): include_package_data=True, install_requires=[], tests_require=['pytest'], - cmdclass={'test': PyTest}, + cmdclass=CMDCLASS, test_suite='random_words.test.test_random_words', - extras_require={ - 'testing': ['pytest'], - }, + extras_require={ 'testing': ['pytest'] }, ) From 025b7a72d641b07a5972f9f3e92e72838a2e0acf Mon Sep 17 00:00:00 2001 From: Karl Knechtel Date: Tue, 30 Jul 2024 19:51:03 -0400 Subject: [PATCH 2/5] Migrate metadata to pyproject.toml A `pyproject.toml` file is created to represent project metadata, following the standards laid out in PEP 621 and implemented by modern Setuptools: https://peps.python.org/pep-0621/ Data which can be represented this way, and which is not specific to the Setuptools build system, has been moved across and removed from the `setup` keyword arguments. --- pyproject.toml | 26 ++++++++++++++++++++++++++ setup.py | 20 -------------------- 2 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..907d217 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[project] +name='RandomWords' +version='0.4.0' +description='A useful module for a random text, e-mails and lorem ipsum.' +readme = "README.rst" +classifiers = [ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python', + 'Natural Language :: English', + 'Development Status :: 5 - Production/Stable', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Topic :: Software Development :: Libraries :: Python Modules' +] +dependencies = [] +license = {file = "LICENSE.txt"} + +[[project.authors]] +name='Tomek Święcicki' +email='tomislater@gmail.com' + +[project.urls] +homepage='https://github.com/tomislater/RandomWords' diff --git a/setup.py b/setup.py index 2956766..39ccbaf 100644 --- a/setup.py +++ b/setup.py @@ -25,27 +25,7 @@ def run_tests(self): setup( - name='RandomWords', - version='0.4.0', - author='Tomek Święcicki', - author_email='tomislater@gmail.com', packages=['random_words'], - url='https://github.com/tomislater/RandomWords', - license='LICENSE.txt', - description='A useful module for a random text, e-mails and lorem ipsum.', - long_description=open('README.rst').read(), - classifiers=[ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Natural Language :: English', - 'Development Status :: 5 - Production/Stable', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], include_package_data=True, install_requires=[], tests_require=['pytest'], From c1765e5f9436765bbfb3fdbfbdf3b5c62fee20ee Mon Sep 17 00:00:00 2001 From: Karl Knechtel Date: Tue, 30 Jul 2024 20:03:56 -0400 Subject: [PATCH 3/5] Improve style Add spaces around equals signs, and use double-quotes for strings. This is the standard formatting for TOML that will be output by typical tools (although the other way is also valid). --- pyproject.toml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 907d217..c37828a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,26 +1,26 @@ [project] -name='RandomWords' -version='0.4.0' -description='A useful module for a random text, e-mails and lorem ipsum.' +name = "RandomWords" +version = "0.4.0" +description = "A useful module for a random text, e-mails and lorem ipsum." readme = "README.rst" classifiers = [ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Natural Language :: English', - 'Development Status :: 5 - Production/Stable', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Topic :: Software Development :: Libraries :: Python Modules' + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Natural Language :: English", + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Software Development :: Libraries :: Python Modules" ] dependencies = [] license = {file = "LICENSE.txt"} [[project.authors]] -name='Tomek Święcicki' -email='tomislater@gmail.com' +name = "Tomek Święcicki" +email = "tomislater@gmail.com" [project.urls] -homepage='https://github.com/tomislater/RandomWords' +homepage = "https://github.com/tomislater/RandomWords" From ad3cbd7d00938ff44dea422b176e7dab1509f661 Mon Sep 17 00:00:00 2001 From: Karl Knechtel Date: Tue, 30 Jul 2024 20:07:00 -0400 Subject: [PATCH 4/5] Use pyproject.toml for package discovery `pyproject.toml` can also do basic configuration of Setuptools for building packages, as shown. Note that `include-package-data` is set as `True` by default when using `pyproject.toml`. --- pyproject.toml | 3 +++ setup.py | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c37828a..5374b2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,3 +24,6 @@ email = "tomislater@gmail.com" [project.urls] homepage = "https://github.com/tomislater/RandomWords" + +[tool.setuptools] +packages = [ "random_words" ] diff --git a/setup.py b/setup.py index 39ccbaf..aa0c816 100644 --- a/setup.py +++ b/setup.py @@ -25,9 +25,6 @@ def run_tests(self): setup( - packages=['random_words'], - include_package_data=True, - install_requires=[], tests_require=['pytest'], cmdclass=CMDCLASS, test_suite='random_words.test.test_random_words', From 7e4b5eca924c7e73ae50f2a5208c6e6ccc02d3a2 Mon Sep 17 00:00:00 2001 From: Karl Knechtel Date: Tue, 30 Jul 2024 20:12:32 -0400 Subject: [PATCH 5/5] Refactoring With the modern approach using `pyproject.toml`, it's not actually necessary to call `setup` to build the project, as long as all necessary information for wheel building is in `pyproject.toml` (which it is now). Therefore, the script now immediately bails out whenever `setuptools.command.test` is not available, and uses a minimal `setup` invocation when it is. Going forward, it is strongly recommended to remove `setup.py` entirely. A `setup.py` file is only required for projects that need to build extensions or have some other compelling reason to execute arbitrary code at build time. The interface implemented here - i.e. calling `setup.py test` - is deprecated, and has been for many years. Instead, consider using a shell alias to make the desired command (i.e. `python -m pytest --strict-markers --verbose --tb=long`) easier to use. For more details, please see: https://setuptools.pypa.io/en/latest/deprecated/commands.html --- setup.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/setup.py b/setup.py index aa0c816..5995cac 100644 --- a/setup.py +++ b/setup.py @@ -7,26 +7,28 @@ from setuptools.command.test import test as TestCommand except ImportError: # setuptools.command.test has been removed. - # Since we can't define `PyTest`, there are no `cmdclass`es to use. - CMDCLASS = {} -else: - class PyTest(TestCommand): - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = ['--strict-markers', '--verbose', '--tb=long'] - self.test_suite = True + # The `setup` call is only necessary when defining this `cmdclass`, since + # building the project is no longer dependend on that. + # Therefore, there's no more to do here. + sys.exit() - def run_tests(self): - import pytest - errcode = pytest.main(self.test_args) - sys.exit(errcode) - # Use `PyTest` as a `cmdclass`. - CMDCLASS = {'test': PyTest} +class PyTest(TestCommand): + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = ['--strict-markers', '--verbose', '--tb=long'] + self.test_suite = True + def run_tests(self): + import pytest + errcode = pytest.main(self.test_args) + sys.exit(errcode) + + +# Use `PyTest` as a `cmdclass`. setup( tests_require=['pytest'], - cmdclass=CMDCLASS, + cmdclass={'test': PyTest}, test_suite='random_words.test.test_random_words', - extras_require={ 'testing': ['pytest'] }, + extras_require={ 'testing': ['pytest'] } )