Skip to content

ENH: Add tab completion for node and interface inputs properties #2735

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 17, 2018

Conversation

leej3
Copy link
Contributor

@leej3 leej3 commented Oct 12, 2018

Summary

Improves ease of use by giving more convenient tab completion for constructing graphs.

Fixes # .

List of changes proposed in this PR (pull-request)

  • adds tab completion to the inputs property of both node and interface. This behavior is currently only observed in Ipython and if the user sets "c.IPCompleter.limit_to__all__ = True" in their ipython config or executes the magic command "%config IPCompleter.limit_to__all__ = True"

Acknowledgment

  • I acknowledge that this contribution will be available under the Apache 2 license.

@effigies
Copy link
Member

Could you add a test for TraitedSpecs (e.g. CommandLine interface) and DynamicTraitedSpecs (e.g. Function interface)?

I'm guessing the reason for including this in BaseInterface, as opposed to BaseTraitedSpec, is that the spec should already be fleshed out by the time this is called. But I think Function at least calls super before adding inputs. Instead, I think maybe the better way to do this will be to update self.__all__ in BaseTraitedSpec on __init__ and when new traits are added.

But if the test passes for both static and dynamic specs, I'm okay with this, as it's pretty simple.

@effigies effigies changed the title PR: ENH add tab completion for node and interface inputs properties ENH: Add tab completion for node and interface inputs properties Oct 13, 2018
@effigies
Copy link
Member

Also, is this targeting 1.x or 2.0?

@codecov-io
Copy link

codecov-io commented Oct 15, 2018

Codecov Report

Merging #2735 into master will decrease coverage by 3.45%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2735      +/-   ##
==========================================
- Coverage   67.44%   63.99%   -3.46%     
==========================================
  Files         340      338       -2     
  Lines       43170    43121      -49     
  Branches     5353     5350       -3     
==========================================
- Hits        29117    27596    -1521     
- Misses      13355    14454    +1099     
- Partials      698     1071     +373
Flag Coverage Δ
#smoketests ?
#unittests 63.99% <100%> (-0.84%) ⬇️
Impacted Files Coverage Δ
nipype/interfaces/base/specs.py 86.93% <100%> (-6.18%) ⬇️
nipype/interfaces/nilearn.py 40% <0%> (-56.67%) ⬇️
nipype/utils/spm_docs.py 25.92% <0%> (-44.45%) ⬇️
nipype/interfaces/freesurfer/base.py 49.59% <0%> (-30.9%) ⬇️
nipype/utils/logger.py 59.7% <0%> (-29.86%) ⬇️
nipype/algorithms/rapidart.py 35.39% <0%> (-29.21%) ⬇️
nipype/interfaces/spm/base.py 58.41% <0%> (-29.05%) ⬇️
nipype/utils/provenance.py 55.73% <0%> (-28.99%) ⬇️
nipype/interfaces/fsl/model.py 55.26% <0%> (-25.35%) ⬇️
nipype/testing/fixtures.py 77.33% <0%> (-21.34%) ⬇️
... and 43 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3ec8065...59a4ba0. Read the comment docs.

@leej3
Copy link
Contributor Author

leej3 commented Oct 15, 2018

Thanks. Your pointers helped me a lot. It does indeed work better by modifying BaseTraitedSpec. I've added tests for my desired behavior.

I'm still struggling to implement the functionality for DynamicTraitedSpec. I'm not sure where to modify it. Do you have any thoughts?

@effigies
Copy link
Member

Perhaps override trait_set to update __all__ after running super().trait_set()?

@leej3
Copy link
Contributor Author

leej3 commented Oct 16, 2018

This is almost working. I would expect the following test to pass but the second assert doesn't:

    def test_DynamicTraitedSpec_tab_completion():
        extract_func = lambda list_out: list_out[0]

        # Define node
        list_extract = Node(Function(
            input_names=["list_out"],output_names=["out_file"],
            function=extract_func), name="list_extract")

        # Check __all__ for node outputs
        expected_output = sorted(list_extract.outputs.editable_traits())
        assert(sorted(list_extract.outputs.__all__) == expected_output)

        # Add trait and retest
        list_extract.outputs.add_trait("added_out_trait","val")
        expected_output = sorted(['added_out_trait',*expected_output])
       assert(sorted(list_extract.outputs.__all__) == expected_output)

This doesn't pass on the commits prior to mine though.
EDIT: Rather, I mean that I would expect to be able to add traits for DynamicTraitedSpec objects but cannot.

@leej3
Copy link
Contributor Author

leej3 commented Oct 16, 2018

Also, is this targeting 1.x or 2.0?

1.x. 2.0 will have to incorporate it following a pending pyhydra refactor.

@effigies effigies added this to the 1.1.4 milestone Oct 16, 2018
@effigies
Copy link
Member

I think the problem may be that Function can't take a lambda. It requires a normal function.

@leej3
Copy link
Contributor Author

leej3 commented Oct 16, 2018

good. to know. i'll change that. It doesn't fix this problem though

assert(sorted(list_extract.outputs.__all__) == expected_output)

# Add trait and retest
list_extract.outputs.add_trait("added_out_trait","val")
Copy link
Member

@effigies effigies Oct 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that the output spec is actually instantiated in IOBase._outputs(), which calls Function._add_output_traits, which only will add things in self._output_names. So to do what you want here, you would need to do:

list_extract._interface._output_names.append('added_out_trait')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed. Thank you. I don't think I would have got that.

leej3 added 2 commits October 16, 2018 16:25
due to an error in understanding of dynamic trait
instantiation:
output spec is actually instantiated in
IOBase._outputs(), which calls Function._add_output_traits,
which only will add things in self._output_names
@@ -83,9 +83,11 @@ def __repr__(self):
""" Return a well-formatted representation of the traits """
outstr = []
for name, value in sorted(self.trait_get().items()):
outstr.append('%s = %s' % (name, value))
if not name == '__all__':
outstr.append('%s = %s' % (name, value))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still necessary, with __all__ becoming a property?

return '\n{}\n'.format('\n'.join(outstr))


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove this line.

def __all__(self):
return [k for k,v in self.items() if not k == "__all__"]


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many blank lines.

@@ -309,6 +311,11 @@ def _get_sorteddict(self,
out = objekt
return out

@property
def __all__(self):
return [k for k,v in self.items() if not k == "__all__"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that items() provides a dict keyed by self.copyable_trait_names(), and you only need keys, I think you can just make this:

@property
def __all__(self):
    return self.copyable_trait_names()

You'll need to change the BET tests to either sort or check set equality, but this should work.

@leej3
Copy link
Contributor Author

leej3 commented Oct 16, 2018

Thanks. I made all those changes

Copy link
Member

@effigies effigies left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks.


# Add trait and retest
list_extract._interface._output_names.append('added_out_trait')
expected_output = sorted(['added_out_trait',*expected_output])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python 2.7, 3.4 don't like the [*x] syntax. How about making expected_output a set above, moving to set comparisons, and changing this to:

expected_output.add('added_out_trait')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice. Thanks for the tip. done

@effigies effigies merged commit eacd567 into nipy:master Oct 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants