diff --git a/docs/index.rst b/docs/index.rst index e66c09e8d1e8..c026d3bf8887 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -122,6 +122,14 @@ monitoring-timeseries monitoring-label +.. toctree:: + :maxdepth: 0 + :hidden: + :caption: Translate + + translate-usage + Client + .. toctree:: :maxdepth: 0 :hidden: diff --git a/docs/translate-client.rst b/docs/translate-client.rst new file mode 100644 index 000000000000..14c76ba9d6c0 --- /dev/null +++ b/docs/translate-client.rst @@ -0,0 +1,15 @@ +Translate Client +================ + +.. automodule:: gcloud.translate.client + :members: + :undoc-members: + :show-inheritance: + +Connection +~~~~~~~~~~ + +.. automodule:: gcloud.translate.connection + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/translate-usage.rst b/docs/translate-usage.rst new file mode 100644 index 000000000000..f79f7e07d1c5 --- /dev/null +++ b/docs/translate-usage.rst @@ -0,0 +1,123 @@ +Using the API +============= + +With `Google Translate`_, you can dynamically translate text +between thousands of language pairs. The Google Translate API +lets websites and programs integrate with Google Translate +programmatically. Google Translate API is available as a +paid service. See the `Pricing`_ and `FAQ`_ pages for details. + +Authentication / Configuration +------------------------------ + +- Use :class:`~gcloud.translate.client.Client` objects to configure + your applications. + +- :class:`~gcloud.translate.client.Client` objects hold both a ``key`` + and a connection to the Translate service. + +- **An API key is required for Translate.** See + `Identifying your application to Google`_ for details. This is + significantly different than the other clients in ``gcloud-python``. + +Methods +------- + +To create a client: + + .. code:: + + >>> from gcloud import translate + >>> client = translate.Client('my-api-key') + +By default, the client targets English when doing detections +and translations, but a non-default value can be used as +well: + + .. code:: + + >>> from gcloud import translate + >>> client = translate.Client('my-api-key', target_language='es') + +The Google Translate API has three supported methods, and they +map to three methods on a client: +:meth:`~gcloud.translate.client.Client.get_languages`, +:meth:`~gcloud.translate.client.Client.detect_language` and +:meth:`~gcloud.translate.client.Client.translate`. + +To get a list of languages supported by Google Translate + + .. code:: + + >>> from gcloud import translate + >>> client = translate.Client('my-api-key') + >>> client.get_languages() + [ + { + 'language': 'af', + 'name': 'Afrikaans', + }, + ... + ] + +To detect the language that some given text is written in: + + .. code:: + + >>> from gcloud import translate + >>> client = translate.Client('my-api-key') + >>> client.detect_language(['Me llamo', 'I am']) + [ + { + 'confidence': 0.25830904, + 'input': 'Me llamo', + 'language': 'es', + }, { + 'confidence': 0.17112699, + 'input': 'I am', + 'language': 'en', + }, + ] + +The `confidence`_ value is an optional floating point value between 0 and 1. +The closer this value is to 1, the higher the confidence level for the +language detection. This member is not always available. + +To translate text: + + .. code:: + + >>> from gcloud import translate + >>> client = translate.Client('my-api-key') + >>> client.translate('koszula') + { + 'translatedText': 'shirt', + 'detectedSourceLanguage': 'pl', + 'input': 'koszula', + } + +or to use a non-default target language: + + .. code:: + + >>> from gcloud import translate + >>> client = translate.Client('my-api-key') + >>> client.translate(['Me llamo Jeff', 'My name is Jeff'], + ... target_language='de') + [ + { + 'translatedText': 'Mein Name ist Jeff', + 'detectedSourceLanguage': 'es', + 'input': 'Me llamo Jeff', + }, { + 'translatedText': 'Mein Name ist Jeff', + 'detectedSourceLanguage': 'en', + 'input': 'My name is Jeff', + }, + ] + +.. _Google Translate: https://cloud.google.com/translate +.. _Pricing: https://cloud.google.com/translate/v2/pricing.html +.. _FAQ: https://cloud.google.com/translate/v2/faq.html +.. _Identifying your application to Google: https://cloud.google.com/translate/v2/using_rest#auth +.. _confidence: https://cloud.google.com/translate/v2/detecting-language-with-rest diff --git a/gcloud/connection.py b/gcloud/connection.py index cb8f7840732a..b7518d020afc 100644 --- a/gcloud/connection.py +++ b/gcloud/connection.py @@ -162,9 +162,10 @@ def build_api_url(cls, path, query_params=None, :type path: string :param path: The path to the resource (ie, ``'/b/bucket-name'``). - :type query_params: dict - :param query_params: A dictionary of keys and values to insert into - the query string of the URL. + :type query_params: dict or list + :param query_params: A dictionary of keys and values (or list of + key-value pairs) to insert into the query + string of the URL. :type api_base_url: string :param api_base_url: The base URL for the API endpoint. @@ -286,10 +287,10 @@ def api_request(self, method, path, query_params=None, :param path: The path to the resource (ie, ``'/b/bucket-name'``). Required. - :type query_params: dict - :param query_params: A dictionary of keys and values to insert into - the query string of the URL. Default is - empty dict. + :type query_params: dict or list + :param query_params: A dictionary of keys and values (or list of + key-value pairs) to insert into the query + string of the URL. :type data: string :param data: The data to send as the body of the request. Default is diff --git a/gcloud/pubsub/connection.py b/gcloud/pubsub/connection.py index 9826e45f2bf0..e3394b3bd888 100644 --- a/gcloud/pubsub/connection.py +++ b/gcloud/pubsub/connection.py @@ -67,9 +67,10 @@ def build_api_url(self, path, query_params=None, :type path: string :param path: The path to the resource. - :type query_params: dict - :param query_params: A dictionary of keys and values to insert into - the query string of the URL. + :type query_params: dict or list + :param query_params: A dictionary of keys and values (or list of + key-value pairs) to insert into the query + string of the URL. :type api_base_url: string :param api_base_url: The base URL for the API endpoint. diff --git a/gcloud/translate/__init__.py b/gcloud/translate/__init__.py new file mode 100644 index 000000000000..cd92828db3e4 --- /dev/null +++ b/gcloud/translate/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Translate API wrapper.""" + +from gcloud.translate.client import Client +from gcloud.translate.connection import Connection diff --git a/gcloud/translate/client.py b/gcloud/translate/client.py new file mode 100644 index 000000000000..04d70f4d04ef --- /dev/null +++ b/gcloud/translate/client.py @@ -0,0 +1,224 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Client for interacting with the Google Cloud Translate API.""" + + +import httplib2 +import six + +from gcloud._helpers import _to_bytes +from gcloud.translate.connection import Connection + + +ENGLISH_ISO_639 = 'en' +"""ISO 639-1 language code for English.""" + + +class Client(object): + """Client to bundle configuration needed for API requests. + + :type api_key: str + :param api_key: The key used to send with requests as a query + parameter. + + :type http: :class:`httplib2.Http` or class that defines ``request()``. + :param http: (Optional) HTTP object to make requests. If not + passed, an :class:`httplib.Http` object is created. + + :type target_language: str + :param target_language: (Optional) The target language used for + translations and language names. (Defaults to + :data:`ENGLISH_ISO_639`.) + """ + + def __init__(self, api_key, http=None, target_language=ENGLISH_ISO_639): + self.api_key = api_key + if http is None: + http = httplib2.Http() + self.connection = Connection(http=http) + self.target_language = target_language + + def get_languages(self, target_language=None): + """Get list of supported languages for translation. + + Response + + See: https://cloud.google.com/translate/v2/\ + discovering-supported-languages-with-rest + + :type target_language: str + :param target_language: (Optional) The language used to localize + returned language names. Defaults to the + target language on the current client. + + :rtype: list + :returns: List of dictionaries. Each dictionary contains a supported + ISO 639-1 language code (using the dictionary key + ``language``). If ``target_language`` is passed, each + dictionary will also contain the name of each supported + language (localized to the target language). + """ + query_params = {'key': self.api_key} + if target_language is None: + target_language = self.target_language + if target_language is not None: + query_params['target'] = target_language + response = self.connection.api_request( + method='GET', path='/languages', query_params=query_params) + return response.get('data', {}).get('languages', ()) + + def detect_language(self, values): + """Detect the language of a string or list of strings. + + See: https://cloud.google.com/translate/v2/\ + detecting-language-with-rest + + :type values: str or list + :param values: String or list of strings that will have + language detected. + + :rtype: str or list + :returns: A list of dictionaries for each queried value. Each + dictionary typically contains three keys + + * ``confidence``: The confidence in language detection, a + float between 0 and 1. + * ``input``: The corresponding input value. + * ``language``: The detected language (as an ISO 639-1 + language code). + + though the key ``confidence`` may not always be present. + + If only a single value is passed, then only a single + dictionary will be returned. + :raises: :class:`ValueError ` if the number of + detections is not equal to the number of values. + :class:`ValueError ` if a value + produces a list of detections with 0 or multiple results + in it. + """ + single_value = False + if isinstance(values, six.string_types): + single_value = True + values = [values] + + query_params = [('key', self.api_key)] + query_params.extend(('q', _to_bytes(value, 'utf-8')) + for value in values) + response = self.connection.api_request( + method='GET', path='/detect', query_params=query_params) + detections = response.get('data', {}).get('detections', ()) + + if len(values) != len(detections): + raise ValueError('Expected same number of values and detections', + values, detections) + + for index, value in enumerate(values): + # Empirically, even clearly ambiguous text like "no" only returns + # a single detection, so we replace the list of detections with + # the single detection contained. + if len(detections[index]) == 1: + detections[index] = detections[index][0] + else: + message = ('Expected a single detection per value, API ' + 'returned %d') % (len(detections[index]),) + raise ValueError(message, value, detections[index]) + + detections[index]['input'] = value + # The ``isReliable`` field is deprecated. + detections[index].pop('isReliable', None) + + if single_value: + return detections[0] + else: + return detections + + def translate(self, values, target_language=None, format_=None, + source_language=None, customization_ids=()): + """Translate a string or list of strings. + + See: https://cloud.google.com/translate/v2/\ + translating-text-with-rest + + :type values: str or list + :param values: String or list of strings to translate. + + :type target_language: str + :param target_language: The language to translate results into. This + is required by the API and defaults to + the target language of the current instance. + + :type format_: str + :param format_: (Optional) One of ``text`` or ``html``, to specify + if the input text is plain text or HTML. + + :type source_language: str + :param source_language: (Optional) The language of the text to + be translated. + + :type customization_ids: str or list + :param customization_ids: (Optional) ID or list of customization IDs + for translation. Sets the ``cid`` parameter + in the query. + + :rtype: str or list list + :returns: A list of dictionaries for each queried value. Each + dictionary typically contains three keys (though not + all will be present in all cases) + + * ``detectedSourceLanguage``: The detected language (as an + ISO 639-1 language code) of the text. + * ``translatedText``: The translation of the text into the + target language. + * ``input``: The corresponding input value. + + If only a single value is passed, then only a single + dictionary will be returned. + :raises: :class:`ValueError ` if the number of + values and translations differ. + """ + single_value = False + if isinstance(values, six.string_types): + single_value = True + values = [values] + + if target_language is None: + target_language = self.target_language + if isinstance(customization_ids, six.string_types): + customization_ids = [customization_ids] + + query_params = [('key', self.api_key), ('target', target_language)] + query_params.extend(('q', _to_bytes(value, 'utf-8')) + for value in values) + query_params.extend(('cid', cid) for cid in customization_ids) + if format_ is not None: + query_params.append(('format', format_)) + if source_language is not None: + query_params.append(('source', source_language)) + + response = self.connection.api_request( + method='GET', path='', query_params=query_params) + + translations = response.get('data', {}).get('translations', ()) + if len(values) != len(translations): + raise ValueError('Expected iterations to have same length', + values, translations) + for value, translation in six.moves.zip(values, translations): + translation['input'] = value + + if single_value: + return translations[0] + else: + return translations diff --git a/gcloud/translate/connection.py b/gcloud/translate/connection.py new file mode 100644 index 000000000000..17c0f9edf764 --- /dev/null +++ b/gcloud/translate/connection.py @@ -0,0 +1,30 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Create / interact with Google Cloud Translate connections.""" + +from gcloud import connection as base_connection + + +class Connection(base_connection.JSONConnection): + """A connection to Google Cloud Translate via the JSON REST API.""" + + API_BASE_URL = 'https://www.googleapis.com' + """The base of the API call URL.""" + + API_VERSION = 'v2' + """The version of the API, used in building the API call's URL.""" + + API_URL_TEMPLATE = '{api_base_url}/language/translate/{api_version}{path}' + """A template for the URL of a particular API call.""" diff --git a/gcloud/translate/test_client.py b/gcloud/translate/test_client.py new file mode 100644 index 000000000000..2492e86ea839 --- /dev/null +++ b/gcloud/translate/test_client.py @@ -0,0 +1,378 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest2 + + +class TestClient(unittest2.TestCase): + + KEY = 'abc-123-my-key' + + def _getTargetClass(self): + from gcloud.translate.client import Client + return Client + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_ctor(self): + from gcloud.translate.connection import Connection + from gcloud.translate.client import ENGLISH_ISO_639 + + http = object() + client = self._makeOne(self.KEY, http=http) + self.assertTrue(isinstance(client.connection, Connection)) + self.assertIsNone(client.connection.credentials) + self.assertTrue(client.connection.http is http) + self.assertEqual(client.target_language, ENGLISH_ISO_639) + + def test_ctor_non_default(self): + from gcloud.translate.connection import Connection + + http = object() + target = 'es' + client = self._makeOne(self.KEY, http=http, target_language=target) + self.assertTrue(isinstance(client.connection, Connection)) + self.assertIsNone(client.connection.credentials) + self.assertTrue(client.connection.http is http) + self.assertEqual(client.target_language, target) + + def test_get_languages(self): + from gcloud.translate.client import ENGLISH_ISO_639 + + client = self._makeOne(self.KEY) + supported = [ + {'language': 'en', 'name': 'English'}, + {'language': 'af', 'name': 'Afrikaans'}, + {'language': 'am', 'name': 'Amharic'}, + ] + data = { + 'data': { + 'languages': supported, + }, + } + conn = client.connection = _Connection(data) + + result = client.get_languages() + self.assertEqual(result, supported) + + # Verify requested. + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '/languages') + self.assertEqual(req['query_params'], + {'key': self.KEY, 'target': ENGLISH_ISO_639}) + + def test_get_languages_no_target(self): + client = self._makeOne(self.KEY, target_language=None) + supported = [ + {'language': 'en'}, + {'language': 'af'}, + {'language': 'am'}, + ] + data = { + 'data': { + 'languages': supported, + }, + } + conn = client.connection = _Connection(data) + + result = client.get_languages() + self.assertEqual(result, supported) + + # Verify requested. + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '/languages') + self.assertEqual(req['query_params'], {'key': self.KEY}) + + def test_get_languages_explicit_target(self): + client = self._makeOne(self.KEY) + target_language = 'en' + supported = [ + {'language': 'en', 'name': 'Spanish'}, + {'language': 'af', 'name': 'Afrikaans'}, + {'language': 'am', 'name': 'Amharic'}, + ] + data = { + 'data': { + 'languages': supported, + }, + } + conn = client.connection = _Connection(data) + + result = client.get_languages(target_language) + self.assertEqual(result, supported) + + # Verify requested. + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '/languages') + self.assertEqual(req['query_params'], + {'key': self.KEY, 'target': target_language}) + + def test_detect_language_bad_result(self): + client = self._makeOne(self.KEY) + value = 'takoy' + conn = client.connection = _Connection({}) + + with self.assertRaises(ValueError): + client.detect_language(value) + + # Verify requested. + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '/detect') + query_params = [ + ('key', self.KEY), + ('q', value.encode('utf-8')), + ] + self.assertEqual(req['query_params'], query_params) + + def test_detect_language_single_value(self): + client = self._makeOne(self.KEY) + value = 'takoy' + detection = { + 'confidence': 1.0, + 'input': value, + 'language': 'ru', + 'isReliable': False, + } + data = { + 'data': { + 'detections': [[detection]], + }, + } + conn = client.connection = _Connection(data) + + result = client.detect_language(value) + self.assertEqual(result, detection) + + # Verify requested. + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '/detect') + query_params = [ + ('key', self.KEY), + ('q', value.encode('utf-8')), + ] + self.assertEqual(req['query_params'], query_params) + + def test_detect_language_multiple_values(self): + client = self._makeOne(self.KEY) + value1 = u'fa\xe7ade' # facade (with a cedilla) + detection1 = { + 'confidence': 0.6166008, + 'input': value1, + 'isReliable': False, + 'language': 'en', + } + value2 = 's\'il vous plait' + detection2 = { + 'confidence': 0.29728225, + 'input': value2, + 'isReliable': False, + 'language': 'fr', + } + data = { + 'data': { + 'detections': [ + [detection1], + [detection2], + ], + }, + } + conn = client.connection = _Connection(data) + + result = client.detect_language([value1, value2]) + self.assertEqual(result, [detection1, detection2]) + + # Verify requested. + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '/detect') + query_params = [ + ('key', self.KEY), + ('q', value1.encode('utf-8')), + ('q', value2.encode('utf-8')), + ] + self.assertEqual(req['query_params'], query_params) + + def test_detect_language_multiple_results(self): + client = self._makeOne(self.KEY) + value = 'soy' + detection1 = { + 'confidence': 0.81496066, + 'input': value, + 'language': 'es', + 'isReliable': False, + } + detection2 = { + 'confidence': 0.222, + 'input': value, + 'language': 'en', + 'isReliable': False, + } + data = { + 'data': { + 'detections': [[detection1, detection2]], + }, + } + client.connection = _Connection(data) + + with self.assertRaises(ValueError): + client.detect_language(value) + + def test_translate_bad_result(self): + client = self._makeOne(self.KEY) + value = 'hvala ti' + conn = client.connection = _Connection({}) + + with self.assertRaises(ValueError): + client.translate(value) + + # Verify requested. + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '') + query_params = [ + ('key', self.KEY), + ('target', 'en'), + ('q', value.encode('utf-8')), + ] + self.assertEqual(req['query_params'], query_params) + + def test_translate_defaults(self): + client = self._makeOne(self.KEY) + value = 'hvala ti' + translation = { + 'detectedSourceLanguage': 'hr', + 'translatedText': 'thank you', + 'input': value, + } + data = { + 'data': { + 'translations': [translation], + }, + } + conn = client.connection = _Connection(data) + + result = client.translate(value) + self.assertEqual(result, translation) + + # Verify requested. + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '') + query_params = [ + ('key', self.KEY), + ('target', 'en'), + ('q', value.encode('utf-8')), + ] + self.assertEqual(req['query_params'], query_params) + + def test_translate_multiple(self): + client = self._makeOne(self.KEY) + value1 = 'hvala ti' + translation1 = { + 'detectedSourceLanguage': 'hr', + 'translatedText': 'thank you', + 'input': value1, + } + value2 = 'Dankon' + translation2 = { + 'detectedSourceLanguage': 'eo', + 'translatedText': 'thank you', + 'input': value2, + } + data = { + 'data': { + 'translations': [translation1, translation2], + }, + } + conn = client.connection = _Connection(data) + + result = client.translate([value1, value2]) + self.assertEqual(result, [translation1, translation2]) + + # Verify requested. + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '') + query_params = [ + ('key', self.KEY), + ('target', 'en'), + ('q', value1.encode('utf-8')), + ('q', value2.encode('utf-8')), + ] + self.assertEqual(req['query_params'], query_params) + + def test_translate_explicit(self): + client = self._makeOne(self.KEY) + value = 'thank you' + target_language = 'eo' + source_language = 'en' + translation = { + 'translatedText': 'Dankon', + 'input': value, + } + data = { + 'data': { + 'translations': [translation], + }, + } + conn = client.connection = _Connection(data) + + cid = '123' + format_ = 'text' + result = client.translate(value, target_language=target_language, + source_language=source_language, + format_=format_, customization_ids=cid) + self.assertEqual(result, translation) + + # Verify requested. + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '') + query_params = [ + ('key', self.KEY), + ('target', target_language), + ('q', value.encode('utf-8')), + ('cid', cid), + ('format', format_), + ('source', source_language), + ] + self.assertEqual(req['query_params'], query_params) + + +class _Connection(object): + + def __init__(self, *responses): + self._responses = responses + self._requested = [] + + def api_request(self, **kw): + self._requested.append(kw) + response, self._responses = self._responses[0], self._responses[1:] + return response diff --git a/gcloud/translate/test_connection.py b/gcloud/translate/test_connection.py new file mode 100644 index 000000000000..766f41a619f1 --- /dev/null +++ b/gcloud/translate/test_connection.py @@ -0,0 +1,51 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest2 + + +class TestConnection(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud.translate.connection import Connection + return Connection + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_build_api_url_no_extra_query_params(self): + conn = self._makeOne() + URI = '/'.join([ + conn.API_BASE_URL, + 'language', + 'translate', + conn.API_VERSION, + 'foo', + ]) + self.assertEqual(conn.build_api_url('/foo'), URI) + + def test_build_api_url_w_extra_query_params(self): + from six.moves.urllib.parse import parse_qsl + from six.moves.urllib.parse import urlsplit + conn = self._makeOne() + query_params = [('q', 'val1'), ('q', 'val2')] + uri = conn.build_api_url('/foo', query_params=query_params) + scheme, netloc, path, qs, _ = urlsplit(uri) + self.assertEqual('%s://%s' % (scheme, netloc), + conn.API_BASE_URL) + expected_path = '/'.join( + ['', 'language', 'translate', conn.API_VERSION, 'foo']) + self.assertEqual(path, expected_path) + params = parse_qsl(qs) + self.assertEqual(params, query_params) diff --git a/scripts/verify_included_modules.py b/scripts/verify_included_modules.py index 57bcfde21855..fcba7d96dfa7 100644 --- a/scripts/verify_included_modules.py +++ b/scripts/verify_included_modules.py @@ -48,6 +48,7 @@ 'gcloud.streaming.stream_slice', 'gcloud.streaming.transfer', 'gcloud.streaming.util', + 'gcloud.translate.__init__', ]) diff --git a/system_tests/attempt_system_tests.py b/system_tests/attempt_system_tests.py index eecaa3ae33f4..c450f8da61d7 100644 --- a/system_tests/attempt_system_tests.py +++ b/system_tests/attempt_system_tests.py @@ -36,6 +36,7 @@ 'monitoring', 'pubsub', 'storage', + 'translate', ) if sys.version_info[:2] == (2, 7): MODULES += ('bigtable', 'bigtable-happybase') diff --git a/system_tests/run_system_test.py b/system_tests/run_system_test.py index fd2d1967f982..d0223a04eb1f 100644 --- a/system_tests/run_system_test.py +++ b/system_tests/run_system_test.py @@ -25,6 +25,7 @@ import pubsub import storage import system_test_utils +import translate TEST_MODULES = { @@ -36,6 +37,7 @@ 'bigtable-happybase': bigtable_happybase, 'logging': logging_, 'monitoring': monitoring, + 'translate': translate, } diff --git a/system_tests/translate.py b/system_tests/translate.py new file mode 100644 index 000000000000..d409a75bdebf --- /dev/null +++ b/system_tests/translate.py @@ -0,0 +1,86 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import os + +import unittest2 + +from gcloud import translate + + +ENV_VAR = 'GCLOUD_TESTS_API_KEY' + + +class Config(object): + """Run-time configuration to be modified at set-up. + + This is a mutable stand-in to allow test set-up to modify + global state. + """ + CLIENT = None + + +def setUpModule(): + api_key = os.getenv(ENV_VAR) + Config.CLIENT = translate.Client(api_key=api_key) + + +class TestTranslate(unittest2.TestCase): + + def test_get_languages(self): + result = Config.CLIENT.get_languages() + # There are **many** more than 10 languages. + self.assertGreater(len(result), 10) + + lang_map = {val['language']: val['name'] for val in result} + self.assertEqual(lang_map['en'], 'English') + self.assertEqual(lang_map['ja'], 'Japanese') + self.assertEqual(lang_map['lv'], 'Latvian') + self.assertEqual(lang_map['zu'], 'Zulu') + + def test_detect_language(self): + values = ['takoy', u'fa\xe7ade', 's\'il vous plait'] + detections = Config.CLIENT.detect_language(values) + self.assertEqual(len(values), len(detections)) + self.assertEqual(detections[0]['language'], 'ru') + self.assertEqual(detections[1]['language'], 'fr') + self.assertEqual(detections[2]['language'], 'fr') + + def test_translate(self): + values = ['hvala ti', 'dankon', + 'Me llamo Jeff', 'My name is Jeff'] + translations = Config.CLIENT.translate(values, + target_language='de') + self.assertEqual(len(values), len(translations)) + + self.assertEqual( + translations[0]['detectedSourceLanguage'], 'hr') + self.assertEqual( + translations[0]['translatedText'], 'danke') + + self.assertEqual( + translations[1]['detectedSourceLanguage'], 'eo') + self.assertEqual( + translations[1]['translatedText'], 'dank') + + self.assertEqual( + translations[2]['detectedSourceLanguage'], 'es') + self.assertEqual( + translations[2]['translatedText'], 'Mein Name ist Jeff') + + self.assertEqual( + translations[3]['detectedSourceLanguage'], 'en') + self.assertEqual( + translations[3]['translatedText'], 'Mein Name ist Jeff')