From c8e8fa08d75ab048f8c67b17803f289c1ef81101 Mon Sep 17 00:00:00 2001 From: rainyl Date: Mon, 31 Jul 2023 11:22:46 +0800 Subject: [PATCH 1/5] support for ignoring args --- tap/__init__.py | 3 ++- tap/tap.py | 14 ++++++++++++-- tap/utils.py | 12 ++++++++++++ tests/test_integration.py | 23 +++++++++++++++++++++-- tests/test_utils.py | 3 +++ 5 files changed, 50 insertions(+), 5 deletions(-) diff --git a/tap/__init__.py b/tap/__init__.py index 35f0552..c6a5f4a 100644 --- a/tap/__init__.py +++ b/tap/__init__.py @@ -2,5 +2,6 @@ from tap._version import __version__ from tap.tap import Tap from tap.tapify import tapify +from tap.utils import TapIgnore -__all__ = ['ArgumentError', 'ArgumentTypeError', 'Tap', 'tapify', '__version__'] +__all__ = ['ArgumentError', 'ArgumentTypeError', 'Tap', 'TapIgnore', 'tapify', '__version__'] diff --git a/tap/tap.py b/tap/tap.py index 5d20547..ddfd4b0 100644 --- a/tap/tap.py +++ b/tap/tap.py @@ -29,7 +29,8 @@ as_python_object, fix_py36_copy, enforce_reproducibility, - PathLike + PathLike, + TapIgnore, ) if sys.version_info >= (3, 10): @@ -169,13 +170,22 @@ def _add_argument(self, *name_or_flags, **kwargs) -> None: # Description if variable in self.class_variables: - kwargs['help'] += ' ' + self.class_variables[variable]['comment'] + comment = self.class_variables[variable]['comment'] + # Ignore attributes with comment starting with "tap: ignore" + if comment.startswith('tap: ignore'): + return + kwargs['help'] += ' ' + comment # Set other kwargs where not provided if variable in self._annotations: # Get type annotation var_type = self._annotations[variable] + # ignore the variables annotated by TapIgnore + # e.g., var_ignore: TapIgnore[str] + if get_origin(var_type) == TapIgnore: + return + # If type is not explicitly provided, set it if it's one of our supported default types if 'type' not in kwargs: # Unbox Union[type] (Optional[type]) and set var_type = type diff --git a/tap/utils.py b/tap/utils.py index a7f9042..08c3b59 100644 --- a/tap/utils.py +++ b/tap/utils.py @@ -23,8 +23,10 @@ Optional, Tuple, Union, + _SpecialForm, ) from typing_inspect import get_args as typing_inspect_get_args, get_origin as typing_inspect_get_origin +from typing_inspect import _GenericAlias if sys.version_info >= (3, 10): from types import UnionType @@ -497,6 +499,10 @@ def get_origin(tp: Any) -> Any: if origin is None: origin = tp + # fix typing_inspect not supporting TapIgnore + if hasattr(tp, '__origin__') and tp.__origin__ == TapIgnore: + origin = TapIgnore + if sys.version_info >= (3, 10) and isinstance(origin, UnionType): origin = UnionType @@ -510,3 +516,9 @@ def get_args(tp: Any) -> Tuple[type, ...]: return tp.__args__ return typing_inspect_get_args(tp) + +@_SpecialForm +def TapIgnore(self, parameters): + if not callable(parameters): + raise TypeError(f"{self} only accepts single type, got {parameters!r:.100}.") + return _GenericAlias(self, (parameters,)) diff --git a/tests/test_integration.py b/tests/test_integration.py index 232c61b..a87ab41 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -5,11 +5,11 @@ import pickle import sys from tempfile import TemporaryDirectory -from typing import Any, Iterable, List, Literal, Optional, Set, Tuple, Union +from typing import Any, Dict, Iterable, List, Literal, Optional, Set, Tuple, Union import unittest from unittest import TestCase -from tap import Tap +from tap import Tap, TapIgnore # Suppress prints from SystemExit @@ -330,6 +330,14 @@ class IntegrationDefaultTap(Tap): # TODO: move these elsewhere since we don't support them as defaults # arg_other_type_required: Person # arg_other_type_default: Person = Person('tap') + arg_ignore: str # tap: ignore + arg_tapignore: TapIgnore + arg_tapignore_int: TapIgnore[int] + arg_tapignore_bool: TapIgnore[bool] + arg_tapignore_str: TapIgnore[str] + arg_tapignore_union: TapIgnore[Union[int, str]] + arg_tapignore_dict: TapIgnore[Dict[int, int]] + arg_tapignore_customed: TapIgnore[Person] class SubclassTests(TestCase): @@ -403,6 +411,17 @@ def test_get_default_args(self) -> None: self.assertEqual(args.arg_tuple_bool, (True, True, True, False)) self.assertEqual(args.arg_tuple_multi, (1.2, 1, 'hi', True, 1.3)) + # test for ignored args + self.assertFalse(hasattr(args, 'arg_ignore')) + self.assertFalse(hasattr(args, 'arg_tapignore')) + self.assertFalse(hasattr(args, 'arg_tapignore_int')) + self.assertFalse(hasattr(args, 'arg_tapignore_bool')) + self.assertFalse(hasattr(args, 'arg_tapignore_str')) + self.assertFalse(hasattr(args, 'arg_tapignore_union')) + self.assertFalse(hasattr(args, 'arg_tapignore_dict')) + self.assertFalse(hasattr(args, 'arg_tapignore_customed')) + + def test_set_default_args(self) -> None: arg_untyped = 'yes' arg_str = 'goodbye' diff --git a/tests/test_utils.py b/tests/test_utils.py index 1fa7eb5..844578b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -20,6 +20,7 @@ UnpicklableObject, as_python_object, enforce_reproducibility, + TapIgnore, ) @@ -134,6 +135,8 @@ def test_type_to_str(self) -> None: self.assertEqual(type_to_str(Set[int]), 'Set[int]') self.assertEqual(type_to_str(Dict[str, int]), 'Dict[str, int]') self.assertEqual(type_to_str(Union[List[int], Dict[float, bool]]), 'Union[List[int], Dict[float, bool]]') + self.assertEqual(type_to_str(TapIgnore), "TapIgnore") + self.assertEqual(type_to_str(TapIgnore[int]), "TapIgnore[int]") def class_decorator(cls): From 48ec80c1ce0c4957345534108ab05b4e66284c2c Mon Sep 17 00:00:00 2001 From: rainyl Date: Thu, 3 Aug 2023 11:16:17 +0800 Subject: [PATCH 2/5] improve tapignore, support specific type --- tap/utils.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tap/utils.py b/tap/utils.py index 08c3b59..f27feeb 100644 --- a/tap/utils.py +++ b/tap/utils.py @@ -23,10 +23,10 @@ Optional, Tuple, Union, - _SpecialForm, + TypeVar, + Generic, ) from typing_inspect import get_args as typing_inspect_get_args, get_origin as typing_inspect_get_origin -from typing_inspect import _GenericAlias if sys.version_info >= (3, 10): from types import UnionType @@ -34,6 +34,9 @@ NO_CHANGES_STATUS = """nothing to commit, working tree clean""" PRIMITIVES = (str, int, float, bool) PathLike = Union[str, os.PathLike] +T = TypeVar('T') +class TapIgnore(Generic[T]): + ... def check_output(command: List[str], suppress_stderr: bool = True, **kwargs) -> str: @@ -131,7 +134,7 @@ def type_to_str(type_annotation: Union[type, Any]) -> str: return type_annotation.__name__ # Typing type - return str(type_annotation).replace('typing.', '') + return str(type_annotation).replace('typing.', '').replace("tap.utils.", "") def get_argument_name(*name_or_flags) -> str: @@ -516,9 +519,3 @@ def get_args(tp: Any) -> Tuple[type, ...]: return tp.__args__ return typing_inspect_get_args(tp) - -@_SpecialForm -def TapIgnore(self, parameters): - if not callable(parameters): - raise TypeError(f"{self} only accepts single type, got {parameters!r:.100}.") - return _GenericAlias(self, (parameters,)) From 0f52e7091d649aa71c449e575077fbfe7b15b142 Mon Sep 17 00:00:00 2001 From: rainyl Date: Thu, 3 Aug 2023 11:27:17 +0800 Subject: [PATCH 3/5] add gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f0e7289..2241c58 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ __pycache__ .eggs .coverage dist +build/ \ No newline at end of file From a82a281920d66d15bac425cb20343b4c4c65ebb5 Mon Sep 17 00:00:00 2001 From: rainyl Date: Thu, 3 Aug 2023 17:08:49 +0800 Subject: [PATCH 4/5] support type hint in IDE, like ClassVar --- .gitignore | 1 + tap/utils.py | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index f0e7289..bb226df 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ __pycache__ .eggs .coverage dist +build diff --git a/tap/utils.py b/tap/utils.py index f27feeb..80e3a80 100644 --- a/tap/utils.py +++ b/tap/utils.py @@ -23,8 +23,7 @@ Optional, Tuple, Union, - TypeVar, - Generic, + ClassVar, ) from typing_inspect import get_args as typing_inspect_get_args, get_origin as typing_inspect_get_origin @@ -34,10 +33,10 @@ NO_CHANGES_STATUS = """nothing to commit, working tree clean""" PRIMITIVES = (str, int, float, bool) PathLike = Union[str, os.PathLike] -T = TypeVar('T') -class TapIgnore(Generic[T]): - ... +# use some hacks to implement the hint in IDE, like ClassVar +TapIgnore = ClassVar +TapIgnore._name = "TapIgnore" def check_output(command: List[str], suppress_stderr: bool = True, **kwargs) -> str: """Runs subprocess.check_output and returns the result as a string. @@ -134,7 +133,7 @@ def type_to_str(type_annotation: Union[type, Any]) -> str: return type_annotation.__name__ # Typing type - return str(type_annotation).replace('typing.', '').replace("tap.utils.", "") + return str(type_annotation).replace('typing.', '') def get_argument_name(*name_or_flags) -> str: From 57d48a454a2cbb3c996e0e82ba5722997b970bcf Mon Sep 17 00:00:00 2001 From: rainyl Date: Thu, 3 Aug 2023 22:10:10 +0800 Subject: [PATCH 5/5] fix ignore on python3.8 --- tap/utils.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tap/utils.py b/tap/utils.py index 80e3a80..b0608c8 100644 --- a/tap/utils.py +++ b/tap/utils.py @@ -23,7 +23,6 @@ Optional, Tuple, Union, - ClassVar, ) from typing_inspect import get_args as typing_inspect_get_args, get_origin as typing_inspect_get_origin @@ -35,8 +34,16 @@ PathLike = Union[str, os.PathLike] # use some hacks to implement the hint in IDE, like ClassVar -TapIgnore = ClassVar -TapIgnore._name = "TapIgnore" +if sys.version_info >= (3, 9): + from typing import ClassVar + TapIgnore = ClassVar + TapIgnore._name = "TapIgnore" +else: + # for python version <= 3.8, the above method not supported, + # so for now, TapIgnore[T] -> T not supported + from typing import TypeVar, Generic + T = TypeVar("T") + class TapIgnore(Generic[T]): ... def check_output(command: List[str], suppress_stderr: bool = True, **kwargs) -> str: """Runs subprocess.check_output and returns the result as a string. @@ -133,7 +140,7 @@ def type_to_str(type_annotation: Union[type, Any]) -> str: return type_annotation.__name__ # Typing type - return str(type_annotation).replace('typing.', '') + return str(type_annotation).replace('typing.', '').replace('tap.utils.', '') def get_argument_name(*name_or_flags) -> str: