Skip to content

Commit f5a6ec9

Browse files
lukesneeringerdaspecster
authored andcommitted
Cloud Speech Veneer does not raise for > 1 result. (#2962)
* Cloud Speech Veneer does not raise for > 1 result. This commit also: - adds `transcript` and `confidence` convenience methods on result objects - updates unit tests appropriately - moves from google.cloud.grpc.* to google.cloud.proto.* - updates dependencies accordingly * Fix linting error, and make imports follow PEP8. * Fix two more linting issues. * Address @daspecster suggestions. Also finally fix all linting issues. * Update speech usage RST file. * Swap back to a list return, fixes coverage. * Final lint change for @daspecster. * Move imports to one per two lines.
1 parent c532aef commit f5a6ec9

File tree

9 files changed

+162
-78
lines changed

9 files changed

+162
-78
lines changed

google-cloud-speech/google/cloud/speech/_gax.py

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,23 @@
1616

1717

1818
from google.cloud.gapic.speech.v1beta1.speech_client import SpeechClient
19-
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import RecognitionAudio
20-
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import RecognitionConfig
21-
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import SpeechContext
22-
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
19+
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import RecognitionAudio
20+
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
21+
RecognitionConfig)
22+
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
23+
SpeechContext)
24+
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
2325
StreamingRecognitionConfig)
24-
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
26+
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
2527
StreamingRecognizeRequest)
2628
from google.longrunning import operations_grpc
2729

2830
from google.cloud._helpers import make_secure_channel
2931
from google.cloud._helpers import make_secure_stub
3032
from google.cloud._http import DEFAULT_USER_AGENT
3133

32-
from google.cloud.speech.alternative import Alternative
3334
from google.cloud.speech.operation import Operation
35+
from google.cloud.speech.result import Result
3436

3537
OPERATIONS_API_HOST = 'speech.googleapis.com'
3638

