Skip to content

Commit ef1e616

Browse files
author
Sharoon Thomas
committed
Update for the OAuth Bing access token based API fixes #2
1 parent 4ca1067 commit ef1e616

File tree

5 files changed

+151
-22
lines changed

5 files changed

+151
-22
lines changed

CHANGELOG

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Changelog
22
=========
33

4+
Version 0.4
5+
-----------
6+
* Updated to use the Oauth based token issued by Bing
7+
* This release is not backward compatibleas the class signature has changed
8+
49
Version 0.3
510
-----------
611
* Encode text as UTF-8 before sending to translator

README.rst

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Microsoft Translator V2 -- Python API
22
=====================================
33

4-
:Version: 0.3
4+
:Version: 0.4
55
:Web: http://openlabs.co.in/
66
:keywords: Micrsoft Translator
77
:copyright: Openlabs Technologies & Consulting (P) LTD
@@ -17,10 +17,24 @@ Example Usage:
1717
::
1818

1919
>>> from microsofttranslator import Translator
20-
>>> translator = Translator('<Your API Key>')
20+
>>> translator = Translator('<Your Client ID>', '<Your Client Secret>')
2121
>>> print translator.translate("Hello", "pt")
2222
"Olá"
2323

24+
Registering your application
25+
----------------------------
26+
27+
To register your application with Azure DataMarket,
28+
visit https://datamarket.azure.com/developer/applications/ using the
29+
LiveID credentials from step 1, and click on “Register”. In the
30+
“Register your application” dialog box, you can define your own
31+
Client ID and Name. The redirect URI is not used for the Microsoft
32+
Translator API. However, the redirect URI field is a mandatory field,
33+
and you must provide a URI to obtain the access code. A description is
34+
optional.
35+
36+
Take a note of the client ID and the client secret value.
37+
2438
Bugs and Development on Github
2539
------------------------------
2640

