From 5bfaee154cd713252adae4b12e7a6ec83c7ae6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 17 May 2021 19:29:48 +0200 Subject: [PATCH] Add support for PEP 621 metadata in flit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: https://github.com/mgorny/pyproject2setuppy/issues/16 Signed-off-by: Michał Górny --- pyproject2setuppy/flit.py | 83 +++++++---- tests/test_flit_pep621.py | 305 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 356 insertions(+), 32 deletions(-) create mode 100644 tests/test_flit_pep621.py diff --git a/pyproject2setuppy/flit.py b/pyproject2setuppy/flit.py index 72418fd..08e0fff 100644 --- a/pyproject2setuppy/flit.py +++ b/pyproject2setuppy/flit.py @@ -1,6 +1,6 @@ # pyproject2setup.py -- flit support # vim:se fileencoding=utf-8 : -# (c) 2019-2020 Michał Górny +# (c) 2019-2021 Michał Górny # 2-clause BSD license from __future__ import absolute_import @@ -13,6 +13,7 @@ import sys from pyproject2setuppy.common import auto_find_packages +from pyproject2setuppy.pep621 import get_pep621_metadata def handle_flit(data): @@ -21,39 +22,57 @@ def handle_flit(data): system. """ - topdata = data['tool']['flit'] - metadata = topdata['metadata'] - modname = metadata['module'] - sys.path.insert(0, '.') - mod = importlib.import_module(modname, '') - - entry_points = defaultdict(list) - if 'scripts' in topdata: - for name, content in topdata['scripts'].items(): - entry_points['console_scripts'].append( - '{} = {}'.format(name, content) - ) - - if 'entrypoints' in topdata: - for group_name, group_content in topdata['entrypoints'].items(): - for name, path in group_content.items(): - entry_points[group_name].append( - '{} = {}'.format(name, path) + # try PEP 621 first + setup_metadata = get_pep621_metadata(data, ['version', 'description']) + if setup_metadata is not None: + if 'flit' in data.get('tool', {}): + raise ValueError('[project] and [tool.flit] cannot be present ' + 'simultaneously') + else: + # tool.flit fallback + topdata = data['tool']['flit'] + metadata = topdata['metadata'] + + entry_points = defaultdict(list) + if 'scripts' in topdata: + for name, content in topdata['scripts'].items(): + entry_points['console_scripts'].append( + '{} = {}'.format(name, content) ) - package_args = auto_find_packages(modname) - - setup(name=modname, - version=mod.__version__, - description=mod.__doc__.strip(), - author=metadata['author'], - author_email=metadata['author-email'], - url=metadata.get('home-page'), - classifiers=metadata.get('classifiers', []), - entry_points=dict(entry_points), - # hack stolen from flit - package_data={'': ['*']}, - **package_args) + if 'entrypoints' in topdata: + for group_name, group_content in topdata['entrypoints'].items(): + for name, path in group_content.items(): + entry_points[group_name].append( + '{} = {}'.format(name, path) + ) + + setup_metadata = { + 'name': metadata['module'], + # use None to match PEP 621 return value for dynamic + 'version': None, + 'description': None, + 'author': metadata['author'], + 'author_email': metadata['author-email'], + 'url': metadata.get('home-page'), + 'classifiers': metadata.get('classifiers', []), + 'entry_points': dict(entry_points), + } + + # handle dynamic metadata if necessary + modname = setup_metadata['name'] + if None in [setup_metadata[x] for x in ('version', 'description')]: + sys.path.insert(0, '.') + mod = importlib.import_module(modname, '') + if setup_metadata['version'] is None: + setup_metadata['version'] = mod.__version__ + if setup_metadata['description'] is None: + setup_metadata['description'] = mod.__doc__.strip() + + setup_metadata.update(auto_find_packages(modname)) + + setup(package_data={'': ['*']}, # hack stolen from flit + **setup_metadata) def handle_flit_thyself(data): diff --git a/tests/test_flit_pep621.py b/tests/test_flit_pep621.py new file mode 100644 index 0000000..ad3852e --- /dev/null +++ b/tests/test_flit_pep621.py @@ -0,0 +1,305 @@ +# vim:se fileencoding=utf-8 : +# (c) 2019-2021 Michał Górny +# 2-clause BSD license + +import unittest + +from pyproject2setuppy.flit import handle_flit, handle_flit_thyself + +from tests.base import BuildSystemTestCase + + +class FlitTestCase(BuildSystemTestCase): + """ + Tests for the flit build system. + """ + + toml_base = ''' +[build-system] +requires = ["flit"] +build-backend = "flit.buildapi" + +[project] +name = "test_module" +authors = [ + {name="Some Guy", email="guy@example.com"}, +] +dynamic = ["version", "description"] +''' + + expected_base = { + 'name': 'test_module', + 'version': '0', + 'description': 'documentation.', + 'author': 'Some Guy', + 'author_email': 'guy@example.com', + 'classifiers': [], + 'entry_points': {}, + 'package_data': {'': ['*']}, + } + + package_files = ['test_module.py'] + + handler = staticmethod(handle_flit) + + def make_package(self): + """ + Make a flit-compatible packags. Adds docstring and version + to the first .py file in package_files. + """ + + d = super(FlitTestCase, self).make_package() + with open(self.package_files[0], 'w') as f: + f.write(''' +""" documentation. """ +__version__ = '0' +''') + return d + + +class FlitBasicTest(unittest.TestCase, FlitTestCase): + """ + Test handling a simple flit package. + """ + + expected_extra = { + 'py_modules': ['test_module'], + } + + +class FlitVersionTest(unittest.TestCase, FlitTestCase): + """ + Test handling a package with non-dynamic version. + """ + + toml_base = ''' +[build-system] +requires = ["flit"] +build-backend = "flit.buildapi" + +[project] +name = "test_module" +version = "0" +authors = [ + {name="Some Guy", email="guy@example.com"}, +] +dynamic = ["description"] +''' + + expected_extra = { + 'py_modules': ['test_module'], + } + + +class FlitDescriptionTest(unittest.TestCase, FlitTestCase): + """ + Test handling a package with non-dynamic description. + """ + + toml_base = ''' +[build-system] +requires = ["flit"] +build-backend = "flit.buildapi" + +[project] +name = "test_module" +description = "documentation." +authors = [ + {name="Some Guy", email="guy@example.com"}, +] +dynamic = ["version"] +''' + + expected_extra = { + 'py_modules': ['test_module'], + } + + +class FlitVersionDescriptionTest(unittest.TestCase, FlitTestCase): + """ + Test handling a package with non-dynamic version and description. + """ + + toml_base = ''' +[build-system] +requires = ["flit"] +build-backend = "flit.buildapi" + +[project] +name = "test_module" +version = "0" +description = "documentation." +authors = [ + {name="Some Guy", email="guy@example.com"}, +] +''' + + expected_extra = { + 'py_modules': ['test_module'], + } + + +class FlitClassifiersTest(unittest.TestCase, FlitTestCase): + """ + Test handling a flit package with trove classifiers. + """ + + toml_extra = ''' +classifiers = [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3" +] +''' + + expected_extra = { + 'py_modules': ['test_module'], + 'classifiers': [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3" + ], + } + + +class FlitPackageTest(unittest.TestCase, FlitTestCase): + """ + Test handling a flit package containing a package instead of module. + """ + + package_files = ['test_module/__init__.py'] + + expected_extra = { + 'packages': ['test_module'] + } + + +class FlitExtraFilesTest(unittest.TestCase, FlitTestCase): + """ + Test handling a flit package with non-python files. + """ + + package_files = ['test_module/__init__.py', + 'test_module/VERSION'] + + expected_extra = { + 'packages': ['test_module'], + } + expected_extra_files = [ + 'test_module/VERSION', + ] + + +class FlitNestedPackageTest(unittest.TestCase, FlitTestCase): + """ + Test handling a flit package containing nested packages. + """ + + package_files = [ + 'test_module/__init__.py', + 'test_module/sub_module/__init__.py', + 'test_module/sub_module/subsub/__init__.py', + ] + + expected_extra = { + 'packages': [ + 'test_module', + 'test_module.sub_module', + 'test_module.sub_module.subsub', + ] + } + + +class FlitCoreTest(FlitTestCase): + """Test for using flit_core backend""" + + toml_base = ''' +[build-system] +requires = ["flit_core"] +build-backend = "flit_core.buildapi" + +[project] +name = "test_module" +authors = [ + {name="Some Guy", email="guy@example.com"}, +] +dynamic = ["version", "description"] +''' + + +class FlitScriptsTest(unittest.TestCase, FlitTestCase): + """Test handling scripts""" + + toml_extra = ''' +[project.scripts] +test-tool = "testlib:main" +''' + + expected_extra = { + 'entry_points': { + 'console_scripts': [ + 'test-tool = testlib:main', + ] + }, + 'py_modules': ['test_module'], + } + + +class FlitGUIScriptsTest(unittest.TestCase, FlitTestCase): + """Test handling scripts""" + + toml_extra = ''' +[project.gui-scripts] +test-tool = "testlib:main" +''' + + expected_extra = { + 'entry_points': { + 'gui_scripts': [ + 'test-tool = testlib:main', + ] + }, + 'py_modules': ['test_module'], + } + + +class FlitEntryPointsTest(unittest.TestCase, FlitTestCase): + """Test handling entry points""" + + toml_extra = ''' +[project.entrypoints."blogtool.parsers"] +".rst" = "some_module:SomeClass" +''' + + expected_extra = { + 'entry_points': { + 'blogtool.parsers': [ + '.rst = some_module:SomeClass', + ] + }, + 'py_modules': ['test_module'], + } + + +class FlitPluginsAndScriptsTest(unittest.TestCase, FlitTestCase): + """Test handling plugins and scripts""" + + toml_extra = ''' +[project.scripts] +test-tool = "testlib:main" + +[project.entrypoints."blogtool.parsers"] +".rst" = "some_module:SomeClass" +''' + + expected_extra = { + 'entry_points': { + 'console_scripts': [ + 'test-tool = testlib:main', + ], + 'blogtool.parsers': [ + '.rst = some_module:SomeClass', + ] + }, + 'py_modules': ['test_module'], + }