From 2499f5001f97eb102f58bcacee21f8e65be4533d Mon Sep 17 00:00:00 2001 From: AndreFCruz Date: Thu, 21 Aug 2025 18:45:24 +0200 Subject: [PATCH 1/3] migrating from setup.py to pyproject.toml --- pyproject.toml | 162 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 97 ----------------------------- 2 files changed, 162 insertions(+), 97 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..aa2fda5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,162 @@ +[build-system] +requires = ["setuptools>=64", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "error-parity" +description = "Achieve error-rate parity between protected groups for any predictor" +license = "MIT" +authors = [ + { name = "AndreFCruz" }, +] + +# Keywords to be used by PyPI search +keywords = ["ml", "optimization", "fairness", "error-parity", "equal-odds"] + +# PyPI classifiers, see https://pypi.org/classifiers/ +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +requires-python = ">=3.8" + +# These are defined below dynamically: +dynamic = [ + "version", + "readme", + "dependencies", + "optional-dependencies", +] + +# Include license file(s) in built distributions +license-files = ["LICENSE"] + +[tool.setuptools.packages.find] +include = ["error_parity*"] +exclude = ["tests*"] + +[tool.setuptools.dynamic] +version = { attr = "error_parity._version.__version__" } +readme = { file = "README.md", content-type = "text/markdown" } + +# Main package dependencies +dependencies = {file = "requirements/main.txt"} + +# Optional dependencies +[tool.setuptools.dynamic.optional-dependencies] +test = {file = "requirements/test.txt"} +dev = {file = "requirements/dev.txt"} +docs = {file = "requirements/docs.txt"} +all = {file = [ + "requirements/dev.txt", + "requirements/test.txt", + "requirements/docs.txt", +]} + +[project.urls] +homepage = "https://github.com/socialfoundations/error-parity" + +# flake8 +[tool.flake8] +max-complexity = 10 +max-line-length = 120 + +per-file-ignores = """ +# imported but unused +**/__init__.py: F401 +""" + +exclude = [ + "docs/", + ".tox/", + "build/", + "dist/", +] + +[tool.pytest.ini_options] +minversion = "8.0" +testpaths = [ + "tests", +] + +# isort +[tool.isort] +profile = "hug" +force_single_line = false +src_paths = ["error_parity", "tests"] + +# Coverage +[tool.coverage.run] +branch = true +source = ["error_parity"] +omit = ["error_parity/_version.py", "tests"] + +[tool.coverage.report] +show_missing = true + +# Setuptools + + +# MyPy +[tool.mypy] +ignore_missing_imports = true +no_implicit_optional = false +strict_optional = false +exclude = [ + "build", + "doc", + "tests", + "notebooks", +] +python_version = "3.11" + +# Tox +[tool.tox] +legacy_tox_ini = """ +[tox] +env_list = + py38 + py39 + py310 + py311 + py312 + lint + type + +[testenv] +description = run unit tests +deps = + pytest>=8 + coverage>=7 + pytest-xdist>=3 +commands = + coverage erase + coverage run -m pytest {posargs:tests} + coverage report -m + +[testenv:type] +description = run type checks +basepython = python3.11 +deps = + mypy>=1.0 +commands = mypy {posargs:error_parity} + +[testenv:lint] +description = run linters +skip_install = true +deps = + flake8>=7.0 + flake8-pyproject +commands = flake8 {posargs:error_parity tests} +""" + + diff --git a/setup.py b/setup.py deleted file mode 100644 index 01e9bfa..0000000 --- a/setup.py +++ /dev/null @@ -1,97 +0,0 @@ -"""This module contains utils to setup a standalone installable package.""" - -import re -from pathlib import Path -from setuptools import setup, find_packages - - -def stream_requirements(fd): - """For a given requirements file descriptor, generate lines of - distribution requirements, ignoring comments and chained requirement - files. - """ - for line in fd: - cleaned = re.sub(r"#.*$", "", line).strip() - if cleaned and not cleaned.startswith("-r"): - yield cleaned - - -def load_requirements(txt_path): - """Short helper for loading requirements from a .txt file. - - Parameters - ---------- - txt_path : Path or str - Path to the requirements file. - - Returns - ------- - list - List of requirements, one list element per line in the text file. - """ - with Path(txt_path).open() as requirements_file: - return list(stream_requirements(requirements_file)) - - -# ---------------------------------------------------------------------------- # -# Requirements # -# ---------------------------------------------------------------------------- # - -ROOT_PATH = Path(__file__).parent -README_PATH = ROOT_PATH / "README.md" - -REQUIREMENTS_PATH = ROOT_PATH / "requirements" / "main.txt" -requirements = load_requirements(REQUIREMENTS_PATH) - -DEV_REQUIREMENTS_PATH = ROOT_PATH / "requirements" / "dev.txt" -dev_requirements = load_requirements(DEV_REQUIREMENTS_PATH) - -TEST_REQUIREMENTS_PATH = ROOT_PATH / "requirements" / "test.txt" -test_requirements = load_requirements(TEST_REQUIREMENTS_PATH) - - -# ---------------------------------------------------------------------------- # -# Version # -# ---------------------------------------------------------------------------- # -SRC_PATH = ROOT_PATH / "error_parity" -VERSION_PATH = SRC_PATH / "_version.py" - -with VERSION_PATH.open("rb") as version_file: - exec(version_file.read()) - - -# ---------------------------------------------------------------------------- # -# SETUP # -# ---------------------------------------------------------------------------- # -setup( - name="error-parity", - version=__version__, - description="Achieve error-rate parity between protected groups for any predictor", - keywords=["ml", "optimization", "fairness", "error-parity", "equal-odds"], - long_description=(README_PATH).read_text(), - long_description_content_type="text/markdown", - python_requires=">=3.8", - packages=find_packages(), - install_requires=requirements, - extras_require={ - "test": test_requirements, - "dev": dev_requirements, - "all": dev_requirements + test_requirements, - }, - author="AndreFCruz", - url="https://github.com/socialfoundations/error-parity", - license="MIT", - classifiers=[ - "Development Status :: 3 - Alpha", - "License :: OSI Approved :: MIT License", - "Intended Audience :: Science/Research", - "Topic :: Scientific/Engineering :: Artificial Intelligence", - "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", - "Programming Language :: Python :: 3.12", - ], -) From 51fac1335fd8d1a64a535be92664bd0f0d2f1b60 Mon Sep 17 00:00:00 2001 From: AndreFCruz Date: Thu, 21 Aug 2025 19:16:14 +0200 Subject: [PATCH 2/3] updated project to use pyproject.toml instead of setup.py --- .gitignore | 1 + error_parity/_version.py | 2 +- error_parity/cvxpy_utils.py | 10 +++++++++- pyproject.toml | 12 ++---------- requirements/main.txt | 4 ++-- requirements/test.txt | 4 ++-- tests/test_evaluation.py | 2 +- 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index d828387..0134fbd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ build/ *.egg-info/ .coverage +.tox # Documentation artifacts _build/ diff --git a/error_parity/_version.py b/error_parity/_version.py index 783a634..99ab443 100644 --- a/error_parity/_version.py +++ b/error_parity/_version.py @@ -1,3 +1,3 @@ """File to keep the package version in one place.""" -__version__ = "0.3.11" +__version__ = "0.3.12" __version_info__ = tuple(__version__.split(".")) diff --git a/error_parity/cvxpy_utils.py b/error_parity/cvxpy_utils.py index 364abe9..4db0246 100644 --- a/error_parity/cvxpy_utils.py +++ b/error_parity/cvxpy_utils.py @@ -414,8 +414,16 @@ def group_positive_prediction_rate(group_idx: int): # Define cvxpy problem prob = cp.Problem(obj, constraints) + # NOTE: ECOS solver has been deprecated in favor of CLARABEL in cvxpy 1.3.2+ + # https://www.cvxpy.org/updates/index.html?h=ecos#ecos-deprecation + # prob.solve(solver=cp.ECOS, abstol=SOLUTION_TOLERANCE, feastol=SOLUTION_TOLERANCE) + # Run solver - prob.solve(solver=cp.ECOS, abstol=SOLUTION_TOLERANCE, feastol=SOLUTION_TOLERANCE) + prob.solve( + solver=cp.CLARABEL, + tol_gap_abs=SOLUTION_TOLERANCE, + tol_feas=SOLUTION_TOLERANCE, + ) # NOTE: these tolerances are supposed to be smaller than the default np.isclose tolerances # (useful when comparing if two points are the same, within the cvxpy accuracy tolerance) diff --git a/pyproject.toml b/pyproject.toml index aa2fda5..1e107fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,12 @@ [build-system] -requires = ["setuptools>=64", "wheel"] +requires = ["setuptools>=77.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "error-parity" description = "Achieve error-rate parity between protected groups for any predictor" license = "MIT" +license-files = ["LICENSE"] authors = [ { name = "AndreFCruz" }, ] @@ -18,7 +19,6 @@ classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", "Topic :: Scientific/Engineering :: Artificial Intelligence", - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -37,8 +37,6 @@ dynamic = [ "optional-dependencies", ] -# Include license file(s) in built distributions -license-files = ["LICENSE"] [tool.setuptools.packages.find] include = ["error_parity*"] @@ -103,9 +101,6 @@ omit = ["error_parity/_version.py", "tests"] [tool.coverage.report] show_missing = true -# Setuptools - - # MyPy [tool.mypy] ignore_missing_imports = true @@ -137,7 +132,6 @@ description = run unit tests deps = pytest>=8 coverage>=7 - pytest-xdist>=3 commands = coverage erase coverage run -m pytest {posargs:tests} @@ -158,5 +152,3 @@ deps = flake8-pyproject commands = flake8 {posargs:error_parity tests} """ - - diff --git a/requirements/main.txt b/requirements/main.txt index 9ff393f..5bc3105 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -1,8 +1,8 @@ pandas -numpy +numpy<2.0.0 scipy tqdm scikit-learn>=1.2 -cvxpy>=1.3.2 +cvxpy[clarabel]~=1.3.2 matplotlib seaborn diff --git a/requirements/test.txt b/requirements/test.txt index 429791f..89bd7bb 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,5 +1,5 @@ -pytest -coverage +pytest>=8.0 +coverage>=7.0 folktables tqdm mypy diff --git a/tests/test_evaluation.py b/tests/test_evaluation.py index d45c33a..9bab4c8 100644 --- a/tests/test_evaluation.py +++ b/tests/test_evaluation.py @@ -68,5 +68,5 @@ def test_equalized_odds_relaxation_costs( higher_p_cost = results[higher_p_norm] # Assert lower-p costs are higher (accuracy is lower) - assert lower_p_cost > higher_p_cost - SOLUTION_TOLERANCE, \ + assert lower_p_cost > higher_p_cost - 2 * SOLUTION_TOLERANCE, \ f"l-{lower_p_norm} cost: {lower_p_cost} < l-{higher_p_norm} cost: {higher_p_cost}" From d06bc6210d05a13459dd63183cb703db42e63e9c Mon Sep 17 00:00:00 2001 From: AndreFCruz Date: Thu, 21 Aug 2025 19:24:14 +0200 Subject: [PATCH 3/3] dropped support for python 3.8 --- .github/workflows/python-tests.yml | 2 +- pyproject.toml | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index d271c6e..f463a53 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 diff --git a/pyproject.toml b/pyproject.toml index 1e107fd..b5ee68c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,14 +20,13 @@ classifiers = [ "Intended Audience :: Science/Research", "Topic :: Scientific/Engineering :: Artificial Intelligence", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] -requires-python = ">=3.8" +requires-python = ">=3.9" # These are defined below dynamically: dynamic = [ @@ -119,7 +118,6 @@ python_version = "3.11" legacy_tox_ini = """ [tox] env_list = - py38 py39 py310 py311