Skip to content

Commit f6273f4

Browse files
committed
fix: addressed @effigies' comments:
- [x] Removed traits.api patches - [x] Deduplicated code with a _recurse_on_path_traits proxy (#2970 (comment)) - [x] Added the two proposed test cases - [x] Optimized loop (see #2970 (comment))
1 parent 86449b1 commit f6273f4

File tree

3 files changed

+42
-55
lines changed

3 files changed

+42
-55
lines changed

nipype/interfaces/base/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
StdOutCommandLineInputSpec)
2121

2222
from .traits_extension import (
23-
traits, Undefined, isdefined,
24-
File, Directory, Str, DictStrStr, has_metadata, ImageFile,
23+
traits, Undefined, isdefined, has_metadata,
24+
File, ImageFile, Directory,
25+
Tuple, Either, Str, DictStrStr,
2526
OutputMultiObject, InputMultiObject,
2627
OutputMultiPath, InputMultiPath)
2728

nipype/interfaces/base/tests/test_traits_extension.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@
88

99

1010
class _test_spec(nib.TraitedSpec):
11-
a = nib.traits.File()
12-
b = nib.traits.Tuple(nib.File(),
13-
nib.File())
11+
a = nib.File()
12+
b = nib.Tuple(nib.File(), nib.File())
1413
c = nib.traits.List(nib.File())
15-
d = nib.traits.Either(nib.File(), nib.traits.Float())
14+
d = nib.Either(nib.File(), nib.traits.Float())
1615
e = nib.OutputMultiObject(nib.File())
1716
f = nib.traits.Dict(nib.Str, nib.File())
18-
g = nib.traits.Either(nib.File, nib.Str)
17+
g = nib.Either(nib.File, nib.Str)
1918
h = nib.Str
2019
ee = nib.OutputMultiObject(nib.Str)
2120

@@ -28,6 +27,10 @@ def test_rebase_path_traits():
2827
spec.trait('a'), '/some/path/f1.txt', '/some/path')
2928
assert a == Path('f1.txt')
3029

30+
a = rebase_path_traits(
31+
spec.trait('a'), '/some/path/f1.txt', '/some/other/path')
32+
assert a == Path('/some/path/f1.txt')
33+
3134
b = rebase_path_traits(
3235
spec.trait('b'), ('/some/path/f1.txt', '/some/path/f2.txt'), '/some/path')
3336
assert b == (Path('f1.txt'), Path('f2.txt'))
@@ -90,6 +93,10 @@ def test_resolve_path_traits():
9093
spec.trait('a'), 'f1.txt', '/some/path')
9194
assert a == Path('/some/path/f1.txt')
9295

96+
a = resolve_path_traits(
97+
spec.trait('a'), '/already/absolute/f1.txt', '/some/path')
98+
assert a == Path('/already/absolute/f1.txt')
99+
93100
b = resolve_path_traits(
94101
spec.trait('b'), ('f1.txt', 'f2.txt'), '/some/path')
95102
assert b == (Path('/some/path/f1.txt'), Path('/some/path/f2.txt'))
@@ -128,10 +135,10 @@ def test_resolve_path_traits():
128135

129136
# This is a problematic case, it is impossible to know whether this
130137
# was meant to be a string or a file.
131-
# Commented out because in this implementation, strings take precedence
132-
# g = resolve_path_traits(
133-
# spec.trait('g'), 'path/either.txt', '/some')
134-
# assert g == Path('/some/path/either.txt')
138+
# In this implementation, strings take precedence
139+
g = resolve_path_traits(
140+
spec.trait('g'), 'path/either.txt', '/some')
141+
assert g == 'path/either.txt'
135142

136143
# This is a problematic case, it is impossible to know whether this
137144
# was meant to be a string or a file.

nipype/interfaces/base/traits_extension.py

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -305,11 +305,6 @@ def validate(self, objekt, name, value, return_pathlike=False):
305305
return value
306306

307307

308-
# Patch in traits these two new
309-
traits.File = File
310-
traits.Directory = Directory
311-
312-
313308
class ImageFile(File):
314309
"""Defines a trait whose value must be a known neuroimaging file."""
315310

@@ -486,7 +481,7 @@ def inner_traits(self):
486481
return self.types
487482

488483

489-
class PatchedEither(TraitType):
484+
class Either(TraitType):
490485
"""Defines a trait whose value can be any of of a specified list of traits."""
491486

492487
def __init__(self, *traits, **metadata):
@@ -501,7 +496,7 @@ def as_ctrait(self):
501496

502497

503498
traits.Tuple = Tuple
504-
traits.Either = PatchedEither
499+
traits.Either = Either
505500

