From 5cc562b5b82a4bffc84da9ac94c112a1342e41f1 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 10 Dec 2019 12:36:12 -0500 Subject: [PATCH 01/17] Use six to reraise a capa error. --- common/lib/capa/capa/capa_problem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index ee327dde52..ef3554641a 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -897,7 +897,7 @@ class LoncapaProblem(object): except Exception as err: log.exception("Error while execing script code: " + all_code) msg = Text("Error while executing script code: %s" % str(err)) - raise responsetypes.LoncapaProblemError(msg) + six.reraise(responsetypes.LoncapaProblemError, responsetypes.LoncapaProblemError(msg)) # Store code source in context, along with the Python path needed to run it correctly. context['script_code'] = all_code From 6e7d54e8204941ab64f69dc89d0ac8b68da9701c Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 10 Dec 2019 12:56:50 -0500 Subject: [PATCH 02/17] Use the view names for redirect. --- lms/djangoapps/branding/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index 5d262ae0df..585a0d74c8 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -43,7 +43,7 @@ def index(request): if configuration_helpers.get_value( 'ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER', settings.FEATURES.get('ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER', True)): - return redirect(reverse('dashboard')) + return redirect('dashboard') enable_mktg_site = configuration_helpers.get_value( 'ENABLE_MKTG_SITE', @@ -62,7 +62,7 @@ def index(request): # keep specialized logic for Edge until we can migrate over Edge to fully use # configuration. if domain and 'edge.edx.org' in domain: - return redirect(reverse("signin_user")) + return redirect("signin_user") # we do not expect this case to be reached in cases where # marketing and edge are enabled From 0e1881f525f300e693165395518df22e689f8dfe Mon Sep 17 00:00:00 2001 From: Jeremy Bowman Date: Tue, 10 Dec 2019 14:07:27 -0500 Subject: [PATCH 03/17] Fixes for celery worker Python 3 bugs (#22479) --- .../lib/xmodule/xmodule/video_module/transcripts_utils.py | 4 +++- lms/djangoapps/courseware/views/views.py | 6 +++--- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/github.in | 2 +- requirements/edx/testing.txt | 2 +- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/common/lib/xmodule/xmodule/video_module/transcripts_utils.py b/common/lib/xmodule/xmodule/video_module/transcripts_utils.py index 5009feb497..89e68c362f 100644 --- a/common/lib/xmodule/xmodule/video_module/transcripts_utils.py +++ b/common/lib/xmodule/xmodule/video_module/transcripts_utils.py @@ -661,9 +661,11 @@ class Transcript(object): try: # With error handling (set to 'ERROR_RAISE'), we will be getting # the exception if something went wrong in parsing the transcript. + if isinstance(content, text_type): + content = content.encode('utf-8') srt_subs = SubRipFile.from_string( # Skip byte order mark(BOM) character - content.encode('utf-8').decode('utf-8-sig'), + content.decode('utf-8-sig'), error_handling=SubRipFile.ERROR_RAISE ) except Error as ex: # Base exception from pysrt diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index e4bdba694e..e933980f03 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -294,9 +294,9 @@ def yt_video_metadata(request): status_code = res.status_code if res.status_code == 200: try: - res = res.json() - if res.get('items', []): - response = res + res_json = res.json() + if res_json.get('items', []): + response = res_json else: logging.warning(u'Unable to find the items in response. Following response ' u'was received: {res}'.format(res=res.text)) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 35696a3f3f..be0022cb15 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -251,7 +251,7 @@ webencodings==0.5.1 # via html5lib webob==1.8.5 # via xblock wrapt==1.10.5 git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6 -git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536fff6d941#egg=xblock-poll==1.5.1 +git+https://github.com/open-craft/xblock-poll@6894592402fd180db864bfce539f17bac6443866#egg=xblock-poll==1.9.0 xblock-utils==1.2.2 xblock==1.2.9 xmlsec==1.3.3 # via python3-saml diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index a21ac12c02..0c9b130561 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -344,7 +344,7 @@ websocket-client==0.56.0 werkzeug==0.16.0 wrapt==1.10.5 git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6 -git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536fff6d941#egg=xblock-poll==1.5.1 +git+https://github.com/open-craft/xblock-poll@6894592402fd180db864bfce539f17bac6443866#egg=xblock-poll==1.9.0 xblock-utils==1.2.2 xblock==1.2.9 xmlsec==1.3.3 diff --git a/requirements/edx/github.in b/requirements/edx/github.in index b4f485fba7..aeaa957d2e 100644 --- a/requirements/edx/github.in +++ b/requirements/edx/github.in @@ -92,5 +92,5 @@ git+https://github.com/edx/xblock-lti-consumer.git@v1.2.1#egg=lti_consumer-xbloc # Third Party XBlocks git+https://github.com/joestump/python-oauth2.git@b94f69b1ad195513547924e380d9265133e995fa#egg=oauth2 -git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536fff6d941#egg=xblock-poll==1.5.1 +git+https://github.com/open-craft/xblock-poll@6894592402fd180db864bfce539f17bac6443866#egg=xblock-poll==1.9.0 git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 361eb7957b..5c86a046f5 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -326,7 +326,7 @@ websocket-client==0.56.0 # via docker werkzeug==0.16.0 # via moto wrapt==1.10.5 git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6 -git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536fff6d941#egg=xblock-poll==1.5.1 +git+https://github.com/open-craft/xblock-poll@6894592402fd180db864bfce539f17bac6443866#egg=xblock-poll==1.9.0 xblock-utils==1.2.2 xblock==1.2.9 xmlsec==1.3.3 From 4b6a522a875e7d5fe6e5c4a934aad455e9dbbb13 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 10 Dec 2019 15:11:17 -0500 Subject: [PATCH 04/17] Upate xblock-lti-consumer to get more Python 3 fixes. --- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/github.in | 2 +- requirements/edx/testing.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index be0022cb15..7a577dc8dc 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -151,7 +151,7 @@ lazy==1.1 lepl==5.1.3 # via rfc6266-parser libsass==0.10.0 loremipsum==1.0.5 -git+https://github.com/edx/xblock-lti-consumer.git@v1.2.1#egg=lti_consumer-xblock==1.2.1 +git+https://github.com/edx/xblock-lti-consumer.git@v1.2.2#egg=lti_consumer-xblock==1.2.2 lxml==3.8.0 mailsnake==1.6.4 mako==1.0.2 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 0c9b130561..1451882e35 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -194,7 +194,7 @@ lazy==1.1 lepl==5.1.3 libsass==0.10.0 loremipsum==1.0.5 -git+https://github.com/edx/xblock-lti-consumer.git@v1.2.1#egg=lti_consumer-xblock==1.2.1 +git+https://github.com/edx/xblock-lti-consumer.git@v1.2.2#egg=lti_consumer-xblock==1.2.2 lxml==3.8.0 m2r==0.2.1 mailsnake==1.6.4 diff --git a/requirements/edx/github.in b/requirements/edx/github.in index aeaa957d2e..3b029192cf 100644 --- a/requirements/edx/github.in +++ b/requirements/edx/github.in @@ -86,7 +86,7 @@ git+https://github.com/edx/crowdsourcehinter.git@a7ffc85b134b7d8909bf1fefd23dbdb -e git+https://github.com/edx/RateXBlock.git@2.0#egg=rate-xblock -e git+https://github.com/edx/DoneXBlock.git@2.0.1#egg=done-xblock -e git+https://github.com/edx-solutions/xblock-google-drive.git@2d176468e33c0713c911b563f8f65f7cf232f5b6#egg=xblock-google-drive -git+https://github.com/edx/xblock-lti-consumer.git@v1.2.1#egg=lti_consumer-xblock==1.2.1 +git+https://github.com/edx/xblock-lti-consumer.git@v1.2.2#egg=lti_consumer-xblock==1.2.2 # Third Party XBlocks diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 5c86a046f5..6529fd0ebc 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -188,7 +188,7 @@ lazy==1.1 lepl==5.1.3 libsass==0.10.0 loremipsum==1.0.5 -git+https://github.com/edx/xblock-lti-consumer.git@v1.2.1#egg=lti_consumer-xblock==1.2.1 +git+https://github.com/edx/xblock-lti-consumer.git@v1.2.2#egg=lti_consumer-xblock==1.2.2 lxml==3.8.0 mailsnake==1.6.4 mako==1.0.2 From 86186e202654f84c3f427b97ca1cf44449c121e2 Mon Sep 17 00:00:00 2001 From: Jeremy Bowman Date: Tue, 10 Dec 2019 15:52:26 -0500 Subject: [PATCH 05/17] Fix graph traversal under Python 3 (#22484) --- common/lib/xmodule/xmodule/x_module.py | 2 +- openedx/core/lib/graph_traversals.py | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 26eeb4cc2a..4ec74f62f5 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -1259,7 +1259,7 @@ class XModuleDescriptor(XModuleDescriptorToXBlockMixin, HTMLSnippet, ResourceTem """ return (hasattr(other, 'scope_ids') and self.scope_ids == other.scope_ids and - list(self.fields.keys()) == list(other.fields.keys()) and + set(self.fields.keys()) == set(other.fields.keys()) and all(getattr(self, field.name) == getattr(other, field.name) for field in self.fields.values())) diff --git a/openedx/core/lib/graph_traversals.py b/openedx/core/lib/graph_traversals.py index d7810567de..3f73802db5 100644 --- a/openedx/core/lib/graph_traversals.py +++ b/openedx/core/lib/graph_traversals.py @@ -127,7 +127,7 @@ def traverse_topologically( used to limit which nodes are actually yielded. Arguments: - start_node (any hashable type) - The starting node for the + start_node (any type) - The starting node for the traversal. get_parents (node->[node]) - Function that returns a list of @@ -204,7 +204,7 @@ def traverse_post_order(start_node, get_children, filter_func=None): stack = deque([_Node(start_node, get_children)]) # Keep track of which nodes have been visited. - visited = set() + visited = [] while stack: # Peek at the current node at the top of the stack. @@ -226,7 +226,7 @@ def traverse_post_order(start_node, get_children, filter_func=None): # Since there are no children left, visit the node and # remove it from the stack. yield current.node - visited.add(current.node) + visited.append(current.node) stack.pop() else: @@ -264,7 +264,8 @@ def _traverse_generic( # Keep track of which nodes have been visited and whether they # were in fact yielded. - yield_results = {} # dict(node:boolean) + visited = [] # nodes + yield_results = [] # booleans # While there are more nodes on the stack... while stack: @@ -280,16 +281,17 @@ def _traverse_generic( parents = get_parents(current_node) # If all of the parents have not yet been visited, continue. - if not all(parent in yield_results for parent in parents): + if not all(parent in visited for parent in parents): continue # If none of the parents have yielded, continue, unless # specified otherwise (via yield_descendants_of_unyielded). - elif not yield_descendants_of_unyielded and not any(yield_results[parent] for parent in parents): + elif not yield_descendants_of_unyielded and not any( + yield_results[visited.index(parent)] for parent in parents): continue # If the current node has already been visited, continue. - if current_node not in yield_results: + if current_node not in visited: # For a topological sort, it's important that we visit # the children even if the parent isn't yielded, in case @@ -316,7 +318,7 @@ def _traverse_generic( unvisited_children = list( child for child in get_children(current_node) - if child not in yield_results + if child not in visited ) # Add the node's unvisited children to the stack in reverse @@ -332,4 +334,5 @@ def _traverse_generic( # Keep track of whether or not the node was yielded so we # know whether or not to yield its children. - yield_results[current_node] = should_yield_node + visited.append(current_node) + yield_results.append(should_yield_node) From 0e1116003a34db174aaca7a7284468568ac4bd50 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Tue, 10 Dec 2019 14:58:30 -0500 Subject: [PATCH 06/17] Ignore bad cached data. When migrating from python 2 to 3 we can get in a situation where the cache has data that the new version of python can't read. In this case drop the bad data and re-cache the correct data. --- lms/djangoapps/mobile_api/middleware.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/mobile_api/middleware.py b/lms/djangoapps/mobile_api/middleware.py index a38f4044dc..0a87057970 100644 --- a/lms/djangoapps/mobile_api/middleware.py +++ b/lms/djangoapps/mobile_api/middleware.py @@ -9,6 +9,7 @@ from django.conf import settings from django.core.cache import cache from django.http import HttpResponse from pytz import UTC +import six from mobile_api.mobile_platform import MobilePlatform from mobile_api.models import AppVersionConfig @@ -104,13 +105,13 @@ class AppVersionUpgrade(object): cached_data = cache.get_many([last_supported_date_cache_key, latest_version_cache_key]) last_supported_date = cached_data.get(last_supported_date_cache_key) - if not last_supported_date: + if last_supported_date != self.NO_LAST_SUPPORTED_DATE and not isinstance(last_supported_date, datetime): last_supported_date = self._get_last_supported_date(platform.NAME, platform.version) cache.set(last_supported_date_cache_key, last_supported_date, self.CACHE_TIMEOUT) request_cache_dict[self.LAST_SUPPORTED_DATE_HEADER] = last_supported_date latest_version = cached_data.get(latest_version_cache_key) - if not latest_version: + if not (latest_version and isinstance(latest_version, six.text_type)): latest_version = self._get_latest_version(platform.NAME) cache.set(latest_version_cache_key, latest_version, self.CACHE_TIMEOUT) request_cache_dict[self.LATEST_VERSION_HEADER] = latest_version From f52a0f6b983fa2e515c8565ad8c923cafea1d776 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 11 Dec 2019 11:35:58 -0500 Subject: [PATCH 07/17] Re-raise issue was in codejail. - Undo the previous change. - Pull in the version of codejail with the fix for the issue. --- common/lib/capa/capa/capa_problem.py | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/github.in | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index ef3554641a..ee327dde52 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -897,7 +897,7 @@ class LoncapaProblem(object): except Exception as err: log.exception("Error while execing script code: " + all_code) msg = Text("Error while executing script code: %s" % str(err)) - six.reraise(responsetypes.LoncapaProblemError, responsetypes.LoncapaProblemError(msg)) + raise responsetypes.LoncapaProblemError(msg) # Store code source in context, along with the Python path needed to run it correctly. context['script_code'] = all_code diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 7a577dc8dc..cc08b376b6 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -6,7 +6,7 @@ # -e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock -e common/lib/capa --e git+https://github.com/edx/codejail.git@33758da2609bd72c2c18efc2d4bdb93596523d5e#egg=codejail +-e git+https://github.com/edx/codejail.git@17b45de7191f561e9a2c1b12c89489a8ba8e0758#egg=codejail -e git+https://github.com/edx/django-wiki.git@v0.0.23#egg=django-wiki -e git+https://github.com/edx/DoneXBlock.git@2.0.1#egg=done-xblock -e git+https://github.com/jazkarta/edx-jsme.git@690dbf75441fa91c7c4899df0b83d77f7deb5458#egg=edx-jsme diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 1451882e35..1027b2991c 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -6,7 +6,7 @@ # -e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock -e common/lib/capa --e git+https://github.com/edx/codejail.git@33758da2609bd72c2c18efc2d4bdb93596523d5e#egg=codejail +-e git+https://github.com/edx/codejail.git@17b45de7191f561e9a2c1b12c89489a8ba8e0758#egg=codejail -e git+https://github.com/edx/django-wiki.git@v0.0.23#egg=django-wiki -e git+https://github.com/edx/DoneXBlock.git@2.0.1#egg=done-xblock -e git+https://github.com/jazkarta/edx-jsme.git@690dbf75441fa91c7c4899df0b83d77f7deb5458#egg=edx-jsme diff --git a/requirements/edx/github.in b/requirements/edx/github.in index 3b029192cf..df23841531 100644 --- a/requirements/edx/github.in +++ b/requirements/edx/github.in @@ -79,7 +79,7 @@ git+https://github.com/edx/django-celery.git@756cb57aad765cb2b0d37372c1855b8f5f3 git+https://github.com/edx/bridgekeeper.git@4e34894e4ac5d0467ed1901811a81fd87ee01937#egg=bridgekeeper==0.0 # Our libraries: --e git+https://github.com/edx/codejail.git@33758da2609bd72c2c18efc2d4bdb93596523d5e#egg=codejail +-e git+https://github.com/edx/codejail.git@17b45de7191f561e9a2c1b12c89489a8ba8e0758#egg=codejail -e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock git+https://github.com/edx/edx-ora2.git@2.4.7#egg=ora2==2.4.7 git+https://github.com/edx/crowdsourcehinter.git@a7ffc85b134b7d8909bf1fefd23dbdb8eb28e467#egg=crowdsourcehinter-xblock==0.2 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 6529fd0ebc..420c7ae43c 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -6,7 +6,7 @@ # -e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock -e common/lib/capa --e git+https://github.com/edx/codejail.git@33758da2609bd72c2c18efc2d4bdb93596523d5e#egg=codejail +-e git+https://github.com/edx/codejail.git@17b45de7191f561e9a2c1b12c89489a8ba8e0758#egg=codejail -e git+https://github.com/edx/django-wiki.git@v0.0.23#egg=django-wiki -e git+https://github.com/edx/DoneXBlock.git@2.0.1#egg=done-xblock -e git+https://github.com/jazkarta/edx-jsme.git@690dbf75441fa91c7c4899df0b83d77f7deb5458#egg=edx-jsme From c958ff449b65e4d48f198751f93ac3a0927348dd Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 11 Dec 2019 11:58:48 -0500 Subject: [PATCH 08/17] Update xblock-lti-consumer with more Python 3 fixes. --- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/github.in | 2 +- requirements/edx/testing.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 7a577dc8dc..5ee716330b 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -151,7 +151,7 @@ lazy==1.1 lepl==5.1.3 # via rfc6266-parser libsass==0.10.0 loremipsum==1.0.5 -git+https://github.com/edx/xblock-lti-consumer.git@v1.2.2#egg=lti_consumer-xblock==1.2.2 +git+https://github.com/edx/xblock-lti-consumer.git@v1.2.3#egg=lti_consumer-xblock==1.2.3 lxml==3.8.0 mailsnake==1.6.4 mako==1.0.2 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 1451882e35..93c7f51ef0 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -194,7 +194,7 @@ lazy==1.1 lepl==5.1.3 libsass==0.10.0 loremipsum==1.0.5 -git+https://github.com/edx/xblock-lti-consumer.git@v1.2.2#egg=lti_consumer-xblock==1.2.2 +git+https://github.com/edx/xblock-lti-consumer.git@v1.2.3#egg=lti_consumer-xblock==1.2.3 lxml==3.8.0 m2r==0.2.1 mailsnake==1.6.4 diff --git a/requirements/edx/github.in b/requirements/edx/github.in index 3b029192cf..a3e329e14f 100644 --- a/requirements/edx/github.in +++ b/requirements/edx/github.in @@ -86,7 +86,7 @@ git+https://github.com/edx/crowdsourcehinter.git@a7ffc85b134b7d8909bf1fefd23dbdb -e git+https://github.com/edx/RateXBlock.git@2.0#egg=rate-xblock -e git+https://github.com/edx/DoneXBlock.git@2.0.1#egg=done-xblock -e git+https://github.com/edx-solutions/xblock-google-drive.git@2d176468e33c0713c911b563f8f65f7cf232f5b6#egg=xblock-google-drive -git+https://github.com/edx/xblock-lti-consumer.git@v1.2.2#egg=lti_consumer-xblock==1.2.2 +git+https://github.com/edx/xblock-lti-consumer.git@v1.2.3#egg=lti_consumer-xblock==1.2.3 # Third Party XBlocks diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 6529fd0ebc..555b384d3a 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -188,7 +188,7 @@ lazy==1.1 lepl==5.1.3 libsass==0.10.0 loremipsum==1.0.5 -git+https://github.com/edx/xblock-lti-consumer.git@v1.2.2#egg=lti_consumer-xblock==1.2.2 +git+https://github.com/edx/xblock-lti-consumer.git@v1.2.3#egg=lti_consumer-xblock==1.2.3 lxml==3.8.0 mailsnake==1.6.4 mako==1.0.2 From 09c995a3cd057e8e252ce41c26664323f15709cb Mon Sep 17 00:00:00 2001 From: Jeremy Bowman Date: Wed, 11 Dec 2019 12:20:42 -0500 Subject: [PATCH 09/17] Update xblock-poll with Python 3 fix (#22497) --- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/github.in | 2 +- requirements/edx/testing.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 7a577dc8dc..22ff44b4b5 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -251,7 +251,7 @@ webencodings==0.5.1 # via html5lib webob==1.8.5 # via xblock wrapt==1.10.5 git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6 -git+https://github.com/open-craft/xblock-poll@6894592402fd180db864bfce539f17bac6443866#egg=xblock-poll==1.9.0 +git+https://github.com/jmbowman/xblock-poll@8e78663fdd3c1d79571eb753d1c601729e9a9325#egg=xblock-poll==1.9.0 xblock-utils==1.2.2 xblock==1.2.9 xmlsec==1.3.3 # via python3-saml diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 1451882e35..c9be8e5373 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -344,7 +344,7 @@ websocket-client==0.56.0 werkzeug==0.16.0 wrapt==1.10.5 git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6 -git+https://github.com/open-craft/xblock-poll@6894592402fd180db864bfce539f17bac6443866#egg=xblock-poll==1.9.0 +git+https://github.com/jmbowman/xblock-poll@8e78663fdd3c1d79571eb753d1c601729e9a9325#egg=xblock-poll==1.9.0 xblock-utils==1.2.2 xblock==1.2.9 xmlsec==1.3.3 diff --git a/requirements/edx/github.in b/requirements/edx/github.in index 3b029192cf..c78bc37055 100644 --- a/requirements/edx/github.in +++ b/requirements/edx/github.in @@ -92,5 +92,5 @@ git+https://github.com/edx/xblock-lti-consumer.git@v1.2.2#egg=lti_consumer-xbloc # Third Party XBlocks git+https://github.com/joestump/python-oauth2.git@b94f69b1ad195513547924e380d9265133e995fa#egg=oauth2 -git+https://github.com/open-craft/xblock-poll@6894592402fd180db864bfce539f17bac6443866#egg=xblock-poll==1.9.0 +git+https://github.com/jmbowman/xblock-poll@8e78663fdd3c1d79571eb753d1c601729e9a9325#egg=xblock-poll==1.9.0 git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 6529fd0ebc..df6de54df2 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -326,7 +326,7 @@ websocket-client==0.56.0 # via docker werkzeug==0.16.0 # via moto wrapt==1.10.5 git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6 -git+https://github.com/open-craft/xblock-poll@6894592402fd180db864bfce539f17bac6443866#egg=xblock-poll==1.9.0 +git+https://github.com/jmbowman/xblock-poll@8e78663fdd3c1d79571eb753d1c601729e9a9325#egg=xblock-poll==1.9.0 xblock-utils==1.2.2 xblock==1.2.9 xmlsec==1.3.3 From ca1265a679efe56e939e6da9cc15286c9e58fab6 Mon Sep 17 00:00:00 2001 From: Jeremy Bowman Date: Wed, 11 Dec 2019 14:37:07 -0500 Subject: [PATCH 10/17] Make XModuleDescriptor hashable again (#22501) --- common/lib/xmodule/xmodule/x_module.py | 11 +++++++++++ openedx/core/lib/graph_traversals.py | 21 +++++++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 4ec74f62f5..051e9cd6e6 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -1263,6 +1263,17 @@ class XModuleDescriptor(XModuleDescriptorToXBlockMixin, HTMLSnippet, ResourceTem all(getattr(self, field.name) == getattr(other, field.name) for field in self.fields.values())) + def __hash__(self): # pylint: disable=useless-super-delegation + """ + This isn't technically appropriate since descriptors are actually mutable, + but in practice we rarely modify them after creation or instantiate two + equivalent descriptors in the same process. And we perform graph + operations on large collections of XBlocks that have simply unacceptable + performance if we have to rely on lists and equality rather than sets, + dictionaries, and identity-based hash functions. + """ + return super(XModuleDescriptor, self).__hash__() + def __repr__(self): return ( "{0.__class__.__name__}(" diff --git a/openedx/core/lib/graph_traversals.py b/openedx/core/lib/graph_traversals.py index 3f73802db5..d7810567de 100644 --- a/openedx/core/lib/graph_traversals.py +++ b/openedx/core/lib/graph_traversals.py @@ -127,7 +127,7 @@ def traverse_topologically( used to limit which nodes are actually yielded. Arguments: - start_node (any type) - The starting node for the + start_node (any hashable type) - The starting node for the traversal. get_parents (node->[node]) - Function that returns a list of @@ -204,7 +204,7 @@ def traverse_post_order(start_node, get_children, filter_func=None): stack = deque([_Node(start_node, get_children)]) # Keep track of which nodes have been visited. - visited = [] + visited = set() while stack: # Peek at the current node at the top of the stack. @@ -226,7 +226,7 @@ def traverse_post_order(start_node, get_children, filter_func=None): # Since there are no children left, visit the node and # remove it from the stack. yield current.node - visited.append(current.node) + visited.add(current.node) stack.pop() else: @@ -264,8 +264,7 @@ def _traverse_generic( # Keep track of which nodes have been visited and whether they # were in fact yielded. - visited = [] # nodes - yield_results = [] # booleans + yield_results = {} # dict(node:boolean) # While there are more nodes on the stack... while stack: @@ -281,17 +280,16 @@ def _traverse_generic( parents = get_parents(current_node) # If all of the parents have not yet been visited, continue. - if not all(parent in visited for parent in parents): + if not all(parent in yield_results for parent in parents): continue # If none of the parents have yielded, continue, unless # specified otherwise (via yield_descendants_of_unyielded). - elif not yield_descendants_of_unyielded and not any( - yield_results[visited.index(parent)] for parent in parents): + elif not yield_descendants_of_unyielded and not any(yield_results[parent] for parent in parents): continue # If the current node has already been visited, continue. - if current_node not in visited: + if current_node not in yield_results: # For a topological sort, it's important that we visit # the children even if the parent isn't yielded, in case @@ -318,7 +316,7 @@ def _traverse_generic( unvisited_children = list( child for child in get_children(current_node) - if child not in visited + if child not in yield_results ) # Add the node's unvisited children to the stack in reverse @@ -334,5 +332,4 @@ def _traverse_generic( # Keep track of whether or not the node was yielded so we # know whether or not to yield its children. - visited.append(current_node) - yield_results.append(should_yield_node) + yield_results[current_node] = should_yield_node From 28ce0e67390e073c36db0fe4aedca162926b4c0e Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 11 Dec 2019 16:05:02 -0500 Subject: [PATCH 11/17] Add logging middleware to determine why our responses are bad. --- common/djangoapps/track/middleware.py | 22 ++++++++++++++++++++++ lms/envs/common.py | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/common/djangoapps/track/middleware.py b/common/djangoapps/track/middleware.py index 77cfda2650..0ff46a15b8 100644 --- a/common/djangoapps/track/middleware.py +++ b/common/djangoapps/track/middleware.py @@ -36,6 +36,28 @@ META_KEY_TO_CONTEXT_KEY = { } +class ResponseLoggingMiddleware(object): + """ + A debugging middleware for the purpose of understanding what the current state of the response + is at this point in the stack. + """ + + def process_response(self, _request, response): + """ + Logs the response at the current point in the middleware stack for debugging purposes. + """ + try: + log.info('Logging response for debugging purposes: %r', response) + log.info('Response container data: %r', response._container) # pylint: disable=protected-access + log.info('Container types: %r', [type(el) for el in response._container]) # pylint: disable=protected-access + + except: # pylint: disable=bare-except + # If this causes an error, we don't want it to bubble up since this is just for logging. + log.exception('Error logging response object') + + return response + + class TrackMiddleware(object): """ Tracks all requests made, as well as setting up context for other server diff --git a/lms/envs/common.py b/lms/envs/common.py index 7c2274edb4..d515d698f6 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1443,6 +1443,10 @@ MIDDLEWARE_CLASSES = [ 'openedx.core.djangoapps.header_control.middleware.HeaderControlMiddleware', 'lms.djangoapps.discussion.django_comment_client.middleware.AjaxExceptionMiddleware', 'django.middleware.common.CommonMiddleware', + + # Debugging Middleware. Remove once Python 3 has been deployed. + 'track.middleware.ResponseLoggingMiddleware', + 'django.contrib.sites.middleware.CurrentSiteMiddleware', 'edx_rest_framework_extensions.auth.jwt.middleware.JwtAuthCookieMiddleware', From 13fdbd0c75b4d10d116a0b859cec93fbaa3c4785 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 11 Dec 2019 18:19:53 -0500 Subject: [PATCH 12/17] Fix serialization issues in codejail --- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/github.in | 2 +- requirements/edx/testing.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index c5800b7b08..6a5ca5fc87 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -6,7 +6,7 @@ # -e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock -e common/lib/capa --e git+https://github.com/edx/codejail.git@17b45de7191f561e9a2c1b12c89489a8ba8e0758#egg=codejail +-e git+https://github.com/edx/codejail.git@6bc47025359a4d6ecf2b6b5776ff99959094d2cc#egg=codejail -e git+https://github.com/edx/django-wiki.git@v0.0.23#egg=django-wiki -e git+https://github.com/edx/DoneXBlock.git@2.0.1#egg=done-xblock -e git+https://github.com/jazkarta/edx-jsme.git@690dbf75441fa91c7c4899df0b83d77f7deb5458#egg=edx-jsme diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 2c941a9cf9..e9590f2fb9 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -6,7 +6,7 @@ # -e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock -e common/lib/capa --e git+https://github.com/edx/codejail.git@17b45de7191f561e9a2c1b12c89489a8ba8e0758#egg=codejail +-e git+https://github.com/edx/codejail.git@6bc47025359a4d6ecf2b6b5776ff99959094d2cc#egg=codejail -e git+https://github.com/edx/django-wiki.git@v0.0.23#egg=django-wiki -e git+https://github.com/edx/DoneXBlock.git@2.0.1#egg=done-xblock -e git+https://github.com/jazkarta/edx-jsme.git@690dbf75441fa91c7c4899df0b83d77f7deb5458#egg=edx-jsme diff --git a/requirements/edx/github.in b/requirements/edx/github.in index 98d8d78176..d0f8c3f19b 100644 --- a/requirements/edx/github.in +++ b/requirements/edx/github.in @@ -79,7 +79,7 @@ git+https://github.com/edx/django-celery.git@756cb57aad765cb2b0d37372c1855b8f5f3 git+https://github.com/edx/bridgekeeper.git@4e34894e4ac5d0467ed1901811a81fd87ee01937#egg=bridgekeeper==0.0 # Our libraries: --e git+https://github.com/edx/codejail.git@17b45de7191f561e9a2c1b12c89489a8ba8e0758#egg=codejail +-e git+https://github.com/edx/codejail.git@6bc47025359a4d6ecf2b6b5776ff99959094d2cc#egg=codejail -e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock git+https://github.com/edx/edx-ora2.git@2.4.7#egg=ora2==2.4.7 git+https://github.com/edx/crowdsourcehinter.git@a7ffc85b134b7d8909bf1fefd23dbdb8eb28e467#egg=crowdsourcehinter-xblock==0.2 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index dfd76fdfbb..219cfa0895 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -6,7 +6,7 @@ # -e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock -e common/lib/capa --e git+https://github.com/edx/codejail.git@17b45de7191f561e9a2c1b12c89489a8ba8e0758#egg=codejail +-e git+https://github.com/edx/codejail.git@6bc47025359a4d6ecf2b6b5776ff99959094d2cc#egg=codejail -e git+https://github.com/edx/django-wiki.git@v0.0.23#egg=django-wiki -e git+https://github.com/edx/DoneXBlock.git@2.0.1#egg=done-xblock -e git+https://github.com/jazkarta/edx-jsme.git@690dbf75441fa91c7c4899df0b83d77f7deb5458#egg=edx-jsme From e005c0461ed7bfd414e3852baf77ebe9d7cbed84 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 12 Dec 2019 11:14:40 -0500 Subject: [PATCH 13/17] Handle the caching of responses between Python 2 and Python 3. --- common/djangoapps/track/middleware.py | 22 ---------------------- common/djangoapps/util/cache.py | 7 +++++++ lms/envs/common.py | 3 --- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/common/djangoapps/track/middleware.py b/common/djangoapps/track/middleware.py index 0ff46a15b8..77cfda2650 100644 --- a/common/djangoapps/track/middleware.py +++ b/common/djangoapps/track/middleware.py @@ -36,28 +36,6 @@ META_KEY_TO_CONTEXT_KEY = { } -class ResponseLoggingMiddleware(object): - """ - A debugging middleware for the purpose of understanding what the current state of the response - is at this point in the stack. - """ - - def process_response(self, _request, response): - """ - Logs the response at the current point in the middleware stack for debugging purposes. - """ - try: - log.info('Logging response for debugging purposes: %r', response) - log.info('Response container data: %r', response._container) # pylint: disable=protected-access - log.info('Container types: %r', [type(el) for el in response._container]) # pylint: disable=protected-access - - except: # pylint: disable=bare-except - # If this causes an error, we don't want it to bubble up since this is just for logging. - log.exception('Error logging response object') - - return response - - class TrackMiddleware(object): """ Tracks all requests made, as well as setting up context for other server diff --git a/common/djangoapps/util/cache.py b/common/djangoapps/util/cache.py index 5987d704e4..bc9503aaff 100644 --- a/common/djangoapps/util/cache.py +++ b/common/djangoapps/util/cache.py @@ -76,6 +76,13 @@ def cache_if_anonymous(*get_parameters): }) response = cache.get(cache_key) # pylint: disable=maybe-no-member + + # A hack to ensure that the response data is a valid text type for both Python 2 and 3. + response_content = response._container.copy() # pylint: disable=protected-member + response.content = b'' + for item in response_content: + response.write(item) + if not response: response = view_func(request, *args, **kwargs) cache.set(cache_key, response, 60 * 3) # pylint: disable=maybe-no-member diff --git a/lms/envs/common.py b/lms/envs/common.py index d515d698f6..bf5aa8a7d4 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1444,9 +1444,6 @@ MIDDLEWARE_CLASSES = [ 'lms.djangoapps.discussion.django_comment_client.middleware.AjaxExceptionMiddleware', 'django.middleware.common.CommonMiddleware', - # Debugging Middleware. Remove once Python 3 has been deployed. - 'track.middleware.ResponseLoggingMiddleware', - 'django.contrib.sites.middleware.CurrentSiteMiddleware', 'edx_rest_framework_extensions.auth.jwt.middleware.JwtAuthCookieMiddleware', From a8ea9844dcb79066273bfc2f550e4e5e642034ad Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Thu, 12 Dec 2019 11:40:59 -0500 Subject: [PATCH 14/17] Deal with cache misses. --- common/djangoapps/util/cache.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/common/djangoapps/util/cache.py b/common/djangoapps/util/cache.py index bc9503aaff..1305ed63a7 100644 --- a/common/djangoapps/util/cache.py +++ b/common/djangoapps/util/cache.py @@ -77,13 +77,13 @@ def cache_if_anonymous(*get_parameters): response = cache.get(cache_key) # pylint: disable=maybe-no-member - # A hack to ensure that the response data is a valid text type for both Python 2 and 3. - response_content = response._container.copy() # pylint: disable=protected-member - response.content = b'' - for item in response_content: - response.write(item) - - if not response: + if response: + # A hack to ensure that the response data is a valid text type for both Python 2 and 3. + response_content = response._container.copy() # pylint: disable=protected-member + response.content = b'' + for item in response_content: + response.write(item) + else: response = view_func(request, *args, **kwargs) cache.set(cache_key, response, 60 * 3) # pylint: disable=maybe-no-member From 40c98fb22ba5341e5e1a49ca6f8168424bb728c2 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 12 Dec 2019 12:17:56 -0500 Subject: [PATCH 15/17] Copy list in a way that is both Py2 and Py3 compatible. --- common/djangoapps/util/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/djangoapps/util/cache.py b/common/djangoapps/util/cache.py index 1305ed63a7..9dee55828f 100644 --- a/common/djangoapps/util/cache.py +++ b/common/djangoapps/util/cache.py @@ -79,7 +79,7 @@ def cache_if_anonymous(*get_parameters): if response: # A hack to ensure that the response data is a valid text type for both Python 2 and 3. - response_content = response._container.copy() # pylint: disable=protected-member + response_content = list(response._container) # pylint: disable=protected-member response.content = b'' for item in response_content: response.write(item) From ddf3a8208fc1f9aa5edb5d13f946695a3a0d38c0 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 10 Dec 2019 13:21:03 -0500 Subject: [PATCH 16/17] Remove items from a different dictionary than the one we're iterating over. --- lms/djangoapps/course_api/blocks/serializers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/course_api/blocks/serializers.py b/lms/djangoapps/course_api/blocks/serializers.py index 9a40f64817..dd39d57832 100644 --- a/lms/djangoapps/course_api/blocks/serializers.py +++ b/lms/djangoapps/course_api/blocks/serializers.py @@ -104,9 +104,11 @@ class BlockSerializer(serializers.Serializer): # pylint: disable=abstract-metho if authorization_denial_reason and authorization_denial_message: data['authorization_denial_reason'] = authorization_denial_reason data['authorization_denial_message'] = authorization_denial_message + cleaned_data = data.copy() for field in data.keys(): # pylint: disable=consider-iterating-dictionary if field not in FIELDS_ALLOWED_IN_AUTH_DENIED_CONTENT: - del data[field] + del cleaned_data[field] + data = cleaned_data return data From 6cb441b87619fe2dd1a679a9d4daf7cc9b34a20b Mon Sep 17 00:00:00 2001 From: Jeremy Bowman Date: Thu, 12 Dec 2019 15:46:26 -0500 Subject: [PATCH 17/17] Upgrade edx-proctoring to fix Py3 bug (#22521) --- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/testing.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 6a5ca5fc87..9bbff7564e 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -112,7 +112,7 @@ edx-oauth2-provider==1.3.1 edx-opaque-keys[django]==2.0.1 edx-organizations==2.1.1 edx-proctoring-proctortrack==1.0.5 -edx-proctoring==2.1.6 +edx-proctoring==2.1.7 edx-rbac==1.0.3 # via edx-enterprise edx-rest-api-client==1.9.2 edx-search==1.2.2 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index e9590f2fb9..084f2a9c18 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -135,7 +135,7 @@ edx-oauth2-provider==1.3.1 edx-opaque-keys[django]==2.0.1 edx-organizations==2.1.1 edx-proctoring-proctortrack==1.0.5 -edx-proctoring==2.1.6 +edx-proctoring==2.1.7 edx-rbac==1.0.3 edx-rest-api-client==1.9.2 edx-search==1.2.2 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 219cfa0895..43aa31cb88 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -132,7 +132,7 @@ edx-oauth2-provider==1.3.1 edx-opaque-keys[django]==2.0.1 edx-organizations==2.1.1 edx-proctoring-proctortrack==1.0.5 -edx-proctoring==2.1.6 +edx-proctoring==2.1.7 edx-rbac==1.0.3 edx-rest-api-client==1.9.2 edx-search==1.2.2