From 0c421684ddeed6c598a46707e285454b8290d66e Mon Sep 17 00:00:00 2001 From: Frances Hartwell Date: Fri, 1 Mar 2024 13:04:57 -0700 Subject: [PATCH] Transition from using setup.py to pyroject.toml to specify project metadata (#379) --- .github/workflows/readme.yml | 2 +- .github/workflows/tutorials.yml | 2 +- Makefile | 3 +- setup.py => pyproject.toml | 130 ++++++++++++++++---------------- setup.cfg | 15 +--- tasks.py | 72 +++++++++++------- tests/test_tasks.py | 36 +++++++++ 7 files changed, 148 insertions(+), 112 deletions(-) rename setup.py => pyproject.toml (53%) create mode 100644 tests/test_tasks.py diff --git a/.github/workflows/readme.yml b/.github/workflows/readme.yml index 9b0d636a..4d76f2da 100644 --- a/.github/workflows/readme.yml +++ b/.github/workflows/readme.yml @@ -21,6 +21,6 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install invoke rundoc . + python -m pip install invoke tomli rundoc . - name: Run the README.md run: invoke readme diff --git a/.github/workflows/tutorials.yml b/.github/workflows/tutorials.yml index 57b72f29..9673a618 100644 --- a/.github/workflows/tutorials.yml +++ b/.github/workflows/tutorials.yml @@ -20,6 +20,6 @@ jobs: - name: Install package and dependencies run: | python -m pip install --upgrade pip - python -m pip install invoke jupyter .[tutorials] + python -m pip install invoke tomli jupyter .[tutorials] - name: invoke tutorials run: invoke tutorials diff --git a/Makefile b/Makefile index 7df83ac8..c37b7307 100644 --- a/Makefile +++ b/Makefile @@ -158,8 +158,7 @@ serve-docs: ## compile the docs watching for changes .PHONY: dist dist: clean ## builds source and wheel package - python setup.py sdist - python setup.py bdist_wheel + python -m build --wheel --sdist ls -l dist .PHONY: publish-confirm diff --git a/setup.py b/pyproject.toml similarity index 53% rename from setup.py rename to pyproject.toml index 6ca5522e..413576f9 100644 --- a/setup.py +++ b/pyproject.toml @@ -1,17 +1,25 @@ -#.dev0!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""The setup script.""" - -from setuptools import setup, find_packages - -with open('README.md', encoding='utf-8') as readme_file: - readme = readme_file.read() - -with open('HISTORY.md', encoding='utf-8') as history_file: - history = history_file.read() - -install_requires = [ +[project] +name = 'copulas' +description = 'Create tabular synthetic data using copulas-based modeling.' +authors = [{name = 'DataCebo, Inc.', email = 'info@sdv.dev' }] +classifiers = [ + 'Development Status :: 2 - Pre-Alpha', + 'Intended Audience :: Developers', + 'License :: Free for non-commercial use', + 'Natural Language :: English', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', +] +keywords = [ 'copulas' ] +version = '0.10.1.dev0' +license = { text = 'BSL-1.1' } +requires-python = '>=3.8,<3.12' +readme = 'README.md' +dependencies = [ "numpy>=1.20.0,<2;python_version<'3.10'", "numpy>=1.23.3,<2;python_version>='3.10'", "pandas>=1.1.3;python_version<'3.10'", @@ -22,7 +30,34 @@ "scipy>=1.9.2,<2;python_version>='3.10'", ] -development_requires = [ +[project.urls] +"Source Code"= "https://github.com/sdv-dev/Copulas/" +"Issue Tracker" = "https://github.com/sdv-dev/Copulas/issues" +"Changes" = "https://github.com/sdv-dev/Copulas/blob/main/HISTORY.md" +"Twitter" = "https://twitter.com/sdv_dev" +"Chat" = "https://bit.ly/sdv-slack-invite" + +[build-system] +requires = ['setuptools', 'wheel'] +build-backend = 'setuptools.build_meta' + +[project.optional-dependencies] +tutorials = [ + 'markupsafe<=2.0.1', + 'scikit-learn>=0.24,<1.2', + 'jupyter>=1.0.0,<2', +] +test = [ + 'copulas[tutorials]', + 'pytest>=6.2.5,<7', + 'pytest-cov>=2.6.0,<3', + 'pytest-rerunfailures>=9.0.0,<10', + 'rundoc>=0.4.3,<0.5', + 'tomli>=2.0.0,<3', +] +dev = [ + 'copulas[tutorials, test]', + # general 'pip>=9.0.1', 'bumpversion>=0.5.3,<0.6', @@ -84,57 +119,20 @@ 'docutils>=0.10,<0.15' ] -tutorials_require = [ - 'markupsafe<=2.0.1', - 'scikit-learn>=0.24,<1.2', - 'jupyter>=1.0.0,<2', -] +[tool.isort] +line_length = 99 +lines_between_types = 0 +multi_line_output = 4 +use_parentheses = true +not_skip = ['__init__.py'] -tests_require = [ - 'pytest>=6.2.5,<7', - 'pytest-cov>=2.6.0,<3', - 'pytest-rerunfailures>=9.0.0,<10', - 'rundoc>=0.4.3,<0.5', -] +[tool.pydocstyle] +convention = 'google' +add-ignore = ['D107', 'D407', 'D417'] -setup_requires = [ - 'pytest-runner>=2.11.1', -] +[tool.setuptools] +include-package-data = true -setup( - author='DataCebo, Inc.', - author_email='info@sdv.dev', - classifiers=[ - 'Development Status :: 2 - Pre-Alpha', - 'Intended Audience :: Developers', - 'License :: Free for non-commercial use', - 'Natural Language :: English', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Topic :: Scientific/Engineering :: Artificial Intelligence', - ], - description='Create tabular synthetic data using copulas-based modeling.', - extras_require={ - 'tutorials': tutorials_require, - 'test': tests_require + tutorials_require, - 'dev': tests_require + development_requires + tutorials_require, - }, - install_requires=install_requires, - license='BSL-1.1', - long_description=readme + '\n\n' + history, - long_description_content_type='text/markdown', - include_package_data=True, - keywords='copulas', - name='copulas', - packages=find_packages(include=['copulas', 'copulas.*']), - python_requires='>=3.8,<3.12', - setup_requires=setup_requires, - test_suite='tests', - tests_require=tests_require, - url='https://github.com/sdv-dev/Copulas', - version='0.10.1.dev0', - zip_safe=False, -) +[tool.setuptools.packages.find] +include = ['copulas', 'copulas.*'] +namespaces = false diff --git a/setup.cfg b/setup.cfg index 75c15031..555d6304 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ values = [bumpversion:part:candidate] -[bumpversion:file:setup.py] +[bumpversion:file:pyproject.toml] search = version='{current_version}' replace = version='{new_version}' @@ -40,23 +40,10 @@ extend-ignore = D107, # Missing docstring in __init__ per-file-ignores = large_scale_evaluation.py:T001 -[pydocstyle] -convention = google -add-ignore = D107, D407, D417 - -[isort] -line_length = 99 -lines_between_types = 0 -multi_line_output = 4 -use_parentheses = True -not_skip = __init__.py [aliases] test = pytest -[tool:pytest] -collect_ignore = ['setup.py'] - [doc8] max-line-length = 99 diff --git a/tasks.py b/tasks.py index 6baeb51f..f2176b61 100644 --- a/tasks.py +++ b/tasks.py @@ -2,9 +2,10 @@ import inspect import operator import os -import re -import pkg_resources -import platform +import tomli +import sys +from packaging.requirements import Requirement +from packaging.version import Version import shutil import stat from pathlib import Path @@ -60,33 +61,48 @@ def _validate_python_version(line): return is_valid +def _get_minimum_versions(dependencies, python_version): + min_versions = {} + for dependency in dependencies: + if '@' in dependency: + name, url = dependency.split(' @ ') + min_versions[name] = f'{name} @ {url}' + continue + + req = Requirement(dependency) + if ';' in dependency: + marker = req.marker + if marker and not marker.evaluate({'python_version': python_version}): + continue # Skip this dependency if the marker does not apply to the current Python version + + if req.name not in min_versions: + min_version = next( + (spec.version for spec in req.specifier if spec.operator in ('>=', '==')), None) + if min_version: + min_versions[req.name] = f'{req.name}=={min_version}' + + elif '@' not in min_versions[req.name]: + existing_version = Version(min_versions[req.name].split('==')[1]) + new_version = next( + (spec.version for spec in req.specifier if spec.operator in ('>=', '==')), existing_version) + if new_version > existing_version: + # Change when a valid newer version is found + min_versions[req.name] = f'{req.name}=={new_version}' + + return list(min_versions.values()) + + @task def install_minimum(c): - with open('setup.py', 'r') as setup_py: - lines = setup_py.read().splitlines() - - versions = [] - started = False - for line in lines: - if started: - if line == ']': - break - - line = line.strip() - if _validate_python_version(line): - requirement = re.match(r'[^>]*', line).group(0) - requirement = re.sub(r"""['",]""", '', requirement) - version = re.search(r'>=?(\d\.?)+', line).group(0) - if version: - version = re.sub(r'>=?', '==', version) - version = re.sub(r"""['",]""", '', version) - requirement += version - versions.append(requirement) - - elif line.startswith('install_requires = ['): - started = True - - c.run(f'python -m pip install {" ".join(versions)}') + with open('pyproject.toml', 'rb') as pyproject_file: + pyproject_data = tomli.load(pyproject_file) + + dependencies = pyproject_data.get('project', {}).get('dependencies', []) + python_version = '.'.join(map(str, sys.version_info[:2])) + minimum_versions = _get_minimum_versions(dependencies, python_version) + + if minimum_versions: + c.run(f'python -m pip install {" ".join(minimum_versions)}') @task diff --git a/tests/test_tasks.py b/tests/test_tasks.py new file mode 100644 index 00000000..2b710842 --- /dev/null +++ b/tests/test_tasks.py @@ -0,0 +1,36 @@ +from tasks import _get_minimum_versions + + +def test_get_minimum_versions(): + """Test the ``_get_minimum_versions`` method. + The method should return the minimum versions of the dependencies for the given python version. + If a library is linked to an URL, the minimum version should be the URL. + """ + # Setup + dependencies = [ + "numpy>=1.20.0,<2;python_version<'3.10'", + "numpy>=1.23.3,<2;python_version>='3.10'", + "pandas>=1.2.0,<2;python_version<'3.10'", + "pandas>=1.3.0,<2;python_version>='3.10'", + 'humanfriendly>=8.2,<11', + 'pandas @ git+https://github.com/pandas-dev/pandas.git@master#egg=pandas' + ] + + # Run + minimum_versions_39 = _get_minimum_versions(dependencies, '3.9') + minimum_versions_310 = _get_minimum_versions(dependencies, '3.10') + + # Assert + expected_versions_39 = [ + 'numpy==1.20.0', + 'pandas @ git+https://github.com/pandas-dev/pandas.git@master#egg=pandas', + 'humanfriendly==8.2', + ] + expected_versions_310 = [ + 'numpy==1.23.3', + 'pandas @ git+https://github.com/pandas-dev/pandas.git@master#egg=pandas', + 'humanfriendly==8.2', + ] + + assert minimum_versions_39 == expected_versions_39 + assert minimum_versions_310 == expected_versions_310