Skip to content

Commit 5992a20

Browse files
committed
debugging interface conversions
1 parent 8d723b8 commit 5992a20

File tree

5 files changed

+89
-78
lines changed

5 files changed

+89
-78
lines changed

nipype2pydra/interface/base.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,7 @@ class OutputsConverter(SpecConverter):
147147
templates: ty.Dict[str, str] = attrs.field(
148148
factory=dict,
149149
converter=default_if_none(factory=dict), # type: ignore
150-
metadata={
151-
"help": "`path_template` values to be provided to output fields"
152-
},
150+
metadata={"help": "`path_template` values to be provided to output fields"},
153151
)
154152
requirements: ty.Dict[str, ty.List[str]] = attrs.field(
155153
factory=dict,
@@ -284,15 +282,15 @@ def from_dict_to_outputs(obj: ty.Union[OutputsConverter, dict]) -> OutputsConver
284282

285283

286284
def from_list_to_tests(
287-
obj: ty.Union[ty.List[TestGenerator], list]
285+
obj: ty.Union[ty.List[TestGenerator], list],
288286
) -> ty.List[TestGenerator]:
289287
if obj is None:
290288
return []
291289
return [from_dict_converter(t, TestGenerator) for t in obj]
292290

293291

294292
def from_list_to_doctests(
295-
obj: ty.Union[ty.List[DocTestGenerator], list]
293+
obj: ty.Union[ty.List[DocTestGenerator], list],
296294
) -> ty.List[DocTestGenerator]:
297295
if obj is None:
298296
return []
@@ -607,7 +605,7 @@ def pydra_fld_input(self, field, nm):
607605
)
608606
if pydra_type in [
609607
File,
610-
Directory
608+
Directory,
611609
]: # since this is a template, the file doesn't exist
612610
pydra_type = Path
613611
elif nm not in self.inputs.callable_defaults:
@@ -677,9 +675,9 @@ def pydra_fld_output(self, field, name):
677675
pydra_metadata["requires"] = pydra_metadata["requires"][0]
678676

679677
if name in self.outputs.templates:
680-
pydra_metadata["path_template"] = self.interface_spec[
681-
"output_templates"
682-
][name]
678+
pydra_metadata["path_template"] = self.interface_spec["output_templates"][
679+
name
680+
]
683681
elif name in self.outputs.callables:
684682
pydra_metadata["callable"] = self.outputs.callables[name]
685683
return (pydra_type, pydra_metadata)
@@ -734,14 +732,17 @@ def pydra_type_converter(self, field, spec_type, name):
734732
pydra_type = MultiOutputFile
735733
else:
736734
pydra_type = MultiOutputObj
737-
elif isinstance(trait_tp, traits.trait_types.List):
735+
elif isinstance(trait_tp, (traits.trait_types.List, traits.trait_types.Tuple)):
736+
seq_type = list if isinstance(trait_tp, traits.trait_types.List) else tuple
738737
if isinstance(field.inner_traits[0].trait_type, traits_extension.File):
739738
if spec_type == "input":
740-
pydra_type = ty.List[File]
739+
pydra_type = seq_type[File]
741740
else:
742741
pydra_type = MultiOutputFile
743742
else:
744-
pydra_type = list
743+
pydra_type = seq_type[
744+
self.pydra_type_converter(field.inner_traits[0], spec_type, name)
745+
]
745746
elif isinstance(trait_tp, traits_extension.File):
746747
if (
747748
spec_type == "output" or trait_tp.exists is True

nipype2pydra/interface/shell_command.py

Lines changed: 42 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
type_to_str,
1717
)
1818
from fileformats.core.mixin import WithClassifiers
19-
from fileformats.generic import File, Directory
19+
from fileformats.generic import File
20+
from pydra.utils.typing import is_optional
2021

2122

