From 3a8bd18da7e2983eef0897f9ab8d4536baa14d79 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Thu, 25 Apr 2024 10:56:55 +0200 Subject: [PATCH] Numpy 2.0 + scipy + matplotlib pre-release workflow (#89) * pin pytest to 8.0 and above * move conftest.py to root and turn warnings into errors * add workflow testing against numpy 2.0 * skip coverage * fix config file used * also install pip-pre scipy and matplotlib * scikit-learn also for nilearn compatibility with numpy 2.0 * don't run nilearn tests on pip-pre * run 3.9 and 3.12 on circle and 3.7 compat on gh * fix workflow name * fix: importorskip is not a mark decorator * catch user warning * fix deprecated readfp * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ignore conftest in module discovery * fix uninstall step of nilearn * try again to rm nilearn * fix module discovery logic * fix numpy deprecation of np.NINF and np.trapz * fix missed importorskip("nilearn") * fix deprecation of find_module in module discovery logic * fix compatibility trapz <-> trapezoid * fix compatibility function * add missed pytest.importorskip("nilearn") * fix imports, drop pkg_utils entirely * fix missed imports * rm unused variable * use relative imports and fix circular import and namespace * revert version import * close log file handler * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix missed importorskip("nilearn") * fix codecov yml * suppress sphinx gallery warning * mv trapezoid_compat * trigger ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .circleci/config.yml | 20 ++++---- .github/workflows/pytest.yaml | 84 +++++++++++++++++++++++++++++++++ codecov.yml | 6 +-- docs/conf.py | 3 ++ nigsp/__init__.py | 26 +++++----- nigsp/blocks.py | 4 +- nigsp/cli/run.py | 9 ++-- nigsp/{tests => }/conftest.py | 42 ++++++++++++----- nigsp/io.py | 2 +- nigsp/objects.py | 2 +- nigsp/operations/__init__.py | 2 +- nigsp/operations/surrogates.py | 2 +- nigsp/operations/timeseries.py | 14 ++++-- nigsp/tests/test_blocks.py | 9 ++-- nigsp/tests/test_integration.py | 4 ++ nigsp/tests/test_viz.py | 64 ++++++++++++++++--------- nigsp/viz.py | 2 +- nigsp/workflow.py | 21 +++++---- setup.cfg | 5 +- versioneer.py | 2 +- 20 files changed, 230 insertions(+), 93 deletions(-) create mode 100644 .github/workflows/pytest.yaml rename nigsp/{tests => }/conftest.py (71%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6d1a4a0..576ef34 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,14 +15,14 @@ orbs: # Define a job to be invoked later in a workflow. # See: https://circleci.com/docs/2.0/configuration-reference/#jobs jobs: - test37: # This is the name of the job, feel free to change it to better match what you're trying to do! + test39: # This is the name of the job, feel free to change it to better match what you're trying to do! # These next lines defines a Docker executors: https://circleci.com/docs/2.0/executor-types/ # You can specify an image from Dockerhub or use one of the convenience images from CircleCI's Developer Hub # A list of available CircleCI Docker convenience images are available here: https://circleci.com/developer/images/image/cimg/python # The executor is the environment in which the steps below will be executed - below will use a python 3.6.14 container # Change the version below to your required version of python docker: - - image: cimg/python:3.7 + - image: cimg/python:3.9 working_directory: /tmp/src/nigsp resource_class: medium # Checkout the code as the first step. This is a dedicated CircleCI step. @@ -58,9 +58,9 @@ jobs: paths: - src/coverage/.coverage.py37 - test310: + test312: docker: - - image: cimg/python:3.10 + - image: cimg/python:3.12 working_directory: /tmp/src/nigsp resource_class: medium steps: @@ -85,7 +85,7 @@ jobs: style_check: docker: - - image: cimg/python:3.7 + - image: cimg/python:3.11 working_directory: /tmp/src/nigsp resource_class: small steps: @@ -105,7 +105,7 @@ jobs: merge_coverage: working_directory: /tmp/src/nigsp docker: - - image: cimg/python:3.10 + - image: cimg/python:3.11 resource_class: small steps: - attach_workspace: @@ -133,13 +133,13 @@ workflows: # Inside the workflow, you define the jobs you want to run. jobs: - style_check - - test37: + - test39: requires: - style_check - - test310: + - test312: requires: - style_check - merge_coverage: requires: - - test37 - - test310 + - test39 + - test312 diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml new file mode 100644 index 0000000..6ccb456 --- /dev/null +++ b/.github/workflows/pytest.yaml @@ -0,0 +1,84 @@ +name: pytest +concurrency: + group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }} + cancel-in-progress: true +on: # yamllint disable-line rule:truthy + pull_request: + push: + branches: [main] + workflow_dispatch: + schedule: + - cron: '0 8 * * 1' + +jobs: + pytest-compat: + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + python-version: ["3.7"] + name: pip compat - py${{ matrix.python-version }} + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + architecture: 'x64' + - name: Install dependencies + run: | + python -m pip install --progress-bar off --upgrade pip setuptools + python -m pip install --progress-bar off .[all,style] + python -m pip install --progress-bar off pytest pytest-cov coverage + - name: Run pytest + run: pytest nigsp --cov=nigsp --cov-report=xml --cov-config=setup.cfg + - name: Upload to codecov + uses: codecov/codecov-action@v4 + with: + files: ./coverage.xml + flags: unittests # optional + name: codecov-umbrella # optional + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true # optional (default = false) + pytest-pip-pre: + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + python-version: ["3.11"] + name: pip pre-release - py${{ matrix.python-version }} + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + architecture: 'x64' + - name: Install dependencies + run: | + python -m pip install --progress-bar off --upgrade pip setuptools + python -m pip install --progress-bar off .[test] + python -m pip install matplotlib + python -m pip install --progress-bar off --upgrade --no-deps --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --timeout=180 matplotlib + python -m pip install --progress-bar off --upgrade --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --timeout=180 numpy scipy + python -m pip uninstall -y nilearn + - name: Run pytest + run: pytest nigsp --cov=nigsp --cov-report=xml --cov-config=setup.cfg + - name: Upload to codecov + uses: codecov/codecov-action@v4 + with: + files: ./coverage.xml + flags: unittests # optional + name: codecov-umbrella # optional + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true # optional (default = false) diff --git a/codecov.yml b/codecov.yml index 80d4b1a..3a5d947 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,10 +1,10 @@ codecov: branch: master strict_yaml_branch: master - require_ci_to_pass: yes + require_ci_to_pass: true bot: "codecov-io" max_report_age: 48 - disable_default_path_fixes: no + disable_default_path_fixes: false coverage: precision: 2 @@ -40,4 +40,4 @@ ignore: comment: layout: "reach,diff,flags,tree" behavior: default - require_changes: no + require_changes: false diff --git a/docs/conf.py b/docs/conf.py index 7e276f9..de08f8e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -89,6 +89,9 @@ nitpicky = True nitpick_ignore = [] +# list of warning types to suppress +suppress_warnings = ["config.cache"] + # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for diff --git a/nigsp/__init__.py b/nigsp/__init__.py index 3d64b72..6bd64d9 100644 --- a/nigsp/__init__.py +++ b/nigsp/__init__.py @@ -1,18 +1,16 @@ -"""Hopefully importing everything.""" - -import pkgutil - +from . import ( + blocks, + cli, + due, + io, + objects, + operations, + references, + utils, + viz, + workflow, +) from ._version import get_versions -from .operations import graph, laplacian, metrics, nifti, surrogates, timeseries - -SKIP_MODULES = ["tests"] __version__ = get_versions()["version"] del get_versions - -__all__ = [] -for loader, module_name, is_pkg in pkgutil.walk_packages(__path__): - if "tests" not in module_name: - __all__.append(module_name) - _module = loader.find_module(module_name).load_module(module_name) - globals()[module_name] = _module diff --git a/nigsp/blocks.py b/nigsp/blocks.py index 5db5df9..06fecba 100644 --- a/nigsp/blocks.py +++ b/nigsp/blocks.py @@ -10,8 +10,8 @@ import logging -from nigsp import io, viz -from nigsp.operations import nifti +from . import io, viz +from .operations import nifti LGR = logging.getLogger(__name__) diff --git a/nigsp/cli/run.py b/nigsp/cli/run.py index 19180dc..e427e90 100644 --- a/nigsp/cli/run.py +++ b/nigsp/cli/run.py @@ -3,17 +3,15 @@ import argparse -from nigsp import __version__ +from .. import __version__ def _get_parser(): - """ - Parse command line inputs for this function. + """Parse command line inputs for this function. Returns ------- parser.parse_args() : argparse dict - """ parser = argparse.ArgumentParser( description=( @@ -251,8 +249,7 @@ def _get_parser(): if __name__ == "__main__": raise RuntimeError( "nigsp/cli/run.py should not be run directly;\n" - "Please `pip install` nigsp and use the " - "`nigsp` command" + "Please `pip install` nigsp and use the `nigsp` command." ) diff --git a/nigsp/tests/conftest.py b/nigsp/conftest.py similarity index 71% rename from nigsp/tests/conftest.py rename to nigsp/conftest.py index bf1adf9..c3c244a 100644 --- a/nigsp/tests/conftest.py +++ b/nigsp/conftest.py @@ -1,13 +1,31 @@ -""" -This configuration test module was taken from phys2bids. -Credit to the original author(s) and to the phys2bids community. -""" +from __future__ import annotations # c.f. PEP 563, PEP 649 import os import ssl +from typing import TYPE_CHECKING from urllib.request import urlretrieve -import pytest +from pytest import fixture + +if TYPE_CHECKING: + from pytest import Config + + +def pytest_configure(config: Config) -> None: + """Configure pytest options.""" + warnings_lines = r""" + error:: + """ + for warning_line in warnings_lines.split("\n"): + warning_line = warning_line.strip() + if warning_line and not warning_line.startswith("#"): + config.addinivalue_line("filterwarnings", warning_line) + + +""" +The following fetch_file and configuration test module was taken from phys2bids. +Credit to the original author(s) and to the phys2bids community. +""" def fetch_file(osf_id, path, filename): @@ -48,37 +66,37 @@ def fetch_file(osf_id, path, filename): return full_path -@pytest.fixture(scope="session") +@fixture(scope="session") def testdir(tmp_path_factory): """Test path that will be used to download all files.""" return tmp_path_factory.getbasetemp() -@pytest.fixture +@fixture(scope="function") def atlas(testdir): return fetch_file("h6nj7", testdir, "atlas.nii.gz") -@pytest.fixture +@fixture(scope="function") def atlastime(testdir): return fetch_file("ts6a8", testdir, "ats.nii.gz") -@pytest.fixture +@fixture(scope="function") def mean_fc(testdir): return fetch_file("jrg8d", testdir, "mean_fc_matlab.tsv") -@pytest.fixture +@fixture(scope="function") def sdi(testdir): return fetch_file("rs4dn", testdir, "SDI_matlab.tsv") -@pytest.fixture +@fixture(scope="function") def sc_mtx(testdir): return fetch_file("vwh75", testdir, "sc.mat") -@pytest.fixture +@fixture(scope="function") def timeseries(testdir): return fetch_file("ay8df", testdir, "func.mat") diff --git a/nigsp/io.py b/nigsp/io.py index 3567c44..c27c8f3 100644 --- a/nigsp/io.py +++ b/nigsp/io.py @@ -26,7 +26,7 @@ import numpy as np -from nigsp.utils import change_var_type +from .utils import change_var_type EXT_1D = [".txt", ".csv", ".tsv", ".1d", ".par", ".tsv.gz", ".csv.gz"] EXT_MAT = [".mat"] diff --git a/nigsp/objects.py b/nigsp/objects.py index 489fb10..635f062 100644 --- a/nigsp/objects.py +++ b/nigsp/objects.py @@ -17,7 +17,7 @@ import logging from copy import deepcopy -from nigsp import operations +from . import operations LGR = logging.getLogger(__name__) diff --git a/nigsp/operations/__init__.py b/nigsp/operations/__init__.py index ccc38fa..459216b 100644 --- a/nigsp/operations/__init__.py +++ b/nigsp/operations/__init__.py @@ -11,7 +11,7 @@ `nigsp.operations.sdi` or `nigsp.metrisc.sdi`. """ -# Import all operations. +from . import graph, laplacian, metrics, nifti, surrogates, timeseries from .graph import nodestrength, zerocross from .laplacian import ( compute_laplacian, diff --git a/nigsp/operations/surrogates.py b/nigsp/operations/surrogates.py index d057d7f..fc1bf8f 100644 --- a/nigsp/operations/surrogates.py +++ b/nigsp/operations/surrogates.py @@ -431,7 +431,7 @@ def _pmf(x, n, p): if return_masked: LGR.info("Returning masked empirical data") stat_mask = np.ma.array( - data=surr[..., -1], mask=np.invert(stat_mask), fill_value=np.NINF + data=surr[..., -1], mask=np.invert(stat_mask), fill_value=-np.inf ).squeeze() else: LGR.info("Returning mask") diff --git a/nigsp/operations/timeseries.py b/nigsp/operations/timeseries.py index 23d260b..85b6956 100644 --- a/nigsp/operations/timeseries.py +++ b/nigsp/operations/timeseries.py @@ -287,6 +287,14 @@ def graph_fourier_transform(timeseries, eigenvec, energy=False, mean=False): return proj +def _trapezoid_compat(*args, **kwargs): + """Compatibility for numpy 1.xx <-> numpy 2.xx.""" + if hasattr(np, "trapezoid"): + return np.trapezoid(*args, **kwargs) + else: + return np.trapz(*args, **kwargs) + + def median_cutoff_frequency_idx(energy): """ Find the frequency that splits the energy of a timeseries in two roughly equal parts. @@ -318,7 +326,7 @@ def median_cutoff_frequency_idx(energy): if energy.ndim == 2: energy = energy.mean(axis=-1) - half_tot_auc = np.trapz(energy, axis=0) / 2 + half_tot_auc = _trapezoid_compat(energy, axis=0) / 2 LGR.debug(f"Total AUC = {half_tot_auc*2}, targeting half of total AUC") # Compute the AUC from first to one to last frequency, @@ -329,10 +337,10 @@ def median_cutoff_frequency_idx(energy): for freq_idx in range(1, energy.size): LGR.debug( f"Frequency idx {freq_idx}, " - f"AUC = {np.trapz(energy[:freq_idx])}, " + f"AUC = {_trapezoid_compat(energy[:freq_idx])}, " f"target AUC = {half_tot_auc}" ) - if np.trapz(energy[:freq_idx]) >= half_tot_auc: + if _trapezoid_compat(energy[:freq_idx]) >= half_tot_auc: break LGR.info(f"Found {freq_idx} as splitting index") diff --git a/nigsp/tests/test_blocks.py b/nigsp/tests/test_blocks.py index 5437014..076a411 100644 --- a/nigsp/tests/test_blocks.py +++ b/nigsp/tests/test_blocks.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Tests for blocks.""" + import shutil import sys from os import makedirs, remove @@ -7,9 +8,8 @@ import nibabel import numpy as np -from nilearn.plotting import find_parcellation_cut_coords +import pytest from pymatreader import read_mat -from pytest import mark from nigsp import blocks from nigsp.io import load_nifti_get_mask @@ -32,7 +32,7 @@ def test_nifti_to_timeseries(atlastime, atlas): remove(atlas) -@mark.parametrize("ext", [(".nii.gz"), (".csv")]) +@pytest.mark.parametrize("ext", [(".nii.gz"), (".csv")]) def test_export_metrics_txt(ext, sc_mtx, atlas, sdi, testdir): testdir = join(testdir, "testdir") makedirs(testdir, exist_ok=True) @@ -85,6 +85,9 @@ def test_export_metrics_nifti(sc_mtx, atlas, sdi, testdir): def test_plot_metrics(atlas, sc_mtx, sdi, testdir): + pytest.importorskip("nilearn") + from nilearn.plotting import find_parcellation_cut_coords + testdir = join(testdir, "testdir") makedirs(testdir, exist_ok=True) atlas_in, _, img = load_nifti_get_mask(atlas, ndim=3) diff --git a/nigsp/tests/test_integration.py b/nigsp/tests/test_integration.py index 8584a4c..4103f93 100644 --- a/nigsp/tests/test_integration.py +++ b/nigsp/tests/test_integration.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 """Integration test.""" + import shutil from os.path import isdir, isfile, join import numpy as np +import pytest from nigsp.workflow import _main @@ -11,6 +13,8 @@ # ### Integration tests def test_integration(timeseries, sc_mtx, atlas, mean_fc, sdi, testdir): """Integration test for FULL workflow.""" + pytest.importorskip("nilearn") + testdir = join(testdir, "testdir") mean_fc_mat = np.genfromtxt(mean_fc) # Compared to original SDI, now we log2 it). diff --git a/nigsp/tests/test_viz.py b/nigsp/tests/test_viz.py index 18be01a..8c0be38 100644 --- a/nigsp/tests/test_viz.py +++ b/nigsp/tests/test_viz.py @@ -6,19 +6,20 @@ from os.path import isfile import matplotlib -import nilearn.plotting +import pytest from numpy import genfromtxt from numpy.random import rand -from pytest import mark, raises from nigsp import viz -@mark.parametrize( +@pytest.mark.parametrize( "mtx", [(rand(3, 4)), (rand(4, 4)), (rand(4, 4, 3)), (rand(4, 4, 3, 1))] ) # ### Unit tests def test_plot_connectivity(mtx): + pytest.importorskip("nilearn") + viz.plot_connectivity(mtx, "annie.png", closeplot=True) assert isfile("annie.png") @@ -26,7 +27,9 @@ def test_plot_connectivity(mtx): remove("annie.png") -@mark.parametrize("timeseries", [(rand(3, 50)), (rand(3, 50, 3)), (rand(3, 50, 4, 1))]) +@pytest.mark.parametrize( + "timeseries", [(rand(3, 50)), (rand(3, 50, 3)), (rand(3, 50, 4, 1))] +) def test_plot_greyplot(timeseries): viz.plot_greyplot(timeseries, "troy.png", closeplot=True) @@ -36,6 +39,8 @@ def test_plot_greyplot(timeseries): def test_plot_nodes(sdi, atlas): + pytest.importorskip("nilearn") + ns = genfromtxt(sdi) viz.plot_nodes(ns, atlas, "abed.png", closeplot=True) @@ -47,9 +52,12 @@ def test_plot_nodes(sdi, atlas): def test_plot_edges(atlas): + pytest.importorskip("nilearn") + mtx = rand(360, 360) mtx = mtx - mtx.min() + 0.3 - viz.plot_edges(mtx, atlas, "britta.png", thr="95%", closeplot=True) + with pytest.warns(UserWarning, match="'adjacency_matrix' is not symmetric"): + viz.plot_edges(mtx, atlas, "britta.png", thr="95%", closeplot=True) assert isfile("britta.png") matplotlib.pyplot.close() @@ -59,19 +67,23 @@ def test_plot_edges(atlas): # ### Break tests def test_break_plot_connectivity(): + pytest.importorskip("nilearn") + + import nilearn.plotting + sys.modules["matplotlib"] = None - with raises(ImportError) as errorinfo: + with pytest.raises(ImportError) as errorinfo: viz.plot_connectivity(rand(3, 3), "craig.png") assert "are required" in str(errorinfo.value) sys.modules["matplotlib"] = matplotlib sys.modules["nilearn.plotting"] = None - with raises(ImportError) as errorinfo: + with pytest.raises(ImportError) as errorinfo: viz.plot_connectivity(rand(3, 3), "craig.png") assert "are required" in str(errorinfo.value) sys.modules["nilearn.plotting"] = nilearn.plotting - with raises(ValueError) as errorinfo: + with pytest.raises(ValueError) as errorinfo: viz.plot_connectivity(rand(3, 3, 3, 4), "craig.png") assert "plot connectivity" in str(errorinfo.value) @@ -80,12 +92,12 @@ def test_break_plot_connectivity(): def test_break_plot_greyplot(): sys.modules["matplotlib"] = None - with raises(ImportError) as errorinfo: + with pytest.raises(ImportError) as errorinfo: viz.plot_greyplot(rand(3, 3), "dan.png") assert "is required" in str(errorinfo.value) sys.modules["matplotlib"] = matplotlib - with raises(ValueError) as errorinfo: + with pytest.raises(ValueError) as errorinfo: viz.plot_greyplot(rand(3, 3, 3, 4), "dan.png") assert "plot greyplots" in str(errorinfo.value) @@ -93,30 +105,34 @@ def test_break_plot_greyplot(): def test_break_plot_nodes(atlas): + pytest.importorskip("nilearn") + + import nilearn.plotting + sys.modules["matplotlib"] = None - with raises(ImportError) as errorinfo: + with pytest.raises(ImportError) as errorinfo: viz.plot_nodes(rand(3), rand(3, 3)) assert "are required" in str(errorinfo.value) sys.modules["matplotlib"] = matplotlib sys.modules["nilearn.plotting"] = None - with raises(ImportError) as errorinfo: + with pytest.raises(ImportError) as errorinfo: viz.plot_nodes(rand(3), rand(3, 3)) assert "are required" in str(errorinfo.value) sys.modules["nilearn.plotting"] = nilearn.plotting - with raises(ValueError) as errorinfo: + with pytest.raises(ValueError) as errorinfo: viz.plot_nodes(rand(3, 3, 4), rand(3, 3)) assert "plot node values" in str(errorinfo.value) - with raises(NotImplementedError) as errorinfo: + with pytest.raises(NotImplementedError) as errorinfo: viz.plot_nodes(rand(3), rand(3, 3, 2)) assert "atlases in nifti" in str(errorinfo.value) - with raises(NotImplementedError) as errorinfo: + with pytest.raises(NotImplementedError) as errorinfo: viz.plot_nodes(rand(3), rand(3, 4)) assert "atlases in nifti" in str(errorinfo.value) - with raises(ValueError) as errorinfo: + with pytest.raises(ValueError) as errorinfo: viz.plot_nodes(rand(3), rand(4, 3)) assert "different length" in str(errorinfo.value) @@ -125,30 +141,34 @@ def test_break_plot_nodes(atlas): def test_break_plot_edges(atlas): + pytest.importorskip("nilearn") + + import nilearn.plotting + sys.modules["matplotlib"] = None - with raises(ImportError) as errorinfo: + with pytest.raises(ImportError) as errorinfo: viz.plot_edges(rand(3), rand(3, 3)) assert "are required" in str(errorinfo.value) sys.modules["matplotlib"] = matplotlib sys.modules["nilearn.plotting"] = None - with raises(ImportError) as errorinfo: + with pytest.raises(ImportError) as errorinfo: viz.plot_edges(rand(3), rand(3, 3)) assert "are required" in str(errorinfo.value) sys.modules["nilearn.plotting"] = nilearn.plotting - with raises(ValueError) as errorinfo: + with pytest.raises(ValueError) as errorinfo: viz.plot_edges(rand(3, 3, 4, 5), rand(3, 3)) assert "plot node values" in str(errorinfo.value) - with raises(NotImplementedError) as errorinfo: + with pytest.raises(NotImplementedError) as errorinfo: viz.plot_edges(rand(3), rand(3, 3, 2)) assert "atlases in nifti" in str(errorinfo.value) - with raises(NotImplementedError) as errorinfo: + with pytest.raises(NotImplementedError) as errorinfo: viz.plot_edges(rand(3), rand(3, 4)) assert "atlases in nifti" in str(errorinfo.value) - with raises(ValueError) as errorinfo: + with pytest.raises(ValueError) as errorinfo: viz.plot_edges(rand(3), rand(4, 3)) assert "different length" in str(errorinfo.value) diff --git a/nigsp/viz.py b/nigsp/viz.py index 8b0bd1e..4ddb65d 100644 --- a/nigsp/viz.py +++ b/nigsp/viz.py @@ -22,7 +22,7 @@ import numpy as np -from nigsp.operations.timeseries import resize_ts +from .operations.timeseries import resize_ts LGR = logging.getLogger(__name__) SET_DPI = 100 diff --git a/nigsp/workflow.py b/nigsp/workflow.py index d5a64cf..2c79284 100644 --- a/nigsp/workflow.py +++ b/nigsp/workflow.py @@ -9,6 +9,7 @@ $ nigsp --help ``` """ + import datetime import logging import os @@ -16,14 +17,12 @@ import numpy as np -from nigsp import _version, blocks, io, references -from nigsp import surrogates as surr -from nigsp import timeseries as ts -from nigsp import utils, viz -from nigsp.cli.run import _get_parser -from nigsp.due import due -from nigsp.objects import SCGraph -from nigsp.operations.metrics import SUPPORTED_METRICS +from . import _version, blocks, io, references, utils, viz +from .due import due +from .objects import SCGraph +from .operations import surrogates as surr +from .operations import timeseries as ts +from .operations.metrics import SUPPORTED_METRICS LGR = logging.getLogger(__name__) LGR.setLevel(logging.INFO) @@ -469,15 +468,17 @@ def nigsp( pass LGR.info(f"End of workflow, find results in {outdir}.") + LGR.removeHandler(log_handler) + log_handler.close() return 0 def _main(argv=None): - options = _get_parser().parse_args(argv) + from .cli.run import _get_parser + options = _get_parser().parse_args(argv) save_bash_call(options.fname, options.outdir, options.outname) - nigsp(**vars(options)) diff --git a/setup.cfg b/setup.cfg index db6990b..1550d22 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,7 +28,7 @@ install_requires = numpy>=1.17 duecredit tests_require = - pytest >=5.3 + pytest >=8.0 test_suite = pytest zip_safe = False packages = find: @@ -69,7 +69,7 @@ style = test = %(all)s %(style)s - pytest >=5.3 + pytest >=8.0 pytest-cov coverage devtools = @@ -138,6 +138,7 @@ omit = setup.py versioneer.py doi.py + conftest.py __init__.py */__init__.py */*/__init__.py diff --git a/versioneer.py b/versioneer.py index 621489e..049a236 100644 --- a/versioneer.py +++ b/versioneer.py @@ -348,7 +348,7 @@ def get_config_from_root(root): else: parser = configparser.ConfigParser() with open(setup_cfg, "r") as f: - parser.readfp(f) + parser.read_file(f) VCS = parser.get("versioneer", "VCS") # mandatory def get(parser, name):