We got a CBF after we imported a PR in grpc Python to g3 (stack trace listed below).
After some digging, we found that it's because the use of functools.wraps unintentionally updates _GapicCallable class attributes _target.
In the PR, we introduced a new attribute _target to grpc multi callables, but the implementation of functools.wraps did more than what's intended, it changed _target attribute of _GapicCallable to the ones from multi callables, causing _GapicCallable._target to change from a function to a byte string, I also provided an example below.
Code example
import functools
class _GapicCallable(object):
def __init__(self, target):
print(f"create _GapicCallable with target: {target}")
self._target = target
def __call__(self, *args, **kwargs):
print(f"_GapicCallable.__call__._target: {self._target}")
def wrap_method(func):
# Fix 2: adding updated
# return functools.wraps(func, updated=[])(
return functools.wraps(func)(
_GapicCallable(func)
)
class TestCallble:
# Fix 1: adding __slots__
# __slots__ = ["_target"]
def __init__(self):
self._target = "test"
test_callable = TestCallble()
wrapped = wrap_method(test_callable) # At this point, _GapicCallable._target is a TestCallble object.
wrapped() # Here, _GapicCallable._target becomes "test" string.
Stack trace
Traceback (most recent call last):
File "/build/work/3acefca9d25380652e9aea15ea6b9206b95e/google3/runfiles/google3/third_party/tink/python/tink/integration/gcpkms/_gcp_kms_client_integration_test.py", line 77, in test_not_bound_to_key_uri_encrypt_decrypt
ciphertext = gcp_aead.encrypt(plaintext, associated_data)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/build/work/3acefca9d25380652e9aea15ea6b9206b95e/google3/runfiles/google3/third_party/tink/python/tink/integration/gcpkms/_gcp_kms_client.py", line 48, in encrypt
response = self.client.encrypt(
^^^^^^^^^^^^^^^^^^^^
File "/build/work/3acefca9d25380652e9aea15ea6b9206b95e/google3/runfiles/google3/third_party/py/google/cloud/kms_v1/gapic/key_management_service_client.py", line 1731, in encrypt
return self._inner_api_calls["encrypt"](
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/build/work/3acefca9d25380652e9aea15ea6b9206b95e/google3/runfiles/google3/third_party/py/google/api_core/gapic_v1/method.py", line 141, in __call__
return wrapped_func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/build/work/3acefca9d25380652e9aea15ea6b9206b95e/google3/runfiles/google3/third_party/py/google/api_core/retry.py", line 269, in retry_wrapped_func
return retry_target(
^^^^^^^^^^^^^
File "/build/work/3acefca9d25380652e9aea15ea6b9206b95e/google3/runfiles/google3/third_party/py/google/api_core/retry.py", line 176, in retry_target
return target()
^^^^^^^^
File "/build/work/3acefca9d25380652e9aea15ea6b9206b95e/google3/runfiles/google3/third_party/py/google/api_core/timeout.py", line 197, in func_with_timeout
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
TypeError: 'bytes' object is not callable
Suggested Fix
We'll add __slot__ to our multi-callable class. In the meantime, we suggest adding updated to functools.wraps to prevent similar issues from happening in the future.
We got a CBF after we imported a PR in grpc Python to g3 (stack trace listed below).
After some digging, we found that it's because the use of
functools.wraps
unintentionally updates_GapicCallable
class attributes_target
.In the PR, we introduced a new attribute
_target
to grpc multi callables, but the implementation offunctools.wraps
did more than what's intended, it changed_target
attribute of_GapicCallable
to the ones from multi callables, causing_GapicCallable._target
to change from a function to a byte string, I also provided an example below.Code example
Stack trace
Suggested Fix
__slot__
to our multi-callable class. In the meantime, we suggest addingupdated
tofunctools.wraps
to prevent similar issues from happening in the future.