-
Notifications
You must be signed in to change notification settings - Fork 126
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
Hook call mismatch warnings #42
Hook call mismatch warnings #42
Conversation
@hpk42 @nicoddemus @RonnyPfannschmidt any opinions on this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @tgoodlet for your work!
@@ -581,7 +595,7 @@ def varnames(func): | |||
try: | |||
func = func.__init__ | |||
except AttributeError: | |||
return () | |||
return (), () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should update the docs for this function given the new return value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i just noticed something dreadful, since this is a public name, this is a breaking change would warrant a major incompatible release
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as an example, if we had used this in pytest and didn't do vendoring, this would be a release that breaks pytest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RonnyPfannschmidt you got a point, we should increase the version to 0.5.0
and mention this change in the CHANGELOG.
In addition, should we make this method private and remove it from the public API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Afaik this isn't "really" part of the public API due to the contents of pluggy.all?
Oh that's a good point.
tox doesn't use it as well, neither does devpi using a local search (BB doesn't seem to have a search feature).
I think we can safely rename it to an internal function to avoid confusion then. IMHO no need to provide a wrapper for it since nobody outside pluggy
uses it as far as we know, and in case someone complains and makes a good case for it we can reintroduce it and promote to the public API.
0.X
is the period of time we can break things if we think that's better. 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RonnyPfannschmidt if you agree then I won't bother
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i do agree on practicality vs purity grounds
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RonnyPfannschmidt so do you still want the wrapper or no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tgoodlet its not needed, i meant that i agree with @nicoddemus that on grounds of probably controlled set of users and pre 1.0 we can just make it private without going the extra mile
@@ -605,10 +623,10 @@ def varnames(func): | |||
|
|||
assert "self" not in args # best naming practises check? | |||
try: | |||
cache["_varnames"] = args | |||
cache["_varnames"] = args, defaults | |||
except TypeError: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this code was here before already, but I'm curious in which situations can cache["_varnames"]
raise a TypeError
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicoddemus yeah I never really understood the weird "storing arg names in the function's dict
" in the first place. Can't we just have a single global cache which hashes by object id?
Maybe @hpk42 can comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tgoodlet i remember that this optimization safes a few hops, and going for a cache on the object is very practical as it indeed ties object lifetime and cache lifetime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RonnyPfannschmidt huh that's an interesting point.
I've always understood that function lifetimes last from module import until interpreter exit unless you explicitly delete references to them. Also consider varnames()
is only normally called once at PluginManager.register()
time (really only once for each hookspec
or hookimpl
) so I might even go so far as to say this whole cache idea is a pre-mature optimization.
That being said @hpk42 had it there originally and I see no strong reason to remove it.
Oh and if we did end up wanting to use a global cache we could just use weakref.WeakValueDictionary
and get the same gc behavior no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicoddemus btw do you think this is worth making a separate issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps writing a series of banchmarks so we could have a safe suite to test optimizations would be something worth exploring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicoddemus agreed that's what #37 is for ;)
Maybe I'll hack on that next in an effort to rally some faith!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tgoodlet for an weak dictionary there is a need to handle some things differently, unless im mistaken there will be a need to decompose to method objects, and it will create a new magical global managing lifetimes, personally i believe its much more safe to bind lifetimes to the objects directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RonnyPfannschmidt I'm not sure it will matter but maybe there's something I'm missing. I do think we should leave it for now.
If you think I should open discussion about then I'll make a separate issue :)
notinspec = set(hookimpl.argnames) - set(hook.argnames) | ||
if notinspec: | ||
raise PluginValidationError( | ||
"Plugin %r for hook %r\nhookimpl definition: %s\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was under the impression we should always use the term "arguments" or "keyword arguments" when conveying information to users, given that for all purposes hookimpls should always match the argument names of hookspecs, but position is not of importance. At least that's how pluggy and pytest hooks work in my mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicoddemus the only thing is if we're going to support actual keyword arguments as in #43, then shouldn't we distinguish?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't #43 about supporting default values?
Let's see if we are using the same nomenclature first. Considering this function:
def foobar(x, y, z=4):
pass
- positional arguments: caller passes by position so order matters:
foobar(1, 2, 10)
- keyword arguments: caller passes using keyword syntax so order does not matter:
foobar(y=2, x=1, z=10)
- default arguments: caller does not need to pass them on:
foobar(1, 2) # z = 4
As far as I understand, hooks are always called using keyword arguments so order is not an issue, that's why my comment was referring to "Positional arguments" as confusing.
Note that I'm talking about the caller here, not the definition. The definition might for example force the caller to call a function using only keywords:
def foobar(*, x, y, z=4):
pass
(Python 3 only of course)
AFAIU, pluggy
always calls using keyword arguments, so specs are implicitly defined as:
def pytest_deselectitems(*, items, session):
pass
And that issue is completely separated from default values.
Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicoddemus no you're definitions are absolutely correct.
Sorry when I said "keyword" I meant to say "default".
So yes my concern is about being explicit with regard to hookimpl
to hookspec
mismatches regarding default arguments which is really not in the scope of this PR (it's coupled here because originally #43 and this were one PR).
I'll revert and thanks for clarifying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh OK that explains it. 😄
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicoddemus alright fixed. Let me know if there's anything else otherwise I'll squash the history.
pluggy.py
Outdated
notincall = set(self.argnames) - set(kwargs.keys()) | ||
if notincall: | ||
warnings.warn( | ||
"Positional arg(s) %s are declared in the hookspec " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment about "Positional arg" I made in my previous comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one I agree with since we only allow calling with "named argument" syntax currently.
@@ -13,16 +13,16 @@ class B(object): | |||
def __call__(self, z): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to test the generated warnings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes good call 👍
@nicoddemus fixed up most of it and added the test. Let me know what you think. |
Warn when either a hook call doesn't match a hookspec. Additionally, - Extend `varnames()` to return the list of both the arg and kwarg names for a function - Rename `_MultiCall.kwargs` to `caller_kwargs` to be more explicit - Store hookspec kwargs on `_HookRelay` and `HookImpl` instances Relates to pytest-dev#15
- don't use "Positional" in warning message - support python 2.6 sets - don't include __multicall__ in args comparison - document `varnames()` new pair return value
61b3884
to
4ab6b1f
Compare
@hpk42 @nicoddemus rebased after #46. I think this should be ready. Is there any more issues you guys want me to sort out? @hpk42 you've been a bit silent :( |
@nicoddemus @hpk42 anything else you guys aren't happy with? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but I think @hpk42 or @RonnyPfannschmidt should merge it as they know pluggy in more depth than me.
@nicoddemus i believe this one can be merged, but we will absolutely have to put that wrapper/rename in place before the next release @tgoodlet if you get that in before @nicoddemus chimes in i can also just merge in any case good work and thanks for staying on top of this "never-ending story" 👍 |
@RonnyPfannschmidt yes I'll get the wrapper in today 👍 And thanks for the thanks! |
@RonnyPfannschmidt @nicoddemus ok I'll squash the history and merge this once I get back later tonight if you guys both give the thumbs up! |
👍 please go ahead! |
Squashed and merged. 👍 Thanks again @tgoodlet, excellent work! |
@nicoddemus my pleasure :) |
@nicoddemus huh weird how that doesn't look like a merge in the git history? |
I did use the "Squash and merge" option in GH's UI, I thought that's what we wanted since there were quite a few interim commits in this PR... if we wanted a normal merge I apologize. |
@nicoddemus i think we should avoid squashes in future, my experience with them has been surprising and frustrating as well |
No problem @RonnyPfannschmidt, my mistake! |
@nicoddemus @RonnyPfannschmidt yeah I when I said "squashed" I practically meant I would I can break this history up into the two commits I had in mind (impl and test) if you guys don't me rewriting history on master? It shouldn't affect anyone given there was no release made and I'm the only one hacking atm. |
hmm, never ever rewrite history on master ^^ we can undo the pr and accept a new one, but rewriting master is a absolute no go |
I agree... let's just accept this as a mistake and move on. 😁 |
This add's proper warnings for hook calls which do not match the corresponding
hookspec
's signature. The idea is to encourage callers of hooks to always keep up to date with the most recent spec.This comes from discussion in #15.
@hpk42 @nicoddemus @RonnyPfannschmidt this is the first part of #34 which I have separated out to simplify the review and narrow focus.