Skip to content

Commit

Permalink
Merge pull request #2663 from daspecster/add-speech-async-gapic
Browse files Browse the repository at this point in the history
Add speech async gapic
  • Loading branch information
daspecster authored Nov 3, 2016
2 parents 5f5b590 + e85d5f7 commit 5aa40c0
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 63 deletions.
35 changes: 30 additions & 5 deletions packages/google-cloud-speech/google/cloud/speech/_gax.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Expand Down
6 changes: 3 additions & 3 deletions packages/google-cloud-speech/google/cloud/speech/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion packages/google-cloud-speech/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
158 changes: 104 additions & 54 deletions packages/google-cloud-speech/unit_tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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

Expand All @@ -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)

Expand All @@ -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):
Expand Down

0 comments on commit 5aa40c0

Please sign in to comment.