2223
logger = logging.getLogger("nipype2pydra")
@@ -59,7 +60,7 @@ def generate_code(self, input_fields, nonstd_types, output_fields) -> str:
5960
set of non-standard types
6061
output_fields : list[tuple[str, type, dict]]
6162
list of output fields, each field is a tuple of (name, type, metadata)
62-
63+
6364
Returns
6465
-------
6566
converted_code : str
@@ -95,75 +96,65 @@ def unwrap_field_type(t):
9596

9697
nonstd_types = copy(nonstd_types)
9798

98-
# def types_to_names(spec_fields):
99-
# spec_fields_str = []
100-
# for el in spec_fields:
101-
# el = list(el)
102-
# field_type = el[1]
103-
# if inspect.isclass(field_type) and issubclass(
104-
# field_type, WithClassifiers
105-
# ):
106-
# field_type_str = unwrap_field_type(field_type)
107-
# else:
108-
# field_type_str = str(field_type)
109-
# if field_type_str.startswith("<class "):
110-
# field_type_str = el[1].__name__
111-
# else:
112-
# # Alter modules in type string to match those that will be imported
113-
# field_type_str = field_type_str.replace("typing", "ty")
114-
# field_type_str = re.sub(
115-
# r"(\w+\.)+(?<!ty\.)(\w+)", r"\2", field_type_str
116-
# )
117-
# if field_type_str == "File":
118-
# nonstd_types.add(File)
119-
# elif field_type_str == "Directory":
120-
# nonstd_types.add(Directory)
121-
# el[1] = "#" + field_type_str + "#"
122-
# spec_fields_str.append(tuple(el))
123-
# return spec_fields_str
124-
12599
input_names = [i[0] for i in input_fields]
126100
output_names = [o[0] for o in output_fields]
127-
# input_fields_str = types_to_names(spec_fields=input_fields)
128-
# input_fields_str = re.sub(
129-
# r"'formatter': '(\w+)'", r"'formatter': \1", input_fields_str
130-
# )
131-
# output_fields_str = types_to_names(spec_fields=output_fields)
132-
# output_fields_str = re.sub(
133-
# r"'callable': '(\w+)'", r"'callable': \1", output_fields_str
134-
# )
135-
# functions_str = self.function_callables()
136-
# functions_imports, functions_str = functions_str.split("\n\n", 1)
137-
# spec_str = functions_str
101+
102+
# Pull out xor fields into task-level xor_sets
103+
xor_sets = set()
104+
for inpt in input_fields:
105+
if len(inpt) == 3:
106+
name, _, mdata = inpt
107+
else:
108+
name, _, __, mdata = inpt
109+
if "xor" in mdata:
110+
xor_sets.add(frozenset(mdata["xor"] + [name]))
138111

139112
input_fields_str = ""
140113
output_fields_str = ""
141-
xor_sets = set()
114+
142115
for inpt in input_fields:
143116
if len(inpt) == 3:
144117
name, type_, mdata = inpt
118+
mdata = copy(mdata) # Copy to avoid modifying the original
145119
else:
146120
name, type_, default, mdata = inpt
121+
mdata = copy(mdata) # Copy to avoid modifying the original
147122
mdata["default"] = default
123+
if (
124+
any(name in x for x in xor_sets)
125+
and type_ is not bool
126+
and not is_optional(type_)
127+
and (inspect.isclass(type_) and not issubclass(type_, ty.Sequence))
128+
):
129+
type_ = type_ | None
148130
type_str = type_to_str(type_, mdata.pop("mandatory", True))
149131
if mdata.pop("copyfile", None):
150132
nonstd_types.add(File)
151133
mdata["copy_mode"] = "File.CopyMode.copy"
152-
if xor := mdata.pop("xor", None):
153-
xor_sets.add(frozenset(xor + [name]))
134+
mdata.pop("xor", None)
154135
args_str = ", ".join(f"{k}={v!r}" for k, v in mdata.items())
155136
if "path_template" in mdata:
156-
output_fields_str = f" {name}: {type_str} = shell.outarg({args_str})\n"
137+
output_fields_str = (
138+
f" {name}: {type_str} = shell.outarg({args_str})\n"
139+
)
157140
else:
158141
input_fields_str += f" {name}: {type_str} = shell.arg({args_str})\n"
159142

