Skip to content

Commit

Permalink
Merge pull request #337 from mgxd/ci/actions
Browse files Browse the repository at this point in the history
MAINT: Raise minimum to 3.10, bump actions
  • Loading branch information
mgxd authored Jan 29, 2024
2 parents c1126ae + da0eb1d commit b0a2778
Show file tree
Hide file tree
Showing 22 changed files with 233 additions and 75 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11']
python-version: ['3.10', '3.11']

steps:
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install nibabies
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10']
python-version: ['3.11']

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 1 # Only fetch the latest commit
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
1 change: 1 addition & 0 deletions nibabies/_warnings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Manipulate Python warnings."""

import logging
import warnings

Expand Down
2 changes: 0 additions & 2 deletions nibabies/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,6 @@ def main():
_copy_any(dseg_tsv, str(config.execution.nibabies_dir / "desc-aparcaseg_dseg.tsv"))
# errno = 0
finally:
from pkg_resources import resource_filename as pkgrf

from ..reports.core import generate_reports

# Generate reports phase
Expand Down
12 changes: 6 additions & 6 deletions nibabies/cli/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ def build_workflow(config_file):

# Called with reports only
if config.execution.reports_only:
from pkg_resources import resource_filename as pkgrf

build_logger.log(
25,
"Running --reports-only on participants %s",
Expand Down Expand Up @@ -132,14 +130,16 @@ def build_boilerplate(workflow):
from shutil import copyfile
from subprocess import CalledProcessError, TimeoutExpired, check_call

from pkg_resources import resource_filename as pkgrf
from nibabies.data import load as load_data

bib = load_data("boilerplate.bib")

# Generate HTML file resolving citations
cmd = [
"pandoc",
"-s",
"--bibliography",
pkgrf("nibabies", "data/boilerplate.bib"),
bib,
"--citeproc",
"--metadata",
'pagetitle="nibabies citation boilerplate"',
Expand All @@ -159,7 +159,7 @@ def build_boilerplate(workflow):
"pandoc",
"-s",
"--bibliography",
pkgrf("nibabies", "data/boilerplate.bib"),
bib,
"--natbib",
str(citation_files["md"]),
"-o",
Expand All @@ -171,4 +171,4 @@ def build_boilerplate(workflow):
except (FileNotFoundError, CalledProcessError, TimeoutExpired):
config.loggers.cli.warning("Could not generate CITATION.tex file:\n%s", " ".join(cmd))
else:
copyfile(pkgrf("nibabies", "data/boilerplate.bib"), citation_files["bib"])
copyfile(bib, citation_files["bib"])
6 changes: 4 additions & 2 deletions nibabies/conftest.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""py.test configuration"""

from pathlib import Path
from tempfile import TemporaryDirectory

import pytest
from pkg_resources import resource_filename

from nibabies.data import load as load_data

FILES = (
"functional.nii",
Expand Down Expand Up @@ -33,5 +35,5 @@ def data_dir():
@pytest.fixture(autouse=True)
def set_namespace(doctest_namespace, data_dir):
doctest_namespace["data_dir"] = data_dir
doctest_namespace["test_data"] = Path(resource_filename("nibabies", "tests/data"))
doctest_namespace["test_data"] = load_data.cached('../tests/data')
doctest_namespace["Path"] = Path
176 changes: 167 additions & 9 deletions nibabies/data/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
"""Data file retrieval
.. autofunction:: load
.. automethod:: load.readable
.. automethod:: load.as_path
.. automethod:: load.cached
.. autoclass:: Loader
"""

from __future__ import annotations

import atexit
from contextlib import ExitStack
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
Expand All @@ -10,16 +29,155 @@
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
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 nibabies.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 nibabies.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`.
__all__ = ["load_resource"]
Any temporary files that are created remain available throughout
the duration of the program, and are deleted when Python exits.
exit_stack = ExitStack()
atexit.register(exit_stack.close)
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)))

path = files(__package__)
__call__ = cached


@cache
def load_resource(fname: str) -> Path:
return exit_stack.enter_context(as_file(path.joinpath(fname)))
load = Loader(__package__)
1 change: 1 addition & 0 deletions nibabies/interfaces/confounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ class GatherConfounds(SimpleInterface):
>>> tmpdir.cleanup()
"""

input_spec = GatherConfoundsInputSpec
output_spec = GatherConfoundsOutputSpec

Expand Down
1 change: 1 addition & 0 deletions nibabies/interfaces/maths.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""A module for interfaces """

import os

import numpy as np
Expand Down
4 changes: 2 additions & 2 deletions nibabies/reports/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from nireports.assembler.report import Report

from nibabies.data import load_resource
from nibabies.data import load as load_data


def run_reports(
Expand All @@ -22,7 +22,7 @@ def run_reports(
run_uuid,
subject=subject,
session=session,
bootstrap_file=load_resource('reports-spec.yml'),
bootstrap_file=load_data.readable('reports-spec.yml'),
reportlets_dir=reportlets_dir,
).generate_report()

Expand Down
7 changes: 3 additions & 4 deletions nibabies/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@

import pytest
from niworkflows.utils.spaces import format_reference
from pkg_resources import resource_filename as pkgrf
from toml import loads

from .. import config
from nibabies import config
from nibabies.data import load as load_data


def _reset_config():
Expand Down Expand Up @@ -58,8 +58,7 @@ def test_reset_config():

def test_config_spaces():
"""Check that all necessary spaces are recorded in the config."""
filename = Path(pkgrf('nibabies', 'data/tests/config.toml'))
settings = loads(filename.read_text())
settings = loads(load_data.readable('tests/config.toml').read_text())
for sectionname, configs in settings.items():
if sectionname != 'environment':
section = getattr(config, sectionname)
Expand Down
6 changes: 4 additions & 2 deletions nibabies/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Union

from nibabies import __version__
from nibabies.data import load_resource
from nibabies.data import load as load_data


def fix_multi_source_name(in_files):
Expand Down Expand Up @@ -144,7 +144,9 @@ def save_fsLR_mcribs(mcribs_dir: str | Path) -> None:
template_dir = Path(mcribs_dir) / 'templates_fsLR'
template_dir.mkdir(exist_ok=True)

for src in load_resource('atlases').glob('*sphere.surf.gii'):
atlases = load_data.cached('atlases')

for src in atlases.glob('*sphere.surf.gii'):
if not (dst := (template_dir / src.name)).exists():
try:
shutil.copyfile(src, dst)
Expand Down
1 change: 1 addition & 0 deletions nibabies/workflows/anatomical/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Base anatomical preprocessing."""

from __future__ import annotations

import typing as ty
Expand Down
Loading

0 comments on commit b0a2778

Please sign in to comment.