diff --git a/sdks/python/apache_beam/internal/code_object_pickler.py b/sdks/python/apache_beam/internal/code_object_pickler.py index 269bccb6b461..213ac6cd7257 100644 --- a/sdks/python/apache_beam/internal/code_object_pickler.py +++ b/sdks/python/apache_beam/internal/code_object_pickler.py @@ -176,7 +176,13 @@ def _search_module_or_class( rest = qual_name_parts[1:] if first_part == '': for name in dir(node): - value = getattr(node, name) + try: + value = getattr(node, name) + except Exception: # pylint: disable=broad-except + # Unrelated attributes (e.g. descriptors that only work on instances, + # or lazy module attributes with missing dependencies) can raise when + # read. They can't be the lambda we are looking for, so skip them. + continue if (hasattr(callable, '__code__') and isinstance(value, type(callable)) and value.__code__ == callable.__code__): diff --git a/sdks/python/apache_beam/internal/code_object_pickler_test.py b/sdks/python/apache_beam/internal/code_object_pickler_test.py index abe404ff02c5..488c5412daf0 100644 --- a/sdks/python/apache_beam/internal/code_object_pickler_test.py +++ b/sdks/python/apache_beam/internal/code_object_pickler_test.py @@ -111,6 +111,19 @@ def process(self): return lambda: 1 +class _InstanceOnlyDescriptor: + def __get__(self, instance, owner=None): + if instance is None: + raise TypeError("only valid on instances") + return 1 + + +class ClassWithLambdaAndRaisingAttribute: + # Sorts before `fn` in dir(), so it is read first during the lambda search. + a_setting = _InstanceOnlyDescriptor() + fn = lambda self, x: x + 1 + + class ClassWithNestedClass: class InnerClass: def process(self): @@ -247,6 +260,16 @@ def test_invalid_lambda_with_hash_path_raises_exception(self): "apache_beam.internal.test_data.module_3." "my_function.__code__.co_consts[, ('',), 1234567890]") + def test_lambda_search_skips_attributes_that_raise(self): + fn = ClassWithLambdaAndRaisingAttribute.fn + path = code_object_pickler.get_code_object_identifier(fn) + self.assertEqual( + path, + "apache_beam.internal.code_object_pickler_test." + "ClassWithLambdaAndRaisingAttribute.fn.__code__") + self.assertEqual( + code_object_pickler.get_code_from_identifier(path), fn.__code__) + def test_adding_local_variable_in_class_preserves_object(self): self.assertEqual( code_object_pickler.get_code_from_identifier(