143+
callable_fields = set(n for n, _, __ in self.callable_output_fields)
144+
160145
for outpt in output_fields:
161146
name, type_, mdata = outpt
162-
cllble = mdata.pop("callable", None)
147+
cllble = mdata.pop(
148+
"callable", f"{name}_callable" if name in callable_fields else None
149+
)
163150
args_str = ", ".join(f"{k}={v!r}" for k, v in mdata.items())
151+
if args_str:
152+
args_str += ", "
164153
if cllble:
165-
args_str += f", callable={cllble}"
166-
output_fields_str += f" {name}: {type_to_str(type_)} = shell.out({args_str})\n"
154+
args_str += f"callable={cllble}"
155+
output_fields_str += (
156+
f" {name}: {type_to_str(type_)} = shell.out({args_str})\n"
157+
)
167158

168159
spec_str = (
169160
self.init_code
@@ -176,7 +167,9 @@ def unwrap_field_type(t):
176167
spec_str += "@shell.define"
177168
if xor_sets:
178169
spec_str += f"(xor={[list(x) for x in xor_sets]})"
179-
spec_str += f"\nclass {self.task_name}(shell.Task['{self.task_name}.Outputs']):\n"
170+
spec_str += (
171+
f"\nclass {self.task_name}(shell.Task['{self.task_name}.Outputs']):\n"
172+
)
180173
spec_str += ' """\n'
181174
spec_str += self.create_doctests(
182175
input_fields=input_fields, nonstd_types=nonstd_types
@@ -254,10 +247,7 @@ def callable_output_fields(self):
254247
return [
255248
f
256249
for f in super().output_fields
257-
if (
258-
"path_template" not in f[-1]
259-
and f[0] not in INBUILT_NIPYPE_TRAIT_NAMES
260-
)
250+
if ("path_template" not in f[-1] and f[0] not in INBUILT_NIPYPE_TRAIT_NAMES)
261251
]
262252

263253
@property

nipype2pydra/package.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,12 @@ def resolve_objects(addresses: ty.Optional[ty.List[str]]) -> list:
7878
return []
7979
objs = []
8080
for address in addresses:
81-
parts = address.split(".")
82-
mod = import_module(".".join(parts[:-1]))
83-
objs.append(getattr(mod, parts[-1]))
81+
if not isinstance(address, str):
82+
objs.append(address)
83+
else:
84+
parts = address.split(".")
85+
mod = import_module(".".join(parts[:-1]))
86+
objs.append(getattr(mod, parts[-1]))
8487
return objs
8588

8689

nipype2pydra/pkg_gen/__init__.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class NipypeInterface:
9393
input_helps: ty.Dict[str, str] = attrs.field(factory=dict)
9494
output_helps: ty.Dict[str, str] = attrs.field(factory=dict)
9595
file_inputs: ty.List[str] = attrs.field(factory=list)
96-
path_inputs: ty.List[str] = attrs.field(factory=list)
96+
# path_inputs: ty.List[str] = attrs.field(factory=list)
9797
str_inputs: ty.List[str] = attrs.field(factory=list)
9898
file_outputs: ty.List[str] = attrs.field(factory=list)
9999
template_outputs: ty.List[str] = attrs.field(factory=list)
@@ -188,8 +188,8 @@ def parse(
188188
parsed.input_helps[inpt_name] = f"{inpt_mdata}: {inpt_desc}"
189189
trait_type_name = type(inpt.trait_type).__name__
190190
if inpt.genfile:
191-
if trait_type_name in ("File", "Directory"):
192-
parsed.path_inputs.append(inpt_name)
191+
# if trait_type_name in ("File", "Directory"):
192+
# parsed.path_inputs.append(inpt_name)
193193
if inpt_name in (parsed.file_outputs + parsed.dir_outputs):
194194
parsed.template_outputs.append(inpt_name)
195195
else:
@@ -204,8 +204,8 @@ def parse(
204204
):
205205
if "fix" in inpt_name:
206206
parsed.str_inputs.append(inpt_name)
207-
else:
208-
parsed.path_inputs.append(inpt_name)
207+
# else:
208+
# parsed.path_inputs.append(inpt_name)
209209
else:
210210
parsed.file_inputs.append(inpt_name)
211211
elif trait_type_name == "Directory" and inpt_name not in parsed.dir_outputs:
@@ -230,16 +230,16 @@ def parse(
230230
else:
231231
parsed.dir_inputs.append(inpt_name)
232232
parsed.multi_inputs.append(inpt_name)
233-
elif trait_type_name in ("File", "Directory"):
234-
parsed.path_inputs.append(inpt_name)
233+
# elif trait_type_name in ("File", "Directory"):
234+
# parsed.path_inputs.append(inpt_name)
235235
return parsed
236236

237237
def generate_yaml_spec(self) -> str:
238238
"""Convert the NipypeInterface to a YAML string"""
239239

240240
input_types = {i: File for i in self.file_inputs}
241241
input_types.update({i: Directory for i in self.dir_inputs})
242-
input_types.update({i: Path for i in self.path_inputs})
242+
# input_types.update({i: Path for i in self.path_inputs})
243243
input_types.update({i: str for i in self.str_inputs})
244244
output_types = {o: File for o in self.file_outputs}
245245
output_types.update({o: Directory for o in self.dir_outputs})
@@ -284,6 +284,8 @@ def generate_yaml_spec(self) -> str:
284284
non_mime = [Path, str]
285285

286286
def type2str(tp):
287+
if isinstance(tp, str):
288+
return tp
287289
if tp in non_mime:
288290
return tp.__name__
289291
return fileformats.core.to_mime(tp, official=False)
@@ -765,7 +767,11 @@ def copy_ignore(_, names):
765767

766768
# Replace "CHANGEME" string with pkg name
767769
for fspath in pkg_dir.glob("**/*"):
768-
if fspath.is_dir() or fspath.suffix in (".pyc", ".pyo", ".pyd"):
770+
if (
771+
fspath.is_dir()
772+
or fspath.suffix in (".pyc", ".pyo", ".pyd")
773+
or fspath.name.startswith(".")
774+
):
769775
continue
770776
with open(fspath) as f:
771777
contents = f.read()
@@ -1121,7 +1127,15 @@ def insert_args_in_method_calls(
11211127
all_constants = set()
11221128
for mod_name, methods in grouped_methods.items():
11231129
mod = import_module(mod_name)
1124-
used = UsedSymbols.find(mod, methods, omit_classes=(BaseInterface, TraitedSpec))
1130+
used = UsedSymbols.find(
1131+
mod,
1132+
methods,
1133+
package=PackageConverter(
1134+
name=mod_name.split(".")[-1],
1135+
nipype_name=mod_name,
1136+
omit_classes=(BaseInterface, TraitedSpec),
1137+
),
1138+
)
11251139
all_funcs.update(methods)
11261140
for func in used.functions:
11271141
all_funcs.add(cleanup_function_body(get_source_code(func)))

nipype2pydra/symbols.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,10 @@ def _find_referenced(
401401
intra_pkg_objs[imported.object.__name__].add(obj)
402402
elif inspect.isclass(obj):
403403
class_def = (obj.__name__, obj)
404-
if class_def not in self.imported_classes:
404+
if (
405+
class_def
406+
not in self.imported_classes + omit_classes
407+
):
405408
self.imported_classes.append(class_def)
406409
intra_pkg_objs[imported.object.__name__].add(obj)
407410
else:

0 commit comments

Comments
 (0)