Skip to content

Commit afbbb80

Browse files
committed
Upgrade to pyhton 3.
Updated to run tests on both python 2 and 3. PROD-774
1 parent 59cbf1d commit afbbb80

16 files changed

Lines changed: 175 additions & 160 deletions

.travis.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
language: python
2-
python: "2.7"
2+
3+
python:
4+
- "2.7"
5+
- "3.5"
6+
7+
sudo: false
8+
39
install:
410
- "pip install six"
511
- "make install"
6-
sudo: false
12+
713
script:
814
- make quality
915
- make test
16+
1017
branches:
1118
only:
1219
- master
20+
1321
after_success: coveralls

lti_consumer/exceptions.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@ class LtiError(Exception):
77
"""
88
General error class for LTI XBlock.
99
"""
10-
pass

lti_consumer/lti.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@
55
https://www.imsglobal.org/activity/learning-tools-interoperability
66
"""
77

8-
import logging
9-
import urllib
8+
from __future__ import absolute_import, unicode_literals
9+
1010
import json
11+
import logging
1112

13+
import six.moves.urllib.error
14+
import six.moves.urllib.parse
1215
from six import text_type
1316

1417
from .exceptions import LtiError
1518
from .oauth import get_oauth_request_signature, verify_oauth_body_signature
1619

17-
1820
log = logging.getLogger(__name__)
1921

2022

@@ -83,14 +85,14 @@ def parse_result_json(json_str):
8385
log.error("[LTI] %s", msg)
8486
raise LtiError(msg)
8587
except (TypeError, ValueError) as err:
86-
msg = "Could not convert resultScore to float: {}".format(err.message)
88+
msg = "Could not convert resultScore to float: {}".format(str(err))
8789
log.error("[LTI] %s", msg)
8890
raise LtiError(msg)
8991

9092
return score, json_obj.get('comment', "")
9193

9294

93-
class LtiConsumer(object):
95+
class LtiConsumer(object): # pylint: disable=bad-option-value, useless-object-inheritance
9496
"""
9597
Limited implementation of the LTI 1.1/2.0 specification.
9698
@@ -202,7 +204,9 @@ def get_signed_lti_parameters(self):
202204
# so '='' becomes '%3D'.
203205
# We send form via browser, so browser will encode it again,
204206
# So we need to decode signature back:
205-
oauth_signature[u'oauth_signature'] = urllib.unquote(oauth_signature[u'oauth_signature']).decode('utf8')
207+
oauth_signature[u'oauth_signature'] = six.moves.urllib.parse.unquote(
208+
oauth_signature[u'oauth_signature']
209+
)
206210

