Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add speech async gapic #2663

Merged
merged 1 commit into from
Nov 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions 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 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 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',
]

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.


setup(
Expand Down
158 changes: 104 additions & 54 deletions 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(

This comment was marked as spam.

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(

This comment was marked as spam.

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

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.


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)

This comment was marked as spam.


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

This comment was marked as spam.


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
5 changes: 0 additions & 5 deletions system_tests/speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ def _make_async_request(self, content=None, source_uri=None,

def _check_results(self, results, num_results=1):
self.assertEqual(len(results), num_results)

top_result = results[0]
self.assertIsInstance(top_result, Alternative)
self.assertEqual(top_result.transcript,
Expand Down Expand Up @@ -153,8 +152,6 @@ def test_sync_recognize_gcs_file(self):
self._check_results(result)

def test_async_recognize_local_file(self):
if Config.USE_GAX:
self.skipTest('async_recognize gRPC not yet implemented.')
with open(AUDIO_FILE, 'rb') as file_obj:
content = file_obj.read()

Expand All @@ -165,8 +162,6 @@ def test_async_recognize_local_file(self):
self._check_results(operation.results, 2)

def test_async_recognize_gcs_file(self):
if Config.USE_GAX:
self.skipTest('async_recognize gRPC not yet implemented.')
bucket_name = Config.TEST_BUCKET.name
blob_name = 'hello.wav'
blob = Config.TEST_BUCKET.blob(blob_name)
Expand Down