Skip to content

Commit fee39bf

Browse files
authored
Fix Speech LRO handling and add HTTP side for multiple results. (googleapis#2965)
* Update after googleapis#2962 to fill out http side and handle new LRO. * Update _OperationsFuture usage. * Mock OperationsClient.
1 parent 03d8b0b commit fee39bf

File tree

6 files changed

+82
-40
lines changed

6 files changed

+82
-40
lines changed

docs/speech-usage.rst

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ See: `Speech Asynchronous Recognize`_
6464
>>> operation.complete
6565
True
6666
>>> for result in operation.results:
67-
... print('=' * 20)
68-
... print(result.transcript)
69-
... print(result.confidence)
67+
... for alternative in result.alternatives:
68+
... print('=' * 20)
69+
... print(alternative.transcript)
70+
... print(alternative.confidence)
7071
====================
7172
'how old is the Brooklyn Bridge'
7273
0.98267895
@@ -93,9 +94,10 @@ Great Britian.
9394
... source_uri='gs://my-bucket/recording.flac', language_code='en-GB',
9495
... max_alternatives=2)
9596
>>> for result in results:
96-
... print('=' * 20)
97-
... print('transcript: ' + result.transcript)
98-
... print('confidence: ' + result.confidence)
97+
... for alternative in result.alternatives:
98+
... print('=' * 20)
99+
... print('transcript: ' + alternative.transcript)
100+
... print('confidence: ' + alternative.confidence)
99101
====================
100102
transcript: Hello, this is a test
101103
confidence: 0.81
@@ -115,9 +117,10 @@ Example of using the profanity filter.
115117
>>> results = sample.sync_recognize(max_alternatives=1,
116118
... profanity_filter=True)
117119
>>> for result in results:
118-
... print('=' * 20)
119-
... print('transcript: ' + result.transcript)
120-
... print('confidence: ' + result.confidence)
120+
... for alternative in result.alternatives:
121+
... print('=' * 20)
122+
... print('transcript: ' + alternative.transcript)
123+
... print('confidence: ' + alternative.confidence)
121124
====================
122125
transcript: Hello, this is a f****** test
123126
confidence: 0.81
@@ -137,9 +140,10 @@ words to the vocabulary of the recognizer.
137140
>>> results = sample.sync_recognize(max_alternatives=2,
138141
... speech_context=hints)
139142
>>> for result in results:
140-
... print('=' * 20)
141-
... print('transcript: ' + result.transcript)
142-
... print('confidence: ' + result.confidence)
143+
... for alternative in result.alternatives:
144+
... print('=' * 20)
145+
... print('transcript: ' + alternative.transcript)
146+
... print('confidence: ' + alternative.confidence)
143147
====================
144148
transcript: Hello, this is a test
145149
confidence: 0.81

