diff --git a/docs/api.rst b/docs/api.rst index 4a99244d332..5921cfd6519 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -7,6 +7,7 @@ Information on specific functions, classes, and methods. api/niworkflows.anat api/niworkflows.cli + api/niworkflows.data api/niworkflows.dwi api/niworkflows.engine api/niworkflows.func diff --git a/docs/conf.py b/docs/conf.py index dd334ab1e84..3eaedb75867 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -111,7 +111,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -247,7 +247,7 @@ apidoc_module_dir = "../niworkflows" apidoc_output_dir = "api" -apidoc_excluded_paths = ["conftest.py", "*/tests/*", "tests/*", "data/*", "testing.py"] +apidoc_excluded_paths = ["conftest.py", "*/tests/*", "tests/*", "testing.py"] apidoc_separate_modules = True apidoc_extra_args = ["--module-first", "-d 1", "-T"] diff --git a/docs/requirements.txt b/docs/requirements.txt index 7e519c6b782..60b52c49481 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,10 +1,9 @@ attrs -furo ~= 2022.4.7 +furo nipype >= 1.5.1 traits < 6.4 packaging pytest -sphinx ~= 4.2 +sphinx sphinxcontrib-apidoc -sphinxcontrib-napoleon templateflow diff --git a/niworkflows/__init__.py b/niworkflows/__init__.py index 7fc319df800..c5a417d9775 100644 --- a/niworkflows/__init__.py +++ b/niworkflows/__init__.py @@ -4,6 +4,7 @@ import logging from .__about__ import __packagename__, __copyright__, __credits__ +from .data import Loader try: from ._version import __version__ except ImportError: # pragma: no cover @@ -16,6 +17,7 @@ "__copyright__", "__credits__", "NIWORKFLOWS_LOG", + "load_resource", ] NIWORKFLOWS_LOG = logging.getLogger(__packagename__) @@ -27,3 +29,5 @@ matplotlib.use("Agg") except ImportError: pass + +load_resource = Loader(__package__) diff --git a/niworkflows/anat/ants.py b/niworkflows/anat/ants.py index 74b90764c9a..5854f7c9d64 100644 --- a/niworkflows/anat/ants.py +++ b/niworkflows/anat/ants.py @@ -25,7 +25,6 @@ # general purpose from collections import OrderedDict from multiprocessing import cpu_count -from pkg_resources import resource_filename as pkgr_fn from warnings import warn # nipype @@ -40,6 +39,7 @@ ThresholdImage, ) +from ..data import load as load_data from ..utils.misc import get_template_specs from ..utils.connections import pop_file as _pop @@ -302,9 +302,7 @@ def init_brain_extraction_wf( else "antsBrainExtractionNoLaplacian_%s.json" ) norm = pe.Node( - Registration( - from_file=pkgr_fn("niworkflows.data", settings_file % normalization_quality) - ), + Registration(from_file=load_data(settings_file % normalization_quality)), name="norm", n_procs=omp_nthreads, mem_gb=mem_gb, diff --git a/niworkflows/conftest.py b/niworkflows/conftest.py index 3714ed134c3..0408bc8155e 100644 --- a/niworkflows/conftest.py +++ b/niworkflows/conftest.py @@ -30,6 +30,8 @@ import pytest import tempfile +from . import load_resource + try: import importlib_resources except ImportError: @@ -40,7 +42,7 @@ def find_resource_or_skip(resource): - pathlike = importlib_resources.files("niworkflows") / resource + pathlike = load_resource(resource) if not pathlike.exists(): pytest.skip(f"Missing resource {resource}; run this test from a source repository") return pathlike @@ -63,7 +65,7 @@ def add_np(doctest_namespace): doctest_namespace["datadir"] = data_dir doctest_namespace["data_dir_canary"] = data_dir_canary doctest_namespace["bids_collect_data"] = collect_data - doctest_namespace["test_data"] = importlib_resources.files("niworkflows") / "tests" / "data" + doctest_namespace["test_data"] = load_resource('tests/data') tmpdir = tempfile.TemporaryDirectory() diff --git a/niworkflows/data/__init__.py b/niworkflows/data/__init__.py index e69de29bb2d..ef1bddab104 100644 --- a/niworkflows/data/__init__.py +++ b/niworkflows/data/__init__.py @@ -0,0 +1,182 @@ +"""Niworkflows data files + +.. autofunction:: load + +.. automethod:: load.readable + +.. automethod:: load.as_path + +.. automethod:: load.cached + +.. autoclass:: Loader +""" +from __future__ import annotations + +import atexit +import os +from contextlib import AbstractContextManager, ExitStack +from functools import cached_property +from pathlib import Path +from types import ModuleType +from typing import Union + +try: + from functools import cache +except ImportError: # PY38 + from functools import lru_cache as cache + +try: # Prefer backport to leave consistency to dependency spec + from importlib_resources import as_file, files +except ImportError: + from importlib.resources import as_file, files # type: ignore + +try: # Prefer stdlib so Sphinx can link to authoritative documentation + from importlib.resources.abc import Traversable +except ImportError: + from importlib_resources.abc import Traversable + +__all__ = ["load"] + + +class Loader: + """A loader for package files relative to a module + + This class wraps :mod:`importlib.resources` to provide a getter + function with an interpreter-lifetime scope. For typical packages + it simply passes through filesystem paths as :class:`~pathlib.Path` + objects. For zipped distributions, it will unpack the files into + a temporary directory that is cleaned up on interpreter exit. + + This loader accepts a fully-qualified module name or a module + object. + + Expected usage:: + + '''Data package + + .. autofunction:: load_data + + .. automethod:: load_data.readable + + .. automethod:: load_data.as_path + + .. automethod:: load_data.cached + ''' + + from niworkflows.data import Loader + + load_data = Loader(__package__) + + :class:`~Loader` objects implement the :func:`callable` interface + and generate a docstring, and are intended to be treated and documented + as functions. + + For greater flexibility and improved readability over the ``importlib.resources`` + interface, explicit methods are provided to access resources. + + +---------------+----------------+------------------+ + | On-filesystem | Lifetime | Method | + +---------------+----------------+------------------+ + | `True` | Interpreter | :meth:`cached` | + +---------------+----------------+------------------+ + | `True` | `with` context | :meth:`as_path` | + +---------------+----------------+------------------+ + | `False` | n/a | :meth:`readable` | + +---------------+----------------+------------------+ + + It is also possible to use ``Loader`` directly:: + + from niworkflows.data import Loader + + Loader(other_package).readable('data/resource.ext').read_text() + + with Loader(other_package).as_path('data') as pkgdata: + # Call function that requires full Path implementation + func(pkgdata) + + # contrast to + + from importlib_resources import files, as_file + + files(other_package).joinpath('data/resource.ext').read_text() + + with as_file(files(other_package) / 'data') as pkgdata: + func(pkgdata) + + .. automethod:: readable + + .. automethod:: as_path + + .. automethod:: cached + """ + + def __init__(self, anchor: Union[str, ModuleType]): + self._anchor = anchor + self.files = files(anchor) + self.exit_stack = ExitStack() + atexit.register(self.exit_stack.close) + # Allow class to have a different docstring from instances + self.__doc__ = self._doc + + @cached_property + def _doc(self): + """Construct docstring for instances + + Lists the public top-level paths inside the location, where + non-public means has a `.` or `_` prefix or is a 'tests' + directory. + """ + top_level = sorted( + os.path.relpath(p, self.files) + "/"[: p.is_dir()] + for p in self.files.iterdir() + if p.name[0] not in (".", "_") and p.name != "tests" + ) + doclines = [ + f"Load package files relative to ``{self._anchor}``.", + "", + "This package contains the following (top-level) files/directories:", + "", + *(f"* ``{path}``" for path in top_level), + ] + + return "\n".join(doclines) + + def readable(self, *segments) -> Traversable: + """Provide read access to a resource through a Path-like interface. + + This file may or may not exist on the filesystem, and may be + efficiently used for read operations, including directory traversal. + + This result is not cached or copied to the filesystem in cases where + that would be necessary. + """ + return self.files.joinpath(*segments) + + def as_path(self, *segments) -> AbstractContextManager[Path]: + """Ensure data is available as a :class:`~pathlib.Path`. + + This method generates a context manager that yields a Path when + entered. + + This result is not cached, and any temporary files that are created + are deleted when the context is exited. + """ + return as_file(self.files.joinpath(*segments)) + + @cache + def cached(self, *segments) -> Path: + """Ensure data is available as a :class:`~pathlib.Path`. + + Any temporary files that are created remain available throughout + the duration of the program, and are deleted when Python exits. + + Results are cached so that multiple calls do not unpack the same + data multiple times, but the cache is sensitive to the specific + argument(s) passed. + """ + return self.exit_stack.enter_context(as_file(self.files.joinpath(*segments))) + + __call__ = cached + + +load = Loader(__package__) diff --git a/niworkflows/func/util.py b/niworkflows/func/util.py index 0c1f0cadbaf..87733aa776f 100644 --- a/niworkflows/func/util.py +++ b/niworkflows/func/util.py @@ -22,13 +22,13 @@ # """Utility workflows.""" from packaging.version import parse as parseversion, Version -from pkg_resources import resource_filename as pkgr_fn from nipype.pipeline import engine as pe from nipype.interfaces import utility as niu, fsl, afni from templateflow.api import get as get_template +from .. import data from ..engine.workflows import LiterateWorkflow as Workflow from ..interfaces.fixes import ( FixHeaderRegistration as Registration, @@ -452,9 +452,7 @@ def init_enhance_and_skullstrip_bold_wf( # Set up spatial normalization norm = pe.Node( - Registration( - from_file=pkgr_fn("niworkflows.data", "epi_atlasbased_brainmask.json") - ), + Registration(from_file=data.load("epi_atlasbased_brainmask.json")), name="norm", n_procs=omp_nthreads, ) diff --git a/niworkflows/interfaces/bids.py b/niworkflows/interfaces/bids.py index 04db3f7285e..4dea4ec1e97 100644 --- a/niworkflows/interfaces/bids.py +++ b/niworkflows/interfaces/bids.py @@ -27,7 +27,6 @@ from pathlib import Path import shutil import os -from pkg_resources import resource_filename as _pkgres import re import nibabel as nb @@ -50,12 +49,13 @@ ) from nipype.interfaces.io import add_traits import templateflow as tf +from .. import data from ..utils.bids import _init_layout, relative_to_root from ..utils.images import set_consumables, unsafe_write_nifti_header_and_data from ..utils.misc import _copy_any, unlink regz = re.compile(r"\.gz$") -_pybids_spec = loads(Path(_pkgres("niworkflows", "data/nipreps.json")).read_text()) +_pybids_spec = loads(data.load.readable("nipreps.json").read_text()) BIDS_DERIV_ENTITIES = _pybids_spec["entities"] BIDS_DERIV_PATTERNS = tuple(_pybids_spec["default_path_patterns"]) diff --git a/niworkflows/interfaces/norm.py b/niworkflows/interfaces/norm.py index a1722a5ed60..85115c3ec54 100644 --- a/niworkflows/interfaces/norm.py +++ b/niworkflows/interfaces/norm.py @@ -24,7 +24,6 @@ from os import path as op from multiprocessing import cpu_count -import pkg_resources as pkgr from packaging.version import Version import numpy as np @@ -40,6 +39,7 @@ from templateflow.api import get as get_template from .. import NIWORKFLOWS_LOG, __version__ +from ..data import load as load_data from .fixes import FixHeaderRegistration as Registration @@ -166,16 +166,13 @@ def _get_settings(self): self.inputs.moving.lower(), self.inputs.flavor ) + data_dir = load_data() # Get a list of settings files that match the flavor. filenames = [ - i - for i in pkgr.resource_listdir("niworkflows", "data") - if i.startswith(filestart) and i.endswith(".json") + i for i in data_dir.iterdir() if i.startswith(filestart) and i.endswith(".json") ] # Return the settings files. - return [ - pkgr.resource_filename("niworkflows.data", f) for f in sorted(filenames) - ] + return [str(data_dir / f) for f in sorted(filenames)] def _run_interface(self, runtime): # Get a list of settings files. diff --git a/niworkflows/interfaces/tests/data/__init__.py b/niworkflows/interfaces/tests/data/__init__.py new file mode 100644 index 00000000000..5962b3a130f --- /dev/null +++ b/niworkflows/interfaces/tests/data/__init__.py @@ -0,0 +1,7 @@ +"""Test data module + +.. autofunction:: load_test_data +""" +from ....data import Loader + +load_test_data = Loader(__package__) diff --git a/niworkflows/interfaces/tests/test_itk.py b/niworkflows/interfaces/tests/test_itk.py index c44acc7c422..4aeb9721b77 100644 --- a/niworkflows/interfaces/tests/test_itk.py +++ b/niworkflows/interfaces/tests/test_itk.py @@ -22,6 +22,7 @@ # import pytest from ..itk import _applytfms +from ... import data from nipype.interfaces.ants.base import Info @@ -32,14 +33,13 @@ def test_applytfms(tmpdir, ext, copy_dtype, in_dtype): import numpy as np import nibabel as nb - from pkg_resources import resource_filename as pkgr_fn in_file = str(tmpdir / ("src" + ext)) nii = nb.Nifti1Image(np.zeros((5, 5, 5), dtype=np.float32), np.eye(4)) nii.set_data_dtype(in_dtype) nii.to_filename(in_file) - in_xform = pkgr_fn("niworkflows", "data/itkIdentityTransform.txt") + in_xform = data.load("itkIdentityTransform.txt") ifargs = {"copy_dtype": copy_dtype, "reference_image": in_file} args = (in_file, in_xform, ifargs, 0, str(tmpdir)) diff --git a/niworkflows/interfaces/tests/test_plotting.py b/niworkflows/interfaces/tests/test_plotting.py index 6249ffe6b65..e6743393ddc 100644 --- a/niworkflows/interfaces/tests/test_plotting.py +++ b/niworkflows/interfaces/tests/test_plotting.py @@ -26,18 +26,17 @@ from niworkflows import viz from niworkflows.utils.timeseries import _cifti_timeseries, _nifti_timeseries from niworkflows.interfaces.plotting import _get_tr -from niworkflows.tests.conftest import datadir +from niworkflows.tests.data import load_test_data def test_cifti_carpetplot(): """Exercise extraction of timeseries from CIFTI2.""" save_artifacts = os.getenv("SAVE_CIRCLE_ARTIFACTS", False) - cifti_file = os.path.join( - datadir, - "sub-01_task-mixedgamblestask_run-02_space-fsLR_den-91k_bold.dtseries.nii", + cifti_file = load_test_data( + "sub-01_task-mixedgamblestask_run-02_space-fsLR_den-91k_bold.dtseries.nii" ) - data, segments = _cifti_timeseries(cifti_file) + data, segments = _cifti_timeseries(str(cifti_file)) viz.plot_carpet( data, segments, @@ -56,15 +55,13 @@ def test_nifti_carpetplot(): """Exercise extraction of timeseries from CIFTI2.""" save_artifacts = os.getenv("SAVE_CIRCLE_ARTIFACTS", False) - nifti_file = os.path.join( - datadir, - "sub-ds205s03_task-functionallocalizer_run-01_bold_volreg.nii.gz", + nifti_file = load_test_data( + "sub-ds205s03_task-functionallocalizer_run-01_bold_volreg.nii.gz" ) - seg_file = os.path.join( - datadir, - "sub-ds205s03_task-functionallocalizer_run-01_bold_parc.nii.gz", + seg_file = load_test_data( + "sub-ds205s03_task-functionallocalizer_run-01_bold_parc.nii.gz" ) - data, segments = _nifti_timeseries(nifti_file, seg_file) + data, segments = _nifti_timeseries(str(nifti_file), str(seg_file)) viz.plot_carpet( data, segments, diff --git a/niworkflows/reports/core.py b/niworkflows/reports/core.py index cb9297db64b..558b83664a0 100644 --- a/niworkflows/reports/core.py +++ b/niworkflows/reports/core.py @@ -30,14 +30,15 @@ import re from itertools import compress from collections import defaultdict -from pkg_resources import resource_filename as pkgrf from bids.layout import BIDSLayout, add_config_paths import jinja2 from nipype.utils.filemanip import copyfile +from .. import data, load_resource + # Add a new figures spec try: - add_config_paths(figures=pkgrf("niworkflows", "data/nipreps.json")) + add_config_paths(figures=data.load('nipreps.json')) except ValueError as e: if "Configuration 'figures' already exists" != str(e): raise @@ -100,17 +101,13 @@ class Reportlet(Element): .. testsetup:: - >>> cwd = os.getcwd() - >>> os.chdir(tmpdir) - >>> from shutil import copytree >>> from bids.layout import BIDSLayout - >>> test_data_path = find_resource_or_skip('data/tests/work') + >>> source = find_resource_or_skip('data/tests/work') >>> testdir = Path(tmpdir) - >>> with importlib_resources.as_file(test_data_path) as source: - ... data_dir = copytree(source, str(testdir / 'work')) + >>> _ = copytree(source, testdir / 'work') >>> out_figs = testdir / 'out' / 'fmriprep' - >>> bl = BIDSLayout(str(testdir / 'work' / 'reportlets'), + >>> bl = BIDSLayout(testdir / 'work' / 'reportlets', ... config='figures', validate=False) >>> bl.get(subject='01', desc='reconall') # doctest: +ELLIPSIS @@ -165,10 +162,6 @@ class Reportlet(Element): >>> r.is_empty() True - .. testcleanup:: - - >>> os.chdir(cwd) - """ def __init__(self, layout, out_dir, config=None): @@ -249,15 +242,11 @@ class Report: .. testsetup:: - >>> cwd = os.getcwd() - >>> os.chdir(tmpdir) - >>> from shutil import copytree >>> from bids.layout import BIDSLayout - >>> test_data_path = find_resource_or_skip('data/tests/work') + >>> source = find_resource_or_skip('data/tests/work') >>> testdir = Path(tmpdir) - >>> with importlib_resources.as_file(test_data_path) as source: - ... data_dir = copytree(source, str(testdir / 'work')) + >>> _ = copytree(source, str(testdir / 'work')) >>> out_figs = testdir / 'out' / 'fmriprep' >>> robj = Report(testdir / 'out', 'madeoutuuid', subject_id='01', packagename='fmriprep', @@ -270,10 +259,6 @@ class Report: >>> len((testdir / 'out' / 'fmriprep' / 'sub-01.html').read_text()) 36713 - .. testcleanup:: - - >>> os.chdir(cwd) - """ def __init__( @@ -303,8 +288,8 @@ def __init__( self.out_filename = f"sub-{self.subject_id}.html" # Default template from niworkflows - self.template_path = Path(pkgrf("niworkflows", "reports/report.tpl")) - self._load_config(Path(config or pkgrf("niworkflows", "reports/default.yml"))) + self.template_path = load_resource('reports') / 'report.tpl' + self._load_config(Path(config or load_resource('reports') / 'default.yml')) assert self.template_path.exists() def _load_config(self, config): @@ -424,14 +409,12 @@ def generate_report(self): .findall((logs_path / "CITATION.tex").read_text())[0] .strip() ) + bib = data.Loader(self.packagename).readable("data/boilerplate.bib") boilerplate.append( ( boiler_idx, "LaTeX", - f"""
{text}
-

