fix: fix edge case on forward unresolveable type#1673
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1673 +/- ##
==========================================
+ Coverage 99.52% 99.59% +0.07%
==========================================
Files 23 23
Lines 5675 5677 +2
==========================================
+ Hits 5648 5654 +6
+ Misses 27 23 -4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
|
The one test failure is due to release of Python 3.15.0b2 and is fixed in PR #1674 |
|
I will rebase once that is landed. I found another edge case related to the function return type that also needs to be covered |
| hints = get_type_hints(func, include_extras=True) | ||
| hints = get_type_hints( | ||
| types.SimpleNamespace(__annotations__=relevant_annotations), | ||
| globalns=getattr(func, "__globals__", {}), |
There was a problem hiding this comment.
If func is wrapped by another decorator using @functools.wraps, its __globals__ attribute points to the decorator's module, not the original function's module. Because globalns is explicitly provided here, get_type_hints() won't traverse the __wrapped__ chain to find the correct globals. This will cause forward references (e.g., string annotations) to fail with NameError or resolve to the wrong type.
Suggested change:
ignored = {next(iter(sig.parameters), None), *skip_params}
ignored.discard(None)
relevant_annotations = {name: ann for name, ann in getattr(func, "__annotations__", {}).items() if name not in ignored}
unwrapped = inspect.unwrap(func)
try:
hints = get_type_hints(
types.SimpleNamespace(__annotations__=relevant_annotations),
globalns=getattr(unwrapped, "__globals__", {}),
include_extras=True,
)The key change is created unrwapped = inspect.unwrap(func) before the try and then using unwrapped instead of func in the call to getattr.
|
@KelvinChung2000 I merged the fix for Python 3.15.0b2. You are free to rebase when you see fit. |
|
@KelvinChung2000 I merged main into this branch since I made changes to I also noticed that you are missing tests for two areas.
@functools.wraps(func)
def handler(self_arg: Any, ns: Any) -> Any:
"""Unpack Namespace into typed kwargs for the subcommand handler."""
filtered = _filtered_namespace_kwargs(ns, accepted=_accepted)
if constants.NS_ATTR_SUBCOMMAND_FUNC in filtered:
cmd2_h = filtered[constants.NS_ATTR_SUBCOMMAND_FUNC]
if isinstance(cmd2_h, functools.partial) and cmd2_h.func is handler:
filtered[constants.NS_ATTR_SUBCOMMAND_FUNC] = None
return _invoke_command_func(
func, self_arg, filtered, leading_names=_leading_names, var_positional_name=_var_positional_name
)
|
Fix an edge case when doing forward declaration on types:
which happens when trying to declare the command in another file, while still wanting to keep the type hinting for
self.