Skip to content

Commit

Permalink
Cloud Speech Veneer does not raise for > 1 result. (#2962)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
lukesneeringer authored and daspecster committed Jan 25, 2017
1 parent 6b7b9ac commit a70815d
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 92 deletions.
28 changes: 14 additions & 14 deletions docs/speech-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ Great Britian.
>>> sample = client.sample(source_uri='gs://my-bucket/recording.flac',
... encoding=speech.Encoding.FLAC,
... sample_rate=44100)
>>> alternatives = sample.sync_recognize(
>>> results = sample.sync_recognize(
... speech.Encoding.FLAC, 16000,
... source_uri='gs://my-bucket/recording.flac', language_code='en-GB',
... max_alternatives=2)
>>> for alternative in alternatives:
>>> for result in results:
... print('=' * 20)
... print('transcript: ' + alternative.transcript)
... print('confidence: ' + alternative.confidence)
... print('transcript: ' + result.transcript)
... print('confidence: ' + result.confidence)
====================
transcript: Hello, this is a test
confidence: 0.81
Expand All @@ -112,12 +112,12 @@ Example of using the profanity filter.
>>> sample = client.sample(source_uri='gs://my-bucket/recording.flac',
... encoding=speech.Encoding.FLAC,
... sample_rate=44100)
>>> alternatives = sample.sync_recognize(max_alternatives=1,
... profanity_filter=True)
>>> for alternative in alternatives:
>>> results = sample.sync_recognize(max_alternatives=1,
... profanity_filter=True)
>>> for result in results:
... print('=' * 20)
... print('transcript: ' + alternative.transcript)
... print('confidence: ' + alternative.confidence)
... print('transcript: ' + result.transcript)
... print('confidence: ' + result.confidence)
====================
transcript: Hello, this is a f****** test
confidence: 0.81
Expand All @@ -134,12 +134,12 @@ words to the vocabulary of the recognizer.
... encoding=speech.Encoding.FLAC,
... sample_rate=44100)
>>> hints = ['hi', 'good afternoon']
>>> alternatives = sample.sync_recognize(max_alternatives=2,
... speech_context=hints)
>>> for alternative in alternatives:
>>> results = sample.sync_recognize(max_alternatives=2,
... speech_context=hints)
>>> for result in results:
... print('=' * 20)
... print('transcript: ' + alternative.transcript)
... print('confidence: ' + alternative.confidence)
... print('transcript: ' + result.transcript)
... print('confidence: ' + result.confidence)
====================
transcript: Hello, this is a test
confidence: 0.81
Expand Down
38 changes: 17 additions & 21 deletions speech/google/cloud/speech/_gax.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,23 @@


from google.cloud.gapic.speech.v1beta1.speech_client import SpeechClient
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 (
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import RecognitionAudio
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
RecognitionConfig)
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
SpeechContext)
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
StreamingRecognitionConfig)
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
StreamingRecognizeRequest)
from google.longrunning import operations_grpc

from google.cloud._helpers import make_secure_channel
from google.cloud._helpers import make_secure_stub
from google.cloud._http import DEFAULT_USER_AGENT

from google.cloud.speech.alternative import Alternative
from google.cloud.speech.operation import Operation
from google.cloud.speech.result import Result

OPERATIONS_API_HOST = 'speech.googleapis.com'