__init__.py

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
:license: BSD, see LICENSE for more details.
1111
"""
1212

13-
__all__ = ['Translator']
13+
__all__ = ['Translator', 'TranslateApiException']
1414

1515
try:
1616
import simplejson as json
@@ -21,38 +21,111 @@ class JSONDecodeError(Exception): pass
2121
# Ugly: No alternative because this exception class doesnt seem to be there
2222
# in the standard python module
2323
import urllib
24+
import urllib2
25+
import warnings
26+
import logging
2427

2528

26-
class ArgumentOutOfRangeException(Exception):
29+
class ArgumentOutOfRangeException(Exception):
2730
def __init__(self, message):
2831
self.message = message.replace('ArgumentOutOfRangeException: ', '')
2932
super(ArgumentOutOfRangeException, self).__init__(self.message)
3033

3134

3235
class TranslateApiException(Exception):
33-
def __init__(self, message):
36+
def __init__(self, message, *args):
3437
self.message = message.replace('TranslateApiException: ', '')
35-
super(TranslateApiException, self).__init__(self.message)
38+
super(TranslateApiException, self).__init__(self.message, *args)
3639

3740

3841
class Translator(object):
3942
"""Implements AJAX API for the Microsoft Translator service
4043
41-
:param app_id: A string containing the Bing AppID.
44+
:param app_id: A string containing the Bing AppID. (Deprecated)
4245
"""
4346

44-
def __init__(self, app_id):
47+
def __init__(self, client_id, client_secret,
48+
scope="http://api.microsofttranslator.com",
49+
grant_type="client_credentials", app_id=None, debug=False):
50+
"""
51+
52+
53+
:param client_id: The client ID that you specified when you registered
54+
your application with Azure DataMarket.
55+
:param client_secret: The client secret value that you obtained when
56+
you registered your application with Azure
57+
DataMarket.
58+
:param scope: Defaults to http://api.microsofttranslator.com
59+
;param grant_type: Defaults to "client_credentials"
60+
:param app_id: Deprecated
61+
:param debug: If true, the logging level will be set to debug
62+
63+
.. versionchanged: 0.4
64+
Bing AppID mechanism is deprecated and is no longer supported.
65+
See: http://msdn.microsoft.com/en-us/library/hh454950
4566
"""
46-
:param app_id: A string containing the Bing AppID.
67+
if app_id is not None:
68+
warnings.warn("""app_id is deprected since v0.4.
69+
See: http://msdn.microsoft.com/en-us/library/hh454950
70+
""", DeprecationWarning, stacklevel=2)
71+
72+
self.client_id = client_id
73+
self.client_secret = client_secret
74+
self.scope = scope
75+
self.grant_type = grant_type
76+
self.access_token = None
77+
self.debug = debug
78+
self.logger = logging.getLogger("microsofttranslator")
79+
if self.debug:
80+
self.logger.setLevel(level=logging.DEBUG)
81+
82+
def get_access_token(self):
83+
"""Bing AppID mechanism is deprecated and is no longer supported.
84+
As mentioned above, you must obtain an access token to use the
85+
Microsoft Translator API. The access token is more secure, OAuth
86+
standard compliant, and more flexible. Users who are using Bing AppID
87+
are strongly recommended to get an access token as soon as possible.
88+
89+
.. note::
90+
The value of access token can be used for subsequent calls to the
91+
Microsoft Translator API. The access token expires after 10
92+
minutes. It is always better to check elapsed time between time at
93+
which token issued and current time. If elapsed time exceeds 10
94+
minute time period renew access token by following obtaining
95+
access token procedure.
96+
97+
:return: The access token to be used with subsequent requests
4798
"""
48-
self.app_id = app_id
99+
args = urllib.urlencode({
100+
'client_id': self.client_id,
101+
'client_secret': self.client_secret,
102+
'scope': self.scope,
103+
'grant_type': self.grant_type
104+
})
105+
response = json.loads(urllib.urlopen(
106+
'https://datamarket.accesscontrol.windows.net/v2/OAuth2-13', args
107+
).read())
108+
109+
self.logger.debug(response)
110+
111+
if "error" in response:
112+
raise TranslateApiException(
113+
response.get('error_description', 'No Error Description'),
114+
response.get('error', 'Unknown Error')
115+
)
116+
return response['access_token']
49117

50118
def call(self, url, params):
51119
"""Calls the given url with the params urlencoded
52120
"""
53-
params['appId'] = self.app_id
54-
response = urllib.urlopen(
55-
"%s?%s" % (url, urllib.urlencode(params))).read()
121+
if not self.access_token:
122+
self.access_token = self.get_access_token()
123+
124+
request = urllib2.Request(
125+
"%s?%s" % (url, urllib.urlencode(params)),
126+
headers={'Authorization': 'Bearer %s' % self.access_token}
127+
)
128+
response = urllib2.urlopen(request).read()
56129
rv = json.loads(response.decode("UTF-8-sig"))
57130

58131
if isinstance(rv, basestring) and \
@@ -65,20 +138,20 @@ def call(self, url, params):
65138

66139
return rv
67140

68-
def translate(self, text, to_lang, from_lang=None,
141+
def translate(self, text, to_lang, from_lang=None,
69142
content_type='text/plain', category='general'):
70143
"""Translates a text string from one language to another.
71144
72145
:param text: A string representing the text to translate.
73-
:param to_lang: A string representing the language code to
146+
:param to_lang: A string representing the language code to
74147
translate the text into.
75-
:param from_lang: A string representing the language code of the
76-
translation text. If left None the response will include the
148+
:param from_lang: A string representing the language code of the
149+
translation text. If left None the response will include the
77150
result of language auto-detection. (Default: None)
78-
:param content_type: The format of the text being translated.
79-
The supported formats are "text/plain" and "text/html". Any HTML
151+
:param content_type: The format of the text being translated.
152+
The supported formats are "text/plain" and "text/html". Any HTML
80153
needs to be well-formed.
81-
:param category: The category of the text to translate. The only
154+
:param category: The category of the text to translate. The only
82155
supported category is "general".
83156
"""
84157
params = {

setup.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ def read(fname):
3434

3535
setup(
3636
name = "microsofttranslator",
37-
version = "0.3",
37+
version = "0.4",
3838
packages = [
39-
'microsofttranslator'
39+
'microsofttranslator',
4040
],
4141
package_dir = {
4242
'microsofttranslator': '.'
@@ -58,4 +58,5 @@ def read(fname):
5858
"Topic :: Software Development :: Internationalization",
5959
"Topic :: Utilities"
6060
],
61+
test_suite = "microsofttranslator.test.test_all",
6162
)

test.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
test
4+
5+
Test the translator
6+
7+
:copyright: (c) 2012 by Openlabs Technologies & Consulting (P) Limited
8+
:license: BSD, see LICENSE for more details.
9+
"""
10+
import unittest
11+
from microsofttranslator import Translator, TranslateApiException
12+
13+
client_id = "translaterpythonapi"
14+
client_secret = "FLghnwW4LJmNgEG+EZkL8uE+wb7+6tkOS8eejHg3AaI="
15+
16+
17+
class TestTranslator(unittest.TestCase):
18+
19+
def test_translate(self):
20+
client = Translator(client_id, client_secret, debug=True)
21+
self.assertEqual(client.translate("hello", "pt"), u'Ol\xe1')
22+
23+
def test_invalid_client_id(self):
24+
client = Translator("foo", "bar")
25+
with self.assertRaises(TranslateApiException):
26+
client.translate("hello", "pt")
27+
28+
def test_all():
29+
loader = unittest.TestLoader()
30+
suite = unittest.TestSuite()
31+
suite.addTests(loader.loadTestsFromTestCase(TestTranslator))
32+
return suite
33+
34+
35+
if __name__ == '__main__':
36+
unittest.main()

0 commit comments

Comments
 (0)