From 3e886a55d5b810e09dece94b738952a17c35cc1e Mon Sep 17 00:00:00 2001 From: Bill Hlavacek Date: Sun, 10 May 2026 15:12:35 -0600 Subject: [PATCH 1/3] Replace distutils.spawn.find_executable with shutil.which distutils was removed from the stdlib in Python 3.12 and emits a DeprecationWarning before then. shutil.which is the modern stdlib equivalent, takes the same argument, and returns the same result (absolute path or None). --- bionetgen/core/utils/utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bionetgen/core/utils/utils.py b/bionetgen/core/utils/utils.py index 7d19fd23..604162cc 100644 --- a/bionetgen/core/utils/utils.py +++ b/bionetgen/core/utils/utils.py @@ -1,7 +1,8 @@ -import os, subprocess -from bionetgen.core.exc import BNGPerlError -from distutils import spawn +import os +import shutil +import subprocess +from bionetgen.core.exc import BNGPerlError from bionetgen.core.utils.logging import BNGLogger @@ -589,7 +590,7 @@ def _try_path(candidate_path): return hit # 3) On PATH - bng_on_path = spawn.find_executable("BNG2.pl") + bng_on_path = shutil.which("BNG2.pl") if bng_on_path: tried.append(bng_on_path) hit = _try_path(bng_on_path) @@ -616,7 +617,7 @@ def test_perl(app=None, perl_path=None): logger.debug("Checking if perl is installed.", loc=f"{__file__} : test_perl()") # find path to perl binary if perl_path is None: - perl_path = spawn.find_executable("perl") + perl_path = shutil.which("perl") if perl_path is None: raise BNGPerlError # check if perl is actually working From 3925a0ab637e7aa2d6c2c92f7d5aefc84853b789 Mon Sep 17 00:00:00 2001 From: Bill Hlavacek Date: Sun, 10 May 2026 15:12:46 -0600 Subject: [PATCH 2/3] Replace distutils.ccompiler with setuptools._distutils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit distutils was removed from the stdlib in Python 3.12. setuptools vendors the same API as setuptools._distutils. The ccompiler import is wrapped in a lazy _new_ccompiler() helper called from CSimulator.__init__, so plain 'import bionetgen' does not require setuptools at runtime — only the cpy simulator path does, which already requires CVODE installed locally. Missing setuptools yields a clear BNGCompileError with install guidance. --- bionetgen/simulator/csimulator.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/bionetgen/simulator/csimulator.py b/bionetgen/simulator/csimulator.py index 34a6bdcd..b65958aa 100644 --- a/bionetgen/simulator/csimulator.py +++ b/bionetgen/simulator/csimulator.py @@ -1,11 +1,30 @@ import ctypes, os, tempfile, bionetgen import numpy as np -from distutils import ccompiler from .bngsimulator import BNGSimulator from bionetgen.main import BioNetGen from bionetgen.core.exc import BNGCompileError + +def _new_ccompiler(): + """Return a distutils ccompiler instance, sourced from setuptools. + + distutils was removed from the stdlib in Python 3.12; setuptools vendors + the same API as ``setuptools._distutils``. Imported lazily so that + ``import bionetgen`` does not require setuptools at runtime — only + instantiating ``CSimulator`` does. + """ + try: + from setuptools._distutils import ccompiler + except ImportError as exc: + raise BNGCompileError( + None, + "CSimulator requires the distutils ccompiler API, which was " + "removed from the stdlib in Python 3.12. Install setuptools " + "(pip install setuptools) to provide it.", + ) from exc + return ccompiler.new_compiler() + # This allows access to the CLIs config setup app = BioNetGen() app.setup() @@ -164,7 +183,7 @@ def __init__(self, model_file, generate_network=False): else: print(f"model format not recognized: {model_file}") # set compiler - self.compiler = ccompiler.new_compiler() + self.compiler = _new_ccompiler() self.compiler.add_include_dir(conf.get("cvode_include")) self.compiler.add_library_dir(conf.get("cvode_lib")) # compile shared library From 514f24720ee4485862ae781f92b356806e75ff5b Mon Sep 17 00:00:00 2001 From: Bill Hlavacek Date: Sun, 10 May 2026 15:21:16 -0600 Subject: [PATCH 3/3] Apply black formatting to _new_ccompiler helper --- bionetgen/simulator/csimulator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bionetgen/simulator/csimulator.py b/bionetgen/simulator/csimulator.py index b65958aa..4e7dd0d5 100644 --- a/bionetgen/simulator/csimulator.py +++ b/bionetgen/simulator/csimulator.py @@ -25,6 +25,7 @@ def _new_ccompiler(): ) from exc return ccompiler.new_compiler() + # This allows access to the CLIs config setup app = BioNetGen() app.setup()