diff --git a/.travis.yml b/.travis.yml
index f1ba242d663c70bba60a0f5bc29e30451c23470e..1990ee85f2ff5900095954b4fc4eb13ac91ccdda 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
 language: python
 sudo: false
 install:
-- pip install -r requirements/travis.txt
+  - pip install -r requirements/travis.txt
 after_success: coveralls
 env:
   global:
@@ -12,19 +12,16 @@ deploy:
   user: edx
   distributions: sdist bdist_wheel
   on:
-    python: 3.5
+    python: 3.8
     tags: true
   password:
     secure: DKlhHIFctJS9k/1sdlKfL34MwVIHxZn6QwrQ4/jMaiucNARP7MUoTEhd5wEN7wNMyafsYfkY960/dJuqgF6ztZzt7jYX+Nu9o0YEOM742f5hcqQcn/k2GrU2X5+hMgAVUL3YP4JlZwst4pRM1eRlzXgsoxqCET84V2biS1d04ivJgKO5T9NNHTeIQDRDpSpUVuloY1qVMS6IFewLjL6XZuCYtCBXBcBN29EDFvy8v683JAZyz332Xr8R0yF/u09XFKnW+migeiT9gWNafIKXgDAidr0gbkF4r71OdGCUxhCwa+/IHnAYCptajzd4QUd2gj5yOccbVhtUondK3DSA6NaAHdwFjmU7XS5XouDlMS83wyTbqGlEXj9dEuY6lq/UXeUjvfmbqUc1W5qi7eKGxY2qZ1+3HucnCVlPbzgEVMxnPN/YtPe59SSay60gFD7KyZfxxavLsuhSFM4+aZ/hyW9pI1vu+k9UuVVEw9QisUORHHg5YYC75BsVXI5kkhXAF7F880cFlV+DPEt7mwM0xsAPcbyStmmJ+7sXkoI6bWF+QsveqgY4SPYD14bZ8v3PK4b5UzrQOHSEpa1NNrm7942lnkySoC5Rm6YIShnLdJ+Gdf8wb4RezqnhmZcKVc/9QXQcUga+nj5CRUb9wFVncmak2tf8aAvfqeML8pHzkrs=
 
 jobs:
   include:
-  - name: Python 3.5 - Tests
-    script: make test
-    python: "3.5"
-  - name: Python 3.8 - Tests
-    script: make test
-    python: "3.8"
-  - name: Code Quality
-    script: make quality
-    python: "3.5"
\ No newline at end of file
+    - name: Python 3.8 - Tests
+      script: make test
+      python: '3.8'
+    - name: Code Quality
+      script: make quality
+      python: '3.8'
diff --git a/lti_consumer/apps.py b/lti_consumer/apps.py
index 197d55e978263ffb728412e0d15148d511c79146..e50a43198a6e757e6f544f9becc0bb0633d396f9 100644
--- a/lti_consumer/apps.py
+++ b/lti_consumer/apps.py
@@ -1,8 +1,6 @@
-# -*- coding: utf-8 -*-
 """
 lti_consumer Django application initialization.
 """
-from __future__ import absolute_import, unicode_literals
 
 from django.apps import AppConfig
 
diff --git a/lti_consumer/lti_1p1/consumer.py b/lti_consumer/lti_1p1/consumer.py
index 96fd516a85462ca1103543981516152f9d229f58..1290fc9861af67eade8626a8d43af11b44ee4e15 100644
--- a/lti_consumer/lti_1p1/consumer.py
+++ b/lti_consumer/lti_1p1/consumer.py
@@ -69,7 +69,7 @@ def parse_result_json(json_str):
     try:
         json_obj = json.loads(json_str)
     except (ValueError, TypeError) as err:
-        msg = "Supplied JSON string in request body could not be decoded: {}".format(json_str)
+        msg = f"Supplied JSON string in request body could not be decoded: {json_str!r}"
         log.error("[LTI] %s", msg)
         raise Lti1p1Error(msg) from err
 
@@ -87,7 +87,7 @@ def parse_result_json(json_str):
     # '@type' must be "Result"
     result_type = json_obj.get("@type")
     if result_type != "Result":
-        msg = "JSON object does not contain correct @type attribute (should be 'Result', is z{})".format(result_type)
+        msg = f"JSON object does not contain correct @type attribute (should be 'Result', is z{result_type!r})"
         log.error("[LTI] %s", msg)
         raise Lti1p1Error(msg)
 
@@ -304,15 +304,15 @@ class LtiConsumer1p1:
         # Parse headers to pass to template as part of context:
         oauth_signature = dict([param.strip().replace('"', '').split('=') for param in oauth_signature.split(',')])
 
-        oauth_signature[u'oauth_nonce'] = oauth_signature.pop(u'OAuth oauth_nonce')
+        oauth_signature['oauth_nonce'] = oauth_signature.pop('OAuth oauth_nonce')
 
         # oauthlib encodes signature with
         # 'Content-Type': 'application/x-www-form-urlencoded'
         # so '='' becomes '%3D'.
         # We send form via browser, so browser will encode it again,
         # So we need to decode signature back:
