From 3f95b16de238d1b0107e1c78682ccd45a0e7198b Mon Sep 17 00:00:00 2001 From: Jemeljanov Date: Tue, 21 May 2024 20:39:10 +0200 Subject: [PATCH 1/2] bug fix for help from typing package, add test for optional and union --- fire/helptext.py | 8 +++++--- fire/helptext_test.py | 36 ++++++++++++++++++++++++++++++++++++ fire/test_components_py3.py | 8 +++++++- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/fire/helptext.py b/fire/helptext.py index 6e7fbb07..0525576b 100644 --- a/fire/helptext.py +++ b/fire/helptext.py @@ -35,7 +35,9 @@ import collections import itertools +import re import sys +import typing from fire import completion from fire import custom_descriptions @@ -538,12 +540,12 @@ def _GetArgType(arg, spec): arg_type = spec.annotations[arg] try: if sys.version_info[0:2] >= (3, 3): + if isinstance(arg_type, typing._GenericAlias): + arg_type = re.search(r'\[(.*?)\]', repr(arg_type)).group(1) + return arg_type return arg_type.__qualname__ return arg_type.__name__ except AttributeError: - # Some typing objects, such as typing.Union do not have either a __name__ - # or __qualname__ attribute. - # repr(typing.Union[int, str]) will return ': typing.Union[int, str]' return repr(arg_type) return '' diff --git a/fire/helptext_test.py b/fire/helptext_test.py index 404d9812..6c32d2b6 100644 --- a/fire/helptext_test.py +++ b/fire/helptext_test.py @@ -164,6 +164,42 @@ def testHelpTextFunctionWithTypesAndDefaultNone(self): help_screen) self.assertNotIn('NOTES', help_screen) + @testutils.skipIf( + sys.version_info[0:2] < (3, 5), + 'Python < 3.5 does not support type hints.') + def testHelpTextFunctionWithTypesAndDefaultNoneFromTypingOptional(self): + component = ( + tc.py3.WithDefaultsAndTypes().typing_optional_get_int) # pytype: disable=module-attr + help_screen = helptext.HelpText( + component=component, + trace=trace.FireTrace(component, name='get_int')) + self.assertIn('NAME\n get_int', help_screen) + self.assertIn('SYNOPSIS\n get_int ', help_screen) + self.assertNotIn('DESCRIPTION', help_screen) + self.assertIn( + 'FLAGS\n -v, --value=VALUE\n' + ' Type: Optional[int]\n Default: None', + help_screen) + self.assertNotIn('NOTES', help_screen) + + @testutils.skipIf( + sys.version_info[0:2] < (3, 5), + 'Python < 3.5 does not support type hints.') + def testHelpTextFunctionWithTypesAndDefaultNoneFromTypingUnion(self): + component = ( + tc.py3.WithDefaultsAndTypes().typing_union_get_int) # pytype: disable=module-attr + help_screen = helptext.HelpText( + component=component, + trace=trace.FireTrace(component, name='get_int')) + self.assertIn('NAME\n get_int', help_screen) + self.assertIn('SYNOPSIS\n get_int ', help_screen) + self.assertNotIn('DESCRIPTION', help_screen) + self.assertIn( + 'FLAGS\n -v, --value=VALUE\n' + ' Type: Optional[int, str]\n Default: None', + help_screen) + self.assertNotIn('NOTES', help_screen) + @testutils.skipIf( sys.version_info[0:2] < (3, 5), 'Python < 3.5 does not support type hints.') diff --git a/fire/test_components_py3.py b/fire/test_components_py3.py index 17fb932c..d76aa323 100644 --- a/fire/test_components_py3.py +++ b/fire/test_components_py3.py @@ -16,7 +16,7 @@ import asyncio import functools -from typing import Tuple +from typing import Tuple, Optional, Union # pylint: disable=keyword-arg-before-vararg @@ -99,3 +99,9 @@ def double(self, count: float = 0) -> float: def get_int(self, value: int = None): return 0 if value is None else value + + def typing_optional_get_int(self, value: Optional[int] = None): + return 0 if value is None else value + + def typing_union_get_int(self, value: Union[int, str] = None): + return 0 if value is None else value \ No newline at end of file From 9bc2ad3f024135b56e9c05a309699ae95c0242b7 Mon Sep 17 00:00:00 2001 From: Jemeljanov Date: Thu, 23 May 2024 00:41:36 +0200 Subject: [PATCH 2/2] Refactor helptext.py to remove unused import and simplify code --- fire/helptext.py | 5 ++--- fire/helptext_test.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/fire/helptext.py b/fire/helptext.py index 0525576b..dfd5fe62 100644 --- a/fire/helptext.py +++ b/fire/helptext.py @@ -35,7 +35,6 @@ import collections import itertools -import re import sys import typing @@ -505,7 +504,7 @@ def _CreateFlagItem(flag, docstring_info, spec, required=False, # We need to handle the case where there is a default of None, but otherwise # the argument has another type. - if arg_default == 'None': + if arg_default == 'None' and not arg_type.startswith('Optional'): arg_type = 'Optional[{}]'.format(arg_type) arg_type = 'Type: {}'.format(arg_type) if arg_type else '' @@ -541,7 +540,7 @@ def _GetArgType(arg, spec): try: if sys.version_info[0:2] >= (3, 3): if isinstance(arg_type, typing._GenericAlias): - arg_type = re.search(r'\[(.*?)\]', repr(arg_type)).group(1) + arg_type = repr(arg_type).replace('typing.', '') return arg_type return arg_type.__qualname__ return arg_type.__name__ diff --git a/fire/helptext_test.py b/fire/helptext_test.py index 6c32d2b6..c171f5e8 100644 --- a/fire/helptext_test.py +++ b/fire/helptext_test.py @@ -196,7 +196,7 @@ def testHelpTextFunctionWithTypesAndDefaultNoneFromTypingUnion(self): self.assertNotIn('DESCRIPTION', help_screen) self.assertIn( 'FLAGS\n -v, --value=VALUE\n' - ' Type: Optional[int, str]\n Default: None', + ' Type: Optional[Union[int, str]]\n Default: None', help_screen) self.assertNotIn('NOTES', help_screen)