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

DM-25627 make qgraph and run subcommands for pipetask #65

Merged
merged 7 commits into from Aug 18, 2020

Conversation

n8pease
Copy link
Contributor

@n8pease n8pease commented Aug 11, 2020

No description provided.

Copy link
Collaborator

@andy-slac andy-slac left a comment

Choose a reason for hiding this comment

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

Looks OK, check few comments. It feels like we are overdoing it with a module per command line option. There is so much boilerplate and duplication everywhere it's hard to read it.

skip_existing_option,
skip_init_writes_option,
task_option,
timeout_option)
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's rather long list, you could do from .. import opt and then @opt.something below.


@staticmethod
def defaultHelp():
return unwrap(f"""Instead of creating a new RUN collection, insert datasets into either the one given
Copy link
Collaborator

Choose a reason for hiding this comment

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

f is not needed.


class input_option(MWOptionDecorator): # noqa: N801

defaultMetavar = "TASK[:LABEL]"
Copy link
Collaborator

Choose a reason for hiding this comment

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

This does not look right, it should be a list of collections.

def __call__(self, f):
return click.option(*self.optionFlags(), cls=MWOption,
callback=self.callback,
default=None if self.required else list(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is list() better than []?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just a general preference for immutable objects

Copy link
Member

Choose a reason for hiding this comment

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

If you want immutable shouldn't it be a tuple?

Copy link
Contributor Author

@n8pease n8pease Aug 13, 2020

Choose a reason for hiding this comment

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

hah. Yes.

Is list() better than []

Trying again: I don't trust that passing default=[] won't end up sharing a list instance among multiple instances of the Option, and depending on how downstream code uses the default value chaos may ensue. list() is guaranteed to create a new list instance each time it is used and is safer. OTOH, () or tuple() would both be fine since () is immutable. Probably () is slightly better because as I understand it tuple() results in an extra line of code being executed (return () or similar)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

and, click won't take a tuple as the default value of an option, so I'm going to stick with list()

Copy link
Collaborator

Choose a reason for hiding this comment

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

My question was mostly rhetorical, there is no real difference between list() and [], both of them make a new empty list (but latter is shorter). What you pass as a parameter is not going to be shared, because it's a new list on every call. The sharing argument is only valid for the default argument value in the declaration of a method, there indeed you should not use mutable values as defaults.

python/lsst/ctrl/mpexec/cli/opt/pipeline_dot.py Outdated Show resolved Hide resolved
skip_existing : `bool`
If all Quantum outputs already exist in the output RUN collection then
that Quantum will be excluded from the QuantumGraph. Will only be used
if `extended-run` flag is set.
Copy link
Collaborator

Choose a reason for hiding this comment

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

extended-run - extend_run

python/lsst/ctrl/mpexec/cli/script/qgraph.py Show resolved Hide resolved
python/lsst/ctrl/mpexec/cli/script/qgraph.py Show resolved Hide resolved
"""A container class for arguments to CmdLineFwk.makeGraph, whose
API (currently) is written to accept inputs from argparse in a generic
container class.
"""
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think types.SimpleNamespace class would work better for this: https://docs.python.org/3/library/types.html#types.SimpleNamespace

Copy link
Contributor Author

Choose a reason for hiding this comment

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

from the docs:

SimpleNamespace may be useful as a replacement for class NS: pass

I had basically done that (class NS: pass) for the build subcommand, @timj did not like it and that's why I wrote the Args classes.

Copy link
Member

Choose a reason for hiding this comment

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

What didn't I like?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in #58, search for "cute" (I don't see a way to link to the particular conversation)

Copy link
Contributor Author

@n8pease n8pease Aug 14, 2020

Choose a reason for hiding this comment

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

since the changes are outdated (gone AFAIK), Args was declared and used like so:

class Args
    pass
args = Args()
args.qgraph = qgraph
args.save_qgraph = save_qgraph
<etc>

Copy link
Member

Choose a reason for hiding this comment

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

I think you are over interpreting my comment. My final quote was:

At the very least a comment like you just made in the class description for Args would be useful.

(I haven't looked at the current context -- SimpleNamespace does look useful as an explicit declaration of intent)


f = CmdLineFwk()
taskFactory = TaskFactory()
f.runPipeline(qgraph, taskFactory, args)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comments as for qgraph.py

@n8pease
Copy link
Contributor Author

n8pease commented Aug 13, 2020

@andy-slac

Looks OK, check few comments. It feels like we are overdoing it with a module per command line option. There is so much boilerplate and duplication everywhere it's hard to read it.

It's a fair point. I was hoping to promote reuse by just defining a shared option for each. It may err on the side of too much boilerplate. I might be able to make the shared option definition more concise, I'll think on that.

@n8pease
Copy link
Contributor Author

n8pease commented Aug 14, 2020

Thinking about ways I could reduce the size of the shared option declarations I've realized I can use functools.partial to bind option decorator arguments to reduce almost all the boilerplate that was needed for MWOptionDecorator subclasses, and can instead have MWOptionDecorator instances be the option decorators that are used. (@andy-slac this is very similar but slightly more than what I presented to you in Slack). MWOptionDecorator becomes like this

class MWOptionDecorator:

    def __init__(self, *param_decls, **kwargs):
        self.partialOpt = partial(click.option, *param_decls, cls=MWOption, **kwargs)
        opt = click.Option(param_decls, **kwargs)
        self._name = opt.name
        self._opts = opt.opts

    def name(self):
        return self._name

    def opts(self):
        return self._opts

    def __call__(self, *args, **kwargs):
        return self.partialOpt(*args, **kwargs)

(initing the Option and fetching its name and opts flags is currently the best way to get these vars from the Option. I've sent a question to the click devs about making the logic for this available by static methods or similar.)

Its use to define shared options is very terse, for example:

butler_config_option = MWOptionDecorator("-b", "--butler-config",
                                         help="Location of the gen3 butler/registry config file.",
                                         type = MWPath(dir_okay=False, file_okay=True, readable=True))

data_query_option = MWOptionDecorator("-d", "--data-query",
                                      help="User data selection expression."
                                      metavar="QUERY")

I think this will be a big improvement.

The function does not generate a dict, it generates a list of tuples.
optionFlags is normally an sequence type
build, run, and qgraph may be "chained" together.

Some options, marked in subcommand help with (f), are forwarded
from one chained subcommand to the next.
@n8pease n8pease merged commit a1b91fd into master Aug 18, 2020
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.

None yet

3 participants