From 348dab4aefb998c17f22451c250c16959df79316 Mon Sep 17 00:00:00 2001 From: Thomas Schultz Date: Thu, 27 Oct 2016 13:41:52 -0400 Subject: [PATCH] Add speech async GAPIC. --- .../google/cloud/speech/_gax.py | 35 +++- .../google/cloud/speech/client.py | 6 +- google-cloud-speech/setup.py | 1 - google-cloud-speech/unit_tests/test_client.py | 158 ++++++++++++------ 4 files changed, 137 insertions(+), 63 deletions(-) diff --git a/google-cloud-speech/google/cloud/speech/_gax.py b/google-cloud-speech/google/cloud/speech/_gax.py index 9437a806b1ce..f75c827674e9 100644 --- a/google-cloud-speech/google/cloud/speech/_gax.py +++ b/google-cloud-speech/google/cloud/speech/_gax.py @@ -14,23 +14,36 @@ """GAX/GAPIC module for managing Speech API requests.""" + from google.cloud.gapic.speech.v1beta1.speech_api import SpeechApi -from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import SpeechContext -from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import RecognitionConfig from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import RecognitionAudio +from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import RecognitionConfig +from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import SpeechContext from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import ( StreamingRecognitionConfig) from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import ( StreamingRecognizeRequest) +from google.longrunning import operations_grpc +from google.cloud._helpers import make_secure_stub +from google.cloud.connection import DEFAULT_USER_AGENT from google.cloud.speech.alternative import Alternative +from google.cloud.speech.operation import Operation + +OPERATIONS_API_HOST = 'speech.googleapis.com' class GAPICSpeechAPI(object): """Manage calls through GAPIC wrappers to the Speech API.""" - def __init__(self): + def __init__(self, client=None): + self._client = client self._gapic_api = SpeechApi() + self._operations_stub = make_secure_stub( + self._client.connection.credentials, + DEFAULT_USER_AGENT, + operations_grpc.OperationsStub, + OPERATIONS_API_HOST) def async_recognize(self, sample, language_code=None, max_alternatives=None, profanity_filter=None, @@ -72,9 +85,21 @@ def async_recognize(self, sample, language_code=None, and phrases. This can also be used to add new words to the vocabulary of the recognizer. - :raises NotImplementedError: Always. + :rtype: :class:`~google.cloud.speech.operation.Operation` + :returns: Instance of ``Operation`` to poll for results. """ - raise NotImplementedError + config = RecognitionConfig( + encoding=sample.encoding, sample_rate=sample.sample_rate, + language_code=language_code, max_alternatives=max_alternatives, + profanity_filter=profanity_filter, + speech_context=SpeechContext(phrases=speech_context)) + + audio = RecognitionAudio(content=sample.content, + uri=sample.source_uri) + api = self._gapic_api + response = api.async_recognize(config=config, audio=audio) + + return Operation.from_pb(response, self) def sync_recognize(self, sample, language_code=None, max_alternatives=None, profanity_filter=None, speech_context=None): diff --git a/google-cloud-speech/google/cloud/speech/client.py b/google-cloud-speech/google/cloud/speech/client.py index 33c1a2e8eb27..a321d92cce41 100644 --- a/google-cloud-speech/google/cloud/speech/client.py +++ b/google-cloud-speech/google/cloud/speech/client.py @@ -17,17 +17,17 @@ from base64 import b64encode import os -from google.cloud._helpers import _to_bytes from google.cloud._helpers import _bytes_to_unicode +from google.cloud._helpers import _to_bytes from google.cloud.client import Client as BaseClient from google.cloud.environment_vars import DISABLE_GRPC from google.cloud.speech._gax import GAPICSpeechAPI +from google.cloud.speech.alternative import Alternative from google.cloud.speech.connection import Connection from google.cloud.speech.encoding import Encoding from google.cloud.speech.operation import Operation from google.cloud.speech.sample import Sample -from google.cloud.speech.alternative import Alternative _USE_GAX = not os.getenv(DISABLE_GRPC, False) @@ -154,7 +154,7 @@ def speech_api(self): """Helper for speech-related API calls.""" if self._speech_api is None: if self._use_gax: - self._speech_api = GAPICSpeechAPI() + self._speech_api = GAPICSpeechAPI(self) else: self._speech_api = _JSONSpeechAPI(self) return self._speech_api diff --git a/google-cloud-speech/setup.py b/google-cloud-speech/setup.py index 536ed0c53782..cd56983412da 100644 --- a/google-cloud-speech/setup.py +++ b/google-cloud-speech/setup.py @@ -52,7 +52,6 @@ REQUIREMENTS = [ 'google-cloud-core >= 0.20.0', 'gapic-google-cloud-speech-v1beta1 >= 0.11.1, < 0.12.0', - 'grpc-google-cloud-speech-v1beta1 >= 0.11.1, < 0.12.0', ] setup( diff --git a/google-cloud-speech/unit_tests/test_client.py b/google-cloud-speech/unit_tests/test_client.py index f26313940fd4..f8d2455b48f6 100644 --- a/google-cloud-speech/unit_tests/test_client.py +++ b/google-cloud-speech/unit_tests/test_client.py @@ -21,6 +21,28 @@ class TestClient(unittest.TestCase): AUDIO_SOURCE_URI = 'gs://sample-bucket/sample-recording.flac' AUDIO_CONTENT = '/9j/4QNURXhpZgAASUkq' + @staticmethod + def _make_result(alternatives): + from google.cloud.grpc.speech.v1beta1 import cloud_speech_pb2 + + return cloud_speech_pb2.SpeechRecognitionResult( + alternatives=[ + cloud_speech_pb2.SpeechRecognitionAlternative( + transcript=alternative['transcript'], + confidence=alternative['confidence'], + ) for alternative in alternatives + ], + ) + + def _make_sync_response(self, *results): + from google.cloud.grpc.speech.v1beta1 import cloud_speech_pb2 + + response = cloud_speech_pb2.SyncRecognizeResponse( + results=results, + ) + + return response + def _getTargetClass(self): from google.cloud.speech.client import Client @@ -69,15 +91,15 @@ def test_create_sample_from_client(self): def test_sync_recognize_content_with_optional_params_no_gax(self): from base64 import b64encode - from google.cloud._helpers import _to_bytes - from google.cloud._helpers import _bytes_to_unicode + from google.cloud._helpers import _bytes_to_unicode + from google.cloud._helpers import _to_bytes from google.cloud._testing import _Monkey - from google.cloud.speech import client as MUT + from google.cloud import speech + from google.cloud.speech import client as MUT from google.cloud.speech.alternative import Alternative from google.cloud.speech.sample import Sample - from unit_tests._fixtures import SYNC_RECOGNIZE_RESPONSE _AUDIO_CONTENT = _to_bytes(self.AUDIO_CONTENT) @@ -131,8 +153,9 @@ def test_sync_recognize_content_with_optional_params_no_gax(self): def test_sync_recognize_source_uri_without_optional_params_no_gax(self): from google.cloud._testing import _Monkey - from google.cloud.speech import client as MUT + from google.cloud import speech + from google.cloud.speech import client as MUT from google.cloud.speech.alternative import Alternative from google.cloud.speech.sample import Sample from unit_tests._fixtures import SYNC_RECOGNIZE_RESPONSE @@ -174,8 +197,9 @@ def test_sync_recognize_source_uri_without_optional_params_no_gax(self): def test_sync_recognize_with_empty_results_no_gax(self): from google.cloud._testing import _Monkey - from google.cloud.speech import client as MUT + from google.cloud import speech + from google.cloud.speech import client as MUT from google.cloud.speech.sample import Sample from unit_tests._fixtures import SYNC_RECOGNIZE_EMPTY_RESPONSE @@ -192,45 +216,71 @@ def test_sync_recognize_with_empty_results_no_gax(self): def test_sync_recognize_with_empty_results_gax(self): from google.cloud._testing import _Monkey - from google.cloud.speech import _gax as MUT + from google.cloud import speech + from google.cloud.speech import _gax from google.cloud.speech.sample import Sample credentials = _Credentials() client = self._makeOne(credentials=credentials, use_gax=True) client.connection = _Connection() + client.connection.credentials = credentials + + def speech_api(): + return _MockGAPICSpeechAPI(response=self._make_sync_response()) + + with _Monkey(_gax, SpeechApi=speech_api): + client._speech_api = _gax.GAPICSpeechAPI(client) + + sample = Sample(source_uri=self.AUDIO_SOURCE_URI, + encoding=speech.Encoding.FLAC, + sample_rate=self.SAMPLE_RATE) with self.assertRaises(ValueError): - mock_no_results = _MockGAPICSpeechAPI - mock_no_results._results = [] - with _Monkey(MUT, SpeechApi=mock_no_results): - sample = Sample(source_uri=self.AUDIO_SOURCE_URI, - encoding=speech.Encoding.FLAC, - sample_rate=self.SAMPLE_RATE) - client.sync_recognize(sample) + client.sync_recognize(sample) def test_sync_recognize_with_gax(self): - from google.cloud import speech - from google.cloud.speech import _gax as MUT from google.cloud._testing import _Monkey + from google.cloud import speech + from google.cloud.speech import _gax + creds = _Credentials() client = self._makeOne(credentials=creds, use_gax=True) client.connection = _Connection() + client.connection.credentials = creds client._speech_api = None + alternatives = [{ + 'transcript': 'testing 1 2 3', + 'confidence': 0.9224355, + }, { + 'transcript': 'testing 4 5 6', + 'confidence': 0.0123456, + }] + result = self._make_result(alternatives) + + def speech_api(): + return _MockGAPICSpeechAPI( + response=self._make_sync_response(result)) + + sample = client.sample(source_uri=self.AUDIO_SOURCE_URI, + encoding=speech.Encoding.FLAC, + sample_rate=self.SAMPLE_RATE) + + with _Monkey(_gax, SpeechApi=speech_api): + client._speech_api = _gax.GAPICSpeechAPI(client) - mock_no_results = _MockGAPICSpeechAPI - mock_no_results._results = [_MockGAPICSyncResult()] + results = client.sync_recognize(sample) - with _Monkey(MUT, SpeechApi=_MockGAPICSpeechAPI): - sample = client.sample(source_uri=self.AUDIO_SOURCE_URI, - encoding=speech.Encoding.FLAC, - sample_rate=self.SAMPLE_RATE) - results = client.sync_recognize(sample) - self.assertEqual(results[0].transcript, - _MockGAPICAlternative.transcript) - self.assertEqual(results[0].confidence, - _MockGAPICAlternative.confidence) + self.assertEqual(len(results), 2) + self.assertEqual(results[0].transcript, + alternatives[0]['transcript']) + self.assertEqual(results[0].confidence, + alternatives[0]['confidence']) + self.assertEqual(results[1].transcript, + alternatives[1]['transcript']) + self.assertEqual(results[1].confidence, + alternatives[1]['confidence']) def test_async_supported_encodings(self): from google.cloud import speech @@ -247,10 +297,10 @@ def test_async_supported_encodings(self): client.async_recognize(sample) def test_async_recognize_no_gax(self): - from unit_tests._fixtures import ASYNC_RECOGNIZE_RESPONSE from google.cloud import speech from google.cloud.speech.operation import Operation from google.cloud.speech.sample import Sample + from unit_tests._fixtures import ASYNC_RECOGNIZE_RESPONSE RETURNED = ASYNC_RECOGNIZE_RESPONSE @@ -270,30 +320,37 @@ def test_async_recognize_no_gax(self): self.assertIsNone(operation.metadata) def test_async_recognize_with_gax(self): - from google.cloud.speech import _gax as MUT from google.cloud._testing import _Monkey + from google.cloud import speech + from google.cloud.speech import _gax + from google.cloud.speech.operation import Operation credentials = _Credentials() client = self._makeOne(credentials=credentials) client.connection = _Connection() + client.connection.credentials = credentials sample = client.sample(source_uri=self.AUDIO_SOURCE_URI, encoding=speech.Encoding.LINEAR16, sample_rate=self.SAMPLE_RATE) - with _Monkey(MUT, SpeechApi=_MockGAPICSpeechAPI): - with self.assertRaises(NotImplementedError): - client.async_recognize(sample) + with _Monkey(_gax, SpeechApi=_MockGAPICSpeechAPI): + operation = client.async_recognize(sample) + + self.assertIsInstance(operation, Operation) + self.assertFalse(operation.complete) + self.assertIsNone(operation.response) def test_speech_api_with_gax(self): - from google.cloud.speech import _gax as MUT from google.cloud._testing import _Monkey + + from google.cloud.speech import _gax from google.cloud.speech.client import GAPICSpeechAPI creds = _Credentials() client = self._makeOne(credentials=creds, use_gax=True) - with _Monkey(MUT, SpeechApi=_MockGAPICSpeechAPI): + with _Monkey(_gax, SpeechApi=_MockGAPICSpeechAPI): self.assertIsNone(client._speech_api) self.assertIsInstance(client.speech_api, GAPICSpeechAPI) @@ -316,33 +373,26 @@ def test_speech_api_preset(self): self.assertIs(client.speech_api, fake_api) -class _MockGAPICAlternative(object): - transcript = 'testing 1 2 3' - confidence = 0.95234356 - - -class _MockGAPICSyncResult(object): - alternatives = [_MockGAPICAlternative()] - +class _MockGAPICSpeechAPI(object): + _requests = None + _response = None + _results = None -class _MockGAPICSpeechResponse(object): - error = None - endpointer_type = None - results = [] - result_index = 0 + def __init__(self, response=None): + self._response = response + def async_recognize(self, config, audio): + from google.longrunning.operations_pb2 import Operation -class _MockGAPICSpeechAPI(object): - _requests = None - _response = _MockGAPICSpeechResponse() - _results = [_MockGAPICSyncResult()] + self.config = config + self.audio = audio + operation = Operation() + return operation def sync_recognize(self, config, audio): self.config = config self.audio = audio - mock_response = self._response - mock_response.results = self._results - return mock_response + return self._response class _Credentials(object):