Skip to content
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

[BEAM-8280] no_annotations decorator #10904

Merged
merged 2 commits into from
Feb 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion sdks/python/apache_beam/typehints/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def foo((a, b)):
funcsigs = None

__all__ = [
'no_annotations',
'with_input_types',
'with_output_types',
'WithTypeHints',
Expand Down Expand Up @@ -221,6 +222,13 @@ def get_signature(func):
return signature


def no_annotations(fn):
"""Decorator that prevents Beam from using type hint annotations on a
callable."""
setattr(fn, '_beam_no_annotations', True)
return fn


class IOTypeHints(NamedTuple(
'IOTypeHints',
[('input_types', Optional[Tuple[Tuple[Any, ...], Dict[str, Any]]]),
Expand Down Expand Up @@ -274,7 +282,7 @@ def from_callable(cls, fn):
Returns:
A new IOTypeHints or None if no annotations found.
"""
if not _enable_from_callable:
if not _enable_from_callable or getattr(fn, '_beam_no_annotations', False):
return None
signature = get_signature(fn)
if (all(param.annotation == param.empty
Expand Down
16 changes: 16 additions & 0 deletions sdks/python/apache_beam/typehints/decorators_test_py3.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
# patches unittest.TestCase to be python3 compatible
import future.tests.base # pylint: disable=unused-import

from apache_beam import Map
from apache_beam.typehints import Any
from apache_beam.typehints import Dict
from apache_beam.typehints import List
from apache_beam.typehints import Tuple
from apache_beam.typehints import TypeCheckError
from apache_beam.typehints import TypeVariable
from apache_beam.typehints import decorators

Expand Down Expand Up @@ -154,5 +156,19 @@ def fn(a, b=None, *args, foo, **kwargs):
decorators.getcallargs_forhints(fn, 5)


class DecoratorsTest(unittest.TestCase):
def test_no_annotations(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to split into three test methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is one test. I'll rewrite it to make it clear. First it shows that the pipeline raises a TypeCheckError with annotations and then it shows no exceptions raised without them.

def fn(a: int) -> int:
return a

with self.assertRaisesRegex(TypeCheckError,
r'requires .*int.* but got .*str'):
_ = ['a', 'b', 'c'] | Map(fn)

# Same pipeline doesn't raise without annotations on fn.
fn = decorators.no_annotations(fn)
_ = ['a', 'b', 'c'] | Map(fn)


if __name__ == '__main__':
unittest.main()