Bibliography

-
{Path(pkgrf(self.packagename, 'data/boilerplate.bib')).read_text()}
-""", + f"
{text}
\n

Bibliography

\n
{bib.read_text()}
\n", ) ) boiler_idx += 1 @@ -519,24 +502,16 @@ def run_reports( .. testsetup:: - >>> cwd = os.getcwd() - >>> os.chdir(tmpdir) - >>> from shutil import copytree - >>> test_data_path = find_resource_or_skip('data/tests/work') + >>> source = find_resource_or_skip('data/tests/work') >>> testdir = Path(tmpdir) - >>> with importlib_resources.as_file(test_data_path) as source: - ... data_dir = copytree(source, str(testdir / 'work')) + >>> _ = copytree(source, testdir / 'work') >>> (testdir / 'fmriprep').mkdir(parents=True, exist_ok=True) >>> run_reports(testdir / 'out', '01', 'madeoutuuid', packagename='fmriprep', ... reportlets_dir=testdir / 'work' / 'reportlets') 0 - .. testcleanup:: - - >>> os.chdir(cwd) - """ return Report( out_dir, diff --git a/niworkflows/reports/tests/test_core.py b/niworkflows/reports/tests/test_core.py index 1483a198b13..2006659076f 100644 --- a/niworkflows/reports/tests/test_core.py +++ b/niworkflows/reports/tests/test_core.py @@ -33,13 +33,9 @@ import pytest +from ... import load_resource from ..core import Report -try: - import importlib_resources -except ImportError: - import importlib.resources as importlib_resources - @pytest.fixture() def bids_sessions(tmpdir_factory): @@ -107,29 +103,28 @@ def bids_sessions(tmpdir_factory): @pytest.fixture() def example_workdir(): - workdir = importlib_resources.files("niworkflows") / "data" / "tests" / "work" - if not workdir.exists(): + from ... import data + workdir = data.load("tests/work") + if not workdir.is_dir(): pytest.skip("Missing example workdir; run this test from a source repository") - return workdir + yield workdir @pytest.fixture() def test_report1(tmp_path, example_workdir): - with importlib_resources.as_file(example_workdir) as workdir: - yield Report( - tmp_path, - "fakeuuid", - reportlets_dir=workdir / "reportlets", - subject_id="01", - packagename="fmriprep", - ) + yield Report( + tmp_path, + "fakeuuid", + reportlets_dir=example_workdir / "reportlets", + subject_id="01", + packagename="fmriprep", + ) @pytest.fixture() -def test_report2(bids_sessions): - out_dir = tempfile.mkdtemp() - return Report( - Path(out_dir), +def test_report2(tmp_path, bids_sessions): + yield Report( + tmp_path, "fakeuuid", reportlets_dir=Path(bids_sessions), subject_id="01", @@ -242,7 +237,7 @@ def test_generated_reportlets(bids_sessions, ordering): subject_id="01", packagename="fmriprep", ) - settings = load(importlib_resources.read_text("niworkflows.reports", "default.yml")) + settings = load(load_resource.readable("reports/default.yml").read_text()) # change settings to only include some missing ordering settings["sections"][3]["ordering"] = ordering report.index(settings["sections"]) diff --git a/niworkflows/tests/conftest.py b/niworkflows/tests/conftest.py index b78c5326ef9..8aecf747582 100644 --- a/niworkflows/tests/conftest.py +++ b/niworkflows/tests/conftest.py @@ -27,9 +27,9 @@ import pytest from templateflow.api import get as get_template from niworkflows.testing import test_data_env, data_env_canary +from niworkflows.tests.data import load_test_data -filepath = os.path.dirname(os.path.realpath(__file__)) -datadir = os.path.realpath(os.path.join(filepath, "data")) +datadir = load_test_data() def _run_interface_mock(objekt, runtime): diff --git a/niworkflows/tests/data/__init__.py b/niworkflows/tests/data/__init__.py new file mode 100644 index 00000000000..daa10c6c2d5 --- /dev/null +++ b/niworkflows/tests/data/__init__.py @@ -0,0 +1,7 @@ +"""Test data module + +.. autofunction:: load_test_data +""" +from ...data import Loader + +load_test_data = Loader(__package__) diff --git a/niworkflows/tests/test_registration.py b/niworkflows/tests/test_registration.py index 1c6f6e409b8..88346372265 100644 --- a/niworkflows/tests/test_registration.py +++ b/niworkflows/tests/test_registration.py @@ -196,7 +196,7 @@ def _agg(objekt, runtime): def test_ANTSRegistrationRPT(monkeypatch, reference, moving): """ the SpatialNormalizationRPT report capable test """ - import pkg_resources as pkgr + from niworkflows import data def _agg(objekt, runtime): outputs = objekt.output_spec() @@ -214,8 +214,6 @@ def _agg(objekt, runtime): generate_report=True, moving_image=moving, fixed_image=reference, - from_file=pkgr.resource_filename( - "niworkflows.data", "t1w-mni_registration_testing_000.json" - ), + from_file=data.load("t1w-mni_registration_testing_000.json"), ) _smoke_test_report(ants_rpt, "testANTSRegistrationRPT.svg") diff --git a/niworkflows/utils/misc.py b/niworkflows/utils/misc.py index 88cbfc0d8cd..8b8802a9170 100644 --- a/niworkflows/utils/misc.py +++ b/niworkflows/utils/misc.py @@ -203,9 +203,9 @@ def _read_pkl(path): def _read_txt(path): """Read a txt crashfile - >>> new_path = Path(__file__).resolve().parent.parent - >>> test_data_path = new_path / 'data' / 'tests' - >>> info = _read_txt(test_data_path / 'crashfile.txt') + >>> from niworkflows import data + >>> crashfile = data.load('tests/crashfile.txt') + >>> info = _read_txt(crashfile) >>> info['node'] # doctest: +ELLIPSIS '...func_preproc_task_machinegame_run_02_wf.carpetplot_wf.conf_plot' >>> info['traceback'] # doctest: +ELLIPSIS @@ -366,15 +366,11 @@ def check_valid_fs_license(): from pathlib import Path import subprocess as sp from tempfile import TemporaryDirectory - from pkg_resources import resource_filename + from .. import data - with TemporaryDirectory() as tmpdir: + with TemporaryDirectory() as tmpdir, data.load.as_path("sentinel.nii.gz") as sentinel: # quick FreeSurfer command - _cmd = ( - "mri_convert", - resource_filename("niworkflows", "data/sentinel.nii.gz"), - str(Path(tmpdir) / "out.mgz"), - ) + _cmd = ("mri_convert", str(sentinel), str(Path(tmpdir) / "out.mgz")) proc = sp.run(_cmd, stdout=sp.PIPE, stderr=sp.STDOUT) return proc.returncode == 0 and "ERROR:" not in proc.stdout.decode() diff --git a/pyproject.toml b/pyproject.toml index 3d3d9152f69..5289158d738 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ "attrs", - "importlib_resources; python_version < '3.9'", + "importlib_resources >= 5.7; python_version < '3.11'", "jinja2", "looseversion", "matplotlib >= 3.4.2", @@ -48,10 +48,10 @@ dependencies = [ [project.optional-dependencies] doc = [ - "furo ~= 2021.10.09", + "furo", "pydot >= 1.2.3", "pydotplus", - "sphinx ~= 4.0", + "sphinx", "sphinxcontrib-apidoc", "sphinxcontrib-napoleon", ]