diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ebf34a43cb75..e7f5bf193753 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -235,8 +235,6 @@ Running System Tests `docs `__ for more details. In order for Logging system tests to work, the Service Account will also have to be made a project Owner. This can be changed under "IAM & Admin". - - ``GOOGLE_CLOUD_TESTS_API_KEY``: The API key for your project with - the Google Translate API (and others) enabled. - Examples of these can be found in ``system_tests/local_test_setup.sample``. We recommend copying this to ``system_tests/local_test_setup``, editing the diff --git a/system_tests/local_test_setup.sample b/system_tests/local_test_setup.sample index 4401bc4e72e4..63eb733a7c85 100644 --- a/system_tests/local_test_setup.sample +++ b/system_tests/local_test_setup.sample @@ -1,4 +1,3 @@ export GOOGLE_APPLICATION_CREDENTIALS="app_credentials.json.sample" export GOOGLE_CLOUD_TESTING_REMOTE="upstream" export GOOGLE_CLOUD_TESTING_BRANCH="master" -export GOOGLE_CLOUD_TESTS_API_KEY="abcd1234" diff --git a/system_tests/translate.py b/system_tests/translate.py index ce016fbf77b4..402be16399be 100644 --- a/system_tests/translate.py +++ b/system_tests/translate.py @@ -13,16 +13,11 @@ # limitations under the License. -import os - import unittest from google.cloud import translate -ENV_VAR = 'GOOGLE_CLOUD_TESTS_API_KEY' - - class Config(object): """Run-time configuration to be modified at set-up. @@ -33,8 +28,7 @@ class Config(object): def setUpModule(): - api_key = os.getenv(ENV_VAR) - Config.CLIENT = translate.Client(api_key=api_key) + Config.CLIENT = translate.Client() class TestTranslate(unittest.TestCase): @@ -61,8 +55,8 @@ def test_detect_language(self): def test_translate(self): values = ['hvala ti', 'dankon', 'Me llamo Jeff', 'My name is Jeff'] - translations = Config.CLIENT.translate(values, - target_language='de') + translations = Config.CLIENT.translate( + values, target_language='de', model=translate.NMT) self.assertEqual(len(values), len(translations)) self.assertEqual( diff --git a/translate/google/cloud/translate/__init__.py b/translate/google/cloud/translate/__init__.py index c58b22301ee5..83ff5f114435 100644 --- a/translate/google/cloud/translate/__init__.py +++ b/translate/google/cloud/translate/__init__.py @@ -14,5 +14,7 @@ """Google Cloud Translate API wrapper.""" +from google.cloud.translate.client import BASE from google.cloud.translate.client import Client +from google.cloud.translate.client import NMT from google.cloud.translate.connection import Connection diff --git a/translate/google/cloud/translate/client.py b/translate/google/cloud/translate/client.py index f28bd996dffd..47c692833abf 100644 --- a/translate/google/cloud/translate/client.py +++ b/translate/google/cloud/translate/client.py @@ -19,37 +19,57 @@ import six from google.cloud._helpers import _to_bytes +from google.cloud.client import Client as BaseClient from google.cloud.translate.connection import Connection ENGLISH_ISO_639 = 'en' """ISO 639-1 language code for English.""" +BASE = 'base' +"""Base translation model.""" -class Client(object): - """Client to bundle configuration needed for API requests. +NMT = 'nmt' +"""Neural Machine Translation model.""" - :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. +class Client(BaseClient): + """Client to bundle configuration needed for API requests. :type target_language: str :param target_language: (Optional) The target language used for translations and language names. (Defaults to :data:`ENGLISH_ISO_639`.) + + :type api_key: str + :param api_key: (Optional) The key used to send with requests as a + query parameter. + + :type credentials: :class:`oauth2client.client.OAuth2Credentials` + :param credentials: (Optional) The OAuth2 Credentials to use for the + connection owned by this client. If not passed (and + if no ``http`` object is passed), falls back to the + default inferred from the environment. + + :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. """ - def __init__(self, api_key, http=None, target_language=ENGLISH_ISO_639): + _connection_class = Connection + + def __init__(self, target_language=ENGLISH_ISO_639, api_key=None, + credentials=None, http=None): self.api_key = api_key - if http is None: - http = httplib2.Http() - self._connection = Connection(http=http) self.target_language = target_language + if api_key is not None: + # If API key auth is desired, make it so that no credentials + # will be auto-detected by the base class constructor. + if http is None: + http = httplib2.Http() + super(Client, self).__init__(credentials=credentials, http=http) + def get_languages(self, target_language=None): """Get list of supported languages for translation. @@ -70,7 +90,9 @@ def get_languages(self, target_language=None): dictionary will also contain the name of each supported language (localized to the target language). """ - query_params = {'key': self.api_key} + query_params = {} + if self.api_key is not None: + query_params['key'] = self.api_key if target_language is None: target_language = self.target_language if target_language is not None: @@ -114,7 +136,9 @@ def detect_language(self, values): single_value = True values = [values] - query_params = [('key', self.api_key)] + query_params = [] + if self.api_key is not None: + query_params.append(('key', self.api_key)) query_params.extend(('q', _to_bytes(value, 'utf-8')) for value in values) response = self._connection.api_request( @@ -146,7 +170,8 @@ def detect_language(self, values): return detections def translate(self, values, target_language=None, format_=None, - source_language=None, customization_ids=()): + source_language=None, customization_ids=(), + model=None): """Translate a string or list of strings. See: https://cloud.google.com/translate/v2/\ @@ -173,7 +198,11 @@ def translate(self, values, target_language=None, format_=None, for translation. Sets the ``cid`` parameter in the query. - :rtype: str or list list + :type model: str + :param model: (Optional) The model used to translate the text. The + only accepted values are :attr:`BASE` and :attr:`NMT`. + + :rtype: str or 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) @@ -183,10 +212,11 @@ def translate(self, values, target_language=None, format_=None, * ``translatedText``: The translation of the text into the target language. * ``input``: The corresponding input value. + * ``model``: The model used to translate the text. If only a single value is passed, then only a single dictionary will be returned. - :raises: :class:`ValueError ` if the number of + :raises: :class:`~exceptions.ValueError` if the number of values and translations differ. """ single_value = False @@ -199,7 +229,9 @@ def translate(self, values, target_language=None, format_=None, if isinstance(customization_ids, six.string_types): customization_ids = [customization_ids] - query_params = [('key', self.api_key), ('target', target_language)] + query_params = [('target', target_language)] + if self.api_key is not None: + query_params.append(('key', self.api_key)) query_params.extend(('q', _to_bytes(value, 'utf-8')) for value in values) query_params.extend(('cid', cid) for cid in customization_ids) @@ -207,6 +239,8 @@ def translate(self, values, target_language=None, format_=None, query_params.append(('format', format_)) if source_language is not None: query_params.append(('source', source_language)) + if model is not None: + query_params.append(('model', model)) response = self._connection.api_request( method='GET', path='', query_params=query_params) diff --git a/translate/google/cloud/translate/connection.py b/translate/google/cloud/translate/connection.py index 453a612edfa9..0582bcd22a3f 100644 --- a/translate/google/cloud/translate/connection.py +++ b/translate/google/cloud/translate/connection.py @@ -20,7 +20,7 @@ class Connection(_http.JSONConnection): """A connection to Google Cloud Translate via the JSON REST API.""" - API_BASE_URL = 'https://www.googleapis.com' + API_BASE_URL = 'https://translation.googleapis.com' """The base of the API call URL.""" API_VERSION = 'v2' @@ -28,3 +28,6 @@ class Connection(_http.JSONConnection): API_URL_TEMPLATE = '{api_base_url}/language/translate/{api_version}{path}' """A template for the URL of a particular API call.""" + + SCOPE = ('https://www.googleapis.com/auth/cloud-platform',) + """The scopes required for authenticating.""" diff --git a/translate/tox.ini b/translate/tox.ini index f33c168d61c2..168559383e71 100644 --- a/translate/tox.ini +++ b/translate/tox.ini @@ -6,6 +6,7 @@ envlist = localdeps = pip install --quiet --upgrade {toxinidir}/../core deps = + mock pytest covercmd = py.test --quiet \ diff --git a/translate/unit_tests/test_client.py b/translate/unit_tests/test_client.py index f3a9ffb76871..2e9a23d6995e 100644 --- a/translate/unit_tests/test_client.py +++ b/translate/unit_tests/test_client.py @@ -27,32 +27,51 @@ def _get_target_class(): def _make_one(self, *args, **kw): return self._get_target_class()(*args, **kw) - def test_ctor(self): + def test_constructor(self): from google.cloud.translate.connection import Connection from google.cloud.translate.client import ENGLISH_ISO_639 http = object() - client = self._make_one(self.KEY, http=http) + client = self._make_one(http=http) self.assertIsInstance(client._connection, Connection) self.assertIsNone(client._connection.credentials) self.assertIs(client._connection.http, http) + self.assertIsNone(client.api_key) self.assertEqual(client.target_language, ENGLISH_ISO_639) - def test_ctor_non_default(self): + def test_constructor_non_default(self): from google.cloud.translate.connection import Connection http = object() target = 'es' - client = self._make_one(self.KEY, http=http, target_language=target) + client = self._make_one( + target_language=target, api_key=self.KEY, http=http) self.assertIsInstance(client._connection, Connection) self.assertIsNone(client._connection.credentials) self.assertIs(client._connection.http, http) + self.assertEqual(self.KEY, client.api_key) + self.assertEqual(client.target_language, target) + + def test_constructor_api_key_override(self): + import mock + from google.cloud.translate.connection import Connection + + target = 'ru' + with mock.patch('httplib2.Http') as http_ctor: + client = self._make_one( + target_language=target, api_key=self.KEY) + + http_ctor.assert_called_once_with() + self.assertIsInstance(client._connection, Connection) + self.assertIsNone(client._connection.credentials) + self.assertIs(client._connection.http, http_ctor.return_value) + self.assertEqual(self.KEY, client.api_key) self.assertEqual(client.target_language, target) def test_get_languages(self): from google.cloud.translate.client import ENGLISH_ISO_639 - client = self._make_one(self.KEY) + client = self._make_one(api_key=self.KEY, http=object()) supported = [ {'language': 'en', 'name': 'English'}, {'language': 'af', 'name': 'Afrikaans'}, @@ -77,7 +96,8 @@ def test_get_languages(self): {'key': self.KEY, 'target': ENGLISH_ISO_639}) def test_get_languages_no_target(self): - client = self._make_one(self.KEY, target_language=None) + client = self._make_one( + target_language=None, http=object()) supported = [ {'language': 'en'}, {'language': 'af'}, @@ -96,12 +116,13 @@ def test_get_languages_no_target(self): # Verify requested. self.assertEqual(len(conn._requested), 1) req = conn._requested[0] + self.assertEqual(len(req), 3) self.assertEqual(req['method'], 'GET') self.assertEqual(req['path'], '/languages') - self.assertEqual(req['query_params'], {'key': self.KEY}) + self.assertEqual(req['query_params'], {}) def test_get_languages_explicit_target(self): - client = self._make_one(self.KEY) + client = self._make_one(api_key=self.KEY, http=object()) target_language = 'en' supported = [ {'language': 'en', 'name': 'Spanish'}, @@ -127,7 +148,7 @@ def test_get_languages_explicit_target(self): {'key': self.KEY, 'target': target_language}) def test_detect_language_bad_result(self): - client = self._make_one(self.KEY) + client = self._make_one(api_key=self.KEY, http=object()) value = 'takoy' conn = client._connection = _Connection({}) @@ -146,7 +167,7 @@ def test_detect_language_bad_result(self): self.assertEqual(req['query_params'], query_params) def test_detect_language_single_value(self): - client = self._make_one(self.KEY) + client = self._make_one(api_key=self.KEY, http=object()) value = 'takoy' detection = { 'confidence': 1.0, @@ -176,7 +197,7 @@ def test_detect_language_single_value(self): self.assertEqual(req['query_params'], query_params) def test_detect_language_multiple_values(self): - client = self._make_one(self.KEY) + client = self._make_one(http=object()) value1 = u'fa\xe7ade' # facade (with a cedilla) detection1 = { 'confidence': 0.6166008, @@ -210,14 +231,13 @@ def test_detect_language_multiple_values(self): 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._make_one(self.KEY) + client = self._make_one(api_key=self.KEY, http=object()) value = 'soy' detection1 = { 'confidence': 0.81496066, @@ -242,7 +262,7 @@ def test_detect_language_multiple_results(self): client.detect_language(value) def test_translate_bad_result(self): - client = self._make_one(self.KEY) + client = self._make_one(api_key=self.KEY, http=object()) value = 'hvala ti' conn = client._connection = _Connection({}) @@ -255,14 +275,14 @@ def test_translate_bad_result(self): self.assertEqual(req['method'], 'GET') self.assertEqual(req['path'], '') query_params = [ - ('key', self.KEY), ('target', 'en'), + ('key', self.KEY), ('q', value.encode('utf-8')), ] self.assertEqual(req['query_params'], query_params) def test_translate_defaults(self): - client = self._make_one(self.KEY) + client = self._make_one(http=object()) value = 'hvala ti' translation = { 'detectedSourceLanguage': 'hr', @@ -285,14 +305,13 @@ def test_translate_defaults(self): 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._make_one(self.KEY) + client = self._make_one(api_key=self.KEY, http=object()) value1 = 'hvala ti' translation1 = { 'detectedSourceLanguage': 'hr', @@ -321,15 +340,15 @@ def test_translate_multiple(self): self.assertEqual(req['method'], 'GET') self.assertEqual(req['path'], '') query_params = [ - ('key', self.KEY), ('target', 'en'), + ('key', self.KEY), ('q', value1.encode('utf-8')), ('q', value2.encode('utf-8')), ] self.assertEqual(req['query_params'], query_params) def test_translate_explicit(self): - client = self._make_one(self.KEY) + client = self._make_one(api_key=self.KEY, http=object()) value = 'thank you' target_language = 'eo' source_language = 'en' @@ -346,9 +365,11 @@ def test_translate_explicit(self): cid = '123' format_ = 'text' + model = 'nmt' result = client.translate(value, target_language=target_language, source_language=source_language, - format_=format_, customization_ids=cid) + format_=format_, customization_ids=cid, + model=model) self.assertEqual(result, translation) # Verify requested. @@ -357,12 +378,13 @@ def test_translate_explicit(self): self.assertEqual(req['method'], 'GET') self.assertEqual(req['path'], '') query_params = [ - ('key', self.KEY), ('target', target_language), + ('key', self.KEY), ('q', value.encode('utf-8')), ('cid', cid), ('format', format_), ('source', source_language), + ('model', model), ] self.assertEqual(req['query_params'], query_params)