From 980f7e511c041b792c598432b8145d19719faef2 Mon Sep 17 00:00:00 2001 From: thomaskreuz Date: Fri, 9 Jun 2023 02:46:37 +0200 Subject: [PATCH 1/3] Update setup.py with new version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0aea3a0..f0e897a 100644 --- a/setup.py +++ b/setup.py @@ -72,7 +72,7 @@ def __str__(self): setup( name='pyspike', packages=find_packages(exclude=['doc']), - version='0.7.0', + version='0.8.0', cmdclass=cmdclass, ext_modules=ext_modules, include_dirs=[numpy_include()], From d3de36c737f6512ddf002d90ffb8a1994c2f4abc Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 11 May 2026 10:57:40 -0700 Subject: [PATCH 2/3] Modernize packaging; fix pip install with build isolation (#84) (#86) * Modernize packaging; fix pip install with build isolation (#84) Issue #84: 'pip install pyspike' fails on modern pip/setuptools with 'ModuleNotFoundError: No module named numpy' because pip builds in an isolated environment that doesn't see numpy from the user's venv, but setup.py calls numpy.get_include() for the Cython extensions. - Add pyproject.toml with full [project] metadata moved from setup.py - Declare numpy, Cython, setuptools>=77 and wheel in build-system.requires - Replace deprecated BSD License classifier with SPDX 'BSD-2-Clause' - Remove setup.cfg (description-file deprecation), metadata in pyproject.toml - Update supported Python versions (drop 3.7/3.8 EOL, add 3.11/3.12/3.13) - Refresh CI to use pip install . so the matrix actually tests the build-isolation path that broke - Slim setup.py to only the Cython/ext_modules logic - Update MANIFEST.in to ship .pyx/.pxd sources in sdist - Remove SetupNoPrompt.py (workaround for a long-disabled input() prompt) Closes #84. * Re-enable no-cython test cases * Use importlib.metadata for __version__ (fix Python 3.12+) pkg_resources is provided by setuptools, and starting with Python 3.12 venv no longer installs setuptools by default. After 'pip install .' in a fresh 3.12+ environment the runtime venv contains only pyspike and numpy, so 'import pyspike' raised ModuleNotFoundError: No module named 'pkg_resources' and every test file failed to collect (pytest exit code 2). Switch to importlib.metadata.version, which has been in the standard library since Python 3.8 and works regardless of whether setuptools is present at runtime. --- .github/workflows/python-package.yml | 78 +++++++++----- MANIFEST.in | 10 +- SetupNoPrompt.py | 5 - pyproject.toml | 53 +++++++++ pyspike/__init__.py | 24 ++--- setup.cfg | 2 - setup.py | 154 ++++++++++----------------- 7 files changed, 179 insertions(+), 147 deletions(-) delete mode 100644 SetupNoPrompt.py create mode 100644 pyproject.toml delete mode 100644 setup.cfg diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bda4a6c..d467935 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -11,41 +11,69 @@ on: jobs: build: - + name: Python ${{ matrix.python-version }} (${{ matrix.backend }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] - cython: ['python -m pip install -q cython', 'echo "No Cython"'] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + # PySpike treats Cython as an optional accelerator. Both install paths + # need to work and produce a passing test suite: + # + # cython -- `pip install .` uses pip's isolated build env, which + # installs Cython per pyproject.toml's build-system + # requires. setup.py compiles the .pyx sources into + # .so modules and the fast path is used at runtime. + # + # no-cython -- `pip install --no-build-isolation .` with Cython + # deliberately absent. setup.py's `try: import Cython` + # raises ImportError, no extensions are built, and + # each pyspike module falls back to python_backend.py + # via its own `try/except ImportError`. + backend: [cython, no-cython] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies + + - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install libblas-dev - sudo apt-get install liblapack-dev - sudo apt-get install gfortran - python -m pip install --upgrade pip - python -m pip install flake8 pytest nose numpy scipy - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Install Cython - run: | - ${{ matrix.cython }} - - name: Install package + sudo apt-get install -y libblas-dev liblapack-dev gfortran + + - name: Upgrade pip + run: python -m pip install --upgrade pip + + - name: Install PySpike — with Cython (build isolation) + if: matrix.backend == 'cython' + run: pip install . + + - name: Install PySpike — without Cython (pure-Python fallback) + if: matrix.backend == 'no-cython' run: | - python SetupNoPrompt.py build_ext --inplace - # - name: Lint with flake8 - # run: | - # # stop the build if there are Python syntax errors or undefined names - # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + # Provide only the bare build prereqs Cython is not among them, so + # setup.py's `try: from Cython.Distutils import build_ext` fails and + # no C extensions are produced. --no-build-isolation makes pip use + # this env instead of provisioning a fresh one (which would install + # Cython per pyproject.toml). + pip install "setuptools>=77" wheel "numpy>=1.25" + pip install --no-build-isolation . + # Sanity check: the compiled extension must NOT be importable. + python -c " + try: + from pyspike.cython import cython_distances + except ImportError: + print('OK: cython_distances absent, runtime will use python_backend') + else: + raise SystemExit('FAIL: cython_distances was built despite no-cython matrix') + " + + - name: Install test dependencies + run: pip install pytest scipy + - name: Test with PyTest - run: | - python -m pytest + run: python -m pytest diff --git a/MANIFEST.in b/MANIFEST.in index aed0ae0..91b877a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,13 @@ include *.rst include *.txt -include pyspike/cython/*.c -include directionality/cython/*.c +include License.txt +include Changelog +include pyproject.toml + +# Cython sources — required so `pip install` from sdist can regenerate +# the .c files and build the extension modules. +recursive-include pyspike/cython *.pyx *.pxd + recursive-include examples *.py *.txt recursive-include test *.py *.txt recursive-include doc * diff --git a/SetupNoPrompt.py b/SetupNoPrompt.py deleted file mode 100644 index 90c3270..0000000 --- a/SetupNoPrompt.py +++ /dev/null @@ -1,5 +0,0 @@ -## interlude to force answer to input('Abort?'): - -import io, sys -sys.stdin = io.StringIO('N\n') -import setup diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fbcf413 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,53 @@ +[build-system] +# setuptools >=77 supports the SPDX-string form `license = "BSD-2-Clause"` +# in [project] (PEP 639). numpy and Cython are required because setup.py +# calls numpy.get_include() and compiles .pyx sources for the C extensions. +requires = [ + "setuptools>=77", + "wheel", + "Cython>=3.0", + "numpy>=1.25", +] +build-backend = "setuptools.build_meta" + +[project] +name = "pyspike" +version = "0.8.1" +description = "A Python library for the numerical analysis of spike train similarity" +readme = "Readme.rst" +requires-python = ">=3.9" +license = "BSD-2-Clause" +license-files = ["License.txt"] +authors = [ + { name = "Mario Mulansky", email = "mario.mulansky@gmx.net" }, +] +keywords = ["data analysis", "spike", "neuroscience"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Information Analysis", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +dependencies = [ + "numpy", +] + +[project.urls] +Homepage = "https://github.com/mariomulansky/PySpike" +Repository = "https://github.com/mariomulansky/PySpike" +Issues = "https://github.com/mariomulansky/PySpike/issues" + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages.find] +exclude = ["doc*", "test*", "examples*"] +# Cython .c files are generated at build time (.gitignore excludes them) and +# the resulting .so files are placed automatically. No package_data needed +# for the wheel; MANIFEST.in handles inclusion of .pyx sources in the sdist. diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 4e31120..1003bbc 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -35,23 +35,17 @@ spike_train_order_bi, spike_train_order_multi, \ optimal_spike_train_sorting, permutate_matrix -# define the __version__ following -# http://stackoverflow.com/questions/17583443 -from pkg_resources import get_distribution, DistributionNotFound -import os.path +# Expose the installed version via importlib.metadata (stdlib since 3.8). +# Previously this used pkg_resources, which depends on setuptools — and from +# Python 3.12 onwards venvs no longer ship setuptools by default, so the old +# import would fail at runtime in fresh 3.12+ environments. +from importlib.metadata import version as _get_version, PackageNotFoundError try: - _dist = get_distribution('pyspike') - # Normalize case for Windows systems - dist_loc = os.path.normcase(_dist.location) - here = os.path.normcase(__file__) - if not here.startswith(os.path.join(dist_loc, 'pyspike')): - # not installed, but there is another version that *is* - raise DistributionNotFound -except DistributionNotFound: - __version__ = 'Please install this project with setup.py' -else: - __version__ = _dist.version + __version__ = _get_version("pyspike") +except PackageNotFoundError: + # Running from a source checkout that hasn't been installed. + __version__ = "0.0.0+unknown" disable_backend_warning = False diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index c855aaa..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -description_file = Readme.rst diff --git a/setup.py b/setup.py index b52cf8b..3a55f1a 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,24 @@ """ setup.py -to compile cython files: -python setup.py build_ext --inplace +Compile the Cython extensions for PySpike. +All packaging metadata (name, version, dependencies, classifiers, ...) lives in +pyproject.toml. This file only declares the C extension modules, because that +still needs imperative setup() configuration. -Copyright 2014-2017, Mario Mulansky +To compile cython files in-place: + python setup.py build_ext --inplace + + +Copyright 2014-2026, Mario Mulansky Distributed under the BSD License """ -from setuptools import setup, find_packages -from distutils.extension import Extension import os.path +from setuptools import Extension, setup + try: from Cython.Distutils import build_ext except ImportError: @@ -22,110 +28,62 @@ class numpy_include(os.PathLike): - """Defers import of numpy until install_requires is through""" - def __str__(self): - import numpy - return numpy.get_include() - - def __fspath__(self): - return str(self) - - -if os.path.isfile("pyspike/cython/cython_add.c") and \ - os.path.isfile("pyspike/cython/cython_get_tau.c") and \ - os.path.isfile("pyspike/cython/cython_profiles.c") and \ - os.path.isfile("pyspike/cython/cython_distances.c") and \ - os.path.isfile("pyspike/cython/cython_directionality.c") and \ - os.path.isfile("pyspike/cython/cython_simulated_annealing.c"): - use_c = True -else: - use_c = False + """Defers import of numpy until the build environment is in place. + + pyproject.toml lists numpy as a build-system requirement, so by the time + setup.py actually runs build_ext, numpy is importable. We can't import it + at module top level, though, because setuptools imports setup.py before + build-system requires are installed. + """ + + def __str__(self): + import numpy + return numpy.get_include() + + def __fspath__(self): + return str(self) + + +_CYTHON_MODULES = ( + "cython_add", + "cython_get_tau", + "cython_profiles", + "cython_distances", + "cython_directionality", + "cython_simulated_annealing", +) + + +def _all_c_sources_present(): + return all( + os.path.isfile(f"pyspike/cython/{name}.c") for name in _CYTHON_MODULES + ) + + +use_c = _all_c_sources_present() if not use_cython and not use_c: - print('Cython not installed. Programs will be slow.') - # Ans = input('Abort? (Y/N)\n') - # if len(Ans)>0 and (Ans[0]=='Y' or Ans[0]=='y'): - # print("\nAborting\n") - # raise RuntimeError('User termination') + print("Cython not installed and no pre-generated .c files found. " + "PySpike will fall back to the pure-Python backend (slow).") cmdclass = {} ext_modules = [] -if use_cython: # Cython is available, compile .pyx -> .c - ext_modules += [ - Extension("pyspike.cython.cython_add", - ["pyspike/cython/cython_add.pyx"]), - Extension("pyspike.cython.cython_get_tau", - ["pyspike/cython/cython_get_tau.pyx"]), - Extension("pyspike.cython.cython_profiles", - ["pyspike/cython/cython_profiles.pyx"]), - Extension("pyspike.cython.cython_distances", - ["pyspike/cython/cython_distances.pyx"]), - Extension("pyspike.cython.cython_directionality", - ["pyspike/cython/cython_directionality.pyx"]), - Extension("pyspike.cython.cython_simulated_annealing", - ["pyspike/cython/cython_simulated_annealing.pyx"]) +if use_cython: # Cython is available, compile .pyx -> .c -> binary + ext_modules = [ + Extension(f"pyspike.cython.{name}", [f"pyspike/cython/{name}.pyx"]) + for name in _CYTHON_MODULES ] - cmdclass.update({'build_ext': build_ext}) -elif use_c: # c files are there, compile to binaries - ext_modules += [ - Extension("pyspike.cython.cython_add", - ["pyspike/cython/cython_add.c"]), - Extension("pyspike.cython.cython_get_tau", - ["pyspike/cython/cython_get_tau.c"]), - Extension("pyspike.cython.cython_profiles", - ["pyspike/cython/cython_profiles.c"]), - Extension("pyspike.cython.cython_distances", - ["pyspike/cython/cython_distances.c"]), - Extension("pyspike.cython.cython_directionality", - ["pyspike/cython/cython_directionality.c"]), - Extension("pyspike.cython.cython_simulated_annealing", - ["pyspike/cython/cython_simulated_annealing.c"]) + cmdclass["build_ext"] = build_ext +elif use_c: # No Cython, but pre-generated .c files are present + ext_modules = [ + Extension(f"pyspike.cython.{name}", [f"pyspike/cython/{name}.c"]) + for name in _CYTHON_MODULES ] -# neither cython nor c files available -> automatic fall-back to python backend +# else: neither Cython nor .c files — fall through to pure-Python backend. setup( - name='pyspike', - packages=find_packages(exclude=['doc', 'test*']), - version='0.8.0', cmdclass=cmdclass, ext_modules=ext_modules, include_dirs=[numpy_include()], - description='A Python library for the numerical analysis of spike\ -train similarity', - author='Mario Mulansky', - author_email='mario.mulansky@gmx.net', - license='BSD', - url='https://github.com/mariomulansky/PySpike', - install_requires=['numpy'], - keywords=['data analysis', 'spike', 'neuroscience'], # arbitrary keywords - classifiers=[ - # How mature is this project? Common values are - # 3 - Alpha - # 4 - Beta - # 5 - Production/Stable - 'Development Status :: 4 - Beta', - - # Indicate who your project is intended for - 'Intended Audience :: Science/Research', - 'Topic :: Scientific/Engineering', - 'Topic :: Scientific/Engineering :: Information Analysis', - - 'License :: OSI Approved :: BSD License', - - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - ], - package_data={ - 'pyspike': ['cython/cython_add.c', - 'cython/cython_profiles.c', - 'cython/cython_get_tau.c', - 'cython/cython_distances.c', - 'cython/cython_directionality.c', - 'cython/cython_simulated_annealing.c'], - 'test': ['Spike_testdata.txt'] - } ) From d49763f8d97e5d180f10b8df380c6203bfff08f2 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 11 May 2026 11:06:50 -0700 Subject: [PATCH 3/3] Bump version to 0.9.0 Minor version bump rather than patch because PySpike now requires Python 3.9 (3.7 and 3.8 support was dropped in the packaging modernization in #86). Highlights since 0.8.0: - pyproject.toml-based packaging, fixes pip install with build isolation (#84) - Python 3.12 and 3.13 support - CI tests both the Cython and pure-Python install paths --- Changelog | 9 +++++++++ doc/conf.py | 4 ++-- pyproject.toml | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Changelog b/Changelog index 9d3a374..1672fa2 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,12 @@ +PySpike v0.9 + * Modernized packaging: project metadata moved into pyproject.toml + * Fixed `pip install pyspike` failing in modern pip's isolated build + environment with "ModuleNotFoundError: No module named 'numpy'" (#84) + * Python 3.12 and 3.13 support: replaced pkg_resources (which depends on + setuptools, no longer shipped in default venvs from 3.12 onwards) with + stdlib importlib.metadata for __version__ lookup + * Dropped support for Python 3.7 and 3.8 (both end-of-life) + PySpike v0.8 * Support Adaptive versions of distances (Minimum Relevant Time Scale) * Support Rate Independent Adaptive spike distance diff --git a/doc/conf.py b/doc/conf.py index 1842be2..af8c890 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -64,9 +64,9 @@ def setup(app): # built documents. # # The short X.Y version. -version = '0.8' +version = '0.9' # The full version, including alpha/beta/rc tags. -release = '0.8.0' +release = '0.9.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pyproject.toml b/pyproject.toml index fbcf413..f7b5830 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta" [project] name = "pyspike" -version = "0.8.1" +version = "0.9.0" description = "A Python library for the numerical analysis of spike train similarity" readme = "Readme.rst" requires-python = ">=3.9"