207211
# Add LTI parameters to OAuth parameters for sending in form.
208212
lti_parameters.update(oauth_signature)
@@ -290,15 +294,15 @@ def verify_result_headers(self, request, verify_content_type=True):
290294
content_type = request.headers.get('Content-Type')
291295
if verify_content_type and content_type != LtiConsumer.CONTENT_TYPE_RESULT_JSON:
292296
log.error("[LTI]: v2.0 result service -- bad Content-Type: %s", content_type)
293-
raise LtiError(
294-
"For LTI 2.0 result service, Content-Type must be %s. Got %s",
297+
error_msg = "For LTI 2.0 result service, Content-Type must be {}. Got {}".format(
295298
LtiConsumer.CONTENT_TYPE_RESULT_JSON,
296299
content_type
297300
)
301+
raise LtiError(error_msg)
298302

299303
__, secret = self.xblock.lti_provider_key_secret
300304
try:
301305
return verify_oauth_body_signature(request, secret, self.xblock.outcome_service_url)
302306
except (ValueError, LtiError) as err:
303-
log.error("[LTI]: v2.0 result service -- OAuth body verification failed: %s", err.message)
304-
raise LtiError(err.message)
307+
log.error("[LTI]: v2.0 result service -- OAuth body verification failed: %s", str(err))
308+
raise LtiError(str(err))

lti_consumer/lti_consumer.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,29 +50,29 @@
5050
GET / PUT / DELETE HTTP methods respectively
5151
"""
5252

53+
from __future__ import absolute_import, unicode_literals
54+
5355
import logging
54-
import bleach
5556
import re
56-
from importlib import import_module
57-
import json
58-
import urllib
59-
6057
from collections import namedtuple
61-
from webob import Response
58+
from importlib import import_module
6259

60+
import six.moves.urllib.error
61+
import six.moves.urllib.parse
62+
import six
63+
import bleach
6364
from django.utils import timezone
64-
65-
from xblock.core import String, Scope, List, XBlock
65+
from webob import Response
66+
from xblock.core import List, Scope, String, XBlock
6667
from xblock.fields import Boolean, Float, Integer
6768
from xblock.fragment import Fragment
6869
from xblock.validation import ValidationMessage
69-
7070
from xblockutils.resources import ResourceLoader
7171
from xblockutils.studio_editable import StudioEditableXBlockMixin
7272

7373
from .exceptions import LtiError
74-
from .oauth import log_authorization_header
7574
from .lti import LtiConsumer
75+
from .oauth import log_authorization_header
7676
from .outcomes import OutcomeService
7777
from .utils import _
7878

@@ -155,7 +155,7 @@ def parse_handler_suffix(suffix):
155155
LaunchTargetOption = namedtuple('LaunchTargetOption', ['display_name', 'value'])
156156

157157

158-
class LaunchTarget(object):
158+
class LaunchTarget(object): # pylint: disable=bad-option-value, useless-object-inheritance
159159
"""
160160
Constants for launch_target field options
161161
"""
@@ -476,7 +476,9 @@ def workbench_scenarios():
476476
def validate_field_data(self, validation, data):
477477
if not isinstance(data.custom_parameters, list):
478478
_ = self.runtime.service(self, "i18n").ugettext
479-
validation.add(ValidationMessage(ValidationMessage.ERROR, unicode(_("Custom Parameters must be a list"))))
479+
validation.add(ValidationMessage(ValidationMessage.ERROR, six.text_type(
480+
_("Custom Parameters must be a list")
481+
)))
480482

481483
def get_settings(self):
482484
"""
@@ -547,7 +549,7 @@ def context_id(self):
547549
context_id is an opaque identifier that uniquely identifies the context (e.g., a course)
548550
that contains the link being launched.
549551
"""
550-
return unicode(self.course_id) # pylint: disable=no-member
552+
return six.text_type(self.course_id) # pylint: disable=no-member
551553

552554
@property
553555
def role(self):
@@ -589,7 +591,7 @@ def user_id(self):
589591
user_id = self.runtime.anonymous_student_id
590592
if user_id is None:
591593
raise LtiError(self.ugettext("Could not get user id for current request"))
592-
return unicode(urllib.quote(user_id))
594+
return six.text_type(six.moves.urllib.parse.quote(user_id))
593595

594596
@property
595597
def resource_link_id(self):
@@ -623,7 +625,7 @@ def resource_link_id(self):
623625
i4x-2-3-lti-31de800015cf4afb973356dbe81496df this part of resource_link_id:
624626
makes resource_link_id to be unique among courses inside same system.
625627
"""
626-
return unicode(urllib.quote(
628+
return six.text_type(six.moves.urllib.parse.quote(
627629
"{}-{}".format(self.runtime.hostname, self.location.html_id()) # pylint: disable=no-member
628630
))
629631

@@ -638,7 +640,7 @@ def lis_result_sourcedid(self):
638640
This field is generally optional, but is required for grading.
639641
"""
640642
return "{context}:{resource_link}:{user_id}".format(
641-
context=urllib.quote(self.context_id),
643+
context=six.moves.urllib.parse.quote(self.context_id),
642644
resource_link=self.resource_link_id,
643645
user_id=self.user_id
644646
)
@@ -701,7 +703,7 @@ def prefixed_custom_parameters(self):
701703
if param_name not in LTI_PARAMETERS:
702704
param_name = 'custom_' + param_name
703705

704-
custom_parameters[unicode(param_name)] = unicode(param_value)
706+
custom_parameters[six.text_type(param_name)] = six.text_type(param_value)
705707
return custom_parameters
706708

707709
@property
@@ -849,7 +851,7 @@ def result_service_handler(self, request, suffix=''):
849851
return Response(status=404)
850852

851853
return Response(
852-
json.dumps(response_body),
854+
json_body=response_body,
853855
content_type=LtiConsumer.CONTENT_TYPE_RESULT_JSON,
854856
)
855857

lti_consumer/oauth.py

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@
22
Utility functions for working with OAuth signatures.
33
"""
44

5-
import logging
6-
import hashlib
5+
from __future__ import absolute_import, unicode_literals
6+
77
import base64
8-
import urllib
8+
import hashlib
9+
import logging
910

11+
import six.moves.urllib.error
12+
import six.moves.urllib.parse
13+
import six
1014
from oauthlib import oauth1
1115

1216
from .exceptions import LtiError
1317

14-
1518
log = logging.getLogger(__name__)
1619

1720

18-
class SignedRequest(object):
21+
class SignedRequest(object): # pylint: disable=bad-option-value, useless-object-inheritance
1922
"""
2023
Encapsulates request attributes needed when working
2124
with the `oauthlib.oauth1` API
@@ -45,14 +48,14 @@ def get_oauth_request_signature(key, secret, url, headers, body):
4548
Returns:
4649
str: Authorization header for the OAuth signed request
4750
"""
48-
client = oauth1.Client(client_key=unicode(key), client_secret=unicode(secret))
51+
client = oauth1.Client(client_key=six.text_type(key), client_secret=six.text_type(secret))
4952
try:
5053
# Add Authorization header which looks like:
5154
# Authorization: OAuth oauth_nonce="80966668944732164491378916897",
5255
# oauth_timestamp="1378916897", oauth_version="1.0", oauth_signature_method="HMAC-SHA1",
5356
# oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"
54-
__, headers, __ = client.sign(
55-
unicode(url.strip()),
57+
_, headers, _ = client.sign(
58+
six.text_type(url.strip()),
5659
http_method=u'POST',
5760
body=body,
5861
headers=headers
@@ -83,7 +86,7 @@ def verify_oauth_body_signature(request, lti_provider_secret, service_url):
8386
"""
8487

8588
headers = {
86-
'Authorization': unicode(request.headers.get('Authorization')),
89+
'Authorization': six.text_type(request.headers.get('Authorization')),
8790
'Content-Type': request.content_type,
8891
}
8992

@@ -94,18 +97,18 @@ def verify_oauth_body_signature(request, lti_provider_secret, service_url):
9497
oauth_headers = dict(oauth_params)
9598
oauth_signature = oauth_headers.pop('oauth_signature')
9699
mock_request_lti_1 = SignedRequest(
97-
uri=unicode(urllib.unquote(service_url)),
98-
http_method=unicode(request.method),
99-
params=oauth_headers.items(),
100+
uri=six.text_type(six.moves.urllib.parse.unquote(service_url)),
101+
http_method=six.text_type(request.method),
102+
params=list(oauth_headers.items()),
100103
signature=oauth_signature
101104
)
102105
mock_request_lti_2 = SignedRequest(
103-
uri=unicode(urllib.unquote(request.url)),
104-
http_method=unicode(request.method),
105-
params=oauth_headers.items(),
106+
uri=six.text_type(six.moves.urllib.parse.unquote(request.url)),
107+
http_method=six.text_type(request.method),
108+
params=list(oauth_headers.items()),
106109
signature=oauth_signature
107110
)
108-
if oauth_body_hash != oauth_headers.get('oauth_body_hash'):
111+
if oauth_body_hash.decode('utf-8') != oauth_headers.get('oauth_body_hash'):
109112
log.error(
110113
"OAuth body hash verification failed, provided: %s, "
111114
"calculated: %s, for url: %s, body is: %s",
@@ -123,7 +126,7 @@ def verify_oauth_body_signature(request, lti_provider_secret, service_url):
123126
"headers:%s url:%s method:%s",
124127
oauth_headers,
125128
service_url,
126-
unicode(request.method)
129+
six.text_type(request.method)
127130
)
128131
raise LtiError("OAuth signature verification has failed.")
129132

@@ -145,18 +148,18 @@ def log_authorization_header(request, client_key, client_secret):
145148
"""
146149
sha1 = hashlib.sha1()
147150
sha1.update(request.body)
148-
oauth_body_hash = unicode(base64.b64encode(sha1.digest())) # pylint: disable=too-many-function-args
151+
oauth_body_hash = six.text_type(base64.b64encode(sha1.digest())) # pylint: disable=too-many-function-args
149152
log.debug("[LTI] oauth_body_hash = %s", oauth_body_hash)
150153
client = oauth1.Client(client_key, client_secret)
151154
params = client.get_oauth_params(request)
152155
params.append((u'oauth_body_hash', oauth_body_hash))
153156
mock_request = SignedRequest(
154-
uri=unicode(urllib.unquote(request.url)),
157+
uri=six.text_type(six.moves.urllib.parse.unquote(request.url)),
155158
headers=request.headers,
156159
body=u"",
157160
decoded_body=u"",
158161
oauth_params=params,
159-
http_method=unicode(request.method),
162+
http_method=six.text_type(request.method),
160163
)
161164
sig = client.get_oauth_signature(mock_request)
162165
mock_request.oauth_params.append((u'oauth_signature', sig))

0 commit comments

Comments
 (0)