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

Refactor how Nox defines and process options #187

Merged
merged 6 commits into from
May 28, 2019
Merged

Conversation

theacodes
Copy link
Collaborator

Closes #146, although there will need to be a follow up to use this information in the documentation.

@theacodes
Copy link
Collaborator Author

This is a huge change and I'm really crossing my fingers that I didn't break anything in process, but the tests seem minimally affected.

@theacodes
Copy link
Collaborator Author

/cc @dhermes @lukesneeringer @stsewd please don't feel obligated to, but if y'all have some time to look at this monster change I'd really appreciate it. If not, that's totally fine! I'll merge it in 2 days.

Copy link
Collaborator

@dhermes dhermes left a comment

Choose a reason for hiding this comment

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

  1. Woof this is big
  2. Did you look at any prior art like the guys of click or azure-cli

nox/_option_set.py Outdated Show resolved Hide resolved
nox/_option_set.py Outdated Show resolved Hide resolved
):
self.name = name
self.flags = flags
self.help = help
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll echo what I said earlier about shadowing __builtins__.id (now you are shadowing help)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I know, but I want/need to match argparse's signature for things that are directly passed through.

Copy link
Collaborator

Choose a reason for hiding this comment

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

kwargs.pop('help') allows you to match the signature without having a shadowed local

specified on the command-line."""
noxfile_value = getattr(noxfile_args, enable_name)
command_value = getattr(command_args, enable_name)
disable_value = getattr(command_args, disable_name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not a big fan of getattr(). Not sure what's going on in this function (i.e. what types are the args? the return isn't guaranteed to be a boolean?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do you have a suggestion for alternative to getattr here?

I added some more details to the docstrings to hopefully explain what this function is doing. Let me know if that helps any.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is essentially runtime code generation. You could instead do build-time code generation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Could you expand on that?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I was suggesting that you write a script which takes some text file (or DSL) of your choosing and spits out code for you. This way you can explicitly define your flags while getting code that's easy to reason about (getattr(foo, bar) ends up being a lot of indirection as compared to foo.known_attr).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see. I think that DSLs and code generation tend to be a lot harder to understand and reason about than some light metaprogramming, but I understand the concern here. I don't think we've crossed the line where inventing such a thing is worth the trade off, but gosh if we ever have to do something like this for another project then yeah.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah a DSL would be overkill.

"""
disable_name = "no_{}".format(name)

kwargs["action"] = "store_true"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not worried if action was set by the caller?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, it shouldn't be. We (the Nox developers) are the only ones using this, and flag pairs are/must be boolean.

nox/_options.py Outdated Show resolved Hide resolved
nox/_options.py Outdated Show resolved Hide resolved
nox/_options.py Show resolved Hide resolved
tests/test_main.py Outdated Show resolved Hide resolved
tests/test_main.py Show resolved Hide resolved
@theacodes
Copy link
Collaborator Author

Thank you so much for taking a look @dhermes. I'll try to address all these comments tomorrow or Saturday.

I apologize for how big this is, but hopefully how we express our options is "clearer".

command_args, name, option.merge_func(command_args, noxfile_args)
)
elif option.noxfile:
value = getattr(command_args, name) or getattr(noxfile_args, name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

getattrwill raise an exception if a default value isn't given. You need to pass None explicitly. Anyway, I'm not sure if it's guarantied that command_args always have the name attr

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

They should always have the same set of properties since they're derived from the same source, but added the None default anyway.

Copy link
Collaborator Author

@theacodes theacodes left a comment

Choose a reason for hiding this comment

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

Okay, @dhermes. Thank you for the very good review. I think this might be ready to go now.

nox/_option_set.py Outdated Show resolved Hide resolved
):
self.name = name
self.flags = flags
self.help = help
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I know, but I want/need to match argparse's signature for things that are directly passed through.

specified on the command-line."""
noxfile_value = getattr(noxfile_args, enable_name)
command_value = getattr(command_args, enable_name)
disable_value = getattr(command_args, disable_name)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do you have a suggestion for alternative to getattr here?

I added some more details to the docstrings to hopefully explain what this function is doing. Let me know if that helps any.

"""
disable_name = "no_{}".format(name)

kwargs["action"] = "store_true"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, it shouldn't be. We (the Nox developers) are the only ones using this, and flag pairs are/must be boolean.

nox/_option_set.py Show resolved Hide resolved
nox/_options.py Outdated Show resolved Hide resolved
nox/_options.py Outdated Show resolved Hide resolved
nox/_options.py Show resolved Hide resolved
tests/test_main.py Outdated Show resolved Hide resolved
tests/test_main.py Show resolved Hide resolved
nox/_option_set.py Outdated Show resolved Hide resolved
nox/_option_set.py Show resolved Hide resolved
@@ -235,5 +280,7 @@ def merge_namespaces(self, command_args, noxfile_args):
command_args, name, option.merge_func(command_args, noxfile_args)
)
elif option.noxfile:
value = getattr(command_args, name) or getattr(noxfile_args, name)
value = getattr(command_args, name, None) or getattr(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ha, big difference here 😀

nox/_options.py Show resolved Hide resolved
nox/_options.py Outdated Show resolved Hide resolved
tests/test_main.py Outdated Show resolved Hide resolved
sys.argv = [sys.executable]
with mock.patch("nox.workflow.execute") as execute:
execute.return_value = 0
with mock.patch("sys.stderr.isatty") as istty:
Copy link
Collaborator

Choose a reason for hiding this comment

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

ISTR you prefer naming the mock object the same as the thing being mocked, in which case istty -> isatty

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Great catch

tests/test_main.py Show resolved Hide resolved
@theacodes theacodes merged commit ff9b959 into master May 28, 2019
@theacodes theacodes deleted the refactor-options branch May 28, 2019 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Re-factor how Nox documents and stores command line flags
3 participants