Skip to content

Commit f07d976

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 33f5aaf commit f07d976

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
@@ -308,11 +308,6 @@ def validate(self, objekt, name, value, return_pathlike=False):
308308
return value
309309

310310

311-
# Patch in traits these two new
312-
traits.File = File
313-
traits.Directory = Directory
314-
315-
316311
class ImageFile(File):
317312
"""Defines a trait whose value must be a known neuroimaging file."""
318313

@@ -489,7 +484,7 @@ def inner_traits(self):
489484
return self.types
490485

491486

492-
class PatchedEither(TraitType):
487+
class Either(TraitType):
493488
"""Defines a trait whose value can be any of of a specified list of traits."""
494489

495490
def __init__(self, *traits, **metadata):
@@ -504,7 +499,7 @@ def as_ctrait(self):
504499

505500

506501
traits.Tuple = Tuple
507-
traits.Either = PatchedEither
502+
traits.Either = Either
508503

509504

510505
def _rebase_path(value, cwd):
@@ -523,34 +518,6 @@ def _rebase_path(value, cwd):
523518
return value
524519

525520

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

567534

568-
def resolve_path_traits(thistrait, value, cwd):
569-
"""Resolve a BasePath-derived trait given an interface spec."""
535+
def _recurse_on_path_traits(func, thistrait, value, cwd):
536+
"""Run func recursively on BasePath-derived traits."""
570537
if thistrait.is_trait_type(BasePath):
571-
value = _resolve_path(value, cwd)
538+
value = func(value, cwd)
572539
elif thistrait.is_trait_type(traits.List):
573540
innertrait, = thistrait.inner_traits
574541
if not isinstance(value, (list, tuple)):
575-
value = resolve_path_traits(innertrait, value, cwd)
542+
value = _recurse_on_path_traits(func, innertrait, value, cwd)
576543
else:
577-
value = [resolve_path_traits(innertrait, v, cwd)
544+
value = [_recurse_on_path_traits(func, innertrait, v, cwd)
578545
for v in value]
579546
elif thistrait.is_trait_type(traits.Dict):
580547
_, innertrait = thistrait.inner_traits
581-
value = {k: resolve_path_traits(innertrait, v, cwd)
548+
value = {k: _recurse_on_path_traits(func, innertrait, v, cwd)
582549
for k, v in value.items()}
583550
elif thistrait.is_trait_type(Tuple):
584-
value = tuple([resolve_path_traits(subtrait, v, cwd)
551+
value = tuple([_recurse_on_path_traits(func, subtrait, v, cwd)
585552
for subtrait, v in zip(thistrait.inner_traits, value)])
586553
elif thistrait.alternatives:
587554
is_str = [f.is_trait_type((traits.String, traits.BaseStr, traits.BaseBytes, Str))
588555
for f in thistrait.alternatives]
589556
if any(is_str) and isinstance(value, (bytes, str)) and not value.startswith('/'):
590557
return value
591-
for subtrait in thistrait.alternatives:
592-
value = resolve_path_traits(subtrait, value, cwd)
558+
is_basepath = [f.is_trait_type(BasePath) for f in thistrait.alternatives]
559+
if any(is_basepath):
560+
subtrait = thistrait.alternatives[is_basepath.index(True)]
561+
value = _recurse_on_path_traits(func, subtrait, value, cwd)
593562
return value
563+
564+
565+
def rebase_path_traits(thistrait, value, cwd):
566+
"""Rebase a BasePath-derived trait given an interface spec."""
567+
return _recurse_on_path_traits(_rebase_path, thistrait, value, cwd)
568+
569+
570+
def resolve_path_traits(thistrait, value, cwd):
571+
"""Resolve a BasePath-derived trait given an interface spec."""
572+
return _recurse_on_path_traits(_resolve_path, thistrait, value, cwd)

0 commit comments

Comments
 (0)