-
-
Notifications
You must be signed in to change notification settings - Fork 124
Fix Union[..., NoneType]
injection by get_type_hints
if a None
default value is used.
#482
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
Changes from 5 commits
376ae56
24c5602
7f000b1
8cc4464
881ffee
58082b9
5c94bd6
b42e203
ed552e4
313ddd8
3ba4ee7
6ffcd23
403e7ce
52c93e9
a1777f3
9e59796
1761a43
54b8eb0
5612f6f
91075ff
1eac721
1f84c68
214dfef
00dedc2
e929085
47e47ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1236,10 +1236,74 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): | |||||||||||||||
) | ||||||||||||||||
else: # 3.8 | ||||||||||||||||
hint = typing.get_type_hints(obj, globalns=globalns, localns=localns) | ||||||||||||||||
if sys.version_info < (3, 11): | ||||||||||||||||
_clean_optional(obj, hint, globalns, localns) | ||||||||||||||||
if include_extras: | ||||||||||||||||
return hint | ||||||||||||||||
return {k: _strip_extras(t) for k, t in hint.items()} | ||||||||||||||||
|
||||||||||||||||
_NoneType = type(None) | ||||||||||||||||
|
||||||||||||||||
def _could_be_inserted_optional(t): | ||||||||||||||||
"""detects Union[..., None] pattern""" | ||||||||||||||||
JelleZijlstra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
# 3.8+ compatible checking before _UnionGenericAlias | ||||||||||||||||
if get_origin(t) is not Union: | ||||||||||||||||
return False | ||||||||||||||||
# Assume if last argument is not None they are user defined | ||||||||||||||||
if t.__args__[-1] is not _NoneType: | ||||||||||||||||
return False | ||||||||||||||||
return True | ||||||||||||||||
|
||||||||||||||||
# < 3.11 | ||||||||||||||||
def _clean_optional(obj, hints, globalns=None, localns=None): | ||||||||||||||||
# reverts injected Union[..., None] cases from typing.get_type_hints | ||||||||||||||||
# when a None default value is used. | ||||||||||||||||
# see https://github.com/python/typing_extensions/issues/310 | ||||||||||||||||
if not hints or isinstance(obj, type): | ||||||||||||||||
return | ||||||||||||||||
defaults = typing._get_defaults(obj) | ||||||||||||||||
JelleZijlstra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
if not defaults: | ||||||||||||||||
return | ||||||||||||||||
original_hints = obj.__annotations__ | ||||||||||||||||
for name, value in hints.items(): | ||||||||||||||||
# Not a Union[..., None] or replacement conditions not fullfilled | ||||||||||||||||
if (not _could_be_inserted_optional(value) | ||||||||||||||||
or name not in defaults | ||||||||||||||||
or defaults[name] is not None | ||||||||||||||||
): | ||||||||||||||||
continue | ||||||||||||||||
original_value = original_hints[name] | ||||||||||||||||
if original_value is None: # should not happen | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why shouldn't this happen? You can put There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At this point a None value should already be converted to Added a comment to clarify this. |
||||||||||||||||
original_value = _NoneType | ||||||||||||||||
# Forward reference | ||||||||||||||||
if isinstance(original_value, str): | ||||||||||||||||
if globalns is None: | ||||||||||||||||
if isinstance(obj, _types.ModuleType): | ||||||||||||||||
globalns = obj.__dict__ | ||||||||||||||||
else: | ||||||||||||||||
nsobj = obj | ||||||||||||||||
# Find globalns for the unwrapped object. | ||||||||||||||||
while hasattr(nsobj, '__wrapped__'): | ||||||||||||||||
nsobj = nsobj.__wrapped__ | ||||||||||||||||
globalns = getattr(nsobj, '__globals__', {}) | ||||||||||||||||
if localns is None: | ||||||||||||||||
localns = globalns | ||||||||||||||||
elif localns is None: | ||||||||||||||||
localns = globalns | ||||||||||||||||
if sys.version_info < (3, 9): | ||||||||||||||||
original_value = ForwardRef(original_value) | ||||||||||||||||
else: | ||||||||||||||||
original_value = ForwardRef( | ||||||||||||||||
original_value, | ||||||||||||||||
is_argument=not isinstance(obj, _types.ModuleType) | ||||||||||||||||
) | ||||||||||||||||
original_evaluated = typing._eval_type(original_value, globalns, localns) | ||||||||||||||||
if sys.version_info < (3, 9) and get_origin(original_evaluated) is Union: | ||||||||||||||||
# Union[str, None, "str"] is not reduced to Union[str, None] | ||||||||||||||||
original_evaluated = Union[original_evaluated.__args__] | ||||||||||||||||
# Compare if values differ | ||||||||||||||||
if original_evaluated != value: | ||||||||||||||||
hints[name] = original_evaluated | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Would this also work? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it would. However there are minor differences involving identities and caching.
If a That's a minor feature and I am fine with dropping it if you want; for now added a comment to clarify this. |
||||||||||||||||
|
||||||||||||||||
# Python 3.9+ has PEP 593 (Annotated) | ||||||||||||||||
if hasattr(typing, 'Annotated'): | ||||||||||||||||
|
Uh oh!
There was an error while loading. Please reload this page.