Skip to content

Commit fb38d6c

Browse files
committed
debugging interface converter
1 parent 251cd20 commit fb38d6c

File tree

9 files changed

+105
-42
lines changed

9 files changed

+105
-42
lines changed

nipype2pydra/cli/convert.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ def convert(
8787
spec = yaml.safe_load(f)
8888
converter.add_interface_from_spec(
8989
spec=spec,
90-
callables_file=(
91-
fspath.parent / (fspath.name[: -len(".yaml")] + "_callables.py")
92-
),
90+
# callables_file=(
91+
# fspath.parent / (fspath.name[: -len(".yaml")] + "_callables.py")
92+
# ),
9393
)
9494

9595
# Load workflow specs

nipype2pydra/cli/pkg_gen.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,10 @@ def pkg_gen(
185185
with open(interfaces_spec_dir / (spec_name + ".yaml"), "w") as f:
186186
f.write(yaml_spec)
187187

188-
callables_fspath = interfaces_spec_dir / f"{spec_name}_callables.py"
188+
# callables_fspath = interfaces_spec_dir / f"{spec_name}_callables.py"
189189

190-
with open(callables_fspath, "w") as f:
191-
f.write(parsed.generate_callables(nipype_interface))
190+
# with open(callables_fspath, "w") as f:
191+
# f.write(parsed.generate_callables(nipype_interface))
192192

193193
if "functions" in spec:
194194
functions_spec_dir = spec_dir / "functions"

nipype2pydra/helpers.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,10 @@ class BaseHelperConverter:
9393
factory=list,
9494
converter=from_list_to_imports,
9595
metadata={
96-
"help": """list import statements required by the test, with each list item
97-
consisting of 'module', 'name', and optionally 'alias' keys"""
96+
"help": (
97+
"list import statements required by the test, with each list item"
98+
"consisting of 'module', 'name', and optionally 'alias' keys"
99+
)
98100
},
99101
)
100102
package: "nipype2pydra.package.PackageConverter" = attrs.field(

nipype2pydra/interface/base.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,10 +339,10 @@ class BaseInterfaceConverter(metaclass=ABCMeta):
339339
factory=OutputsConverter,
340340
converter=from_dict_to_outputs,
341341
)
342-
callables_module: ModuleType = attrs.field(
343-
converter=import_module_from_path,
344-
default=None,
345-
)
342+
# callables_module: ModuleType = attrs.field(
343+
# converter=import_module_from_path,
344+
# default=None,
345+
# )
346346
tests: ty.List[TestGenerator] = attrs.field( # type: ignore
347347
factory=list, converter=from_list_to_tests
348348
)
@@ -884,7 +884,7 @@ def _converted_test(self):
884884
else:
885885
value = attrs.NOTHING
886886
if value is not attrs.NOTHING:
887-
spec_str += f" task.inputs.{nm} = {value}\n"
887+
spec_str += f" task.{nm} = {value}\n"
888888
if hasattr(self.nipype_interface, "_cmd"):
889889
spec_str += r' print(f"CMDLINE: {task.cmdline}\n\n")' + "\n"
890890
spec_str += " res = task(worker=PassAfterTimeoutWorker)\n"

nipype2pydra/interface/shell.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@
2222

2323
logger = logging.getLogger("nipype2pydra")
2424

25-
CALLABLES_ARGS = ["inputs", "stdout", "stderr", "output_dir"]
25+
OUT_FUNC_ARGS = ["callable", "formatter"] # arguments to shell.out that are functions
26+
CALLABLE_ARGS = [
27+
"inputs",
28+
"stdout",
29+
"stderr",
30+
"output_dir",
31+
] # Arguments for callable methods
2632

2733

2834
@attrs.define(slots=False)
@@ -93,13 +99,26 @@ def generate_code(self, input_fields, nonstd_types, output_fields) -> str:
9399

94100
# Pull out xor fields into task-level xor_sets
95101
xor_sets = set()
102+
has_zero_pos = False
96103
for inpt in input_fields:
97104
if len(inpt) == 3:
98105
name, _, mdata = inpt
99106
else:
100107
name, _, __, mdata = inpt
101108
if "xor" in mdata:
102-
xor_sets.add(frozenset(mdata["xor"] + [name]))
109+
xor_sets.add(frozenset(list(mdata["xor"]) + [name]))
110+
if mdata.get("position", None) == 0:
111+
has_zero_pos = True
112+
113+
# Increment positions if there is a zero position
114+
if has_zero_pos:
115+
for inpt in input_fields:
116+
if len(inpt) == 3:
117+
name, _, mdata = inpt
118+
else:
119+
name, _, __, mdata = inpt
120+
if "position" in mdata and mdata["position"] >= 0:
121+
mdata["position"] = mdata.pop("position") + 1
103122

104123
input_fields_str = ""
105124
output_fields_str = ""
@@ -126,26 +145,24 @@ def generate_code(self, input_fields, nonstd_types, output_fields) -> str:
126145
mdata.pop("xor", None)
127146
args_str = ", ".join(f"{k}={v!r}" for k, v in mdata.items())
128147
if "path_template" in mdata:
129-
output_fields_str = (
148+
output_fields_str += (
130149
f" {name}: {type_str} = shell.outarg({args_str})\n"
131150
)
132151
else:
133152
input_fields_str += f" {name}: {type_str} = shell.arg({args_str})\n"
134153

135-
callable_fields = set(n for n, _, __ in self.callable_output_fields)
154+
# callable_fields = set(n for n, _, __ in self.callable_output_fields)
136155

137156
for outpt in output_fields:
138157
name, type_, mdata = outpt
139-
cllble = mdata.pop(
140-
"callable", f"{name}_callable" if name in callable_fields else None
141-
)
142-
args_str = ", ".join(f"{k}={v!r}" for k, v in mdata.items())
143-
if args_str:
144-
args_str += ", "
145-
if cllble:
146-
args_str += f"callable={cllble}"
158+
func_args = []
159+
for func_arg in OUT_FUNC_ARGS:
160+
if func_arg in mdata:
161+
func_args.append(f"{func_arg}={mdata[func_arg]}")
162+
mdata.pop(func_arg)
163+
args = [f"{k}={v!r}" for k, v in mdata.items()] + func_args
147164
output_fields_str += (
148-
f" {name}: {type_to_str(type_)} = shell.out({args_str})\n"
165+
f" {name}: {type_to_str(type_)} = shell.out({', '.join(args)})\n"
149166
)
150167

151168
spec_str = (
@@ -182,7 +199,7 @@ def generate_code(self, input_fields, nonstd_types, output_fields) -> str:
182199
s[0] == self.nipype_interface._list_outputs
183200
for s in self.used.method_stacks[m.__name__]
184201
):
185-
additional_args = CALLABLES_ARGS
202+
additional_args = CALLABLE_ARGS
186203
else:
187204
additional_args = []
188205
method_str = self.process_method(
@@ -418,7 +435,7 @@ def callables_code(self):
418435
if not agg_body.strip():
419436
return ""
420437
agg_body = self.unwrap_nested_methods(
421-
agg_body, additional_args=CALLABLES_ARGS, inputs_as_dict=True
438+
agg_body, additional_args=CALLABLE_ARGS, inputs_as_dict=True
422439
)
423440
agg_body = self.replace_supers(
424441
agg_body,
@@ -477,13 +494,15 @@ def callables_code(self):
477494
)
478495
lo_body = self._process_inputs(lo_body)
479496
lo_body = re.sub(
480-
r"(\w+) = self\.output_spec\(\).(?:trait_)get\(\)", r"\1 = {}", lo_body
497+
r"(\w+) = self\.output_spec\(\).(?:trait_)get\(\)",
498+
r"\1 = {}",
499+
lo_body,
481500
)
482501

483502
if not lo_body.strip():
484503
return ""
485504
lo_body = self.unwrap_nested_methods(
486-
lo_body, additional_args=CALLABLES_ARGS, inputs_as_dict=True
505+
lo_body, additional_args=CALLABLE_ARGS, inputs_as_dict=True
487506
)
488507
lo_body = self.replace_supers(
489508
lo_body,

nipype2pydra/interface/tests/test_interface.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,26 @@
1313
INBUILT_NIPYPE_TRAIT_NAMES,
1414
)
1515
from nipype2pydra.package import PackageConverter
16+
from nipype2pydra.symbols import clear_caches
1617
from conftest import EXAMPLE_INTERFACES_DIR
1718

1819

1920
logging.basicConfig(level=logging.INFO)
2021

2122

2223
XFAIL_INTERFACES = [
23-
"fsl-prob_track_x2",
24-
"fsl-flameo",
25-
"fsl-make_dyadic_vectors",
26-
"fsl-dual_regression",
27-
"fsl-epi_de_warp",
24+
"ants-interfaces-ai",
25+
"ants-interfaces-measure_image_similarity",
26+
"ants-interfaces-threshold_image",
27+
"fsl-interfaces-prob_track_x2",
28+
"fsl-interfaces-flameo",
29+
"fsl-interfaces-make_dyadic_vectors",
30+
"fsl-interfaces-dual_regression",
31+
"fsl-interfaces-epi_de_warp",
2832
]
2933

3034
XFAIL_INTERFACES_IN_COMBINED = [
35+
"ants-ai",
3136
"freesurfer-smooth",
3237
"freesurfer-apply_mask",
3338
"afni-merge",
@@ -42,7 +47,11 @@
4247
@pytest.fixture(
4348
params=[
4449
str(p.relative_to(EXAMPLE_INTERFACES_DIR)).replace("/", "-")[:-5]
45-
for p in (EXAMPLE_INTERFACES_DIR).glob("**/*.yaml")
50+
for p in (EXAMPLE_INTERFACES_DIR).glob("**/interfaces/*.yaml")
51+
if (
52+
str(p.relative_to(EXAMPLE_INTERFACES_DIR)).replace("/", "-")[:-5]
53+
not in XFAIL_INTERFACES
54+
)
4655
]
4756
)
4857
def interface_spec_file(request):
@@ -54,6 +63,8 @@ def interface_spec_file(request):
5463
def test_interface_convert(
5564
interface_spec_file, cli_runner, work_dir, gen_test_conftest
5665
):
66+
# Clear UsedSymbol caches
67+
clear_caches()
5768

5869
try:
5970
with open(interface_spec_file) as f:
@@ -74,8 +85,8 @@ def test_interface_convert(
7485

7586
converter = pkg_converter.add_interface_from_spec(
7687
spec=interface_spec,
77-
callables_file=interface_spec_file.parent
78-
/ (interface_spec_file.stem + "_callables.py"),
88+
# callables_file=interface_spec_file.parent
89+
# / (interface_spec_file.stem + "_callables.py"),
7990
)
8091

8192
converter.write(pkg_root)
@@ -89,7 +100,7 @@ def test_interface_convert(
89100
nipype_ports.append(pkg_converter.nipype_port_converters[address])
90101
for _, func in converter.used.imported_funcs:
91102
if full_address(func) not in list(pkg_converter.workflows):
92-
intra_pkg_modules[func.__module__].add(func)
103+
intra_pkg_modules[func.__module__].add(func)
93104

94105
already_converted = set()
95106
for converter in tqdm(

nipype2pydra/package.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,10 @@ def translate_submodule(
507507
def untranslate_submodule(self, pydra_module_name: str) -> str:
508508
"""Translates a module name from the Nipype package to the Pydra package"""
509509
relpath = ImportStatement.get_relative_package(pydra_module_name, self.name)
510+
if relpath.startswith(".auto"):
511+
relpath = relpath[5:]
512+
if relpath.startswith(".nipype_ports"):
513+
return "nipype" + relpath[13:]
510514
if relpath == self.nipype_name:
511515
raise ValueError(
512516
f"Module {pydra_module_name} is not in the nipype package {self.name}"
@@ -741,7 +745,9 @@ def nipype_port_converters(self) -> ty.Dict[str, interface.BaseInterfaceConverte
741745
)
742746

743747
def add_interface_from_spec(
744-
self, spec: ty.Dict[str, ty.Any], callables_file: Path
748+
self,
749+
spec: ty.Dict[str, ty.Any],
750+
# callables_file: Path
745751
) -> interface.BaseInterfaceConverter:
746752
output_module = self.translate_submodule(
747753
spec["nipype_module"], sub_pkg="auto" if self.interface_only else None
@@ -750,7 +756,7 @@ def add_interface_from_spec(
750756
converter = self.interfaces[f"{spec['nipype_module']}.{spec['task_name']}"] = (
751757
interface.get_converter(
752758
output_module=output_module,
753-
callables_module=callables_file,
759+
# callables_module=callables_file,
754760
package=self,
755761
**spec,
756762
)

nipype2pydra/pkg_gen/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,8 @@ def _gen_tests(
446446

447447
for doctest_str in doctest_blocks:
448448
if ">>>" in doctest_str:
449+
450+
doctest_str = re.sub(r"\n\.\.\.\s+", " ", doctest_str)
449451
try:
450452
cmdline, inpts, directive, imports = extract_doctest_inputs(
451453
doctest_str, self.name
@@ -641,7 +643,7 @@ def initialise_task_repo(
641643
"""Copy the task template to the output directory and customise it for the given
642644
package name and return the created package directory"""
643645

644-
pkg_dir = output_dir / f"pydra-{pkg}"
646+
pkg_dir = output_dir / f"pydra-tasks-{pkg}"
645647

646648
def copy_ignore(_, names):
647649
return [n for n in names if n in (".git", "__pycache__", ".pytest_cache")]

nipype2pydra/symbols.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ def find(
199199
tuple(f.__name__ if not isinstance(f, str) else f for f in function_bodies),
200200
collapse_intra_pkg,
201201
pull_out_inline_imports,
202+
tuple(package.all_import_translations),
202203
)
203204
try:
204205
return cls._cache[cache_key]
@@ -216,6 +217,16 @@ def find(
216217
)
217218
return used
218219

220+
@classmethod
221+
def clear_cache(cls):
222+
cls._cache = {}
223+
cls._cache = {}
224+
cls._stmts_cache = {}
225+
cls._imports_cache = {}
226+
cls._funcs_cache = {}
227+
cls._classes_cache = {}
228+
cls._constants_cache = {}
229+
219230
@classmethod
220231
def _module_statements(cls, module) -> list:
221232
try:
@@ -667,6 +678,12 @@ class UsedClassSymbols(UsedSymbols):
667678

668679
_class_attrs_cache = {}
669680

681+
@classmethod
682+
def clear_cache(cls):
683+
"""Clear the cache for the class attributes"""
684+
cls._class_attrs_cache = {}
685+
super().clear_cache()
686+
670687
@classmethod
671688
def find(
672689
cls,
@@ -721,6 +738,7 @@ def find(
721738
pull_out_inline_imports,
722739
absolute_imports,
723740
tuple(always_include),
741+
tuple(package.all_import_translations),
724742
)
725743
try:
726744
return cls._cache[cache_key]
@@ -933,3 +951,8 @@ def local_class_attrs(cls, klass) -> ty.Dict[str, str]:
933951
class_attrs = dict(class_attrs)
934952
cls._constants_cache[cache_key] = class_attrs
935953
return class_attrs
954+
955+
956+
def clear_caches():
957+
UsedSymbols.clear_cache()
958+
UsedClassSymbols.clear_cache()

0 commit comments

Comments
 (0)