-        oauth_signature[u'oauth_signature'] = urllib.parse.unquote(
-            oauth_signature[u'oauth_signature']
+        oauth_signature['oauth_signature'] = urllib.parse.unquote(
+            oauth_signature['oauth_signature']
         )
 
         # Add LTI parameters to OAuth parameters for sending in form.
diff --git a/lti_consumer/lti_1p1/contrib/tests/test_django.py b/lti_consumer/lti_1p1/contrib/tests/test_django.py
index 6c32791e1038b219690ba06579f6ddcef8a36ab0..3925478927afcca08a5e7b82baf70960388c4413 100644
--- a/lti_consumer/lti_1p1/contrib/tests/test_django.py
+++ b/lti_consumer/lti_1p1/contrib/tests/test_django.py
@@ -1,10 +1,10 @@
-# -*- coding: utf-8 -*-
 """
 Unit tests for lti_consumer.lti module
 """
 
+from unittest.mock import ANY, Mock, patch
+
 from django.test.testcases import TestCase
-from mock import Mock, patch, ANY
 
 from lti_consumer.lti_1p1.contrib.django import lti_embed
 
diff --git a/lti_consumer/lti_1p1/oauth.py b/lti_consumer/lti_1p1/oauth.py
index e77802095621c9dfc57a29ef67affcf0fb8c0d5e..c1a127f23f155d07604ef1a631008f830d9f2a98 100644
--- a/lti_consumer/lti_1p1/oauth.py
+++ b/lti_consumer/lti_1p1/oauth.py
@@ -52,7 +52,7 @@ def get_oauth_request_signature(key, secret, url, headers, body):
         # oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"
         _, headers, _ = client.sign(
             str(url.strip()),
-            http_method=u'POST',
+            http_method='POST',
             body=body,
             headers=headers
         )
@@ -148,17 +148,17 @@ def log_authorization_header(request, client_key, client_secret):
     log.debug("[LTI] oauth_body_hash = %s", oauth_body_hash)
     client = oauth1.Client(client_key, client_secret)
     params = client.get_oauth_params(request)
-    params.append((u'oauth_body_hash', oauth_body_hash))
+    params.append(('oauth_body_hash', oauth_body_hash))
     mock_request = SignedRequest(
         uri=str(urllib.parse.unquote(request.url)),
         headers=request.headers,
-        body=u"",
-        decoded_body=u"",
+        body="",
+        decoded_body="",
         oauth_params=params,
         http_method=str(request.method),
     )
     sig = client.get_oauth_signature(mock_request)
-    mock_request.oauth_params.append((u'oauth_signature', sig))
+    mock_request.oauth_params.append(('oauth_signature', sig))
 
     __, headers, _ = client._render(mock_request)  # pylint: disable=protected-access
     log.debug(
diff --git a/lti_consumer/lti_1p1/tests/test_consumer.py b/lti_consumer/lti_1p1/tests/test_consumer.py
index 0e294a37a0cd21c95f7cd7e331b8b7dedafd21b5..88482b1c70f5f3710a2f6154f4ec994f0cfe3da5 100644
--- a/lti_consumer/lti_1p1/tests/test_consumer.py
+++ b/lti_consumer/lti_1p1/tests/test_consumer.py
@@ -1,11 +1,10 @@
-# -*- coding: utf-8 -*-
 """
 Unit tests for lti_consumer.lti_1p1.consumer module
 """
 
 import unittest
 
-from mock import Mock, patch
+from unittest.mock import Mock, patch
 
 from lti_consumer.lti_1p1.exceptions import Lti1p1Error
 from lti_consumer.lti_1p1.consumer import LtiConsumer1p1, parse_result_json
@@ -13,74 +12,74 @@ from lti_consumer.tests.unit.test_utils import make_request
 
 INVALID_JSON_INPUTS = [
     ([
-        u"kk",   # ValueError
-        u"{{}",  # ValueError
-        u"{}}",  # ValueError
+        "kk",   # ValueError
+        "{{}",  # ValueError
+        "{}}",  # ValueError
         3,       # TypeError
         {},      # TypeError
-    ], u"Supplied JSON string in request body could not be decoded"),
+    ], "Supplied JSON string in request body could not be decoded"),
     ([
-        u"3",        # valid json, not array or object
-        u"[]",       # valid json, array too small
-        u"[3, {}]",  # valid json, 1st element not an object
-    ], u"Supplied JSON string is a list that does not contain an object as the first element"),
+        "3",        # valid json, not array or object
+        "[]",       # valid json, array too small
+        "[3, {}]",  # valid json, 1st element not an object
+    ], "Supplied JSON string is a list that does not contain an object as the first element"),
     ([
-        u'{"@type": "NOTResult"}',  # @type key must have value 'Result'
-    ], u"JSON object does not contain correct @type attribute"),
+        '{"@type": "NOTResult"}',  # @type key must have value 'Result'
+    ], "JSON object does not contain correct @type attribute"),
     ([
         # @context missing
-        u'{"@type": "Result", "resultScore": 0.1}',
-    ], u"JSON object does not contain required key"),
+        '{"@type": "Result", "resultScore": 0.1}',
+    ], "JSON object does not contain required key"),
     ([
-        u'''
+        '''
         {"@type": "Result",
          "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
          "resultScore": 100}'''  # score out of range
-    ], u"score value outside the permitted range of 0.0-1.0."),
+    ], "score value outside the permitted range of 0.0-1.0."),
     ([
-        u'''
+        '''
         {"@type": "Result",
          "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
          "resultScore": -2}'''  # score out of range
-    ], u"score value outside the permitted range of 0.0-1.0."),
+    ], "score value outside the permitted range of 0.0-1.0."),
     ([
-        u'''
+        '''
         {"@type": "Result",
          "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
          "resultScore": "1b"}''',   # score ValueError
-        u'''
+        '''
         {"@type": "Result",
          "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
          "resultScore": {}}''',   # score TypeError
-    ], u"Could not convert resultScore to float"),
+    ], "Could not convert resultScore to float"),
 ]
 
 VALID_JSON_INPUTS = [
-    (u'''
+    ('''
     {"@type": "Result",
      "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
-     "resultScore": 0.1}''', 0.1, u""),  # no comment means we expect ""
-    (u'''
+     "resultScore": 0.1}''', 0.1, ""),  # no comment means we expect ""
+    ('''
     [{"@type": "Result",
      "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
      "@id": "anon_id:abcdef0123456789",
-     "resultScore": 0.1}]''', 0.1, u""),  # OK to have array of objects -- just take the first.  @id is okay too
-    (u'''
+     "resultScore": 0.1}]''', 0.1, ""),  # OK to have array of objects -- just take the first.  @id is okay too
+    ('''
     {"@type": "Result",
      "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
      "resultScore": 0.1,
-     "comment": "ಠ益ಠ"}''', 0.1, u"ಠ益ಠ"),  # unicode comment
-    (u'''
+     "comment": "ಠ益ಠ"}''', 0.1, "ಠ益ಠ"),  # unicode comment
+    ('''
     {"@type": "Result",
-     "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result"}''', None, u""),  # no score means we expect None
-    (u'''
+     "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result"}''', None, ""),  # no score means we expect None
+    ('''
     {"@type": "Result",
      "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
-     "resultScore": 0.0}''', 0.0, u""),  # test lower score boundary
-    (u'''
+     "resultScore": 0.0}''', 0.0, ""),  # test lower score boundary
+    ('''
     {"@type": "Result",
      "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
-     "resultScore": 1.0}''', 1.0, u""),  # test upper score boundary
+     "resultScore": 1.0}''', 1.0, ""),  # test upper score boundary
 ]
 
 GET_RESULT_RESPONSE = {
diff --git a/lti_consumer/lti_1p1/tests/test_oauth.py b/lti_consumer/lti_1p1/tests/test_oauth.py
index d7ef35bf061f055f869c7a53d31f8061a532355d..619dd6af2fc5c4faf9cb241b46694e91914440d9 100644
--- a/lti_consumer/lti_1p1/tests/test_oauth.py
+++ b/lti_consumer/lti_1p1/tests/test_oauth.py
@@ -4,7 +4,7 @@ Unit tests for lti_consumer.oauth module
 
 import unittest
 
-from mock import Mock, patch
+from unittest.mock import Mock, patch
 
 from lti_consumer.lti_1p1.exceptions import Lti1p1Error
 from lti_consumer.lti_1p1.oauth import (get_oauth_request_signature,
@@ -13,14 +13,14 @@ from lti_consumer.lti_1p1.oauth import (get_oauth_request_signature,
 from lti_consumer.tests.unit.test_utils import make_request
 
 OAUTH_PARAMS = [
-    (u'oauth_nonce', u'80966668944732164491378916897'),
-    (u'oauth_timestamp', u'1378916897'),
-    (u'oauth_version', u'1.0'),
-    (u'oauth_signature_method', u'HMAC-SHA1'),
-    (u'oauth_consumer_key', u'test'),
-    (u'oauth_signature', u'frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D'),
+    ('oauth_nonce', '80966668944732164491378916897'),
+    ('oauth_timestamp', '1378916897'),
+    ('oauth_version', '1.0'),
+    ('oauth_signature_method', 'HMAC-SHA1'),
+    ('oauth_consumer_key', 'test'),
+    ('oauth_signature', 'frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D'),
 ]
-OAUTH_PARAMS_WITH_BODY_HASH = OAUTH_PARAMS + [(u'oauth_body_hash', u'2jmj7l5rSw0yVb/vlWAYkK/YBwk=')]
+OAUTH_PARAMS_WITH_BODY_HASH = OAUTH_PARAMS + [('oauth_body_hash', '2jmj7l5rSw0yVb/vlWAYkK/YBwk=')]
 
 
 class TestGetOauthRequestSignature(unittest.TestCase):
@@ -36,7 +36,7 @@ class TestGetOauthRequestSignature(unittest.TestCase):
         mock_client_sign.return_value = '', {'Authorization': ''}, ''
         signature = get_oauth_request_signature('test', 'secret', '', {}, '')
 
-        mock_client_sign.assert_called_with('', http_method=u'POST', body='', headers={})
+        mock_client_sign.assert_called_with('', http_method='POST', body='', headers={})
         self.assertEqual(signature, '')
 
     @patch('oauthlib.oauth1.Client.sign')
diff --git a/lti_consumer/lti_1p3/constants.py b/lti_consumer/lti_1p3/constants.py
index 4fbb5574271c913c58e6600e37dd8cc61d12bc9f..54e6781141c78d74c765e22498166acf92dd376c 100644
--- a/lti_consumer/lti_1p3/constants.py
+++ b/lti_consumer/lti_1p3/constants.py
@@ -35,12 +35,12 @@ LTI_1P3_ROLE_MAP = {
 }
 
 
-LTI_1P3_ACCESS_TOKEN_REQUIRED_CLAIMS = set([
+LTI_1P3_ACCESS_TOKEN_REQUIRED_CLAIMS = {
     "grant_type",
     "client_assertion_type",
     "client_assertion",
     "scope",
-])
+}
 
 
 LTI_1P3_ACCESS_TOKEN_SCOPES = [
diff --git a/lti_consumer/lti_1p3/extensions/rest_framework/serializers.py b/lti_consumer/lti_1p3/extensions/rest_framework/serializers.py
index cc89ceb29469db05a45728d208c3c1d808a2293b..30afb794623f5709594836eda4619dee9b685377 100644
--- a/lti_consumer/lti_1p3/extensions/rest_framework/serializers.py
+++ b/lti_consumer/lti_1p3/extensions/rest_framework/serializers.py
@@ -32,7 +32,7 @@ class UsageKeyField(serializers.Field):
         try:
             return UsageKey.from_string(data)
         except InvalidKeyError as err:
-            raise serializers.ValidationError("Invalid usage key: {}".format(data)) from err
+            raise serializers.ValidationError(f"Invalid usage key: {data!r}") from err
 
 
 class LtiAgsLineItemSerializer(serializers.ModelSerializer):
diff --git a/lti_consumer/lti_1p3/tests/extensions/rest_framework/test_authentication.py b/lti_consumer/lti_1p3/tests/extensions/rest_framework/test_authentication.py
index 3530115218c149ed405a41b83e4590bedce0cf60..97b4ac65ee5a7e8567a45dedd3c259d469f4b69c 100644
--- a/lti_consumer/lti_1p3/tests/extensions/rest_framework/test_authentication.py
+++ b/lti_consumer/lti_1p3/tests/extensions/rest_framework/test_authentication.py
@@ -1,19 +1,17 @@
 """
 Unit tests for LTI 1.3 consumer implementation
 """
-from __future__ import absolute_import, unicode_literals
 
-import ddt
-from mock import MagicMock, patch
+from unittest.mock import MagicMock, patch
 
+import ddt
 from Cryptodome.PublicKey import RSA
 from django.test.testcases import TestCase
 from rest_framework import exceptions
 
-from lti_consumer.models import LtiConfiguration
 from lti_consumer.lti_1p3.consumer import LtiConsumer1p3
 from lti_consumer.lti_1p3.extensions.rest_framework.authentication import Lti1p3ApiAuthentication
-
+from lti_consumer.models import LtiConfiguration
 
 # Variables required for testing and verification
 ISS = "http://test-platform.example/"
@@ -79,7 +77,7 @@ class TestLtiAuthentication(TestCase):
             expiration=3600
         )
         mock_request.headers = {
-            "Authorization": "Bearer {}".format(token),
+            "Authorization": f"Bearer {token}",
         }
 
         # Set the lti config id in the "url"
diff --git a/lti_consumer/lti_1p3/tests/extensions/rest_framework/test_permissions.py b/lti_consumer/lti_1p3/tests/extensions/rest_framework/test_permissions.py
index be5d50041be8730b658cc09e1fad4bd6b381ab19..2c749c9fe3c72101efb7ad2e3d7a9c30187070b6 100644
--- a/lti_consumer/lti_1p3/tests/extensions/rest_framework/test_permissions.py
+++ b/lti_consumer/lti_1p3/tests/extensions/rest_framework/test_permissions.py
@@ -1,18 +1,16 @@
 """
 Unit tests for LTI 1.3 consumer implementation
 """
-from __future__ import absolute_import, unicode_literals
 
-import ddt
-from mock import MagicMock
+from unittest.mock import MagicMock
 
+import ddt
 from Cryptodome.PublicKey import RSA
 from django.test.testcases import TestCase
 
-from lti_consumer.models import LtiConfiguration
 from lti_consumer.lti_1p3.consumer import LtiConsumer1p3
 from lti_consumer.lti_1p3.extensions.rest_framework.permissions import LtiAgsPermissions
-
+from lti_consumer.models import LtiConfiguration
 
 # Variables required for testing and verification
 ISS = "http://test-platform.example/"
@@ -96,7 +94,7 @@ class TestLtiAuthentication(TestCase):
         # Make token and include it in the mock request
         token = self._make_token(token_scopes)
         self.mock_request.headers = {
-            "Authorization": "Bearer {}".format(token)
+            "Authorization": f"Bearer {token}"
         }
 
         # Test list view
@@ -124,7 +122,7 @@ class TestLtiAuthentication(TestCase):
         # Make token and include it in the mock request
         token = self._make_token([])
         self.mock_request.headers = {
-            "Authorization": "Bearer {}".format(token)
+            "Authorization": f"Bearer {token}"
         }
 
         # Test list view
@@ -163,7 +161,7 @@ class TestLtiAuthentication(TestCase):
         # Make token and include it in the mock request
         token = self._make_token(token_scopes)
         self.mock_request.headers = {
-            "Authorization": "Bearer {}".format(token)
+            "Authorization": f"Bearer {token}"
         }
 
         for action in ['create', 'update', 'partial_update', 'delete']:
@@ -184,7 +182,7 @@ class TestLtiAuthentication(TestCase):
         # Make token and include it in the mock request
         token = self._make_token([])
         self.mock_request.headers = {
-            "Authorization": "Bearer {}".format(token)
+            "Authorization": f"Bearer {token}"
         }
 
         # Test list view
@@ -210,7 +208,7 @@ class TestLtiAuthentication(TestCase):
         # Make token and include it in the mock request
         token = self._make_token(token_scopes)
         self.mock_request.headers = {
-            "Authorization": "Bearer {}".format(token)
+            "Authorization": f"Bearer {token}"
         }
 
         # Test results view
@@ -237,7 +235,7 @@ class TestLtiAuthentication(TestCase):
         # Make token and include it in the mock request
         token = self._make_token(token_scopes)
         self.mock_request.headers = {
-            "Authorization": "Bearer {}".format(token)
+            "Authorization": f"Bearer {token}"
         }
 
         # Test scores view
diff --git a/lti_consumer/lti_1p3/tests/test_ags.py b/lti_consumer/lti_1p3/tests/test_ags.py
index 94f4cd30f794fe294f5939df81e7de11e158f5f8..483162bec63703e73cbb929ac6e388ba208afe10 100644
--- a/lti_consumer/lti_1p3/tests/test_ags.py
+++ b/lti_consumer/lti_1p3/tests/test_ags.py
@@ -1,7 +1,6 @@
 """
 Unit tests for LTI 1.3 consumer implementation
 """
-from __future__ import absolute_import, unicode_literals
 
 from django.test.testcases import TestCase
 
diff --git a/lti_consumer/lti_1p3/tests/test_consumer.py b/lti_consumer/lti_1p3/tests/test_consumer.py
index 65e795627827d30409b4f069446769d0daec94fe..e748c30e42f3d89baf36a6a3f1a917c445b435fa 100644
--- a/lti_consumer/lti_1p3/tests/test_consumer.py
+++ b/lti_consumer/lti_1p3/tests/test_consumer.py
@@ -1,24 +1,21 @@
 """
 Unit tests for LTI 1.3 consumer implementation
 """
-from __future__ import absolute_import, unicode_literals
 
 import json
-from urllib.parse import urlparse, parse_qs
-import ddt
-
-from mock import patch
-from django.test.testcases import TestCase
+from unittest.mock import patch
+from urllib.parse import parse_qs, urlparse
 
+import ddt
 from Cryptodome.PublicKey import RSA
+from django.test.testcases import TestCase
 from jwkest.jwk import load_jwks
 from jwkest.jws import JWS
 
-from lti_consumer.lti_1p3.constants import LTI_1P3_CONTEXT_TYPE
-from lti_consumer.lti_1p3.consumer import LtiConsumer1p3, LtiAdvantageConsumer
-from lti_consumer.lti_1p3.ags import LtiAgs
 from lti_consumer.lti_1p3 import exceptions
-
+from lti_consumer.lti_1p3.ags import LtiAgs
+from lti_consumer.lti_1p3.constants import LTI_1P3_CONTEXT_TYPE
+from lti_consumer.lti_1p3.consumer import LtiAdvantageConsumer, LtiConsumer1p3
 
 # Variables required for testing and verification
 ISS = "http://test-platform.example/"
diff --git a/lti_consumer/lti_1p3/tests/test_key_handlers.py b/lti_consumer/lti_1p3/tests/test_key_handlers.py
index cde6127868768862601266c30876fb3cb9bd84b7..ab7a7734e299f57bd9b170a5a76ad87c6525938a 100644
--- a/lti_consumer/lti_1p3/tests/test_key_handlers.py
+++ b/lti_consumer/lti_1p3/tests/test_key_handlers.py
@@ -1,20 +1,19 @@
 """
 Unit tests for LTI 1.3 consumer implementation
 """
-from __future__ import absolute_import, unicode_literals
 
 import json
-import ddt
-
-from mock import patch
-from django.test.testcases import TestCase
+from unittest.mock import patch
 
+import ddt
 from Cryptodome.PublicKey import RSA
+from django.test.testcases import TestCase
 from jwkest.jwk import RSAKey, load_jwks
 from jwkest.jws import JWS
 
-from lti_consumer.lti_1p3.key_handlers import PlatformKeyHandler, ToolKeyHandler
 from lti_consumer.lti_1p3 import exceptions
+from lti_consumer.lti_1p3.key_handlers import PlatformKeyHandler, ToolKeyHandler
+
 from .utils import create_jwt
 
 
diff --git a/lti_consumer/lti_xblock.py b/lti_consumer/lti_xblock.py
index 8bb24545c1b56bdea600ce84f8e421a480c643fb..523a1a859cfad4abe4af6e27319c1659e4bcb018 100644
--- a/lti_consumer/lti_xblock.py
+++ b/lti_consumer/lti_xblock.py
@@ -99,9 +99,9 @@ DOCS_ANCHOR_TAG_OPEN = (
 )
 RESULT_SERVICE_SUFFIX_PARSER = re.compile(r"^user/(?P<anon_id>\w+)", re.UNICODE)
 ROLE_MAP = {
-    'student': u'Student',
-    'staff': u'Administrator',
-    'instructor': u'Instructor',
+    'student': 'Student',
+    'staff': 'Administrator',
+    'instructor': 'Instructor',
 }
 
 
@@ -624,7 +624,7 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
         """
         Get system user role and convert it to LTI role.
         """
-        return ROLE_MAP.get(self.runtime.get_user_role(), u'Student')
+        return ROLE_MAP.get(self.runtime.get_user_role(), 'Student')
 
     @property
     def course(self):
@@ -716,7 +716,7 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
         makes resource_link_id to be unique among courses inside same system.
         """
         return str(urllib.parse.quote(
-            "{}-{}".format(self.runtime.hostname, self.location.html_id())  # pylint: disable=no-member
+            f"{self.runtime.hostname}-{self.location.html_id()}"  # pylint: disable=no-member
         ))
 
     @property
@@ -1193,7 +1193,7 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
                 args.append(request.body)
             response_body = getattr(
                 self,
-                "_result_service_{}".format(request.method.lower())
+                f"_result_service_{request.method.lower()}"
             )(*args)
         except (AttributeError, LtiError):
             return Response(status=404)
@@ -1286,7 +1286,7 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
         """
         self.set_user_module_score(user, None, None)
 
-    def set_user_module_score(self, user, score, max_score, comment=u''):
+    def set_user_module_score(self, user, score, max_score, comment=''):
         """
         Sets the module user state, including grades and comments, and also scoring in db's courseware_studentmodule
 
diff --git a/lti_consumer/models.py b/lti_consumer/models.py
index 17daab6be295652790604b0a077146c0008772e7..bd162fafc735166bd7bd34a388accab1f2557d13 100644
--- a/lti_consumer/models.py
+++ b/lti_consumer/models.py
@@ -307,7 +307,7 @@ class LtiConfiguration(models.Model):
         return self._get_lti_1p1_consumer()
 
     def __str__(self):
-        return "[{}] {} - {}".format(self.config_store, self.version, self.location)
+        return f"[{self.config_store}] {self.version} - {self.location}"
 
     class Meta:
         app_label = 'lti_consumer'
diff --git a/lti_consumer/outcomes.py b/lti_consumer/outcomes.py
index a96b8ff9b16330352bee07f024dd635f85be8542..d715ff245b2a6529dcd2d5a62a1da1f0ae580bb2 100644
--- a/lti_consumer/outcomes.py
+++ b/lti_consumer/outcomes.py
@@ -194,13 +194,13 @@ class OutcomeService:
 
             values = {
                 'imsx_codeMajor': 'success',
-                'imsx_description': 'Score for {sourced_id} is now {score}'.format(sourced_id=sourced_id, score=score),
+                'imsx_description': f'Score for {sourced_id} is now {score}',
                 'imsx_messageIdentifier': escape(imsx_message_identifier),
                 'response': '<replaceResultResponse/>'
             }
-            log.debug(u"[LTI]: Grade is saved.")
+            log.debug("[LTI]: Grade is saved.")
             return response_xml_template.format(**values)
 
         unsupported_values['imsx_messageIdentifier'] = escape(imsx_message_identifier)
-        log.debug(u"[LTI]: Incorrect action.")
+        log.debug("[LTI]: Incorrect action.")
         return response_xml_template.format(**unsupported_values)
diff --git a/lti_consumer/plugin/urls.py b/lti_consumer/plugin/urls.py
index 1cf1d7f563db419ed0c5ed3dc31ee688370cff18..2f68bd5a718490fe6d4b7f24aae1ffb0ab3b56d7 100644
--- a/lti_consumer/plugin/urls.py
+++ b/lti_consumer/plugin/urls.py
@@ -2,7 +2,6 @@
 URL mappings for LTI Consumer plugin.
 """
 
-from __future__ import absolute_import
 
 from django.conf import settings
 from django.conf.urls import url, include
@@ -26,7 +25,7 @@ router.register(r'lti-ags', LtiAgsLineItemViewset, basename='lti-ags-view')
 app_name = 'lti_consumer'
 urlpatterns = [
     url(
-        'lti_consumer/v1/public_keysets/{}$'.format(settings.USAGE_ID_PATTERN),
+        f'lti_consumer/v1/public_keysets/{settings.USAGE_ID_PATTERN}$',
         public_keyset_endpoint,
         name='lti_consumer.public_keyset_endpoint'
     ),
@@ -36,7 +35,7 @@ urlpatterns = [
         name='lti_consumer.launch_gate'
     ),
     url(
-        'lti_consumer/v1/token/{}$'.format(settings.USAGE_ID_PATTERN),
+        f'lti_consumer/v1/token/{settings.USAGE_ID_PATTERN}$',
         access_token_endpoint,
         name='lti_consumer.access_token'
     ),
diff --git a/lti_consumer/tests/unit/plugin/test_views.py b/lti_consumer/tests/unit/plugin/test_views.py
index f83e1ec29ba9c27d3826c4fe03e46d6df4933803..be6030247d481f1fe307161c20622d3e73734de3 100644
--- a/lti_consumer/tests/unit/plugin/test_views.py
+++ b/lti_consumer/tests/unit/plugin/test_views.py
@@ -2,7 +2,7 @@
 Tests for LTI 1.3 endpoint views.
 """
 import json
-from mock import patch
+from unittest.mock import patch
 
 from django.http import HttpResponse
 from django.test.testcases import TestCase
@@ -19,7 +19,7 @@ class TestLti1p3KeysetEndpoint(TestCase):
         super().setUp()
 
         self.location = 'block-v1:course+test+2020+type@problem+block@test'
-        self.url = '/lti_consumer/v1/public_keysets/{}'.format(self.location)
+        self.url = f'/lti_consumer/v1/public_keysets/{self.location}'
 
         # Set up LTI Configuration
         self.lti_config = LtiConfiguration.objects.create(
@@ -116,7 +116,7 @@ class TestLti1p3AccessTokenEndpoint(TestCase):
         super().setUp()
 
         self.location = 'block-v1:course+test+2020+type@problem+block@test'
-        self.url = '/lti_consumer/v1/token/{}'.format(self.location)
+        self.url = f'/lti_consumer/v1/token/{self.location}'
 
         # Patch settings calls to LMS method
         xblock_handler_patcher = patch(
diff --git a/lti_consumer/tests/unit/plugin/test_views_lti_ags.py b/lti_consumer/tests/unit/plugin/test_views_lti_ags.py
index cb40c291855acaaa0a892fd66d4a1a4e745f27c5..cbe3b3efe69848c0763b5ef38f54ea23dc01d853 100644
--- a/lti_consumer/tests/unit/plugin/test_views_lti_ags.py
+++ b/lti_consumer/tests/unit/plugin/test_views_lti_ags.py
@@ -3,7 +3,7 @@ Tests for LTI Advantage Assignments and Grades Service views.
 """
 import json
 from datetime import timedelta
-from mock import patch, PropertyMock, Mock
+from unittest.mock import patch, PropertyMock, Mock
 
 from Cryptodome.PublicKey import RSA
 import ddt
@@ -90,7 +90,7 @@ class LtiAgsLineItemViewSetTestCase(APITransactionTestCase):
         })
         # pylint: disable=no-member
         self.client.credentials(
-            HTTP_AUTHORIZATION="Bearer {}".format(token)
+            HTTP_AUTHORIZATION=f"Bearer {token}"
         )
 
 
diff --git a/lti_consumer/tests/unit/test_api.py b/lti_consumer/tests/unit/test_api.py
index e0b92e28a4758d981842e46da402a3b4c1eaae6f..888d229e04e6def90e1c685c6110839d01aacb9f 100644
--- a/lti_consumer/tests/unit/test_api.py
+++ b/lti_consumer/tests/unit/test_api.py
@@ -1,15 +1,16 @@
 """
 Tests for LTI API.
 """
+from unittest.mock import Mock, patch
+
 from Cryptodome.PublicKey import RSA
 from django.test.testcases import TestCase
-from mock import Mock, patch
 
 from lti_consumer.api import (
     _get_or_create_local_lti_config,
-    get_lti_consumer,
     get_lti_1p3_launch_info,
     get_lti_1p3_launch_start_url,
+    get_lti_consumer
 )
 from lti_consumer.lti_xblock import LtiConsumerXBlock
 from lti_consumer.models import LtiConfiguration
diff --git a/lti_consumer/tests/unit/test_lti_xblock.py b/lti_consumer/tests/unit/test_lti_xblock.py
index bfbac794de115b639842ac5af40df6d98d8bce09..9fb1358f5c553ab3f50da6494acc85c4541da8c0 100644
--- a/lti_consumer/tests/unit/test_lti_xblock.py
+++ b/lti_consumer/tests/unit/test_lti_xblock.py
@@ -2,25 +2,23 @@
 Unit tests for LtiConsumerXBlock
 """
 
-from datetime import timedelta
 import json
 import urllib.parse
+from datetime import timedelta
+from unittest.mock import Mock, NonCallableMock, PropertyMock, patch
 
 import ddt
 from Cryptodome.PublicKey import RSA
 from django.test.testcases import TestCase
 from django.utils import timezone
 from jwkest.jwk import RSAKey
-from mock import Mock, PropertyMock, NonCallableMock, patch
 
 from lti_consumer.api import get_lti_1p3_launch_info
 from lti_consumer.exceptions import LtiError
+from lti_consumer.lti_1p3.tests.utils import create_jwt
 from lti_consumer.lti_xblock import LtiConsumerXBlock, parse_handler_suffix
 from lti_consumer.tests.unit import test_utils
-from lti_consumer.tests.unit.test_utils import (FAKE_USER_ID, make_request,
-                                                make_xblock)
-from lti_consumer.lti_1p3.tests.utils import create_jwt
-
+from lti_consumer.tests.unit.test_utils import FAKE_USER_ID, make_request, make_xblock
 
 HTML_PROBLEM_PROGRESS = '<div class="problem-progress">'
 HTML_ERROR_MESSAGE = '<h3 class="error_message">'
@@ -164,7 +162,7 @@ class TestProperties(TestLtiConsumerXBlock):
         key = 'test'
         secret = 'secret'
         self.xblock.lti_id = provider
-        type(mock_course).lti_passports = PropertyMock(return_value=["{}:{}:{}".format(provider, key, secret)])
+        type(mock_course).lti_passports = PropertyMock(return_value=[f"{provider}:{key}:{secret}"])
         lti_provider_key, lti_provider_secret = self.xblock.lti_provider_key_secret
 
         self.assertEqual(lti_provider_key, key)
@@ -179,7 +177,7 @@ class TestProperties(TestLtiConsumerXBlock):
         key = '1:10:test'
         secret = 'secret'
         self.xblock.lti_id = provider
-        type(mock_course).lti_passports = PropertyMock(return_value=["{}:{}:{}".format(provider, key, secret)])
+        type(mock_course).lti_passports = PropertyMock(return_value=[f"{provider}:{key}:{secret}"])
         lti_provider_key, lti_provider_secret = self.xblock.lti_provider_key_secret
 
         self.assertEqual(lti_provider_key, key)
@@ -194,7 +192,7 @@ class TestProperties(TestLtiConsumerXBlock):
         key = 'test'
         secret = 'secret'
         self.xblock.lti_id = 'wrong_provider'
-        type(mock_course).lti_passports = PropertyMock(return_value=["{}:{}:{}".format(provider, key, secret)])
+        type(mock_course).lti_passports = PropertyMock(return_value=[f"{provider}:{key}:{secret}"])
         lti_provider_key, lti_provider_secret = self.xblock.lti_provider_key_secret
 
         self.assertEqual(lti_provider_key, '')
@@ -209,7 +207,7 @@ class TestProperties(TestLtiConsumerXBlock):
         key = 'test'
         secret = 'secret'
         self.xblock.lti_id = provider
-        type(mock_course).lti_passports = PropertyMock(return_value=["{}{}{}".format(provider, key, secret)])
+        type(mock_course).lti_passports = PropertyMock(return_value=[f"{provider}{key}{secret}"])
 
         with self.assertRaises(LtiError):
             _, _ = self.xblock.lti_provider_key_secret
@@ -242,7 +240,7 @@ class TestProperties(TestLtiConsumerXBlock):
         """
         self.assertEqual(
             self.xblock.resource_link_id,
-            "{}-{}".format(self.xblock.runtime.hostname, self.xblock.location.html_id())
+            f"{self.xblock.runtime.hostname}-{self.xblock.location.html_id()}"
         )
 
     @patch('lti_consumer.lti_xblock.LtiConsumerXBlock.context_id')
@@ -255,14 +253,14 @@ class TestProperties(TestLtiConsumerXBlock):
         mock_resource_link_id.__get__ = Mock(return_value='resource_link_id')
         mock_context_id.__get__ = Mock(return_value='context_id')
 
-        self.assertEqual(self.xblock.lis_result_sourcedid, "context_id:resource_link_id:{}".format(FAKE_USER_ID))
+        self.assertEqual(self.xblock.lis_result_sourcedid, f"context_id:resource_link_id:{FAKE_USER_ID}")
 
     def test_outcome_service_url(self):
         """
         Test `outcome_service_url` calls `runtime.handler_url` with thirdparty kwarg
         """
         handler_url = 'http://localhost:8005/outcome_service_handler'
-        self.xblock.runtime.handler_url = Mock(return_value="{}/?".format(handler_url))
+        self.xblock.runtime.handler_url = Mock(return_value=f"{handler_url}/?")
         url = self.xblock.outcome_service_url
 
         self.xblock.runtime.handler_url.assert_called_with(self.xblock, 'outcome_service_handler', thirdparty=True)
@@ -273,7 +271,7 @@ class TestProperties(TestLtiConsumerXBlock):
         Test `result_service_url` calls `runtime.handler_url` with thirdparty kwarg
         """
         handler_url = 'http://localhost:8005/result_service_handler'
-        self.xblock.runtime.handler_url = Mock(return_value="{}/?".format(handler_url))
+        self.xblock.runtime.handler_url = Mock(return_value=f"{handler_url}/?")
         url = self.xblock.result_service_url
 
         self.xblock.runtime.handler_url.assert_called_with(self.xblock, 'result_service_handler', thirdparty=True)
@@ -291,12 +289,12 @@ class TestProperties(TestLtiConsumerXBlock):
         self.xblock.custom_parameters = ['param_1=true', 'param_2 = false', 'lti_version=1.1']
 
         expected_params = {
-            u'custom_component_display_name': self.xblock.display_name,
-            u'custom_component_due_date': now.strftime('%Y-%m-%d %H:%M:%S'),
-            u'custom_component_graceperiod': str(one_day.total_seconds()),
-            u'custom_param_1': u'true',
-            u'custom_param_2': u'false',
-            u'lti_version': u'1.1'
+            'custom_component_display_name': self.xblock.display_name,
+            'custom_component_due_date': now.strftime('%Y-%m-%d %H:%M:%S'),
+            'custom_component_graceperiod': str(one_day.total_seconds()),
+            'custom_param_1': 'true',
+            'custom_param_2': 'false',
+            'lti_version': '1.1'
         }
 
         params = self.xblock.prefixed_custom_parameters
@@ -450,7 +448,7 @@ class TestGetLti1p1Consumer(TestLtiConsumerXBlock):
         secret = 'secret'
         self.xblock.lti_id = provider
         self.xblock.location = 'block-v1:course+test+2020+type@problem+block@test'
-        type(mock_course).lti_passports = PropertyMock(return_value=["{}:{}:{}".format(provider, key, secret)])
+        type(mock_course).lti_passports = PropertyMock(return_value=[f"{provider}:{key}:{secret}"])
 
         with patch('lti_consumer.models.LtiConfiguration.block', return_value=self.xblock):
             self.xblock._get_lti_consumer()  # pylint: disable=protected-access
@@ -614,7 +612,7 @@ class TestLtiLaunchHandler(TestLtiConsumerXBlock):
         provider = 'lti_provider'
         key = 'test'
         secret = 'secret'
-        type(mock_course).lti_passports = PropertyMock(return_value=["{}:{}:{}".format(provider, key, secret)])
+        type(mock_course).lti_passports = PropertyMock(return_value=[f"{provider}:{key}:{secret}"])
 
         request = make_request('', 'GET')
         response = self.xblock.lti_launch_handler(request)
@@ -888,7 +886,7 @@ class TestResultServiceHandler(TestLtiConsumerXBlock):
         Test `get_outcome_service_url` with default parameter
         """
         handler_url = 'http://localhost:8005/outcome_service_handler'
-        self.xblock.runtime.handler_url = Mock(return_value="{}/?".format(handler_url))
+        self.xblock.runtime.handler_url = Mock(return_value=f"{handler_url}/?")
         url = self.xblock.get_outcome_service_url()
 
         self.xblock.runtime.handler_url.assert_called_with(self.xblock, 'outcome_service_handler', thirdparty=True)
@@ -899,7 +897,7 @@ class TestResultServiceHandler(TestLtiConsumerXBlock):
         Test `get_outcome_service_url` calls service name grade_handler
         """
         handler_url = 'http://localhost:8005/outcome_service_handler'
-        self.xblock.runtime.handler_url = Mock(return_value="{}/?".format(handler_url))
+        self.xblock.runtime.handler_url = Mock(return_value=f"{handler_url}/?")
         url = self.xblock.get_outcome_service_url('grade_handler')
 
         self.xblock.runtime.handler_url.assert_called_with(self.xblock, 'outcome_service_handler', thirdparty=True)
@@ -910,7 +908,7 @@ class TestResultServiceHandler(TestLtiConsumerXBlock):
         Test `get_outcome_service_url` calls with service name lti_2_0_result_rest_handler
         """
         handler_url = 'http://localhost:8005/result_service_handler'
-        self.xblock.runtime.handler_url = Mock(return_value="{}/?".format(handler_url))
+        self.xblock.runtime.handler_url = Mock(return_value=f"{handler_url}/?")
         url = self.xblock.get_outcome_service_url('lti_2_0_result_rest_handler')
 
         self.xblock.runtime.handler_url.assert_called_with(self.xblock, 'result_service_handler', thirdparty=True)
@@ -1051,7 +1049,7 @@ class TestParseSuffix(TestLtiConsumerXBlock):
         Test `parse_handler_suffix` when `suffix` parameter can be parsed
         :return:
         """
-        parsed = parse_handler_suffix("user/{}".format(FAKE_USER_ID))
+        parsed = parse_handler_suffix(f"user/{FAKE_USER_ID}")
         self.assertEqual(parsed, FAKE_USER_ID)
 
 
@@ -1088,21 +1086,21 @@ class TestGetContext(TestLtiConsumerXBlock):
         """
         Test that allowed tags are not escaped in context['comment']
         """
-        comment = u'<{0}>This is a comment</{0}>!'.format(tag)
+        comment = '<{0}>This is a comment</{0}>!'.format(tag)
         self.xblock.set_user_module_score(Mock(), 0.92, 1.0, comment)
         context = self.xblock._get_context_for_template()  # pylint: disable=protected-access
 
-        self.assertIn('<{}>'.format(tag), context['comment'])
+        self.assertIn(f'<{tag}>', context['comment'])
 
     def test_comment_retains_image_src(self):
         """
         Test that image tag has src and other attrs are sanitized
         """
-        comment = u'<img src="example.com/image.jpeg" onerror="myFunction()">'
+        comment = '<img src="example.com/image.jpeg" onerror="myFunction()">'
         self.xblock.set_user_module_score(Mock(), 0.92, 1.0, comment)
         context = self.xblock._get_context_for_template()  # pylint: disable=protected-access
 
-        self.assertIn(u'<img src="example.com/image.jpeg">', context['comment'])
+        self.assertIn('<img src="example.com/image.jpeg">', context['comment'])
 
 
 @ddt.ddt
@@ -1204,7 +1202,7 @@ class TestLtiConsumer1p3XBlock(TestCase):
         # Craft request sent back by LTI tool
         request = make_request('', 'GET')
         request.query_string = (
-            "client_id={}&".format(client_id) +
+            f"client_id={client_id}&" +
             "redirect_uri=http://tool.example/launch&" +
             "state=state_test_123&" +
             "nonce=nonce&" +
diff --git a/lti_consumer/tests/unit/test_models.py b/lti_consumer/tests/unit/test_models.py
index 2375fb6b94dcc0260d6ed21b86f7880748a93b8f..1a8079107519ee075da738636a0ca3b88aabfc51 100644
--- a/lti_consumer/tests/unit/test_models.py
+++ b/lti_consumer/tests/unit/test_models.py
@@ -2,16 +2,16 @@
 Unit tests for LTI models.
 """
 from datetime import timedelta
+from unittest.mock import patch
+
 from Cryptodome.PublicKey import RSA
-from django.utils import timezone
 from django.test.testcases import TestCase
-
+from django.utils import timezone
 from jwkest.jwk import RSAKey
-from mock import patch
 
 from lti_consumer.lti_1p1.consumer import LtiConsumer1p1
 from lti_consumer.lti_xblock import LtiConsumerXBlock
-from lti_consumer.models import LtiAgsLineItem, LtiConfiguration, LtiAgsScore
+from lti_consumer.models import LtiAgsLineItem, LtiAgsScore, LtiConfiguration
 from lti_consumer.tests.unit.test_utils import make_xblock
 
 
@@ -102,7 +102,7 @@ class TestLtiConfigurationModel(TestCase):
 
         self.assertEqual(
             str(lti_config),
-            "[CONFIG_ON_XBLOCK] lti_1p3 - {}".format(dummy_location)
+            f"[CONFIG_ON_XBLOCK] lti_1p3 - {dummy_location}"
         )
 
     def test_lti_consumer_ags_enabled(self):
diff --git a/lti_consumer/tests/unit/test_outcomes.py b/lti_consumer/tests/unit/test_outcomes.py
index c27672d560cecfed4e151d6495f38d0aa99204ec..9fe0337751e84e1b8a807c8438bba5e9e7a5fa21 100644
--- a/lti_consumer/tests/unit/test_outcomes.py
+++ b/lti_consumer/tests/unit/test_outcomes.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 """
 Unit tests for lti_consumer.outcomes module
 """
@@ -7,7 +6,7 @@ import textwrap
 import unittest
 from copy import copy
 
-from mock import Mock, PropertyMock, patch
+from unittest.mock import Mock, PropertyMock, patch
 
 from lti_consumer.exceptions import LtiError
 from lti_consumer.outcomes import OutcomeService, parse_grade_xml_body
@@ -321,10 +320,10 @@ class TestParseGradeXmlBody(unittest.TestCase):
 
         msg_id, sourced_id, score, action = parse_grade_xml_body(request_body_template)
 
-        self.assertEqual(msg_id, u'ţéšţ_message_id')
-        self.assertEqual(sourced_id, u'ţéšţ_sourced_id')
+        self.assertEqual(msg_id, 'ţéšţ_message_id')
+        self.assertEqual(sourced_id, 'ţéšţ_sourced_id')
         self.assertEqual(score, 1.0)
-        self.assertEqual(action, u'ţéšţ_action')
+        self.assertEqual(action, 'ţéšţ_action')
 
 
 class TestOutcomeService(TestLtiConsumerXBlock):
diff --git a/lti_consumer/tests/unit/test_utils.py b/lti_consumer/tests/unit/test_utils.py
index da42a607c88249342afb543b51f2c337e45114c3..f45158759c3057a10aa637c5d68475e45078a384 100644
--- a/lti_consumer/tests/unit/test_utils.py
+++ b/lti_consumer/tests/unit/test_utils.py
@@ -2,7 +2,7 @@
 Utility functions used within unit tests
 """
 
-from mock import Mock
+from unittest.mock import Mock
 from webob import Request
 from workbench.runtime import WorkbenchRuntime
 from xblock.fields import ScopeIds
diff --git a/lti_consumer/utils.py b/lti_consumer/utils.py
index bbce7853ef9e3aeb49ae0638d1fe81daa8f55f68..80fa8bf8d7bc34579627ac877e6e6bec1c26c039 100644
--- a/lti_consumer/utils.py
+++ b/lti_consumer/utils.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 """
 Utility functions for LTI Consumer block
 """
diff --git a/requirements/base.txt b/requirements/base.txt
index 6cbb5b593b2e5e6d5727f8ec23773ebad437efae..6a07b76538ff535d61c97ecd369a90277115a5d8 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -5,19 +5,19 @@
 #    make upgrade
 #
 appdirs==1.4.4            # via fs
-bleach==3.2.1             # via -r requirements/base.in
+bleach==3.2.2             # via -r requirements/base.in
 certifi==2020.12.5        # via requests
 chardet==4.0.0            # via requests
 django-filter==2.4.0      # via -r requirements/base.in
 django==2.2.17            # via -c requirements/constraints.txt, -r requirements/base.in, django-filter, edx-opaque-keys, jsonfield2
 edx-opaque-keys[django]==2.1.1  # via -r requirements/base.in
-fs==2.4.11                # via xblock
+fs==2.4.12                # via xblock
 future==0.18.2            # via pyjwkest
 idna==2.10                # via requests
 jsonfield2==3.0.3         # via -c requirements/constraints.txt, -r requirements/base.in
 lazy==1.4                 # via -r requirements/base.in
 lxml==4.6.2               # via -r requirements/base.in, xblock
-mako==1.1.3               # via -r requirements/base.in, xblock-utils
+mako==1.1.4               # via -r requirements/base.in, xblock-utils
 markupsafe==1.1.1         # via mako, xblock
 oauthlib==3.1.0           # via -r requirements/base.in
 packaging==20.8           # via bleach
@@ -27,14 +27,13 @@ pyjwkest==1.4.2           # via -r requirements/base.in
 pymongo==3.11.2           # via edx-opaque-keys
 pyparsing==2.4.7          # via packaging
 python-dateutil==2.8.1    # via xblock
-pytz==2020.4              # via django, fs, xblock
-pyyaml==5.3.1             # via xblock
+pytz==2020.5              # via django, fs, xblock
+pyyaml==5.4.1             # via xblock
 requests==2.25.1          # via pyjwkest
 simplejson==3.17.2        # via xblock-utils
 six==1.15.0               # via bleach, edx-opaque-keys, fs, pyjwkest, python-dateutil, stevedore, xblock
 sqlparse==0.4.1           # via django
 stevedore==1.32.0         # via -c requirements/constraints.txt, edx-opaque-keys
-typing==3.7.4.3           # via fs
 urllib3==1.26.2           # via requests
 web-fragments==0.3.2      # via xblock, xblock-utils
 webencodings==0.5.1       # via bleach
diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt
index f3a39fcfab74abed3efd4ba37622e6dad825b44b..a6f6c30fd9e516961a0b2d8fa44ae6128408fd60 100644
--- a/requirements/pip_tools.txt
+++ b/requirements/pip_tools.txt
@@ -5,8 +5,7 @@
 #    make upgrade
 #
 click==7.1.2              # via pip-tools
-pip-tools==5.4.0          # via -r requirements/pip_tools.in
-six==1.15.0               # via pip-tools
+pip-tools==5.5.0          # via -r requirements/pip_tools.in
 
 # The following packages are considered to be unsafe in a requirements file:
 # pip
diff --git a/requirements/test.txt b/requirements/test.txt
index 5ff2002fb3df380d0260fc3d7f7c0b1725caf33e..b32edd276708e90ec8886b8e3d08cb2264e2c165 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -6,33 +6,33 @@
 #
 appdirs==1.4.4            # via -r requirements/base.txt, fs
 astroid==2.4.2            # via pylint, pylint-celery
-bleach==3.2.1             # via -r requirements/base.txt
-boto3==1.16.42            # via fs-s3fs
-botocore==1.19.42         # via boto3, s3transfer
+bleach==3.2.2             # via -r requirements/base.txt
+boto3==1.16.57            # via fs-s3fs
+botocore==1.19.57         # via boto3, s3transfer
 certifi==2020.12.5        # via -r requirements/base.txt, requests
 chardet==4.0.0            # via -r requirements/base.txt, requests
 click-log==0.3.2          # via edx-lint
 click==7.1.2              # via click-log, edx-lint
 coverage==5.3.1           # via coveralls
-coveralls==2.2.0          # via -r requirements/test.in
+coveralls==3.0.0          # via -r requirements/test.in
 ddt==1.4.1                # via -r requirements/test.in
 django-filter==2.4.0      # via -r requirements/base.txt
-django-pyfs==2.2          # via -r requirements/test.in
+django-pyfs==3.0          # via -r requirements/test.in
 djangorestframework==3.9.4  # via -c requirements/constraints.txt, -r requirements/test.in
 docopt==0.6.2             # via coveralls
 edx-lint==1.6             # via -r requirements/test.in
 edx-opaque-keys[django]==2.1.1  # via -r requirements/base.txt
 fs-s3fs==1.1.1            # via django-pyfs
-fs==2.4.11                # via -r requirements/base.txt, django-pyfs, fs-s3fs, xblock
+fs==2.4.12                # via -r requirements/base.txt, django-pyfs, fs-s3fs, xblock
 future==0.18.2            # via -r requirements/base.txt, pyjwkest
 idna==2.10                # via -r requirements/base.txt, requests
-isort==4.3.21             # via pylint
+isort==5.7.0              # via pylint
 jmespath==0.10.0          # via boto3, botocore
 jsonfield2==3.0.3         # via -c requirements/constraints.txt, -r requirements/base.txt
 lazy-object-proxy==1.4.3  # via astroid
 lazy==1.4                 # via -r requirements/base.txt
 lxml==4.6.2               # via -r requirements/base.txt, xblock
-mako==1.1.3               # via -r requirements/base.txt, xblock-utils
+mako==1.1.4               # via -r requirements/base.txt, xblock-utils
 markupsafe==1.1.1         # via -r requirements/base.txt, mako, xblock
 mccabe==0.6.1             # via pylint
 mock==3.0.5               # via -c requirements/constraints.txt, -r requirements/test.in
@@ -49,17 +49,15 @@ pylint==2.6.0             # via edx-lint, pylint-celery, pylint-django, pylint-p
 pymongo==3.11.2           # via -r requirements/base.txt, edx-opaque-keys
 pyparsing==2.4.7          # via -r requirements/base.txt, packaging
 python-dateutil==2.8.1    # via -r requirements/base.txt, botocore, xblock
-pytz==2020.4              # via -r requirements/base.txt, django, fs, xblock
-pyyaml==5.3.1             # via -r requirements/base.txt, xblock
+pytz==2020.5              # via -r requirements/base.txt, django, fs, xblock
+pyyaml==5.4.1             # via -r requirements/base.txt, xblock
 requests==2.25.1          # via -r requirements/base.txt, coveralls, pyjwkest
-s3transfer==0.3.3         # via boto3
+s3transfer==0.3.4         # via boto3
 simplejson==3.17.2        # via -r requirements/base.txt, xblock-utils
 six==1.15.0               # via -r requirements/base.txt, astroid, bleach, edx-lint, edx-opaque-keys, fs, fs-s3fs, mock, pyjwkest, python-dateutil, stevedore, xblock
 sqlparse==0.4.1           # via -r requirements/base.txt, django
 stevedore==1.32.0         # via -c requirements/constraints.txt, -r requirements/base.txt, edx-opaque-keys
 toml==0.10.2              # via pylint
-typed-ast==1.4.1          # via astroid
-typing==3.7.4.3           # via -r requirements/base.txt, fs
 urllib3==1.26.2           # via -r requirements/base.txt, botocore, requests
 web-fragments==0.3.2      # via -r requirements/base.txt, xblock, xblock-utils
 webencodings==0.5.1       # via -r requirements/base.txt, bleach
diff --git a/requirements/tox.txt b/requirements/tox.txt
index 97c27e44ff99bc0b55622e450bc73bd8ed79499d..e265effdd40a6847d100db6a762bfa9fbf1dbacb 100644
--- a/requirements/tox.txt
+++ b/requirements/tox.txt
@@ -7,14 +7,11 @@
 appdirs==1.4.4            # via virtualenv
 distlib==0.3.1            # via virtualenv
 filelock==3.0.12          # via tox, virtualenv
-importlib-metadata==2.1.1  # via pluggy, tox, virtualenv
-importlib-resources==3.2.1  # via virtualenv
 packaging==20.8           # via tox
 pluggy==0.13.1            # via tox
 py==1.10.0                # via tox
 pyparsing==2.4.7          # via packaging
 six==1.15.0               # via tox, virtualenv
 toml==0.10.2              # via tox
-tox==3.20.1               # via -r requirements/tox.in
-virtualenv==20.2.2        # via tox
-zipp==1.1.1               # via -c requirements/constraints.txt, importlib-metadata, importlib-resources
+tox==3.21.2               # via -r requirements/tox.in
+virtualenv==20.4.0        # via tox
diff --git a/requirements/travis.txt b/requirements/travis.txt
index 3d7651c8dfc1f7fb0bae5b578b9115d8639fbd16..432c01c076d652f0c792f2feccabf6d7378aee14 100644
--- a/requirements/travis.txt
+++ b/requirements/travis.txt
@@ -6,19 +6,19 @@
 #
 appdirs==1.4.4            # via -r requirements/test.txt, -r requirements/tox.txt, fs, virtualenv
 astroid==2.4.2            # via -r requirements/test.txt, pylint, pylint-celery
-bleach==3.2.1             # via -r requirements/test.txt
-boto3==1.16.42            # via -r requirements/test.txt, fs-s3fs
-botocore==1.19.42         # via -r requirements/test.txt, boto3, s3transfer
+bleach==3.2.2             # via -r requirements/test.txt
+boto3==1.16.57            # via -r requirements/test.txt, fs-s3fs
+botocore==1.19.57         # via -r requirements/test.txt, boto3, s3transfer
 certifi==2020.12.5        # via -r requirements/test.txt, requests
 chardet==4.0.0            # via -r requirements/test.txt, requests
 click-log==0.3.2          # via -r requirements/test.txt, edx-lint
 click==7.1.2              # via -r requirements/test.txt, click-log, edx-lint
 coverage==5.3.1           # via -r requirements/test.txt, coveralls
-coveralls==2.2.0          # via -r requirements/test.txt
+coveralls==3.0.0          # via -r requirements/test.txt
 ddt==1.4.1                # via -r requirements/test.txt
 distlib==0.3.1            # via -r requirements/tox.txt, virtualenv
 django-filter==2.4.0      # via -r requirements/test.txt
-django-pyfs==2.2          # via -r requirements/test.txt
+django-pyfs==3.0          # via -r requirements/test.txt
 django==2.2.17            # via -c requirements/constraints.txt, -r requirements/test.txt, django-filter, django-pyfs, edx-opaque-keys, jsonfield2, xblock-sdk
 djangorestframework==3.9.4  # via -c requirements/constraints.txt, -r requirements/test.txt
 docopt==0.6.2             # via -r requirements/test.txt, coveralls
@@ -26,18 +26,16 @@ edx-lint==1.6             # via -r requirements/test.txt
 edx-opaque-keys[django]==2.1.1  # via -r requirements/test.txt
 filelock==3.0.12          # via -r requirements/tox.txt, tox, virtualenv
 fs-s3fs==1.1.1            # via -r requirements/test.txt, django-pyfs
-fs==2.4.11                # via -r requirements/test.txt, django-pyfs, fs-s3fs, xblock
+fs==2.4.12                # via -r requirements/test.txt, django-pyfs, fs-s3fs, xblock
 future==0.18.2            # via -r requirements/test.txt, pyjwkest
 idna==2.10                # via -r requirements/test.txt, requests
-importlib-metadata==2.1.1  # via -r requirements/tox.txt, pluggy, tox, virtualenv
-importlib-resources==3.2.1  # via -r requirements/tox.txt, virtualenv
-isort==4.3.21             # via -r requirements/test.txt, pylint
+isort==5.7.0              # via -r requirements/test.txt, pylint
 jmespath==0.10.0          # via -r requirements/test.txt, boto3, botocore
 jsonfield2==3.0.3         # via -c requirements/constraints.txt, -r requirements/test.txt
 lazy-object-proxy==1.4.3  # via -r requirements/test.txt, astroid
 lazy==1.4                 # via -r requirements/test.txt
 lxml==4.6.2               # via -r requirements/test.txt, xblock
-mako==1.1.3               # via -r requirements/test.txt, xblock-utils
+mako==1.1.4               # via -r requirements/test.txt, xblock-utils
 markupsafe==1.1.1         # via -r requirements/test.txt, mako, xblock
 mccabe==0.6.1             # via -r requirements/test.txt, pylint
 mock==3.0.5               # via -c requirements/constraints.txt, -r requirements/test.txt
@@ -56,20 +54,18 @@ pylint==2.6.0             # via -r requirements/test.txt, edx-lint, pylint-celer
 pymongo==3.11.2           # via -r requirements/test.txt, edx-opaque-keys
 pyparsing==2.4.7          # via -r requirements/test.txt, -r requirements/tox.txt, packaging
 python-dateutil==2.8.1    # via -r requirements/test.txt, botocore, xblock
-pytz==2020.4              # via -r requirements/test.txt, django, fs, xblock
-pyyaml==5.3.1             # via -r requirements/test.txt, xblock
+pytz==2020.5              # via -r requirements/test.txt, django, fs, xblock
+pyyaml==5.4.1             # via -r requirements/test.txt, xblock
 requests==2.25.1          # via -r requirements/test.txt, coveralls, pyjwkest
-s3transfer==0.3.3         # via -r requirements/test.txt, boto3
+s3transfer==0.3.4         # via -r requirements/test.txt, boto3
 simplejson==3.17.2        # via -r requirements/test.txt, xblock-utils
 six==1.15.0               # via -r requirements/test.txt, -r requirements/tox.txt, astroid, bleach, edx-lint, edx-opaque-keys, fs, fs-s3fs, mock, pyjwkest, python-dateutil, stevedore, tox, virtualenv, xblock
 sqlparse==0.4.1           # via -r requirements/test.txt, django
 stevedore==1.32.0         # via -c requirements/constraints.txt, -r requirements/test.txt, edx-opaque-keys
 toml==0.10.2              # via -r requirements/test.txt, -r requirements/tox.txt, pylint, tox
-tox==3.20.1               # via -r requirements/tox.txt
-typed-ast==1.4.1          # via -r requirements/test.txt, astroid
-typing==3.7.4.3           # via -r requirements/test.txt, fs
+tox==3.21.2               # via -r requirements/tox.txt
 urllib3==1.26.2           # via -r requirements/test.txt, botocore, requests
-virtualenv==20.2.2        # via -r requirements/tox.txt, tox
+virtualenv==20.4.0        # via -r requirements/tox.txt, tox
 web-fragments==0.3.2      # via -r requirements/test.txt, xblock, xblock-utils
 webencodings==0.5.1       # via -r requirements/test.txt, bleach
 webob==1.8.6              # via -r requirements/test.txt, xblock
@@ -77,7 +73,6 @@ wrapt==1.12.1             # via -r requirements/test.txt, astroid
 xblock-sdk==0.2.2         # via -r requirements/test.txt
 xblock-utils==2.1.2       # via -r requirements/test.txt
 xblock==1.4.0             # via -r requirements/test.txt, xblock-utils
-zipp==1.1.1               # via -c requirements/constraints.txt, -r requirements/tox.txt, importlib-metadata, importlib-resources
 
 # The following packages are considered to be unsafe in a requirements file:
 # setuptools
diff --git a/setup.py b/setup.py
index 822b9a869c380fc4aa685cec72c58ee2d58cc0a6..6e7e4dc23c94dac2c936cb69d32c639a569efefd 100644
--- a/setup.py
+++ b/setup.py
@@ -80,7 +80,6 @@ setup(
         'License :: OSI Approved :: GNU Affero General Public License v3',
         'Natural Language :: English',
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.8",
     ]
 )
diff --git a/test.py b/test.py
index eb32b0414d31a1f1ff415be54bc7f526c47f4d52..582c72e8a534481559c025b2ce6eae392d94ff03 100644
--- a/test.py
+++ b/test.py
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
 """
 Run tests for the LTI Consumer XBlock
 """
@@ -8,7 +7,7 @@ import os
 import sys
 
 if __name__ == '__main__':
-    os.environ.setdefault('DJANGO_SETTINGS_MODULE', u'test_settings')
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_settings')
 
     try:
         from django.conf import settings  # pylint: disable=wrong-import-position
@@ -27,7 +26,7 @@ if __name__ == '__main__':
             )
         raise
 
-    settings.INSTALLED_APPS += (u'lti_consumer',)
+    settings.INSTALLED_APPS += ('lti_consumer',)
 
     arguments = sys.argv[1:]
     options = [argument for argument in arguments if argument.startswith('-')]