Expand Down Expand Up @@ -235,15 +237,9 @@ def sync_recognize(self, sample, language_code=None, max_alternatives=None,
words to the vocabulary of the recognizer.
:rtype: list
:returns: A list of dictionaries. One dict for each alternative. Each
dictionary typically contains two keys (though not
all will be present in all cases)
:returns: List of :class:`google.cloud.speech.result.Result` objects.
* ``transcript``: The detected text from the audio recording.
* ``confidence``: The confidence in language detection, float
between 0 and 1.
:raises: ValueError if more than one result is returned or no results.
:raises: ValueError if there are no results.
"""
config = RecognitionConfig(
encoding=sample.encoding, sample_rate=sample.sample_rate,
Expand All @@ -254,13 +250,13 @@ def sync_recognize(self, sample, language_code=None, max_alternatives=None,
uri=sample.source_uri)
api = self._gapic_api
api_response = api.sync_recognize(config=config, audio=audio)
if len(api_response.results) == 1:
results = api_response.results.pop()
alternatives = results.alternatives
return [Alternative.from_pb(alternative)
for alternative in alternatives]
else:
raise ValueError('More than one result or none returned from API.')

# Sanity check: If we got no results back, raise an error.
if len(api_response.results) == 0:
raise ValueError('No results returned from the Speech API.')

# Iterate over any results that came back.
return [Result.from_pb(result) for result in api_response.results]


def _stream_requests(sample, language_code=None, max_alternatives=None,
Expand Down
6 changes: 5 additions & 1 deletion speech/google/cloud/speech/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ class Client(BaseClient):
def __init__(self, credentials=None, http=None, use_gax=None):
super(Client, self).__init__(credentials=credentials, http=http)
self._connection = Connection(
credentials=self._credentials, http=self._http)
credentials=self._credentials,
http=self._http,
)

# Save on the actual client class whether we use GAX or not.
if use_gax is None:
self._use_gax = _USE_GAX
else:
Expand Down
18 changes: 10 additions & 8 deletions speech/google/cloud/speech/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

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

from google.cloud.grpc.speech.v1beta1 import cloud_speech_pb2
from google.cloud.proto.speech.v1beta1 import cloud_speech_pb2

from google.cloud import operation
from google.cloud.speech.alternative import Alternative
from google.cloud.speech.result import Result


operation.register_type(cloud_speech_pb2.AsyncRecognizeMetadata)
Expand Down Expand Up @@ -58,11 +58,13 @@ def _update_state(self, operation_pb):
if result_type != 'response':
return

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

result = pb_results[0]
self.results = [Alternative.from_pb(alternative)
for alternative in result.alternatives]
# Save the results to the Operation object.
self.results = []
for pb_result in pb_results:
self.results.append(Result.from_pb(pb_result))
69 changes: 67 additions & 2 deletions speech/google/cloud/speech/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,53 @@
from google.cloud.speech.alternative import Alternative


class Result(object):
"""Speech recognition result representation.
This is the object that comes back on sync or async requests
(but not streaming requests).
:type alternatives: list
:param alternatives: List of
:class:`~google.cloud.speech.alternative.Alternative`.
"""
def __init__(self, alternatives):
self.alternatives = alternatives

@classmethod
def from_pb(cls, result):
"""Factory: construct instance of ``SpeechRecognitionResult``.
:type result: :class:`~google.cloud.grpc.speech.v1beta1\
.cloud_speech_pb2.StreamingRecognizeResult`
:param result: Instance of ``StreamingRecognizeResult`` protobuf.
:rtype: :class:`~google.cloud.speech.result.SpeechRecognitionResult`
:returns: Instance of ``SpeechRecognitionResult``.
"""
alternatives = [Alternative.from_pb(result) for result
in result.alternatives]
return cls(alternatives=alternatives)

@property
def confidence(self):
"""Return the confidence for the most probable alternative.
:rtype: float
:returns: Confidence value, between 0 and 1.
"""
return self.alternatives[0].confidence

@property
def transcript(self):
"""Return the transcript for the most probable alternative.
:rtype: str
:returns: Speech transcript.
"""
return self.alternatives[0].transcript


class StreamingSpeechResult(object):
"""Streaming speech result representation.
Expand Down Expand Up @@ -46,9 +93,27 @@ def from_pb(cls, response):
:rtype: :class:`~google.cloud.speech.result.StreamingSpeechResult`
:returns: Instance of ``StreamingSpeechResult``.
"""
alternatives = [Alternative.from_pb(alternative)
for alternative in response.alternatives]
alternatives = [Alternative.from_pb(result) for result
in response.alternatives]
is_final = response.is_final
stability = response.stability
return cls(alternatives=alternatives, is_final=is_final,
stability=stability)

@property
def confidence(self):
"""Return the confidence for the most probable alternative.
:rtype: float
:returns: Confidence value, between 0 and 1.
"""
return self.alternatives[0].confidence

@property
def transcript(self):
"""Return the transcript for the most probable alternative.
:rtype: str
:returns: Speech transcript.
"""
return self.alternatives[0].transcript
2 changes: 1 addition & 1 deletion speech/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
REQUIREMENTS = [
'google-cloud-core >= 0.22.1, < 0.23dev',
'grpcio >= 1.0.2, < 2.0dev',
'gapic-google-cloud-speech-v1beta1 >= 0.14.0, < 0.15dev',
'gapic-google-cloud-speech-v1beta1 >= 0.15.0, < 0.16dev',
]

setup(
Expand Down
15 changes: 4 additions & 11 deletions speech/unit_tests/test__gax.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,8 @@ def _call_fut(self, sample, language_code, max_alternatives,
def test_ctor(self):
from google.cloud import speech
from google.cloud.speech.sample import Sample
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 (
StreamingRecognitionConfig)
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
RecognitionConfig, SpeechContext, StreamingRecognitionConfig,
StreamingRecognizeRequest)

sample = Sample(content=self.AUDIO_CONTENT,
Expand Down Expand Up @@ -103,10 +98,8 @@ def test_stream_requests(self):
from io import BytesIO
from google.cloud import speech
from google.cloud.speech.sample import Sample
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
StreamingRecognitionConfig)
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
StreamingRecognizeRequest)
from google.cloud.proto.speech.v1beta1.cloud_speech_pb2 import (
StreamingRecognitionConfig, StreamingRecognizeRequest)

sample = Sample(stream=BytesIO(self.AUDIO_CONTENT),
encoding=speech.Encoding.FLAC,
Expand Down
2 changes: 1 addition & 1 deletion speech/unit_tests/test_alternative.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_from_api_repr_with_no_confidence(self):
self.assertIsNone(alternative.confidence)

def test_from_pb_with_no_confidence(self):
from google.cloud.grpc.speech.v1beta1 import cloud_speech_pb2
from google.cloud.proto.speech.v1beta1 import cloud_speech_pb2

text = 'the double trouble'
pb_value = cloud_speech_pb2.SpeechRecognitionAlternative(
Expand Down
Loading

0 comments on commit a70815d

Please sign in to comment.