@@ -235,15 +237,9 @@ def sync_recognize(self, sample, language_code=None, max_alternatives=None,
235237
words to the vocabulary of the recognizer.
236238
237239
:rtype: list
238-
:returns: A list of dictionaries. One dict for each alternative. Each
239-
dictionary typically contains two keys (though not
240-
all will be present in all cases)
240+
:returns: List of :class:`google.cloud.speech.result.Result` objects.
241241
242-
* ``transcript``: The detected text from the audio recording.
243-
* ``confidence``: The confidence in language detection, float
244-
between 0 and 1.
245-
246-
:raises: ValueError if more than one result is returned or no results.
242+
:raises: ValueError if there are no results.
247243
"""
248244
config = RecognitionConfig(
249245
encoding=sample.encoding, sample_rate=sample.sample_rate,
@@ -254,13 +250,13 @@ def sync_recognize(self, sample, language_code=None, max_alternatives=None,
254250
uri=sample.source_uri)
255251
api = self._gapic_api
256252
api_response = api.sync_recognize(config=config, audio=audio)
257-
if len(api_response.results) == 1:
258-
results = api_response.results.pop()
259-
alternatives = results.alternatives
260-
return [Alternative.from_pb(alternative)
261-
for alternative in alternatives]
262-
else:
263-
raise ValueError('More than one result or none returned from API.')
253+
254+
# Sanity check: If we got no results back, raise an error.
255+
if len(api_response.results) == 0:
256+
raise ValueError('No results returned from the Speech API.')
257+
258+
# Iterate over any results that came back.
259+
return [Result.from_pb(result) for result in api_response.results]
264260

265261

266262
def _stream_requests(sample, language_code=None, max_alternatives=None,

google-cloud-speech/google/cloud/speech/client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ class Client(BaseClient):
6060
def __init__(self, credentials=None, http=None, use_gax=None):
6161
super(Client, self).__init__(credentials=credentials, http=http)
6262
self._connection = Connection(
63-
credentials=self._credentials, http=self._http)
63+
credentials=self._credentials,
64+
http=self._http,
65+
)
66+
67+
# Save on the actual client class whether we use GAX or not.
6468
if use_gax is None:
6569
self._use_gax = _USE_GAX
6670
else:

google-cloud-speech/google/cloud/speech/operation.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414

1515
"""Long running operation representation for Google Speech API"""
1616

17-
from google.cloud.grpc.speech.v1beta1 import cloud_speech_pb2
17+
from google.cloud.proto.speech.v1beta1 import cloud_speech_pb2
1818

1919
from google.cloud import operation
20-
from google.cloud.speech.alternative import Alternative
20+
from google.cloud.speech.result import Result
2121

2222

2323
operation.register_type(cloud_speech_pb2.AsyncRecognizeMetadata)
@@ -58,11 +58,13 @@ def _update_state(self, operation_pb):
5858
if result_type != 'response':
5959
return
6060

61+
# Retrieve the results.
62+
# If there were no results at all, raise an exception.
6163
pb_results = self.response.results
62-
if len(pb_results) != 1:
63-
raise ValueError('Expected exactly one result, found:',
64-
pb_results)
64+
if len(pb_results) == 0:
65+
raise ValueError('Speech API returned no results.')
6566

66-
result = pb_results[0]
67-
self.results = [Alternative.from_pb(alternative)
68-
for alternative in result.alternatives]
67+
# Save the results to the Operation object.
68+
self.results = []
69+
for pb_result in pb_results:
70+
self.results.append(Result.from_pb(pb_result))

google-cloud-speech/google/cloud/speech/result.py

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,53 @@
1717
from google.cloud.speech.alternative import Alternative
1818

1919

20+
class Result(object):
21+
"""Speech recognition result representation.
22+
23+
This is the object that comes back on sync or async requests
24+
(but not streaming requests).
25+
26+
:type alternatives: list
27+
:param alternatives: List of
28+
:class:`~google.cloud.speech.alternative.Alternative`.
29+
"""
30+
def __init__(self, alternatives):
31+
self.alternatives = alternatives
32+
33+
@classmethod
34+
def from_pb(cls, result):
35+
"""Factory: construct instance of ``SpeechRecognitionResult``.
36+
37+
:type result: :class:`~google.cloud.grpc.speech.v1beta1\
38+
.cloud_speech_pb2.StreamingRecognizeResult`
39+
:param result: Instance of ``StreamingRecognizeResult`` protobuf.
40+
41+
:rtype: :class:`~google.cloud.speech.result.SpeechRecognitionResult`
42+
:returns: Instance of ``SpeechRecognitionResult``.
43+
"""
44+
alternatives = [Alternative.from_pb(result) for result
45+
in result.alternatives]
46+
return cls(alternatives=alternatives)
47+
48+
@property
49+
def confidence(self):
50+
"""Return the confidence for the most probable alternative.
51+
52+
:rtype: float
53+
:returns: Confidence value, between 0 and 1.
54+
"""
55+
return self.alternatives[0].confidence
56+
57+
@property
58+
def transcript(self):
59+
"""Return the transcript for the most probable alternative.
60+
61+
:rtype: str
62+
:returns: Speech transcript.
63+
"""
64+
return self.alternatives[0].transcript
65+
66+
2067
class StreamingSpeechResult(object):
2168
"""Streaming speech result representation.
2269
@@ -46,9 +93,27 @@ def from_pb(cls, response):
4693
:rtype: :class:`~google.cloud.speech.result.StreamingSpeechResult`
4794
:returns: Instance of ``StreamingSpeechResult``.
4895
"""
49-
alternatives = [Alternative.from_pb(alternative)
50-
for alternative in response.alternatives]
96+
alternatives = [Alternative.from_pb(result) for result
97+
in response.alternatives]
5198
is_final = response.is_final
5299
stability = response.stability
53100
return cls(alternatives=alternatives, is_final=is_final,
54101
stability=stability)
102+
103+
@property
104+
def confidence(self):
105+
"""Return the confidence for the most probable alternative.
106+
107+
:rtype: float
108+
:returns: Confidence value, between 0 and 1.
109+
"""
110+
return self.alternatives[0].confidence
111+
112+
@property
113+
def transcript(self):
114+
"""Return the transcript for the most probable alternative.
115+
116+
:rtype: str
117+
:returns: Speech transcript.
118+
"""
119+
return self.alternatives[0].transcript

google-cloud-speech/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
REQUIREMENTS = [
5252
'google-cloud-core >= 0.22.1, < 0.23dev',
5353
'grpcio >= 1.0.2, < 2.0dev',
54-
'gapic-google-cloud-speech-v1beta1 >= 0.14.0, < 0.15dev',
54+
'gapic-google-cloud-speech-v1beta1 >= 0.15.0, < 0.16dev',
5555
]
5656

5757
setup(

google-cloud-speech/unit_tests/test__gax.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,8 @@ def _call_fut(self, sample, language_code, max_alternatives,
3636
def test_ctor(self):
3737
from google.cloud import speech
3838
from google.cloud.speech.sample import Sample
39-
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
40-
SpeechContext)
41-
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
42-
RecognitionConfig)
43-
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
44-
StreamingRecognitionConfig)
45-
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
39+
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
40+
RecognitionConfig, SpeechContext, StreamingRecognitionConfig,
4641
StreamingRecognizeRequest)
4742

4843
sample = Sample(content=self.AUDIO_CONTENT,
@@ -103,10 +98,8 @@ def test_stream_requests(self):
10398
from io import BytesIO
10499
from google.cloud import speech
105100
from google.cloud.speech.sample import Sample
106-
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
107-
StreamingRecognitionConfig)
108-
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
109-
StreamingRecognizeRequest)
101+
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
102+
StreamingRecognitionConfig, StreamingRecognizeRequest)
110103

111104
sample = Sample(stream=BytesIO(self.AUDIO_CONTENT),
112105
encoding=speech.Encoding.FLAC,

google-cloud-speech/unit_tests/test_alternative.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def test_from_api_repr_with_no_confidence(self):
5454
self.assertIsNone(alternative.confidence)
5555

5656
def test_from_pb_with_no_confidence(self):
57-
from google.cloud.grpc.speech.v1beta1 import cloud_speech_pb2
57+
from google.cloud.proto.speech.v1beta1 import cloud_speech_pb2
5858

5959
text = 'the double trouble'
6060
pb_value = cloud_speech_pb2.SpeechRecognitionAlternative(

google-cloud-speech/unit_tests/test_client.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def _make_credentials():
2424

2525

2626
def _make_result(alternatives=()):
27-
from google.cloud.grpc.speech.v1beta1 import cloud_speech_pb2
27+
from google.cloud.proto.speech.v1beta1 import cloud_speech_pb2
2828

2929
return cloud_speech_pb2.SpeechRecognitionResult(
3030
alternatives=[
@@ -37,7 +37,7 @@ def _make_result(alternatives=()):
3737

3838

3939
def _make_streaming_result(alternatives=(), is_final=True, stability=1.0):
40-
from google.cloud.grpc.speech.v1beta1 import cloud_speech_pb2
40+
from google.cloud.proto.speech.v1beta1 import cloud_speech_pb2
4141

4242
return cloud_speech_pb2.StreamingRecognitionResult(
4343
alternatives=[
@@ -52,7 +52,7 @@ def _make_streaming_result(alternatives=(), is_final=True, stability=1.0):
5252

5353

5454
def _make_streaming_response(*results):
55-
from google.cloud.grpc.speech.v1beta1 import cloud_speech_pb2
55+
from google.cloud.proto.speech.v1beta1 import cloud_speech_pb2
5656

5757
response = cloud_speech_pb2.StreamingRecognizeResponse(
5858
results=results,
@@ -61,7 +61,7 @@ def _make_streaming_response(*results):
6161

6262

6363
def _make_sync_response(*results):
64-
from google.cloud.grpc.speech.v1beta1 import cloud_speech_pb2
64+
from google.cloud.proto.speech.v1beta1 import cloud_speech_pb2
6565

6666
response = cloud_speech_pb2.SyncRecognizeResponse(
6767
results=results,
@@ -202,7 +202,7 @@ def test_sync_recognize_source_uri_without_optional_params_no_gax(self):
202202
sample = client.sample(source_uri=self.AUDIO_SOURCE_URI,
203203
encoding=encoding, sample_rate=self.SAMPLE_RATE)
204204

205-
response = sample.sync_recognize()
205+
response = [i for i in sample.sync_recognize()]
206206

207207
self.assertEqual(len(client._connection._requested), 1)
208208
req = client._connection._requested[0]
@@ -231,7 +231,7 @@ def test_sync_recognize_with_empty_results_no_gax(self):
231231
sample_rate=self.SAMPLE_RATE)
232232

233233
with self.assertRaises(ValueError):
234-
sample.sync_recognize()
234+
next(sample.sync_recognize())
235235

236236
def test_sync_recognize_with_empty_results_gax(self):
237237
from google.cloud._testing import _Monkey
@@ -274,7 +274,7 @@ def speech_api(channel=None):
274274
sample_rate=self.SAMPLE_RATE)
275275

276276
with self.assertRaises(ValueError):
277-
sample.sync_recognize()
277+
next(sample.sync_recognize())
278278

279279
def test_sync_recognize_with_gax(self):
280280
from google.cloud._testing import _Monkey
@@ -326,16 +326,19 @@ def speech_api(channel=None):
326326
self.assertEqual(
327327
channel_args, [(creds, _gax.DEFAULT_USER_AGENT, host)])
328328

329-
results = sample.sync_recognize()
329+
results = [i for i in sample.sync_recognize()]
330330

331-
self.assertEqual(len(results), 2)
331+
self.assertEqual(len(results), 1)
332+
self.assertEqual(len(results[0].alternatives), 2)
332333
self.assertEqual(results[0].transcript,
334+
results[0].alternatives[0].transcript,
333335
alternatives[0]['transcript'])
334336
self.assertEqual(results[0].confidence,
337+
results[0].alternatives[0].confidence,
335338
alternatives[0]['confidence'])
336-
self.assertEqual(results[1].transcript,
339+
self.assertEqual(results[0].alternatives[1].transcript,
337340
alternatives[1]['transcript'])
338-
self.assertEqual(results[1].confidence,
341+
self.assertEqual(results[0].alternatives[1].confidence,
339342
alternatives[1]['confidence'])
340343

341344
def test_async_supported_encodings(self):
@@ -535,19 +538,23 @@ def speech_api(channel=None):
535538
self.assertEqual(results[0].stability, 0.122435)
536539
self.assertEqual(results[1].stability, 0.1432343)
537540
self.assertFalse(results[1].is_final)
538-
self.assertEqual(results[1].alternatives[0].transcript,
541+
self.assertEqual(results[1].transcript,
542+
results[1].alternatives[0].transcript,
539543
alternatives[0]['transcript'])
540-
self.assertEqual(results[1].alternatives[0].confidence,
544+
self.assertEqual(results[1].confidence,
545+
results[1].alternatives[0].confidence,
541546
alternatives[0]['confidence'])
542547
self.assertEqual(results[1].alternatives[1].transcript,
543548
alternatives[1]['transcript'])
544549
self.assertEqual(results[1].alternatives[1].confidence,
545550
alternatives[1]['confidence'])
546551
self.assertTrue(results[2].is_final)
547552
self.assertEqual(results[2].stability, 0.9834534)
548-
self.assertEqual(results[2].alternatives[0].transcript,
553+
self.assertEqual(results[2].transcript,
554+
results[2].alternatives[0].transcript,
549555
alternatives[0]['transcript'])
550-
self.assertEqual(results[2].alternatives[0].confidence,
556+
self.assertEqual(results[2].confidence,
557+
results[2].alternatives[0].confidence,
551558
alternatives[0]['confidence'])
552559

553560
def test_stream_recognize(self):

0 commit comments

Comments
 (0)