Skip to content

Commit d81518e

Browse files
authored
Merge pull request #472 from effigies/tox-uv
chore: Update dependencies, transition to tox-uv
2 parents 656a51d + 9eb9cd6 commit d81518e

File tree

6 files changed

+217
-219
lines changed

6 files changed

+217
-219
lines changed

.github/workflows/build-test-publish.yml

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,32 @@ jobs:
4949
AFNI_IMSAVE_WARNINGS: NO
5050
AFNI_TTATLAS_DATASET: /opt/afni/atlases
5151
AFNI_PLUGINPATH: /opt/afni/plugins
52+
MARKS: ${{ matrix.marks }}
53+
DEPENDS: ${{ matrix.dependencies }}
5254
strategy:
53-
max-parallel: 6
55+
fail-fast: false
5456
matrix:
55-
python-version: ["3.9", "3.10", "3.11", "3.12"]
56-
marks: ["not slow"]
57+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
58+
dependencies: ["latest", "pre"]
59+
marks: ["fast"]
5760
include:
5861
- python-version: "3.9"
59-
marks: "slow and not veryslow"
62+
dependencies: "min"
63+
marks: "fast"
64+
- python-version: "3.9"
65+
dependencies: "latest"
66+
marks: "slow"
6067
- python-version: "3.12"
68+
dependencies: "latest"
6169
marks: "veryslow"
70+
exclude:
71+
- python-version: "3.9"
72+
dependencies: "pre"
73+
- python-version: "3.10"
74+
dependencies: "pre"
6275

6376
steps:
77+
- uses: actions/checkout@v4
6478
- uses: actions/cache@v4
6579
with:
6680
path: /var/lib/apt
@@ -89,11 +103,15 @@ jobs:
89103
curl -O https://afni.nimh.nih.gov/pub/dist/bin/misc/@update.afni.binaries && \
90104
tcsh @update.afni.binaries -package linux_ubuntu_16_64 -bindir ${AFNI_HOME}
91105
fi
106+
ls -l ${AFNI_HOME}
107+
echo "PATH=${AFNI_HOME}:$PATH" | tee -a $GITHUB_ENV
92108
93109
- name: Git settings (pacify DataLad)
94110
run: |
95111
git config --global user.name 'NiPreps Bot'
96112
git config --global user.email '[email protected]'
113+
- name: Install the latest version of uv
114+
uses: astral-sh/setup-uv@v4
97115
- name: Set up Python ${{ matrix.python-version }}
98116
uses: conda-incubator/setup-miniconda@v3
99117
with:
@@ -110,22 +128,14 @@ jobs:
110128
~/conda_pkgs_dir
111129
/home/runner/.cache/pip
112130
key: python-${{ matrix.python-version }}-${{ env.CACHE_NUM }}
113-
restore-keys: |
114-
python-${{ matrix.python-version }}-${{ env.CACHE_NUM }}
115131
- name: Install DataLad
116132
run: |
117-
conda install git-annex=*=alldep* pip
118-
pip install datalad datalad-osf
133+
conda install git-annex=*=alldep*
134+
uv tool install datalad --with=datalad-next --with=datalad-osf
135+
uv tool install datalad-osf --with=datalad-next
119136
- name: Install fsl and ANTs
120137
run: |
121138
conda install fsl-fugue fsl-topup ants
122-
- uses: actions/checkout@v4
123-
- name: Install dependencies
124-
timeout-minutes: 5
125-
run: |
126-
pip install .[tests]
127-
128-
129139
- uses: actions/cache@v4
130140
with:
131141
path: ~/.cache/templateflow
@@ -134,7 +144,7 @@ jobs:
134144
tf-cache-
135145
- name: Get TemplateFlow's required objects
136146
run: |
137-
python tools/cache_templateflow.py
147+
uv run tools/cache_templateflow.py
138148
139149
- uses: actions/cache@v4
140150
with:
@@ -198,18 +208,16 @@ jobs:
198208
mkdir -p $( dirname $FS_LICENSE )
199209
echo "b2VzdGViYW5Ac3RhbmZvcmQuZWR1CjMwNzU2CiAqQ1MzYkJ5VXMxdTVNCiBGU2kvUGJsejJxR1V3Cg==" | base64 -d > $FS_LICENSE
200210
201-
- name: Run pytest with coverage
211+
- name: Install tox
202212
run: |
203-
export PATH=${AFNI_HOME}:$PATH
204-
export FSLDIR=${CONDA_PREFIX}
205-
pytest -v --cov sdcflows --cov-report xml:cov.xml --doctest-modules -n auto sdcflows \
206-
--durations=20 --durations-min=10 -m "$MARKS"
207-
env:
208-
MARKS: ${{ matrix.marks }}
213+
uv tool install tox --with=tox-uv --with=tox-gh-actions
214+
- name: Show tox config
215+
run: tox c
216+
- name: Run tox
217+
run: tox -v --exit-and-dump-after 1200
209218

