Skip to content

Commit de65409

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

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy