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/__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..b0608c8 100644 --- a/tap/utils.py +++ b/tap/utils.py @@ -33,6 +33,17 @@ PRIMITIVES = (str, int, float, bool) PathLike = Union[str, os.PathLike] +# use some hacks to implement the hint in IDE, like ClassVar +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. @@ -129,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: @@ -497,6 +508,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 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):