Index: cms/translations/xtm/xtm_api.py
diff --git a/cms/translations/xtm/xtm_api.py b/cms/translations/xtm/xtm_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd6a20ce05866c6f3997e872d6bbf192c009a027
--- /dev/null
+++ b/cms/translations/xtm/xtm_api.py
@@ -0,0 +1,432 @@
+# This file is part of the Adblock Plus web scripts,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# Adblock Plus is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# Adblock Plus is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Adblock Plus. If not, see .
+
+from __future__ import unicode_literals
+
+import json
+
+import requests
+
+__all__ = [
+ 'XTMCloudAPI', 'XTMCloudException', 'get_token',
+]
+
+_BASE_URL = 'https://wstest2.xtm-intl.com/rest-api/'
+
+
+class XTMCloudException(Exception):
+ _BASE_MESSAGE = ('Error: XTM Cloud API failed while {0}, with error '
+ 'code {1}: {2}')
+
+ def __init__(self, code, msg, action):
+ """Constructor.
+
+ Parameters
+ ----------
+ code: int
+ The error code returned by the API.
+ msg: str
+ The error message returned by the API.
+ action: str
+ The action that caused the exception.
+
+ """
+ full_message = self._BASE_MESSAGE.format(action, code, msg)
+ super(XTMCloudException, self).__init__(full_message)
+
+ self.code = code
+ self.message = msg
+ self.action = action
+
+
+class XTMCloudAPI(object):
+
+ _AUTHORIZATION_TMP = 'XTM-Basic {0}'
+
+ class _UrlPaths:
+ CREATE = 'projects'
+ DOWNLOAD = 'projects/{0}/files/download'
+ UPLOAD = 'projects/{0}/files/upload'
+ GET_TARGET_LANG = 'projects/{0}/metrics'
+ ADD_TARGET_LANG = 'projects/{0}/target-languages'
+
+ _MATCH_TYPE = {
+ False: 'NO_MATCH',
+ True: 'MATCH_NAMES',
+ }
+
+ class _SuccessCodes:
+ CREATE = 201
+ UPLOAD = 200
+ DOWNLOAD = 200
+ GET_TARGET_LANGS = 200
+ ADD_TARGET_LANGS = 200
+
+ def __init__(self, token):
+ """Constructor.
+
+ Parameters
+ ----------
+ token: str
+ Token used to authenticate with the API.
+
+ """
+ self._token = token
+ self.base_url = _BASE_URL
+
+ def _execute(self, url, data=None, files=None, stream=False,
+ params=None, headers=None):
+ """Send request to the API and return the response.
+
+ Parameters
+ ----------
+ url: str
+ The url we're making the request to.
+ data: dict
+ The data to be sent to the API. If provided, the request will be
+ a POST request, otherwise GET. Default None.
+ files: dict
+ The files to be uploaded(if any). Default None.
+ params: dict
+ The URL parameters to be specified.
+ stream: bool
+ Whether using the stream option when executing the request or not.
+ headers: dict
+ Default headers to be sent with the request.
+
+ Returns
+ -------
+ The contents of the API response.
+
+ """
+ auth_header = {
+ 'Authorization': self._AUTHORIZATION_TMP.format(self._token),
+ }
+
+ if headers:
+ headers.update(auth_header)
+ else:
+ headers = auth_header
+
+ if data:
+ response = requests.post(url, data=data, files=files,
+ headers=headers, params=params,
+ stream=stream)
+ else:
+ response = requests.get(url, headers=headers, stream=True,
+ params=params)
+
+ return response
+
+ def _construct_files_dict(self, files, param_tmp):
+ """Convert a list of files to an uploadable format.
+
+ Parameters
+ ----------
+ files: iterable
+ Of the files to be uploaded. See create_project() for expected
+ format.
+
+ Returns
+ -------
+ dict
+ With the names of the files to be uploaded.
+ dict
+ With the files in the correct format for upload.
+
+ """
+ files_data = {}
+ file_names = {}
+ idx = 0
+
+ for name in files:
+ file_names[(param_tmp + '.name').format(idx)] = name
+ files_data[(param_tmp + '.file').format(idx)] = files[name]
+ idx += 1
+
+ return file_names, files_data
+
+ def create_project(self, name, description, reference_id, target_languages,
+ customer_id, workflow_id, source_language='en_US',
+ files=None):
+ """Create a new project with XTM Cloud.
+
+ Parameters
+ ----------
+ name: str
+ The name of the project we want to create.
+ description: str
+ The description of the project.
+ reference_id: str
+ The referenceID of the project.
+ target_languages: iterable
+ The target language(s) for the project.
+ customer_id: int
+ The id of the customer creating the project
+ workflow_id: int
+ The id of the main workflow
+ source_language: str
+ The source language for the project.
+ files: dict
+ With the files to be uploaded on creation.
+ Expects this format:
+ :
+
+ Returns
+ -------
+ int
+ The id of the created project.
+ iterable
+ Containing the information for all the jobs that were initiated.
+ See docs for upload_files() for the fully documented format.
+
+ Raises
+ ------
+ XTMCloudException
+ If the creation of the project was not successful.
+
+ """
+ data = {
+ 'name': name,
+ 'description': description,
+ 'referenceId': reference_id,
+ 'customerId': customer_id,
+ 'workflowId': workflow_id,
+ 'sourceLanguage': source_language,
+ 'targetLanguages': target_languages,
+ }
+
+ if files:
+ file_names, files_to_upload = self._construct_files_dict(
+ files, 'translationFiles[{}]')
+ data.update(file_names)
+ else:
+ # Hacky way to go around 415 error code
+ files_to_upload = {'a': 'b'}
+
+ url = self.base_url + self._UrlPaths.CREATE
+
+ response = self._execute(url, data=data, files=files_to_upload)
+
+ if response.status_code != self._SuccessCodes.CREATE:
+ # The creation was not successful
+ raise XTMCloudException(response.status_code,
+ response.text.decode('utf-8'),
+ 'creating job')
+
+ data = json.loads(response.text.encode('utf-8'))
+
+ return data['projectId'], data['jobs']
+
+ def upload_files(self, files, project_id, overwrite=True):
+ """Upload a set of files to a project.
+
+ Parameters
+ ----------
+ files: dict
+ With the files to be uploaded on creation.
+ Expects this format:
+ :
+ project_id: int
+ The id of the project we're uploading to.
+ overwrite: bool
+ Whether the files to be uploaded are going to overwrite the files
+ that are already in the XTM project or not. Default False.
+
+ Returns
+ -------
+ iterable
+ With the upload results. Each element will have the form:
+
+ {
+ 'fileName': ,
+ 'jobId': ,
+ 'sourceLanguage':