diff --git a/api_core/google/api_core/gapic_v2/__init__.py b/api_core/google/api_core/gapic_v2/__init__.py new file mode 100644 index 000000000000..4a5cc70016c9 --- /dev/null +++ b/api_core/google/api_core/gapic_v2/__init__.py @@ -0,0 +1,27 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.api_core.gapic_v1 import client_info +from google.api_core.gapic_v1 import config +from google.api_core.gapic_v1 import method +from google.api_core.gapic_v1 import routing_header +from google.api_core.gapic_v2 import dispatch + +__all__ = [ + 'client_info', + 'config', + 'dispatch', + 'method', + 'routing_header', +] diff --git a/api_core/google/api_core/gapic_v2/dispatch.py b/api_core/google/api_core/gapic_v2/dispatch.py new file mode 100644 index 000000000000..7b107a2b96eb --- /dev/null +++ b/api_core/google/api_core/gapic_v2/dispatch.py @@ -0,0 +1,37 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import functools + + +def dispatch(func): + """Return a decorated method that dispatches on the second argument. + + This is the equivalent of :meth:`functools.singledispatch`, but for + bound methods. + """ + base_dispatcher = functools.singledispatch(func) + + # Define a wrapper function that works off args[1] instead of args[0]. + # This is needed because we are overloading *methods*, and their first + # argument is always `self`. + @functools.wraps(base_dispatcher) + def wrapper(*args, **kwargs): + return base_dispatcher.dispatch(args[1].__class__)(*args, **kwargs) + + # The register function is not changed, so let singledispatch do the work. + wrapper.register = base_dispatcher.register + + # Done; return the decorated method. + return wrapper diff --git a/api_core/google/api_core/operation.py b/api_core/google/api_core/operation.py index 51a7a9676b63..c76ac78ed163 100644 --- a/api_core/google/api_core/operation.py +++ b/api_core/google/api_core/operation.py @@ -94,6 +94,18 @@ def metadata(self): return protobuf_helpers.from_any_pb( self._metadata_type, self._operation.metadata) + @classmethod + def deserialize(self, payload): + """Deserialize a ``google.longrunning.Operation`` protocol buffer. + + Args: + payload (bytes): A serialized operation protocol buffer. + + Returns: + ~.operations_pb2.Operation: An Operation protobuf object. + """ + return operations_pb2.Operation.FromString(payload) + def _set_result_from_operation(self): """Set the result or exception from the operation if it is complete.""" # This must be done in a lock to prevent the polling thread diff --git a/api_core/tests/unit/gapic/test_dispatch.py b/api_core/tests/unit/gapic/test_dispatch.py new file mode 100644 index 000000000000..aa7d87162c27 --- /dev/null +++ b/api_core/tests/unit/gapic/test_dispatch.py @@ -0,0 +1,36 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +import six + +from google.api_core.gapic_v2.dispatch import dispatch + + +@pytest.mark.skipif(six.PY2, reason='dispatch only works on Python 3.') +def test_dispatch(): + class Foo(object): + @dispatch + def bar(self, number, letter): + return 'Brought by the letter {} and the number {}.'.format( + letter, number, + ) + + @bar.register(str) + def _bar_with_string(self, letter): + return self.bar(11, letter) + + foo = Foo() + assert foo.bar(8, 'L') == 'Brought by the letter L and the number 8.' + assert foo.bar('Z') == 'Brought by the letter Z and the number 11.' diff --git a/api_core/tests/unit/test_operation.py b/api_core/tests/unit/test_operation.py index 211fea6adc04..240eb867885c 100644 --- a/api_core/tests/unit/test_operation.py +++ b/api_core/tests/unit/test_operation.py @@ -221,3 +221,11 @@ def test_from_gapic(): assert future._metadata_type == struct_pb2.Struct assert future.operation.name == TEST_OPERATION_NAME assert future.done + + +def test_deserialize(): + op = make_operation_proto(name='foobarbaz') + serialized = op.SerializeToString() + deserialized_op = operation.Operation.deserialize(serialized) + assert op.name == deserialized_op.name + assert type(op) is type(deserialized_op)