[
https://issues.apache.org/jira/browse/BEAM-1081?focusedWorklogId=155586&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-155586
]
ASF GitHub Bot logged work on BEAM-1081:
----------------------------------------
Author: ASF GitHub Bot
Created on: 17/Oct/18 20:24
Start Date: 17/Oct/18 20:24
Worklog Time Spent: 10m
Work Description: pabloem closed pull request #6670: [BEAM-1081]
Annotations custom message support and classes tests.
URL: https://github.com/apache/beam/pull/6670
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/sdks/python/apache_beam/utils/annotations.py
b/sdks/python/apache_beam/utils/annotations.py
index 29121329fc2..4f41a1b1e49 100644
--- a/sdks/python/apache_beam/utils/annotations.py
+++ b/sdks/python/apache_beam/utils/annotations.py
@@ -63,6 +63,16 @@ def exp_multiply(arg1, arg2):
print(arg1, '*', arg2, '(the experimental way)=', end=' ')
return (arg1*arg2)*(arg1/arg2)*(arg2/arg1)
+# If a custom message is needed, on both annotations types the
+# arg custom_message can be used.::
+
+ @experimental(since='v.1', current='multiply'
+ custom_message='Experimental since %since%
+ Please use %current% insted.')
+ def exp_multiply(arg1, arg2):
+ print(arg1, '*', arg2, '(the experimental way)=', end=' ')
+ return (arg1*arg2)*(arg1/arg2)*(arg2/arg1)
+
# Set a warning filter to control how often warnings are produced.::
warnings.simplefilter("always")
@@ -84,17 +94,27 @@ def exp_multiply(arg1, arg2):
warnings.simplefilter("once")
-def annotate(label, since, current, extra_message):
- """Decorates a function with a deprecated or experimental annotation.
+def annotate(label, since, current, extra_message, custom_message=None):
+ """Decorates an API with a deprecated or experimental annotation.
Args:
label: the kind of annotation ('deprecated' or 'experimental').
since: the version that causes the annotation.
current: the suggested replacement function.
extra_message: an optional additional message.
+ custom_message: if the default message does not suffice, the message
+ can be changed using this argument. A string
+ whit replacement tokens.
+ A replecement string is were the previus args will
+ be located on the custom message.
+ The following replacement strings can be used:
+ %name% -> API.__name__
+ %since% -> since (Mandatory for the decapreted annotation)
+ %current% -> current
+ %extra% -> extra_message
Returns:
- The decorator for the function.
+ The decorator for the API.
"""
def _annotate(fnc):
@wraps(fnc)
@@ -103,12 +123,23 @@ def inner(*args, **kwargs):
warning_type = DeprecationWarning
else:
warning_type = FutureWarning
- message = '%s is %s' % (fnc.__name__, label)
- if label == 'deprecated':
- message += ' since %s' % since
- message += '. Use %s instead.' % current if current else '.'
- if extra_message:
- message += ' ' + extra_message
+ if custom_message is None:
+ message = '%s is %s' % (fnc.__name__, label)
+ if label == 'deprecated':
+ message += ' since %s' % since
+ message += '. Use %s instead.' % current if current else '.'
+ if extra_message:
+ message += ' ' + extra_message
+ else:
+ if label == 'deprecated' and '%since%' not in custom_message:
+ raise TypeError("Replacement string %since% not found on \
+ custom message")
+ emptyArg = lambda x: '' if x is None else x
+ message = custom_message\
+ .replace('%name%', fnc.__name__)\
+ .replace('%since%', emptyArg(since))\
+ .replace('%current%', emptyArg(current))\
+ .replace('%extra%', emptyArg(extra_message))
warnings.warn(message, warning_type, stacklevel=2)
return fnc(*args, **kwargs)
return inner
diff --git a/sdks/python/apache_beam/utils/annotations_test.py
b/sdks/python/apache_beam/utils/annotations_test.py
index 2901e3b3632..f9a16b046cf 100644
--- a/sdks/python/apache_beam/utils/annotations_test.py
+++ b/sdks/python/apache_beam/utils/annotations_test.py
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -36,7 +35,7 @@ def fnc_test_deprecated_with_since_current_message():
self.check_annotation(
warning=w, warning_size=1,
warning_type=DeprecationWarning,
- fnc_name='fnc_test_deprecated_with_since_current_message',
+ obj_name='fnc_test_deprecated_with_since_current_message',
annotation_type='deprecated',
label_check_list=[('since', True),
('instead', True),
@@ -50,7 +49,7 @@ def fnc_test_deprecated_with_since_current():
fnc_test_deprecated_with_since_current()
self.check_annotation(warning=w, warning_size=1,
warning_type=DeprecationWarning,
- fnc_name='fnc_test_deprecated_with_since_current',
+ obj_name='fnc_test_deprecated_with_since_current',
annotation_type='deprecated',
label_check_list=[('since', True),
('instead', True)])
@@ -63,7 +62,7 @@ def fnc_test_deprecated_without_current():
fnc_test_deprecated_without_current()
self.check_annotation(warning=w, warning_size=1,
warning_type=DeprecationWarning,
- fnc_name='fnc_test_deprecated_without_current',
+ obj_name='fnc_test_deprecated_without_current',
annotation_type='deprecated',
label_check_list=[('since', True),
('instead', False)])
@@ -78,6 +77,15 @@ def fnc_test_deprecated_without_since_should_fail():
fnc_test_deprecated_without_since_should_fail()
assert not w
+ def test_deprecated_without_since_custom_should_fail(self):
+ with warnings.catch_warnings(record=True) as w:
+ with self.assertRaises(TypeError):
+ @deprecated(custom_message='Test %since%')
+ def fnc_test_deprecated_without_since_custom_should_fail():
+ return 'lol'
+ fnc_test_deprecated_without_since_custom_should_fail()
+ assert not w
+
def test_experimental_with_current_message(self):
with warnings.catch_warnings(record=True) as w:
@experimental(current='multiply', extra_message='Do this')
@@ -87,7 +95,7 @@ def fnc_test_experimental_with_current_message():
self.check_annotation(
warning=w, warning_size=1,
warning_type=FutureWarning,
- fnc_name='fnc_test_experimental_with_current_message',
+ obj_name='fnc_test_experimental_with_current_message',
annotation_type='experimental',
label_check_list=[('instead', True),
('Do this', True)])
@@ -100,7 +108,7 @@ def fnc_test_experimental_with_current():
fnc_test_experimental_with_current()
self.check_annotation(warning=w, warning_size=1,
warning_type=FutureWarning,
- fnc_name='fnc_test_experimental_with_current',
+ obj_name='fnc_test_experimental_with_current',
annotation_type='experimental',
label_check_list=[('instead', True)])
@@ -112,7 +120,7 @@ def fnc_test_experimental_without_current():
fnc_test_experimental_without_current()
self.check_annotation(warning=w, warning_size=1,
warning_type=FutureWarning,
- fnc_name='fnc_test_experimental_without_current',
+ obj_name='fnc_test_experimental_without_current',
annotation_type='experimental',
label_check_list=[('instead', False)])
@@ -132,27 +140,206 @@ def fnc2_test_annotate_frequency():
fnc2_test_annotate_frequency()
self.check_annotation(warning=[w[0]], warning_size=1,
warning_type=FutureWarning,
- fnc_name='fnc_test_annotate_frequency',
+ obj_name='fnc_test_annotate_frequency',
annotation_type='experimental',
label_check_list=[])
self.check_annotation(warning=[w[1]], warning_size=1,
warning_type=FutureWarning,
- fnc_name='fnc2_test_annotate_frequency',
+ obj_name='fnc2_test_annotate_frequency',
annotation_type='experimental',
label_check_list=[])
+ def test_frequency_class(self):
+ """Tests that the filter 'once' is sufficient to print once per
+ warning independently of location."""
+ with warnings.catch_warnings(record=True) as w:
+ @experimental()
+ class Class_test_annotate_frequency(object):
+ fooo = 'lol'
+
+ def __init__(self):
+ pass
+
+ def foo(self):
+ return 'lol'
+
+ @experimental()
+ class Class2_test_annotate_frequency(object):
+ fooo = 'lol'
+
+ def __init__(self):
+ pass
+
+ def foo(self):
+ return 'lol'
+
+ foo = Class_test_annotate_frequency()
+ foo.foo()
+ foo1 = Class_test_annotate_frequency()
+ foo1.foo()
+ foo2 = Class2_test_annotate_frequency()
+ foo2.foo()
+ self.check_annotation(warning=[w[0]], warning_size=1,
+ warning_type=FutureWarning,
+ obj_name='Class_test_annotate_frequency',
+ annotation_type='experimental',
+ label_check_list=[])
+ self.check_annotation(warning=[w[1]], warning_size=1,
+ warning_type=FutureWarning,
+ obj_name='Class2_test_annotate_frequency',
+ annotation_type='experimental',
+ label_check_list=[])
+
+ def test_decapreted_custom_no_replacements(self):
+ """Tests if custom message prints an empty string
+ for each replacement token when only the
+ custom_message and since parameter are given."""
+ with warnings.catch_warnings(record=True) as w:
+ strSince = 'v1'
+ strCustom = 'Replacement:%since%%current%%extra%'
+
+ @deprecated(since=strSince, custom_message=strCustom)
+ def fnc_test_experimental_custom_no_replacements():
+ return 'lol'
+ fnc_test_experimental_custom_no_replacements()
+ self.check_custom_annotation(warning=w, warning_size=1,
+ warning_type=DeprecationWarning,
+ obj_name='fnc_test_experimental_custom_no_\
+ replacements',
+ annotation_type='experimental',
+ intended_message=strCustom
+ .replace('%since%', strSince)
+ .replace('%current%', '')
+ .replace('%extra%', ''))
+
+ def test_enforce_custom_since_deprecated_must_fail(self):
+ """Tests since replacement token inclusion on the
+ custom message for the decapreted string. If no
+ since replacement token is given, the annotation must fail"""
+ with warnings.catch_warnings(record=True) as w:
+ with self.assertRaises(TypeError):
+ strSince = 'v1'
+ strCustom = 'Replacement:'
+
+ @deprecated(since=strSince, custom_message=strCustom)
+ def fnc_test_experimental_custom_no_replacements():
+ return 'lol'
+ fnc_test_experimental_custom_no_replacements()
+ assert not w
+
+ def test_deprecated_with_since_current_message_custom(self):
+ with warnings.catch_warnings(record=True) as w:
+ strSince = 'v.1'
+ strCurrent = 'multiply'
+ strExtra = 'Do this'
+ strCustom = "%name% Will be deprecated from %since%. \
+ Please use %current% insted. Will %extra%"
+
+ @deprecated(since=strSince, current=strCurrent, extra_message=strExtra,
+ custom_message=strCustom)
+ def fnc_test_deprecated_with_since_current_message_custom():
+ return 'lol'
+ strName = fnc_test_deprecated_with_since_current_message_custom .__name__
+ fnc_test_deprecated_with_since_current_message_custom()
+ self.check_custom_annotation(warning=w, warning_size=1,
+ warning_type=DeprecationWarning,
+ obj_name='fnc_test_deprecated_with_since_\
+ current_message_custom',
+ annotation_type='deprecated',
+ intended_message=strCustom
+ .replace('%name%', strName)
+ .replace('%since%', strSince)
+ .replace('%current%', strCurrent)
+ .replace('%extra%', strExtra))
+
+ def test_deprecated_with_since_current_message_class(self):
+ with warnings.catch_warnings(record=True) as w:
+ @deprecated(since='v.1', current='multiply', extra_message='Do this')
+ class Class_test_deprecated_with_since_current_message(object):
+ fooo = 'lol'
+
+ def __init__(self):
+ pass
+
+ def foo(self):
+ return 'lol'
+
+ foo = Class_test_deprecated_with_since_current_message()
+ strName = Class_test_deprecated_with_since_current_message.__name__
+ foo.foo()
+ self.check_annotation(warning=w, warning_size=1,
+ warning_type=DeprecationWarning,
+ obj_name=strName,
+ annotation_type='deprecated',
+ label_check_list=[('since', True),
+ ('instead', True),
+ ('Do this', True)])
+
+ def test_experimental_with_current_message_custom(self):
+ with warnings.catch_warnings(record=True) as w:
+ strCurrent = 'multiply'
+ strExtra = 'DoThis'
+ strCustom = '%name% Function on experimental phase, use %current% \
+ for stability. Will %extra%.'
+
+ @experimental(current=strCurrent, extra_message=strExtra,
+ custom_message=strCustom)
+ def fnc_test_experimental_with_current_message_custom():
+ return 'lol'
+ strName = fnc_test_experimental_with_current_message_custom.__name__
+ fnc_test_experimental_with_current_message_custom()
+ self.check_custom_annotation(warning=w, warning_size=1,
+ warning_type=FutureWarning,
+ obj_name='fnc_test_experimental\
+ _with_current_message_custom',
+ annotation_type='experimental',
+ intended_message=strCustom
+ .replace('%name%', strName)
+ .replace('%current%', strCurrent)
+ .replace('%extra%', strExtra))
+
+ def test_experimental_with_current_message_class(self):
+ with warnings.catch_warnings(record=True) as w:
+ @experimental(current='multiply', extra_message='Do this')
+ class Class_test_experimental_with_current_message(object):
+ fooo = 'lol'
+
+ def __init__(self):
+ pass
+
+ def foo(self):
+ return 'lol'
+
+ foo = Class_test_experimental_with_current_message()
+ strName = Class_test_experimental_with_current_message.__name__
+ foo.foo()
+ self.check_annotation(warning=w, warning_size=1,
+ warning_type=FutureWarning,
+ obj_name=strName,
+ annotation_type='experimental',
+ label_check_list=[('instead', True),
+ ('Do this', True)])
+
# helper function
- def check_annotation(self, warning, warning_size, warning_type, fnc_name,
+ def check_annotation(self, warning, warning_size, warning_type, obj_name,
annotation_type, label_check_list):
self.assertEqual(1, warning_size)
self.assertTrue(issubclass(warning[-1].category, warning_type))
- self.assertIn(fnc_name + ' is ' + annotation_type,
str(warning[-1].message))
+ self.assertIn(obj_name + ' is ' + annotation_type,
str(warning[-1].message))
for label in label_check_list:
if label[1] is True:
self.assertIn(label[0], str(warning[-1].message))
else:
self.assertNotIn(label[0], str(warning[-1].message))
+ # Helper function for custom messages
+ def check_custom_annotation(self, warning, warning_size, warning_type,
+ obj_name,
+ annotation_type, intended_message):
+ self.assertEqual(1, warning_size)
+ self.assertTrue(issubclass(warning[-1].category, warning_type))
+ self.assertIn(intended_message, str(warning[-1].message))
+
if __name__ == '__main__':
unittest.main()
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
Issue Time Tracking
-------------------
Worklog Id: (was: 155586)
Time Spent: 1h 10m (was: 1h)
> annotations should support custom messages and classes
> ------------------------------------------------------
>
> Key: BEAM-1081
> URL: https://issues.apache.org/jira/browse/BEAM-1081
> Project: Beam
> Issue Type: Improvement
> Components: sdk-py-core
> Reporter: Ahmet Altay
> Priority: Minor
> Labels: newbie, starter
> Time Spent: 1h 10m
> Remaining Estimate: 0h
>
> Update
> https://github.com/apache/incubator-beam/blob/python-sdk/sdks/python/apache_beam/utils/annotations.py
> to add 2 new features:
> 1. ability to customize message
> 2. ability to tag classes (not only functions)
--
This message was sent by Atlassian JIRA
(v7.6.3#76005)