diff --git a/cpmpy/solvers/TEMPLATE.py b/cpmpy/solvers/TEMPLATE.py index dd3626779..4656aa300 100644 --- a/cpmpy/solvers/TEMPLATE.py +++ b/cpmpy/solvers/TEMPLATE.py @@ -50,8 +50,7 @@ from typing import Optional import warnings -import pkg_resources -from pkg_resources import VersionConflict +from packaging.version import Version from .solver_interface import SolverInterface, SolverStatus, ExitStatus from ..expressions.core import Expression, Comparison, Operator @@ -81,14 +80,14 @@ def supported(): try: import TEMPLATEpy as gp # optionally enforce a specific version - pkg_resources.require("TEMPLATEpy>=2.1.0") + tpl_version = CPM_template.version() + if Version(tpl_version) < Version("2.1.0"): + warnings.warn(f"CPMpy uses features only available from TEMPLATEpy version 0.2.1, " + f"but you have version {tpl_version}.") + return False return True except ModuleNotFoundError: # if solver's Python package is not installed return False - except VersionConflict: # unsupported version of TEMPLATEpy (optional) - warnings.warn(f"CPMpy uses features only available from TEMPLATEpy version 0.2.1, " - f"but you have version {pkg_resources.get_distribution('TEMPLATEpy').version}.") - return False except Exception as e: raise e @@ -97,9 +96,10 @@ def version(cls) -> Optional[str]: """ Returns the installed version of the solver's Python API. """ + from importlib.metadata import version, PackageNotFoundError try: - return pkg_resources.get_distribution('TEMPLATEpy').version - except pkg_resources.DistributionNotFound: + return version('TEMPLATEpy') + except PackageNotFoundError: return None # [GUIDELINE] If your solver supports different subsolvers, implement below method to return a list of subsolver names diff --git a/cpmpy/solvers/choco.py b/cpmpy/solvers/choco.py index de432e0da..854cb12b6 100644 --- a/cpmpy/solvers/choco.py +++ b/cpmpy/solvers/choco.py @@ -45,10 +45,9 @@ from typing import Optional import numpy as np +from packaging.version import Version import warnings -import pkg_resources -from pkg_resources import VersionConflict from ..transformations.normalize import toplevel_list from .solver_interface import SolverInterface, SolverStatus, ExitStatus @@ -89,16 +88,16 @@ def supported(): try: # check if pychoco is installed import pychoco as chc + chc_version = CPM_choco.version() # check it's the correct version # CPMPy uses features only available from 0.2.1 - pkg_resources.require("pychoco>=0.2.1") + if Version(chc_version) < Version("0.2.1"): + warnings.warn(f"CPMpy uses features only available from Pychoco version 0.2.1, " + f"but you have version {chc_version}.") + return False return True except ModuleNotFoundError: return False - except VersionConflict: # unsupported version of pychoco - warnings.warn(f"CPMpy uses features only available from Pychoco version 0.2.1, " - f"but you have version {pkg_resources.get_distribution('pychoco').version}.") - return False except Exception as e: raise e @@ -107,9 +106,10 @@ def version() -> Optional[str]: """ Returns the installed version of the solver's Python API. """ + from importlib.metadata import version, PackageNotFoundError try: - return pkg_resources.get_distribution('pychoco').version - except pkg_resources.DistributionNotFound: + return version('pychoco') + except PackageNotFoundError: return None def __init__(self, cpm_model=None, subsolver=None): diff --git a/cpmpy/solvers/cplex.py b/cpmpy/solvers/cplex.py index 79d256ca7..5cb7f1885 100644 --- a/cpmpy/solvers/cplex.py +++ b/cpmpy/solvers/cplex.py @@ -125,12 +125,13 @@ def version() -> Optional[str]: Two version numbers get returned: ``/`` """ + from importlib.metadata import version, PackageNotFoundError try: - import pkg_resources import cplex - cpx = cplex.Cplex() - return f"{pkg_resources.get_distribution('docplex').version}/{cpx.get_version()}" - except (pkg_resources.DistributionNotFound, ModuleNotFoundError): + cplex_version = cplex.Cplex().get_version() + docplex_version = version("docplex") + return f"{docplex_version}/{cplex_version}" + except (PackageNotFoundError, ModuleNotFoundError): return None def __init__(self, cpm_model=None, subsolver=None): diff --git a/cpmpy/solvers/cpo.py b/cpmpy/solvers/cpo.py index f20913f34..7b2a72c07 100644 --- a/cpmpy/solvers/cpo.py +++ b/cpmpy/solvers/cpo.py @@ -42,10 +42,8 @@ CPM_cpo """ -import time from typing import Optional import warnings -import pkg_resources from .solver_interface import SolverInterface, SolverStatus, ExitStatus from .. import DirectConstraint @@ -117,11 +115,13 @@ def version() -> Optional[str]: For CPO, two version numbers get returned: ``/`` """ + from importlib.metadata import version, PackageNotFoundError try: import docplex.cp as docp - s = docp.solver.solver.CpoSolver(docp.model.CpoModel()) - return f"{pkg_resources.get_distribution('docplex').version}/{s.get_solver_version()}" - except (pkg_resources.DistributionNotFound, ModuleNotFoundError): + cpo_version = docp.solver.solver.CpoSolver(docp.model.CpoModel()).get_solver_version() + docplex_version = version("docplex") + return f"{docplex_version}/{cpo_version}" + except (PackageNotFoundError, ModuleNotFoundError): return None def __init__(self, cpm_model=None, subsolver=None): diff --git a/cpmpy/solvers/exact.py b/cpmpy/solvers/exact.py index 37c00c1b0..d18b564b8 100644 --- a/cpmpy/solvers/exact.py +++ b/cpmpy/solvers/exact.py @@ -50,8 +50,7 @@ import time from typing import Optional -import pkg_resources -from pkg_resources import VersionConflict +from packaging.version import Version from .solver_interface import SolverInterface, SolverStatus, ExitStatus from ..expressions.core import * @@ -90,16 +89,14 @@ def supported(): try: # check if exact is installed import exact - # check installed version - pkg_resources.require("exact>=2.1.0") + xct_version = CPM_exact.version() + if Version(xct_version) < Version("2.1.0"): + warnings.warn(f"CPMpy requires Exact version >=2.1.0 is required but you have version " + f"{xct_version}, beware exact>=2.1.0 requires Python 3.10 or higher.") + return False return True except ModuleNotFoundError: # exact is not installed return False - except VersionConflict: # unsupported version of exact - warnings.warn(f"CPMpy requires Exact version >=2.1.0 is required but you have version " - f"{pkg_resources.get_distribution('exact').version}, beware exact>=2.1.0 requires " - f"Python 3.10 or higher.") - return False except Exception as e: raise e @@ -108,9 +105,10 @@ def version() -> Optional[str]: """ Returns the installed version of the solver's Python API. """ + from importlib.metadata import version, PackageNotFoundError try: - return pkg_resources.get_distribution('exact').version - except pkg_resources.DistributionNotFound: + return version('exact') + except PackageNotFoundError: return None @@ -563,8 +561,6 @@ def add(self, cpm_expr_orig): assert isinstance(lhs, Operator) # can be sum, wsum or mul if lhs.name == "mul": - assert pkg_resources.require("exact>=2.1.0"), f"Multiplication constraint {cpm_expr} " \ - f"only supported by Exact version 2.1.0 and above" if is_num(rhs): # make dummy var rhs = cp.intvar(rhs, rhs) xct_rhs = self.solver_var(rhs) diff --git a/cpmpy/solvers/gcs.py b/cpmpy/solvers/gcs.py index 39321f406..3eee21e1a 100644 --- a/cpmpy/solvers/gcs.py +++ b/cpmpy/solvers/gcs.py @@ -51,7 +51,6 @@ CPM_gcs """ from typing import Optional -import pkg_resources from cpmpy.transformations.comparison import only_numexpr_equality from cpmpy.transformations.reification import reify_rewrite, only_bv_reifies @@ -107,9 +106,10 @@ def version() -> Optional[str]: """ Returns the installed version of the solver's Python API. """ + from importlib.metadata import version, PackageNotFoundError try: - return pkg_resources.get_distribution('gcspy').version - except pkg_resources.DistributionNotFound: + return version('gcspy') + except PackageNotFoundError: return None def __init__(self, cpm_model=None, subsolver=None): diff --git a/cpmpy/solvers/gurobi.py b/cpmpy/solvers/gurobi.py index 9d1e6f012..266047856 100644 --- a/cpmpy/solvers/gurobi.py +++ b/cpmpy/solvers/gurobi.py @@ -43,7 +43,6 @@ """ from typing import Optional -import pkg_resources from .solver_interface import SolverInterface, SolverStatus, ExitStatus from ..exceptions import NotSupportedError @@ -117,9 +116,10 @@ def version() -> Optional[str]: """ Returns the installed version of the solver's Python API. """ + from importlib.metadata import version, PackageNotFoundError try: - return pkg_resources.get_distribution('gurobipy').version - except pkg_resources.DistributionNotFound: + return version("gurobipy") + except PackageNotFoundError: return None def __init__(self, cpm_model=None, subsolver=None): diff --git a/cpmpy/solvers/hexaly.py b/cpmpy/solvers/hexaly.py index e6d670209..74f693b3d 100644 --- a/cpmpy/solvers/hexaly.py +++ b/cpmpy/solvers/hexaly.py @@ -41,8 +41,6 @@ from typing import Optional -from importlib.metadata import version, PackageNotFoundError - from .solver_interface import SolverInterface, SolverStatus, ExitStatus from ..expressions.core import Expression, Comparison, Operator, BoolVal from ..expressions.globalconstraints import GlobalConstraint, GlobalFunction, DirectConstraint @@ -81,6 +79,7 @@ def version(cls) -> Optional[str]: """ Returns the installed version of the solver's Python API. """ + from importlib.metadata import version, PackageNotFoundError try: return version('hexaly') except PackageNotFoundError: diff --git a/cpmpy/solvers/minizinc.py b/cpmpy/solvers/minizinc.py index d1227ec91..6bed87386 100644 --- a/cpmpy/solvers/minizinc.py +++ b/cpmpy/solvers/minizinc.py @@ -62,7 +62,6 @@ from datetime import timedelta # for mzn's timeout import numpy as np -import pkg_resources from .solver_interface import SolverInterface, SolverStatus, ExitStatus from ..exceptions import MinizincNameException, MinizincBoundsException @@ -225,10 +224,13 @@ def version() -> Optional[str]: For Minizinc, two version numbers get returned: ``/`` """ + from importlib.metadata import version, PackageNotFoundError try: from minizinc import default_driver - return f"{pkg_resources.get_distribution('minizinc').version}/{'.'.join(str(a) for a in default_driver.parsed_version)}" - except (pkg_resources.DistributionNotFound, ModuleNotFoundError): + mzn_version = version("minizinc") + solver_version = '.'.join(str(a) for a in default_driver.parsed_version) + return f"{mzn_version}/{solver_version}" + except (PackageNotFoundError, ModuleNotFoundError): return None # variable name can not be any of these keywords diff --git a/cpmpy/solvers/ortools.py b/cpmpy/solvers/ortools.py index afc68026f..abc284fec 100644 --- a/cpmpy/solvers/ortools.py +++ b/cpmpy/solvers/ortools.py @@ -46,7 +46,6 @@ import sys from typing import Optional # for stdout checking import numpy as np -import pkg_resources from .solver_interface import SolverInterface, SolverStatus, ExitStatus from ..exceptions import NotSupportedError @@ -96,9 +95,10 @@ def version() -> Optional[str]: """ Returns the installed version of the solver's Python API. """ + from importlib.metadata import version, PackageNotFoundError try: - return pkg_resources.get_distribution('ortools').version - except pkg_resources.DistributionNotFound: + return version('ortools') + except PackageNotFoundError: return None diff --git a/cpmpy/solvers/pindakaas.py b/cpmpy/solvers/pindakaas.py index 20540903a..d5b0b6514 100755 --- a/cpmpy/solvers/pindakaas.py +++ b/cpmpy/solvers/pindakaas.py @@ -38,7 +38,6 @@ """ import time -import pkg_resources from datetime import timedelta from typing import Optional @@ -89,9 +88,10 @@ def supported(): @staticmethod def version() -> Optional[str]: """Return the installed version of the solver's Python API.""" + from importlib.metadata import version, PackageNotFoundError try: - return pkg_resources.get_distribution("pindakaas").version - except pkg_resources.DistributionNotFound: + return version('pindakaas') + except PackageNotFoundError: return None def __init__(self, cpm_model=None, subsolver=None): diff --git a/cpmpy/solvers/pumpkin.py b/cpmpy/solvers/pumpkin.py index df9441833..9d36dc89b 100644 --- a/cpmpy/solvers/pumpkin.py +++ b/cpmpy/solvers/pumpkin.py @@ -37,11 +37,13 @@ Module details ============== """ +import warnings from typing import Optional from importlib.metadata import version, PackageNotFoundError from os.path import join import numpy as np +from packaging.version import Version from cpmpy.exceptions import NotSupportedError from .solver_interface import SolverInterface, SolverStatus, ExitStatus @@ -75,6 +77,11 @@ def supported(): # try to import the package try: import pumpkin_solver as psp + pum_version = CPM_pumpkin.version() + if Version(pum_version) < Version("0.2.2"): + warnings.warn(f"CPMpy uses features only available from Pumpkin version >=0.2.2 " + f"but you have version {pum_version}") + return False return True except ModuleNotFoundError: return False @@ -87,6 +94,7 @@ def version() -> Optional[str]: """ Returns the installed version of the solver's Python API. """ + from importlib.metadata import version, PackageNotFoundError try: return version('pumpkin-solver') except PackageNotFoundError: diff --git a/cpmpy/solvers/pysat.py b/cpmpy/solvers/pysat.py index def9e6804..b60216da1 100644 --- a/cpmpy/solvers/pysat.py +++ b/cpmpy/solvers/pysat.py @@ -54,7 +54,6 @@ from threading import Timer from typing import Optional import warnings -import pkg_resources from .solver_interface import SolverInterface, SolverStatus, ExitStatus from ..exceptions import NotSupportedError @@ -162,9 +161,10 @@ def version() -> Optional[str]: """ Returns the installed version of the solver's Python API. """ + from importlib.metadata import version, PackageNotFoundError try: - return pkg_resources.get_distribution('python-sat').version - except pkg_resources.DistributionNotFound: + return version('python-sat') + except PackageNotFoundError: return None def __init__(self, cpm_model=None, subsolver=None): diff --git a/cpmpy/solvers/pysdd.py b/cpmpy/solvers/pysdd.py index 8996d0b46..496b3616b 100644 --- a/cpmpy/solvers/pysdd.py +++ b/cpmpy/solvers/pysdd.py @@ -45,7 +45,6 @@ """ from functools import reduce from typing import Optional -import pkg_resources from .solver_interface import SolverInterface, SolverStatus, ExitStatus from ..exceptions import NotSupportedError @@ -89,9 +88,10 @@ def version() -> Optional[str]: """ Returns the installed version of the solver's Python API. """ + from importlib.metadata import version, PackageNotFoundError try: - return pkg_resources.get_distribution('pysdd').version - except pkg_resources.DistributionNotFound: + return version('pysdd') + except PackageNotFoundError: return None diff --git a/cpmpy/solvers/z3.py b/cpmpy/solvers/z3.py index 3451583d3..98fe99838 100644 --- a/cpmpy/solvers/z3.py +++ b/cpmpy/solvers/z3.py @@ -46,7 +46,6 @@ ============== """ from typing import Optional -import pkg_resources from cpmpy.transformations.get_variables import get_variables from .solver_interface import SolverInterface, SolverStatus, ExitStatus @@ -94,9 +93,10 @@ def version(cls) -> Optional[str]: """ Returns the installed version of the solver's Python API. """ + from importlib.metadata import version, PackageNotFoundError try: - return pkg_resources.get_distribution('z3-solver').version - except pkg_resources.DistributionNotFound: + return version('z3-solver') + except PackageNotFoundError: return None def __init__(self, cpm_model=None, subsolver="sat"): diff --git a/requirements.txt b/requirements.txt index 8d69c51cf..244f47f42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ numpy < 2.0 ortools >= 9.9 -setuptools # for pkg_resources to check solver versions +packaging # to check solver versions diff --git a/tests/test_solvers.py b/tests/test_solvers.py index b0416e7e9..21697b82b 100644 --- a/tests/test_solvers.py +++ b/tests/test_solvers.py @@ -1163,7 +1163,9 @@ def test_model_no_vars(self, solver): assert num_sols == 0 def test_version(self, solver): - assert SolverLookup.lookup(solver).version() is not None + solver_version = SolverLookup.lookup(solver).version() + assert solver_version is not None + assert isinstance(solver_version, str) def test_optimisation_direction(self, solver): x = cp.intvar(0, 10, shape=1)