Skip to content

Commit 8d0f433

Browse files
authored
rf(resources): Use acres over vendored data loader (#3323)
Created https://github.com/nipreps/acres based on fMRIPrep's data loader, to help avoid getting out-of-sync.
1 parent 19201db commit 8d0f433

File tree

3 files changed

+26
-192
lines changed

3 files changed

+26
-192
lines changed

fmriprep/data/__init__.py

+1-169
Original file line numberDiff line numberDiff line change
@@ -7,176 +7,8 @@
77
.. automethod:: load.as_path
88
99
.. automethod:: load.cached
10-
11-
.. autoclass:: Loader
1210
"""
1311

14-
from __future__ import annotations
15-
16-
import atexit
17-
import os
18-
from contextlib import AbstractContextManager, ExitStack
19-
from functools import cached_property
20-
from pathlib import Path
21-
from types import ModuleType
22-
23-
try:
24-
from functools import cache
25-
except ImportError: # PY38
26-
from functools import lru_cache as cache
27-
28-
try: # Prefer backport to leave consistency to dependency spec
29-
from importlib_resources import as_file, files
30-
except ImportError:
31-
from importlib.resources import as_file, files # type: ignore
32-
33-
try: # Prefer stdlib so Sphinx can link to authoritative documentation
34-
from importlib.resources.abc import Traversable
35-
except ImportError:
36-
from importlib_resources.abc import Traversable
37-
38-
__all__ = ['load']
39-
40-
41-
class Loader:
42-
"""A loader for package files relative to a module
43-
44-
This class wraps :mod:`importlib.resources` to provide a getter
45-
function with an interpreter-lifetime scope. For typical packages
46-
it simply passes through filesystem paths as :class:`~pathlib.Path`
47-
objects. For zipped distributions, it will unpack the files into
48-
a temporary directory that is cleaned up on interpreter exit.
49-
50-
This loader accepts a fully-qualified module name or a module
51-
object.
52-
53-
Expected usage::
54-
55-
'''Data package
56-
57-
.. autofunction:: load_data
58-
59-
.. automethod:: load_data.readable
60-
61-
.. automethod:: load_data.as_path
62-
63-
.. automethod:: load_data.cached
64-
'''
65-
66-
from fmriprep.data import Loader
67-
68-
load_data = Loader(__package__)
69-
70-
:class:`~Loader` objects implement the :func:`callable` interface
71-
and generate a docstring, and are intended to be treated and documented
72-
as functions.
73-
74-
For greater flexibility and improved readability over the ``importlib.resources``
75-
interface, explicit methods are provided to access resources.
76-
77-
+---------------+----------------+------------------+
78-
| On-filesystem | Lifetime | Method |
79-
+---------------+----------------+------------------+
80-
| `True` | Interpreter | :meth:`cached` |
81-
+---------------+----------------+------------------+
82-
| `True` | `with` context | :meth:`as_path` |
83-
+---------------+----------------+------------------+
84-
| `False` | n/a | :meth:`readable` |
85-
+---------------+----------------+------------------+
86-
87-
It is also possible to use ``Loader`` directly::
88-
89-
from fmriprep.data import Loader
90-
91-
Loader(other_package).readable('data/resource.ext').read_text()
92-
93-
with Loader(other_package).as_path('data') as pkgdata:
94-
# Call function that requires full Path implementation
95-
func(pkgdata)
96-
97-
# contrast to
98-
99-
from importlib_resources import files, as_file
100-
101-
files(other_package).joinpath('data/resource.ext').read_text()
102-
103-
with as_file(files(other_package) / 'data') as pkgdata:
104-
func(pkgdata)
105-
106-
.. automethod:: readable
107-
108-
.. automethod:: as_path
109-
110-
.. automethod:: cached
111-
"""
112-
113-
def __init__(self, anchor: str | ModuleType):
114-
self._anchor = anchor
115-
self.files = files(anchor)
116-
self.exit_stack = ExitStack()
117-
atexit.register(self.exit_stack.close)
118-
# Allow class to have a different docstring from instances
119-
self.__doc__ = self._doc
120-
121-
@cached_property
122-
def _doc(self):
123-
"""Construct docstring for instances
124-
125-
Lists the public top-level paths inside the location, where
126-
non-public means has a `.` or `_` prefix or is a 'tests'
127-
directory.
128-
"""
129-
top_level = sorted(
130-
os.path.relpath(p, self.files) + '/'[: p.is_dir()]
131-
for p in self.files.iterdir()
132-
if p.name[0] not in ('.', '_') and p.name != 'tests'
133-
)
134-
doclines = [
135-
f'Load package files relative to ``{self._anchor}``.',
136-
'',
137-
'This package contains the following (top-level) files/directories:',
138-
'',
139-
*(f'* ``{path}``' for path in top_level),
140-
]
141-
142-
return '\n'.join(doclines)
143-
144-
def readable(self, *segments) -> Traversable:
145-
"""Provide read access to a resource through a Path-like interface.
146-
147-
This file may or may not exist on the filesystem, and may be
148-
efficiently used for read operations, including directory traversal.
149-
150-
This result is not cached or copied to the filesystem in cases where
151-
that would be necessary.
152-
"""
153-
return self.files.joinpath(*segments)
154-
155-
def as_path(self, *segments) -> AbstractContextManager[Path]:
156-
"""Ensure data is available as a :class:`~pathlib.Path`.
157-
158-
This method generates a context manager that yields a Path when
159-
entered.
160-
161-
This result is not cached, and any temporary files that are created
162-
are deleted when the context is exited.
163-
"""
164-
return as_file(self.files.joinpath(*segments))
165-
166-
@cache # noqa: B019
167-
def cached(self, *segments) -> Path:
168-
"""Ensure data is available as a :class:`~pathlib.Path`.
169-
170-
Any temporary files that are created remain available throughout
171-
the duration of the program, and are deleted when Python exits.
172-
173-
Results are cached so that multiple calls do not unpack the same
174-
data multiple times, but the cache is sensitive to the specific
175-
argument(s) passed.
176-
"""
177-
return self.exit_stack.enter_context(as_file(self.files.joinpath(*segments)))
178-
179-
__call__ = cached
180-
12+
from acres import Loader
18113

18214
load = Loader(__package__)

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ classifiers = [
1919
license = {file = "LICENSE"}
2020
requires-python = ">=3.10"
2121
dependencies = [
22+
"acres",
2223
"looseversion",
2324
"nibabel >= 4.0.1",
2425
"nipype >= 1.8.5",

requirements.txt

+24-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# This file was autogenerated by uv via the following command:
22
# uv pip compile --extra=container --strip-extras pyproject.toml -o requirements.txt -P 3.10
3+
acres==0.1.0
34
annexremote==1.6.5
45
# via
56
# datalad
@@ -18,13 +19,13 @@ bids-validator==1.14.6
1819
# via pybids
1920
bokeh==3.4.1
2021
# via tedana
21-
boto3==1.34.127
22+
boto3==1.34.144
2223
# via datalad
23-
botocore==1.34.127
24+
botocore==1.34.144
2425
# via
2526
# boto3
2627
# s3transfer
27-
certifi==2024.6.2
28+
certifi==2024.7.4
2829
# via
2930
# requests
3031
# sentry-sdk
@@ -43,7 +44,7 @@ click==8.1.7
4344
# codecarbon
4445
# nipype
4546
# pybids
46-
codecarbon==2.4.2
47+
codecarbon==2.5.0
4748
contourpy==1.2.1
4849
# via
4950
# bokeh
@@ -52,7 +53,7 @@ cryptography==42.0.8
5253
# via secretstorage
5354
cycler==0.12.1
5455
# via matplotlib
55-
datalad==1.1.0
56+
datalad==1.1.1
5657
# via
5758
# datalad-next
5859
# datalad-osf
@@ -67,23 +68,23 @@ etelemetry==0.3.1
6768
# via nipype
6869
fasteners==0.19
6970
# via datalad
70-
filelock==3.15.1
71+
filelock==3.15.4
7172
# via nipype
72-
fonttools==4.53.0
73+
fonttools==4.53.1
7374
# via matplotlib
7475
formulaic==0.5.2
7576
# via pybids
7677
greenlet==3.0.3
7778
# via sqlalchemy
7879
h5py==3.11.0
7980
# via nitransforms
80-
humanize==4.9.0
81+
humanize==4.10.0
8182
# via
8283
# datalad
8384
# datalad-next
8485
idna==3.7
8586
# via requests
86-
imageio==2.34.1
87+
imageio==2.34.2
8788
# via scikit-image
8889
indexed-gzip==1.8.7
8990
# via smriprep
@@ -146,7 +147,7 @@ mapca==0.0.5
146147
# via tedana
147148
markupsafe==2.1.5
148149
# via jinja2
149-
matplotlib==3.8.4
150+
matplotlib==3.9.1
150151
# via
151152
# nireports
152153
# nitime
@@ -193,7 +194,7 @@ nipype==1.8.6
193194
# sdcflows
194195
# smriprep
195196
nireports==23.2.1
196-
nitime==0.10.2
197+
nitime==0.11
197198
nitransforms==23.0.1
198199
# via
199200
# niworkflows
@@ -257,9 +258,9 @@ pandas==2.2.2
257258
# pybids
258259
# seaborn
259260
# tedana
260-
patool==2.2.0
261+
patool==2.3.0
261262
# via datalad
262-
pillow==10.3.0
263+
pillow==10.4.0
263264
# via
264265
# bokeh
265266
# imageio
@@ -271,7 +272,7 @@ prometheus-client==0.20.0
271272
# via codecarbon
272273
prov==2.0.1
273274
# via nipype
274-
psutil==5.9.8
275+
psutil==6.0.0
275276
# via codecarbon
276277
py-cpuinfo==9.0.0
277278
# via codecarbon
@@ -288,9 +289,9 @@ pybtex-apa-style==1.3
288289
# via tedana
289290
pycparser==2.22
290291
# via cffi
291-
pydot==2.0.0
292+
pydot==3.0.1
292293
# via nipype
293-
pynvml==11.5.0
294+
pynvml==11.5.3
294295
# via codecarbon
295296
pyparsing==3.1.2
296297
# via
@@ -305,7 +306,7 @@ python-dateutil==2.9.0.post0
305306
# nipype
306307
# pandas
307308
# prov
308-
python-gitlab==4.6.0
309+
python-gitlab==4.8.0
309310
# via datalad
310311
pytz==2024.1
311312
# via
@@ -318,7 +319,7 @@ pyyaml==6.0.1
318319
# niworkflows
319320
# pybtex
320321
# smriprep
321-
rapidfuzz==3.9.3
322+
rapidfuzz==3.9.4
322323
# via codecarbon
323324
rdflib==6.3.2
324325
# via
@@ -336,9 +337,9 @@ requests==2.32.3
336337
# templateflow
337338
requests-toolbelt==1.0.0
338339
# via python-gitlab
339-
s3transfer==0.10.1
340+
s3transfer==0.10.2
340341
# via boto3
341-
scikit-image==0.23.2
342+
scikit-image==0.24.0
342343
# via
343344
# niworkflows
344345
# sdcflows
@@ -368,7 +369,7 @@ seaborn==0.13.2
368369
# niworkflows
369370
secretstorage==3.3.3
370371
# via keyring
371-
sentry-sdk==2.5.1
372+
sentry-sdk==2.10.0
372373
simplejson==3.19.2
373374
# via nipype
374375
six==1.16.0
@@ -379,7 +380,7 @@ six==1.16.0
379380
# pybtex
380381
# python-dateutil
381382
smriprep==0.15.0
382-
sqlalchemy==2.0.30
383+
sqlalchemy==2.0.31
383384
# via pybids
384385
svgutils==0.3.4
385386
# via
@@ -396,7 +397,7 @@ threadpoolctl==3.5.0
396397
# via
397398
# scikit-learn
398399
# tedana
399-
tifffile==2024.5.22
400+
tifffile==2024.7.2
400401
# via scikit-image
401402
toml==0.10.2
402403
# via sdcflows

0 commit comments

Comments
 (0)