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

Improve type detection for annotations #3556

Merged

Conversation

ThunderKey
Copy link
Member

When running the ghostwriter with type annotations (#3548) on bigger projects I've detected the following issues:

  1. Optional with a single type argument got translated to Optional[int, None], which does not work.
  2. UnionType expressions inside of generic arguments (e.g. List[int | str]) got translated to List[types.UnionType[int, str]]. types.UnionType does not support generics and this does therefore not work.
  3. If the from __future__ import annotations marker is used, the annotations are strings and not types. I've added a patch that evaluates those annotations in Python 3.10 and newer. We could add a patch for earlier versions by recreating the eval_str argument, but I'm not sure this edge case of an optional feature is worth the added complexity: https://github.com/python/cpython/blob/498598e8c2d64232d26c075de87c513415176bbf/Lib/inspect.py#L260:#L280

Sorry that I did not detect those issues earlier. Now I've tested it on multiple projects and it seems more stable.

Copy link
Member

@Zac-HD Zac-HD left a comment

Choose a reason for hiding this comment

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

Thanks for the follow-up, this looks really good 😍

One small and one larger comment below, then I'm looking forward to merging!

"""Get non-vararg parameters of `func` as an ordered dict."""
var_param_kinds = (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
try:
params = list(get_signature(func).parameters.values())
params = list(get_signature(func, eval_str=eval_str).parameters.values())
except Exception:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
except Exception:
except Exception:
if eval_str:
return _get_params(func)

Evaluating string annotations can fail due to syntax errors, name resolution, or whatever arbitrary code execution we invoked failing; so in those cases I'd rather fall back to unevaluated annotations (above) or switching to annotate=False for that argument. I trust your judgement as to which is preferable.

In either case, a test demonstrating that would be nice - say f(a: "invalid ::: syntax", b: "1/0").

hypothesis-python/src/hypothesis/internal/reflection.py Outdated Show resolved Hide resolved
@ThunderKey
Copy link
Member Author

  1. Thanks for the hint about the double backticks in .rts. I am indeed more accustomed to markdown :)
  2. You're absolutely right, it would be way better to catch those. I've used the simplest way known to me: If a function fails to be parsed it will jus skip this. Other functions will get annotations (if applicable)
  3. That's a very nice idea with the kwargs. Unfortunately, mypy had some problems with the kwargs dict. I therefore used a simple if and call the inspect.signature in the branches.

Comment on lines 174 to 175
else:
sig = inspect.signature(target, follow_wrapped=follow_wrapped)
Copy link
Member

Choose a reason for hiding this comment

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

Nice solution 🙂

Just needs a # pragma: no cover comment on one of these lines - we configure coverage to ignore branching on sys.version, but that doesn't cover the else:.

Copy link
Member

@Zac-HD Zac-HD left a comment

Choose a reason for hiding this comment

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

With that coverage pragma added, I'll be very happy to merge!

Comment on lines 834 to 838
try:
params = _get_params(func, eval_str=True)
except Exception:
# don't add parameters if the annotations could not be evaluated
pass
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 we need to handle this within the _get_params function, because an error due to eval_str=True will instead trigger the docstring-based fallback, and potentially lead to a wrong signature being returned!

We can get the signature as described above, and then discard any annotations that are strings at this stage.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've now had a closer look at the _get_params function. As it turns out, the fallbacks will be ignored anyway, because they don't set the annotation property of the inspect.Parameter. I've refactored the code to call the get_signature directly.

@Zac-HD
Copy link
Member

Zac-HD commented Jan 26, 2023

Fantastic work - thanks again!

@Zac-HD Zac-HD merged commit 4c5b65d into HypothesisWorks:master Jan 26, 2023
@Zac-HD
Copy link
Member

Zac-HD commented Jan 27, 2023

@ThunderKey, I've sent you an invitation to the Hypothesis team - there's no obligation attached, but we'd love it if you wanted to stick around, write PRs or review code as the mood takes you, or just generally remain associated with the project. The contributor /guides/ directory in this repo will answer most questions about how this works, and I'm always happy to take more here or by email if that would be helpful.

@ThunderKey ThunderKey deleted the improve-type-detection-for-annotations branch January 27, 2023 14:14
@ThunderKey
Copy link
Member Author

Awesome, thank you @Zac-HD for the invitation! I'll have a look at the guides :)

JensHeinrich pushed a commit to JensHeinrich/hypothesis that referenced this pull request Feb 16, 2023
…detection-for-annotations

Improve type detection for annotations
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

2 participants