506501

507502
def _rebase_path(value, cwd):
@@ -520,34 +515,6 @@ def _rebase_path(value, cwd):
520515
return value
521516

522517

523-
def rebase_path_traits(thistrait, value, cwd):
524-
"""Rebase a BasePath-derived trait given an interface spec."""
525-
if thistrait.is_trait_type(BasePath):
526-
value = _rebase_path(value, cwd)
527-
elif thistrait.is_trait_type(traits.List):
528-
innertrait, = thistrait.inner_traits
529-
if not isinstance(value, (list, tuple)):
530-
value = rebase_path_traits(innertrait, value, cwd)
531-
else:
532-
value = [rebase_path_traits(innertrait, v, cwd)
533-
for v in value]
534-
elif thistrait.is_trait_type(traits.Dict):
535-
_, innertrait = thistrait.inner_traits
536-
value = {k: rebase_path_traits(innertrait, v, cwd)
537-
for k, v in value.items()}
538-
elif thistrait.is_trait_type(Tuple):
539-
value = tuple([rebase_path_traits(subtrait, v, cwd)
540-
for subtrait, v in zip(thistrait.inner_traits, value)])
541-
elif thistrait.alternatives:
542-
is_str = [f.is_trait_type((traits.String, traits.BaseStr, traits.BaseBytes, Str))
543-
for f in thistrait.alternatives]
544-
if any(is_str) and isinstance(value, (bytes, str)) and not value.startswith('/'):
545-
return value
546-
for subtrait in thistrait.alternatives:
547-
value = rebase_path_traits(subtrait, value, cwd)
548-
return value
549-
550-
551518
def _resolve_path(value, cwd):
552519
if isinstance(value, list):
553520
return [_resolve_path(v, cwd) for v in value]
@@ -562,29 +529,41 @@ def _resolve_path(value, cwd):
562529
return value
563530

564531

565-
def resolve_path_traits(thistrait, value, cwd):
566-
"""Resolve a BasePath-derived trait given an interface spec."""
532+
def _recurse_on_path_traits(func, thistrait, value, cwd):
533+
"""Run func recursively on BasePath-derived traits."""
567534
if thistrait.is_trait_type(BasePath):
568-
value = _resolve_path(value, cwd)
535+
value = func(value, cwd)
569536
elif thistrait.is_trait_type(traits.List):
570537
innertrait, = thistrait.inner_traits
571538
if not isinstance(value, (list, tuple)):
572-
value = resolve_path_traits(innertrait, value, cwd)
539+
value = _recurse_on_path_traits(func, innertrait, value, cwd)
573540
else:
574-
value = [resolve_path_traits(innertrait, v, cwd)
541+
value = [_recurse_on_path_traits(func, innertrait, v, cwd)
575542
for v in value]
576543
elif thistrait.is_trait_type(traits.Dict):
577544
_, innertrait = thistrait.inner_traits
578-
value = {k: resolve_path_traits(innertrait, v, cwd)
545+
value = {k: _recurse_on_path_traits(func, innertrait, v, cwd)
579546
for k, v in value.items()}
580547
elif thistrait.is_trait_type(Tuple):
581-
value = tuple([resolve_path_traits(subtrait, v, cwd)
548+
value = tuple([_recurse_on_path_traits(func, subtrait, v, cwd)
582549
for subtrait, v in zip(thistrait.inner_traits, value)])
583550
elif thistrait.alternatives:
584551
is_str = [f.is_trait_type((traits.String, traits.BaseStr, traits.BaseBytes, Str))
585552
for f in thistrait.alternatives]
586553
if any(is_str) and isinstance(value, (bytes, str)) and not value.startswith('/'):
587554
return value
588-
for subtrait in thistrait.alternatives:
589-
value = resolve_path_traits(subtrait, value, cwd)
555+
is_basepath = [f.is_trait_type(BasePath) for f in thistrait.alternatives]
556+
if any(is_basepath):
557+
subtrait = thistrait.alternatives[is_basepath.index(True)]
558+
value = _recurse_on_path_traits(func, subtrait, value, cwd)
590559
return value
560+
561+
562+
def rebase_path_traits(thistrait, value, cwd):
563+
"""Rebase a BasePath-derived trait given an interface spec."""
564+
return _recurse_on_path_traits(_rebase_path, thistrait, value, cwd)
565+
566+
567+
def resolve_path_traits(thistrait, value, cwd):
568+
"""Resolve a BasePath-derived trait given an interface spec."""
569+
return _recurse_on_path_traits(_resolve_path, thistrait, value, cwd)

0 commit comments

Comments
 (0)