speech/google/cloud/speech/_gax.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ def async_recognize(self, sample, language_code=None,
104104
audio = RecognitionAudio(content=sample.content,
105105
uri=sample.source_uri)
106106
api = self._gapic_api
107-
response = api.async_recognize(config=config, audio=audio)
107+
operation_future = api.async_recognize(config=config, audio=audio)
108108

109-
return Operation.from_pb(response, self)
109+
return Operation.from_pb(operation_future.last_operation_data(), self)
110110

111111
def streaming_recognize(self, sample, language_code=None,
112112
max_alternatives=None, profanity_filter=None,

speech/google/cloud/speech/client.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from google.cloud.environment_vars import DISABLE_GRPC
2424

2525
from google.cloud.speech._gax import GAPICSpeechAPI
26-
from google.cloud.speech.alternative import Alternative
26+
from google.cloud.speech.result import Result
2727
from google.cloud.speech.connection import Connection
2828
from google.cloud.speech.operation import Operation
2929
from google.cloud.speech.sample import Sample
@@ -235,12 +235,11 @@ def sync_recognize(self, sample, language_code=None, max_alternatives=None,
235235
api_response = self._connection.api_request(
236236
method='POST', path='speech:syncrecognize', data=data)
237237

238-
if len(api_response['results']) == 1:
239-
result = api_response['results'][0]
240-
return [Alternative.from_api_repr(alternative)
241-
for alternative in result['alternatives']]
238+
if len(api_response['results']) > 0:
239+
results = api_response['results']
240+
return [Result.from_api_repr(result) for result in results]
242241
else:
243-
raise ValueError('More than one result or none returned from API.')
242+
raise ValueError('No results were returned from the API')
244243

245244

246245
def _build_request_data(sample, language_code=None, max_alternatives=None,

speech/google/cloud/speech/result.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,34 @@ def __init__(self, alternatives):
3232

3333
@classmethod
3434
def from_pb(cls, result):
35-
"""Factory: construct instance of ``SpeechRecognitionResult``.
35+
"""Factory: construct instance of ``Result``.
3636
3737
:type result: :class:`~google.cloud.grpc.speech.v1beta1\
38-
.cloud_speech_pb2.StreamingRecognizeResult`
39-
:param result: Instance of ``StreamingRecognizeResult`` protobuf.
38+
.cloud_speech_pb2.SpeechRecognitionResult`
39+
:param result: Instance of ``SpeechRecognitionResult`` protobuf.
4040
41-
:rtype: :class:`~google.cloud.speech.result.SpeechRecognitionResult`
42-
:returns: Instance of ``SpeechRecognitionResult``.
41+
:rtype: :class:`~google.cloud.speech.result.Result`
42+
:returns: Instance of ``Result``.
4343
"""
44-
alternatives = [Alternative.from_pb(result) for result
44+
alternatives = [Alternative.from_pb(alternative) for alternative
4545
in result.alternatives]
4646
return cls(alternatives=alternatives)
4747

48+
@classmethod
49+
def from_api_repr(cls, result):
50+
"""Factory: construct instance of ``Result``.
51+
52+
:type result: dict
53+
:param result: Dictionary of a :class:`~google.cloud.grpc.speech.\
54+
v1beta1.cloud_speech_pb2.SpeechRecognitionResult`
55+
56+
:rtype: :class:`~google.cloud.speech.result.Result`
57+
:returns: Instance of ``Result``.
58+
"""
59+
alternatives = [Alternative.from_api_repr(alternative) for alternative
60+
in result['alternatives']]
61+
return cls(alternatives=alternatives)
62+
4863
@property
4964
def confidence(self):
5065
"""Return the confidence for the most probable alternative.

speech/unit_tests/test_client.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ def test_sync_recognize_content_with_optional_params_no_gax(self):
129129

130130
from google.cloud import speech
131131
from google.cloud.speech.alternative import Alternative
132+
from google.cloud.speech.result import Result
132133
from unit_tests._fixtures import SYNC_RECOGNIZE_RESPONSE
133134

134135
_B64_AUDIO_CONTENT = _bytes_to_unicode(b64encode(self.AUDIO_CONTENT))
@@ -174,13 +175,16 @@ def test_sync_recognize_content_with_optional_params_no_gax(self):
174175
alternative = SYNC_RECOGNIZE_RESPONSE['results'][0]['alternatives'][0]
175176
expected = Alternative.from_api_repr(alternative)
176177
self.assertEqual(len(response), 1)
177-
self.assertIsInstance(response[0], Alternative)
178-
self.assertEqual(response[0].transcript, expected.transcript)
179-
self.assertEqual(response[0].confidence, expected.confidence)
178+
self.assertIsInstance(response[0], Result)
179+
self.assertEqual(len(response[0].alternatives), 1)
180+
alternative = response[0].alternatives[0]
181+
self.assertEqual(alternative.transcript, expected.transcript)
182+
self.assertEqual(alternative.confidence, expected.confidence)
180183

181184
def test_sync_recognize_source_uri_without_optional_params_no_gax(self):
182185
from google.cloud import speech
183186
from google.cloud.speech.alternative import Alternative
187+
from google.cloud.speech.result import Result
184188
from unit_tests._fixtures import SYNC_RECOGNIZE_RESPONSE
185189

186190
RETURNED = SYNC_RECOGNIZE_RESPONSE
@@ -214,9 +218,12 @@ def test_sync_recognize_source_uri_without_optional_params_no_gax(self):
214218
expected = Alternative.from_api_repr(
215219
SYNC_RECOGNIZE_RESPONSE['results'][0]['alternatives'][0])
216220
self.assertEqual(len(response), 1)
217-
self.assertIsInstance(response[0], Alternative)
218-
self.assertEqual(response[0].transcript, expected.transcript)
219-
self.assertEqual(response[0].confidence, expected.confidence)
221+
self.assertIsInstance(response[0], Result)
222+
self.assertEqual(len(response[0].alternatives), 1)
223+
alternative = response[0].alternatives[0]
224+
225+
self.assertEqual(alternative.transcript, expected.transcript)
226+
self.assertEqual(alternative.confidence, expected.confidence)
220227

221228
def test_sync_recognize_with_empty_results_no_gax(self):
222229
from google.cloud import speech
@@ -710,19 +717,26 @@ class _MockGAPICSpeechAPI(object):
710717
_requests = None
711718
_response = None
712719
_results = None
720+
713721
SERVICE_ADDRESS = 'foo.apis.invalid'
714722

715723
def __init__(self, response=None, channel=None):
716724
self._response = response
717725
self._channel = channel
718726

719727
def async_recognize(self, config, audio):
728+
from google.gapic.longrunning.operations_client import OperationsClient
729+
from google.gax import _OperationFuture
720730
from google.longrunning.operations_pb2 import Operation
731+
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
732+
AsyncRecognizeResponse)
721733

722734
self.config = config
723735
self.audio = audio
724-
operation = Operation()
725-
return operation
736+
operations_client = mock.Mock(spec=OperationsClient)
737+
operation_future = _OperationFuture(Operation(), operations_client,
738+
AsyncRecognizeResponse, {})
739+
return operation_future
726740

727741
def sync_recognize(self, config, audio):
728742
self.config = config

system_tests/speech.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,10 @@ def test_sync_recognize_local_file(self):
144144

145145
results = self._make_sync_request(content=content,
146146
max_alternatives=2)
147-
self._check_results(results, 2)
147+
self.assertEqual(len(results), 1)
148+
alternatives = results[0].alternatives
149+
self.assertEqual(len(alternatives), 2)
150+
self._check_results(alternatives, 2)
148151

149152
def test_sync_recognize_gcs_file(self):
150153
bucket_name = Config.TEST_BUCKET.name
@@ -155,9 +158,10 @@ def test_sync_recognize_gcs_file(self):
155158
blob.upload_from_file(file_obj)
156159

157160
source_uri = 'gs://%s/%s' % (bucket_name, blob_name)
158-
result = self._make_sync_request(source_uri=source_uri,
159-
max_alternatives=1)
160-
self._check_results(result)
161+
results = self._make_sync_request(source_uri=source_uri,
162+
max_alternatives=1)
163+
self.assertEqual(len(results), 1)
164+
self._check_results(results[0].alternatives)
161165

162166
def test_async_recognize_local_file(self):
163167
with open(AUDIO_FILE, 'rb') as file_obj:
@@ -167,7 +171,10 @@ def test_async_recognize_local_file(self):
167171
max_alternatives=2)
168172

169173
_wait_until_complete(operation)
170-
self._check_results(operation.results, 2)
174+
self.assertEqual(len(operation.results), 1)
175+
alternatives = operation.results[0].alternatives
176+
self.assertEqual(len(alternatives), 2)
177+
self._check_results(alternatives, 2)
171178

172179
def test_async_recognize_gcs_file(self):
173180
bucket_name = Config.TEST_BUCKET.name
@@ -182,7 +189,10 @@ def test_async_recognize_gcs_file(self):
182189
max_alternatives=2)
183190

184191
_wait_until_complete(operation)
185-
self._check_results(operation.results, 2)
192+
self.assertEqual(len(operation.results), 1)
193+
alternatives = operation.results[0].alternatives
194+
self.assertEqual(len(alternatives), 2)
195+
self._check_results(alternatives, 2)
186196

187197
def test_stream_recognize(self):
188198
if not Config.USE_GAX:

0 commit comments

Comments
 (0)