diff --git a/.github/workflows/readme.yml b/.github/workflows/readme.yml index 14fddf4..fc21dac 100644 --- a/.github/workflows/readme.yml +++ b/.github/workflows/readme.yml @@ -22,5 +22,6 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install invoke rundoc . + python -m pip install tomli - name: Run the README.md run: invoke readme diff --git a/Dockerfile b/Dockerfile index d5e678f..281c347 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN mkdir /SDGym && \ mkdir /SDGym/sdgym && \ # Copy code -COPY setup.py README.md HISTORY.md MANIFEST.in LICENSE Makefile setup.cfg /SDGym/ +COPY pyproject.toml README.md HISTORY.md MANIFEST.in LICENSE Makefile setup.cfg /SDGym/ COPY /sdgym/ /SDGym/sdgym WORKDIR /SDGym diff --git a/Makefile b/Makefile index f3331fc..a0bb432 100644 --- a/Makefile +++ b/Makefile @@ -123,8 +123,7 @@ coverage: ## check code coverage quickly with the default Python .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 51% rename from setup.py rename to pyproject.toml index 1a90960..70e8947 100644 --- a/setup.py +++ b/pyproject.toml @@ -1,17 +1,29 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""The setup script.""" - -from setuptools import find_packages, setup - -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 = [ +[build-system] +requires = ['setuptools', 'wheel'] +build-backend = 'setuptools.build_meta' + +[project] +name = 'sdgym' +description = 'Benchmark tabular synthetic data generators using a variety of datasets' +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 = ['machine learning', 'synthetic data generation', 'benchmark', 'generative models'] +version = '0.7.1.dev0' +license = { text = 'BSL-1.1' } +requires-python = '>=3.8,<3.12' +readme = 'README.md' +dependencies = [ 'appdirs>=1.3,<2', 'boto3>=1.15.0,<2', 'botocore>=1.18,<2', @@ -38,24 +50,27 @@ 'sdv>=1.3.0,<2', ] +[project.urls] +"Source Code"= "https://github.com/sdv-dev/SDGym/" +"Issue Tracker" = "https://github.com/sdv-dev/SDGym/issues" +"Twitter" = "https://twitter.com/sdv_dev" +"Chat" = "https://bit.ly/sdv-slack-invite" -dask_requires = [ - 'dask', - 'distributed', -] +[project.entry-points] +sdgym = { main = 'sdgym.cli.__main__:main' } -setup_requires = [ - 'pytest-runner>=2.11.1', -] - -tests_require = [ +[project.optional-dependencies] +dask = ['dask', 'distributed'] +test = [ 'pytest>=3.4.2', 'pytest-cov>=2.6.0', 'jupyter>=1.0.0,<2', 'rundoc>=0.4.3,<0.5', + 'tomli>=2.0.0,<3', ] +dev = [ + 'sdgym[dask, test]', -development_requires = [ # general 'bumpversion>=0.5.3,<0.6', 'pip>=9.0.1', @@ -101,49 +116,24 @@ # Invoke 'invoke', ] +all = [ + 'sdgym[dask, test, dev]', +] + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages.find] +include = ['sdgym', 'sdgym.*'] +namespaces = false + +[tool.isort] +line_length = 99 +lines_between_types = 0 +multi_line_output = 4 +not_skip = ['__init__.py'] +use_parentheses = 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=( - 'Benchmark tabular synthetic data generators using a variety of datasets' - ), - entry_points={ - 'console_scripts': [ - 'sdgym=sdgym.cli.__main__:main' - ], - }, - extras_require={ - 'all': development_requires + tests_require + dask_requires, - 'dev': development_requires + tests_require + dask_requires, - 'test': tests_require, - 'dask': dask_requires, - }, - include_package_data=True, - install_requires=install_requires, - license='BSL-1.1', - long_description=readme + '\n\n' + history, - long_description_content_type='text/markdown', - keywords='machine learning synthetic data generation benchmark generative models', - name='sdgym', - packages=find_packages(include=['sdgym', 'sdgym.*']), - python_requires='>=3.8,<3.12', - setup_requires=setup_requires, - test_suite='tests', - tests_require=tests_require, - url='https://github.com/sdv-dev/SDGym', - version='0.7.1.dev0', - zip_safe=False, -) +[tool.pydocstyle] +convention = 'google' +add-ignore = ['D107', 'D407', 'D417'] diff --git a/setup.cfg b/setup.cfg index 3e30e01..c2e3618 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}' @@ -36,17 +36,5 @@ extend-ignore = D105, # Missing docstring in magic method PD005, # Use arithmetic operator instead of method SFS3 # String literal formatting using f-string -[isort] -line_length = 99 -lines_between_types = 0 -multi_line_output = 4 -not_skip = __init__.py -use_parentheses = True - [aliases] test = pytest - -[pydocstyle] -convention = google -add-ignore = D107, D407, D417 - diff --git a/tasks.py b/tasks.py index 8f9aac6..50ab8ec 100644 --- a/tasks.py +++ b/tasks.py @@ -2,14 +2,18 @@ import inspect import operator import os -import re -import pkg_resources import platform +import re import shutil import stat +import sys from pathlib import Path +import pkg_resources +import tomli from invoke import task +from packaging.requirements import Requirement +from packaging.version import Version COMPARISONS = { '>=': operator.ge, @@ -53,49 +57,45 @@ def readme(c): shutil.rmtree(test_path) -def _validate_python_version(line): - is_valid = True - for python_version_match in re.finditer(r"python_version(<=?|>=?|==)\'(\d\.?)+\'", line): - python_version = python_version_match.group(0) - comparison = re.search(r'(>=?|<=?|==)', python_version).group(0) - version_number = python_version.split(comparison)[-1].replace("'", "") - comparison_function = COMPARISONS[comparison] - is_valid = is_valid and comparison_function( - pkg_resources.parse_version(platform.python_version()), - pkg_resources.parse_version(version_number), - ) +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 - return is_valid + 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: + min_versions[req.name] = f'{req.name}=={new_version}' # Change when a valid newer version is found + + 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 == ']': - started = False - continue - - 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\.?)+\w*', 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 0000000..a4cf300 --- /dev/null +++ b/tests/test_tasks.py @@ -0,0 +1,37 @@ +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