210-
- uses: codecov/codecov-action@v4
219+
- uses: codecov/codecov-action@v5
211220
with:
212-
file: cov.xml
213221
token: ${{ secrets.CODECOV_TOKEN }}
214222
if: ${{ always() }}
215223

pyproject.toml

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,24 @@ classifiers = [
1717
"Programming Language :: Python :: 3.10",
1818
"Programming Language :: Python :: 3.11",
1919
"Programming Language :: Python :: 3.12",
20+
"Programming Language :: Python :: 3.13",
2021
]
2122
license = "Apache-2.0"
2223
requires-python = ">=3.9"
2324
dependencies = [
25+
"acres >= 0.2.0",
2426
"attrs >= 20.1.0",
25-
"nibabel >=3.1.0",
26-
"nipype >=1.8.5,<2.0",
27-
"traits <6.4",
27+
"nibabel >= 3.0",
28+
"nipype >= 1.8.5",
2829
"migas >= 0.4.0",
2930
"niworkflows >= 1.7.0",
30-
"nitransforms >= 23.0.1",
31-
"numpy >= 1.21.0",
31+
"nitransforms >= 24.1.0",
32+
"numpy >= 1.22",
3233
"pybids >= 0.16.4",
3334
"scikit-image >= 0.18",
3435
"scipy >= 1.8.1",
35-
"templateflow",
36-
"toml",
36+
"templateflow >= 23.1",
37+
"toml >= 0.10",
3738
]
3839
dynamic = ["version"]
3940

@@ -49,22 +50,13 @@ doc = [
4950
"importlib_resources",
5051
"ipykernel",
5152
"ipython",
52-
"matplotlib >= 2.2.0",
5353
"nbsphinx",
54-
"nibabel",
55-
"nipype >= 1.5.1",
56-
"niworkflows >= 1.10.0",
57-
"numpy",
58-
"packaging",
5954
"pandoc",
6055
"pydot >= 1.2.3",
6156
"pydotplus",
62-
"scipy",
6357
"sphinx >= 7.2.2",
6458
"sphinx-argparse",
6559
"sphinxcontrib-apidoc",
66-
"templateflow",
67-
"traits < 6.4"
6860
]
6961

7062
mem = [
@@ -79,11 +71,11 @@ dev = [
7971
]
8072

8173
test = [
82-
"coverage",
83-
"pytest",
84-
"pytest-cov",
74+
"coverage[toml] >=5.2.1",
75+
"pytest >= 6",
76+
"pytest-cov >= 2.11",
8577
"pytest-env",
86-
"pytest-xdist"
78+
"pytest-xdist >= 2.5",
8779
]
8880

8981
# Aliases
@@ -159,8 +151,22 @@ per-file-ignores = [
159151
]
160152

161153
[tool.pytest.ini_options]
154+
minversion = "6"
155+
testpaths = ["sdcflows"]
156+
log_cli_level = "INFO"
157+
xfail_strict = true
162158
norecursedirs = [".git"]
163-
addopts = "-svx --doctest-modules --strict-markers"
159+
addopts = [
160+
"-svx",
161+
"-ra",
162+
"--strict-config",
163+
"--strict-markers",
164+
"--doctest-modules",
165+
# Config pytest-cov
166+
"--cov=sdcflows",
167+
"--cov-report=xml",
168+
"--cov-config=pyproject.toml",
169+
]
164170
doctest_optionflags = "ALLOW_UNICODE NORMALIZE_WHITESPACE ELLIPSIS"
165171
env = "PYTHONHASHSEED=0"
166172
filterwarnings = ["ignore::DeprecationWarning"]
@@ -173,7 +179,6 @@ markers = [
173179

174180
[tool.coverage.run]
175181
branch = true
176-
concurrency = 'multiprocessing'
177182
omit = [
178183
'*/tests/*',
179184
'*/__init__.py',

sdcflows/data/__init__.py

Lines changed: 2 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -10,173 +10,6 @@
1010
1111
.. autoclass:: Loader
1212
"""
13-
from __future__ import annotations
13+
from acres import Loader
1414

15-
import atexit
16-
import os
17-
from contextlib import AbstractContextManager, ExitStack
18-
from functools import cached_property
19-
from pathlib import Path
20-
from types import ModuleType
21-
from typing import Union
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 sdcflows.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 sdcflows.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: Union[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
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-
181-
182-
load = Loader(__package__)
15+
load = Loader(__spec__.name)

0 commit comments

Comments
 (0)