Skip to content
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

shell_task decorator #655

Closed
wants to merge 11 commits into from
Closed

Conversation

tclose
Copy link
Contributor

@tclose tclose commented May 15, 2023

Types of changes

  • New feature (non-breaking change which adds functionality)

Summary

Extends PR #653 to include @shell_task decorator, which streamlines the code required to define a shell command task. See discussion #647 for rationale behind this approach

Checklist

  • I have added tests to cover my changes (if necessary)
  • I have updated documentation (if necessary)

@tclose tclose requested review from effigies and djarecka May 15, 2023 03:02
@tclose tclose changed the title Shell cmd decorator extended shell_task decorator May 15, 2023
@codecov
Copy link

codecov bot commented May 15, 2023

Codecov Report

Patch coverage: 96.33% and project coverage change: +0.30 🎉

Comparison is base (426564e) 81.77% compared to head (ac3c436) 82.07%.

❗ Current head ac3c436 differs from pull request most recent head 1b3f72e. Consider uploading reports for the commit 1b3f72e to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #655      +/-   ##
==========================================
+ Coverage   81.77%   82.07%   +0.30%     
==========================================
  Files          20       21       +1     
  Lines        4400     4508     +108     
  Branches     1264        0    -1264     
==========================================
+ Hits         3598     3700     +102     
- Misses        798      808      +10     
+ Partials        4        0       -4     
Flag Coverage Δ
unittests 82.07% <96.33%> (+0.30%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
pydra/mark/shell.py 96.26% <96.26%> (ø)
pydra/mark/__init__.py 100.00% <100.00%> (ø)

... and 4 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@tclose tclose force-pushed the shell-cmd-decorator-extended branch from 0357fc8 to b471f45 Compare May 15, 2023 03:20
@tclose tclose marked this pull request as ready for review May 16, 2023 12:04
@tclose
Copy link
Contributor Author

tclose commented May 16, 2023

I have now implemented the @shell_task decorator, which can be used as per the discussion thread like

# Split out common arguments into separate attrs classes
@attrs.define(kw_only=True, slots=False)
class RecurseOption:
    recurse: bool = shell_arg(
        help_string="Recursively descend the directory tree",
        argstr="-R",
    )

# Define an interface for the `ls` command
@shell_task
class Ls:
    executable = "ls"

    class Inputs(RecurseOption):
        directory: os.PathLike = shell_arg(
            help_string="the directory to list the contents of",
            argstr="",
            mandatory=True,
            position=-1,
        )
        hidden: bool = shell_arg(
            help_string="display hidden FS objects",
            argstr="-a",
            default=False,
        )

    class Outputs:
        entries: list = shell_out(
            help_string="list of entries returned by ls command",
            callable=list_entries,
        )

or alternatively as a function in order to dynamically generate interfaces

Ls = shell_task(
    "Ls",
    executable="ls",
    input_fields={
        "directory": {
            "type": os.PathLike,
            "help_string": "the directory to list the contents of",
            "argstr": "",
            "mandatory": True,
            "position": -1,
        },
        "hidden": {
            "type": bool,
            "help_string": "display hidden FS objects",
            "argstr": "-a",
        },
    output_fields={
        "entries": {
            "type": list,
            "help_string": "list of entries returned by ls command",
            "callable": list_entries,
        }
    },
    inputs_bases=[RecursiveOptions],
)

The decorator/function automatically generates the input_spec and output_spec SpecInfor class attributes, but I think they could be redundant now, as the attrs-generated classes for inputs and outputs are generated at MyTask.Inputs and MyTask.Outputs, respectively, with output_file_template generated outputs automatically inserted.

If this syntax looks alright I can update the docs to match it.

If there is interest, I could also look at refreshing the function task decorator so that it can be used in the same way for dynamic classes and so it also generates Inputs/Outputs classes so we can do without input_spec and output_spec all together.

Copy link
Collaborator

@djarecka djarecka left a comment

Choose a reason for hiding this comment

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

I would have to get used to this syntax, but I can see that this might be more readable!
I left some comments under the tests to be sure if I fully understand the idea and syntax. Perhaps a very short description to less trivial tests would be useful 



def test_shell_bases_dynamic(A, tmpdir):
B = shell_task(
Copy link
Collaborator

Choose a reason for hiding this comment

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

so I understand that this has the same input_spec and executable/args as A?

Could we also overwrite executable or args?

result = b()

assert b.inputs.x == xpath
assert result.output.y == str(ypath)
Copy link
Collaborator

Choose a reason for hiding this comment

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

perhaps we should also check result.output.out_file_size to check if this is correctly added to the output_spec

result = b()

assert b.inputs.x == xpath
assert result.output.y == str(ypath)
Copy link
Collaborator

Choose a reason for hiding this comment

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

see comment in the previous test

}
},
bases=[A],
inputs_bases=[A.Inputs],
Copy link
Collaborator

Choose a reason for hiding this comment

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

not sure if I understand the idea behind this syntax (i.e. separately providing bases and inputs_bases

with pytest.raises(
RuntimeError, match=("`shell_task` should not be provided any other arguments")
):
shell_task(A, executable="cp")
Copy link
Collaborator

Choose a reason for hiding this comment

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

it might be an answer from one of my previous question. Is this a misuse because you try to overwrite executable?
Would it rise an error if args provided?
Perhaps the error message could be less general?

@tclose
Copy link
Contributor Author

tclose commented Sep 11, 2023

closing to see where the #692 discussion goes first

@tclose tclose closed this Sep 11, 2023
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.

2 participants