From 9978749bdefacf20f7871bcaeee7e106c740ecbd Mon Sep 17 00:00:00 2001
From: Alie Langston
Date: Thu, 7 Sep 2023 10:05:26 -0400
Subject: [PATCH 001/104] feat: give audit learners access to la bot
---
CHANGELOG.rst | 4 ++++
learning_assistant/__init__.py | 2 +-
learning_assistant/views.py | 4 ++--
tests/test_views.py | 14 +++++++++++---
4 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 4aaa3fa..beb5678 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -11,6 +11,10 @@ Change Log
.. There should always be an "Unreleased" section for changes pending release.
+1.3.3 - 2023-09-07
+******************
+* Allow any enrolled learner to access API.
+
1.3.2 - 2023-08-25
******************
* Remove deserialization of prompt field, as it is represented in the python
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 080d4b1..5f9a684 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '1.3.2'
+__version__ = '1.3.3'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index bff3dff..5a7d5fa 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -53,12 +53,12 @@ def post(self, request, course_id):
data={'detail': 'Learning assistant not enabled for course.'}
)
- # If user does not have a verified enrollment, or is not staff, they should not have access
+ # If user does not have an enrollment record, or is not staff, they should not have access
user_role = get_user_role(request.user, course_key)
enrollment_object = CourseEnrollment.get_enrollment(request.user, course_key)
enrollment_mode = enrollment_object.mode if enrollment_object else None
if (
- (enrollment_mode not in CourseMode.VERIFIED_MODES)
+ (enrollment_mode not in CourseMode.ALL_MODES)
and user_role not in ('staff', 'instructor')
):
return Response(
diff --git a/tests/test_views.py b/tests/test_views.py
index 6bf9c45..a8d08db 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -91,9 +91,13 @@ def test_course_waffle_inactive(self, mock_waffle):
@patch('learning_assistant.views.learning_assistant_is_active')
@patch('learning_assistant.views.get_user_role')
- def test_user_not_verified_not_staff(self, mock_role, mock_waffle):
+ @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseMode')
+ def test_user_no_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_role, mock_waffle):
mock_waffle.return_value = True
mock_role.return_value = 'student'
+ mock_mode.ALL_MODES = ['verified']
+ mock_enrollment.return_value = None
response = self.client.post(reverse('chat', kwargs={'course_id': self.course_id}))
self.assertEqual(response.status_code, 403)
@@ -133,9 +137,13 @@ def test_invalid_messages(self, mock_role, mock_waffle):
@patch('learning_assistant.views.get_chat_response')
@patch('learning_assistant.views.learning_assistant_is_active')
@patch('learning_assistant.views.get_user_role')
- def test_chat_response(self, mock_role, mock_waffle, mock_chat_response):
+ @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseMode')
+ def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle, mock_chat_response):
mock_waffle.return_value = True
- mock_role.return_value = 'staff'
+ mock_role.return_value = 'student'
+ mock_mode.ALL_MODES = ['verified']
+ mock_enrollment.return_value = MagicMock(mode='verified')
mock_chat_response.return_value = (200, {'role': 'assistant', 'content': 'Something else'})
CoursePrompt.objects.create(
From e7ffd7245ea9f603f8d2b0c2adcf7e96b3dc2b90 Mon Sep 17 00:00:00 2001
From: Alie Langston
Date: Mon, 11 Sep 2023 10:41:19 -0400
Subject: [PATCH 002/104] feat: use reduced message to avoid maxing out tokens
---
CHANGELOG.rst | 4 +++
learning_assistant/__init__.py | 2 +-
learning_assistant/utils.py | 41 ++++++++++++++++++++++++--
learning_assistant/views.py | 2 +-
tests/test_utils.py | 53 +++++++++++++++++++++++++++++-----
5 files changed, 90 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index beb5678..6841ee5 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -11,6 +11,10 @@ Change Log
.. There should always be an "Unreleased" section for changes pending release.
+1.4.0 - 2023-09-11
+******************
+* Send reduced message list if needed to avoid going over token limit
+
1.3.3 - 2023-09-07
******************
* Allow any enrolled learner to access API.
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 5f9a684..60ac235 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '1.3.3'
+__version__ = '1.4.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/utils.py b/learning_assistant/utils.py
index 5920cd0..110f449 100644
--- a/learning_assistant/utils.py
+++ b/learning_assistant/utils.py
@@ -12,7 +12,42 @@
log = logging.getLogger(__name__)
-def get_chat_response(message_list):
+def _estimated_message_tokens(message):
+ """
+ Estimates how many tokens are in a given message.
+ """
+ chars_per_token = 3.5
+ json_padding = 8
+
+ return int((len(message) - message.count(' ')) / chars_per_token) + json_padding
+
+
+def get_reduced_message_list(system_list, message_list):
+ """
+ If messages are larger than allotted token amount, return a smaller list of messages.
+ """
+ total_system_tokens = sum(_estimated_message_tokens(system_message['content']) for system_message in system_list)
+
+ max_tokens = getattr(settings, 'CHAT_COMPLETION_MAX_TOKENS', 16385)
+ response_tokens = getattr(settings, 'CHAT_COMPLETION_RESPONSE_TOKENS', 1000)
+ remaining_tokens = max_tokens - response_tokens - total_system_tokens
+
+ new_message_list = []
+ total_message_tokens = 0
+
+ while total_message_tokens < remaining_tokens and len(message_list) != 0:
+ new_message = message_list.pop()
+ total_message_tokens += _estimated_message_tokens(new_message['content'])
+ if total_message_tokens >= remaining_tokens:
+ break
+
+ # insert message at beginning of list, because we are traversing the message list from most recent to oldest
+ new_message_list.insert(0, new_message)
+
+ return new_message_list
+
+
+def get_chat_response(system_list, message_list):
"""
Pass message list to chat endpoint, as defined by the CHAT_COMPLETION_API setting.
"""
@@ -22,7 +57,9 @@ def get_chat_response(message_list):
headers = {'Content-Type': 'application/json', 'x-api-key': completion_endpoint_key}
connect_timeout = getattr(settings, 'CHAT_COMPLETION_API_CONNECT_TIMEOUT', 1)
read_timeout = getattr(settings, 'CHAT_COMPLETION_API_READ_TIMEOUT', 15)
- body = {'message_list': message_list}
+
+ reduced_messages = get_reduced_message_list(system_list, message_list)
+ body = {'message_list': system_list + reduced_messages}
try:
response = requests.post(
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 5a7d5fa..4749008 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -94,6 +94,6 @@ def post(self, request, course_id):
'course_id': course_id
}
)
- status_code, message = get_chat_response(message_setup + message_list)
+ status_code, message = get_chat_response(message_setup, message_list)
return Response(status=status_code, data=message)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index fad8966..27afa8e 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,6 +1,7 @@
"""
Tests for the utils functions
"""
+import copy
import json
from unittest.mock import MagicMock, patch
@@ -10,7 +11,7 @@
from django.test import TestCase, override_settings
from requests.exceptions import ConnectTimeout
-from learning_assistant.utils import get_chat_response
+from learning_assistant.utils import get_chat_response, get_reduced_message_list
@ddt.ddt
@@ -20,6 +21,10 @@ class GetChatResponseTests(TestCase):
"""
def setUp(self):
super().setUp()
+ self.system_message = [
+ {'role': 'system', 'content': 'Do this'},
+ {'role': 'system', 'content': 'Do that'},
+ ]
self.message_list = [
{'role': 'assistant', 'content': 'Hello'},
{'role': 'user', 'content': 'Goodbye'},
@@ -27,13 +32,13 @@ def setUp(self):
@override_settings(CHAT_COMPLETION_API=None)
def test_no_endpoint_setting(self):
- status_code, message = get_chat_response(self.message_list)
+ status_code, message = get_chat_response(self.system_message, self.message_list)
self.assertEqual(status_code, 404)
self.assertEqual(message, 'Completion endpoint is not defined.')
@override_settings(CHAT_COMPLETION_API_KEY=None)
def test_no_endpoint_key_setting(self):
- status_code, message = get_chat_response(self.message_list)
+ status_code, message = get_chat_response(self.system_message, self.message_list)
self.assertEqual(status_code, 404)
self.assertEqual(message, 'Completion endpoint is not defined.')
@@ -47,7 +52,7 @@ def test_200_response(self):
body=json.dumps(message_response),
)
- status_code, message = get_chat_response(self.message_list)
+ status_code, message = get_chat_response(self.system_message, self.message_list)
self.assertEqual(status_code, 200)
self.assertEqual(message, message_response)
@@ -61,7 +66,7 @@ def test_non_200_response(self):
body=json.dumps(message_response),
)
- status_code, message = get_chat_response(self.message_list)
+ status_code, message = get_chat_response(self.system_message, self.message_list)
self.assertEqual(status_code, 500)
self.assertEqual(message, message_response)
@@ -72,7 +77,7 @@ def test_non_200_response(self):
@patch('learning_assistant.utils.requests')
def test_timeout(self, exception, mock_requests):
mock_requests.post = MagicMock(side_effect=exception())
- status_code, _ = get_chat_response(self.message_list)
+ status_code, _ = get_chat_response(self.system_message, self.message_list)
self.assertEqual(status_code, 502)
@patch('learning_assistant.utils.requests')
@@ -83,12 +88,44 @@ def test_post_request_structure(self, mock_requests):
connect_timeout = settings.CHAT_COMPLETION_API_CONNECT_TIMEOUT
read_timeout = settings.CHAT_COMPLETION_API_READ_TIMEOUT
headers = {'Content-Type': 'application/json', 'x-api-key': settings.CHAT_COMPLETION_API_KEY}
- body = json.dumps({'message_list': self.message_list})
+ body = json.dumps({'message_list': self.system_message + self.message_list})
- get_chat_response(self.message_list)
+ get_chat_response(self.system_message, self.message_list)
mock_requests.post.assert_called_with(
completion_endpoint,
headers=headers,
data=body,
timeout=(connect_timeout, read_timeout)
)
+
+
+class GetReducedMessageListTests(TestCase):
+ """
+ Tests for the _reduced_message_list helper function
+ """
+ def setUp(self):
+ super().setUp()
+ self.system_message = [
+ {'role': 'system', 'content': 'Do this'},
+ {'role': 'system', 'content': 'Do that'},
+ ]
+ self.message_list = [
+ {'role': 'assistant', 'content': 'Hello'},
+ {'role': 'user', 'content': 'Goodbye'},
+ ]
+
+ @override_settings(CHAT_COMPLETION_MAX_TOKENS=30)
+ @override_settings(CHAT_COMPLETION_RESPONSE_TOKENS=1)
+ def test_message_list_reduced(self):
+ """
+ If the number of tokens in the message list is greater than allowed, assert that messages are removed
+ """
+ # pass in copy of list, as it is modified as part of the reduction
+ reduced_message_list = get_reduced_message_list(self.system_message, copy.deepcopy(self.message_list))
+ self.assertEqual(len(reduced_message_list), 1)
+ self.assertEqual(reduced_message_list, self.message_list[-1:])
+
+ def test_message_list(self):
+ reduced_message_list = get_reduced_message_list(self.system_message, copy.deepcopy(self.message_list))
+ self.assertEqual(len(reduced_message_list), 2)
+ self.assertEqual(reduced_message_list, self.message_list)
From fea50fbefd35bf3eb62e4d143ae10aa7e5e522e3 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 19 Sep 2023 11:20:44 -0400
Subject: [PATCH 003/104] chore: Updating Python Requirements
---
requirements/base.txt | 14 ++++----------
requirements/ci.txt | 8 +++-----
requirements/dev.txt | 36 ++++++++++++++++++------------------
requirements/doc.txt | 37 +++++++++++++------------------------
requirements/pip-tools.txt | 6 +++++-
requirements/pip.txt | 2 +-
requirements/quality.txt | 22 +++++++---------------
requirements/test.txt | 19 +++++--------------
8 files changed, 56 insertions(+), 88 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 4d944b2..9f65c9a 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -18,7 +18,7 @@ click==8.1.7
# via edx-django-utils
cryptography==41.0.3
# via pyjwt
-django==3.2.20
+django==3.2.21
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/base.in
@@ -46,7 +46,7 @@ drf-jwt==1.19.2
# via edx-drf-extensions
edx-django-utils==5.7.0
# via edx-drf-extensions
-edx-drf-extensions==8.9.1
+edx-drf-extensions==8.10.0
# via -r requirements/base.in
edx-opaque-keys==2.5.0
# via
@@ -70,9 +70,7 @@ pymongo==3.13.0
# via edx-opaque-keys
pynacl==1.5.0
# via edx-django-utils
-python-dateutil==2.8.2
- # via edx-drf-extensions
-pytz==2023.3
+pytz==2023.3.post1
# via
# django
# djangorestframework
@@ -80,17 +78,13 @@ requests==2.31.0
# via edx-drf-extensions
semantic-version==2.10.0
# via edx-drf-extensions
-six==1.16.0
- # via
- # edx-drf-extensions
- # python-dateutil
sqlparse==0.4.4
# via django
stevedore==5.1.0
# via
# edx-django-utils
# edx-opaque-keys
-typing-extensions==4.7.1
+typing-extensions==4.8.0
# via
# asgiref
# edx-opaque-keys
diff --git a/requirements/ci.txt b/requirements/ci.txt
index d493b38..e1844ab 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -10,11 +10,11 @@ charset-normalizer==3.2.0
# via requests
codecov==2.1.13
# via -r requirements/ci.in
-coverage==7.3.0
+coverage==7.3.1
# via codecov
distlib==0.3.7
# via virtualenv
-filelock==3.12.3
+filelock==3.12.4
# via
# tox
# virtualenv
@@ -41,9 +41,7 @@ tox==3.28.0
# tox-battery
tox-battery==0.6.2
# via -r requirements/ci.in
-typing-extensions==4.7.1
- # via filelock
urllib3==2.0.4
# via requests
-virtualenv==20.24.3
+virtualenv==20.24.5
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 9e0b081..b69fcd5 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -13,7 +13,7 @@ astroid==2.15.6
# -r requirements/quality.txt
# pylint
# pylint-celery
-build==0.10.0
+build==1.0.3
# via
# -r requirements/pip-tools.txt
# pip-tools
@@ -53,7 +53,7 @@ code-annotations==1.5.0
# edx-lint
codecov==2.1.13
# via -r requirements/ci.txt
-coverage[toml]==7.3.0
+coverage[toml]==7.3.1
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -75,7 +75,7 @@ distlib==0.3.7
# via
# -r requirements/ci.txt
# virtualenv
-django==3.2.20
+django==3.2.21
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/quality.txt
@@ -111,9 +111,9 @@ edx-django-utils==5.7.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-drf-extensions==8.9.1
+edx-drf-extensions==8.10.0
# via -r requirements/quality.txt
-edx-i18n-tools==1.1.0
+edx-i18n-tools==1.2.0
# via -r requirements/dev.in
edx-lint==5.3.4
# via -r requirements/quality.txt
@@ -125,7 +125,7 @@ exceptiongroup==1.1.3
# via
# -r requirements/quality.txt
# pytest
-filelock==3.12.3
+filelock==3.12.4
# via
# -r requirements/ci.txt
# tox
@@ -135,6 +135,10 @@ idna==3.4
# -r requirements/ci.txt
# -r requirements/quality.txt
# requests
+importlib-metadata==6.8.0
+ # via
+ # -r requirements/pip-tools.txt
+ # build
iniconfig==2.0.0
# via
# -r requirements/quality.txt
@@ -250,7 +254,7 @@ pyproject-hooks==1.0.0
# via
# -r requirements/pip-tools.txt
# build
-pytest==7.4.0
+pytest==7.4.2
# via
# -r requirements/quality.txt
# pytest-cov
@@ -259,15 +263,11 @@ pytest-cov==4.1.0
# via -r requirements/quality.txt
pytest-django==4.5.2
# via -r requirements/quality.txt
-python-dateutil==2.8.2
- # via
- # -r requirements/quality.txt
- # edx-drf-extensions
python-slugify==8.0.1
# via
# -r requirements/quality.txt
# code-annotations
-pytz==2023.3
+pytz==2023.3.post1
# via
# -r requirements/quality.txt
# django
@@ -295,9 +295,7 @@ six==1.16.0
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
- # edx-drf-extensions
# edx-lint
- # python-dateutil
# tox
snowballstemmer==2.2.0
# via
@@ -344,14 +342,12 @@ types-pyyaml==6.0.12.11
# via
# -r requirements/quality.txt
# responses
-typing-extensions==4.7.1
+typing-extensions==4.8.0
# via
- # -r requirements/ci.txt
# -r requirements/quality.txt
# asgiref
# astroid
# edx-opaque-keys
- # filelock
# pylint
urllib3==2.0.4
# via
@@ -359,7 +355,7 @@ urllib3==2.0.4
# -r requirements/quality.txt
# requests
# responses
-virtualenv==20.24.3
+virtualenv==20.24.5
# via
# -r requirements/ci.txt
# tox
@@ -371,6 +367,10 @@ wrapt==1.15.0
# via
# -r requirements/quality.txt
# astroid
+zipp==3.17.0
+ # via
+ # -r requirements/pip-tools.txt
+ # importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# pip
diff --git a/requirements/doc.txt b/requirements/doc.txt
index b08530e..050409f 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -12,9 +12,7 @@ asgiref==3.7.2
# django
babel==2.12.1
# via sphinx
-bleach==6.0.0
- # via readme-renderer
-build==0.10.0
+build==1.0.3
# via -r requirements/doc.in
certifi==2023.7.22
# via
@@ -36,7 +34,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.5.0
# via -r requirements/test.txt
-coverage[toml]==7.3.0
+coverage[toml]==7.3.1
# via
# -r requirements/test.txt
# pytest-cov
@@ -47,7 +45,7 @@ cryptography==41.0.3
# secretstorage
ddt==1.6.0
# via -r requirements/test.txt
-django==3.2.20
+django==3.2.21
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -90,7 +88,7 @@ edx-django-utils==5.7.0
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-drf-extensions==8.9.1
+edx-drf-extensions==8.10.0
# via -r requirements/test.txt
edx-opaque-keys==2.5.0
# via
@@ -108,6 +106,7 @@ imagesize==1.4.1
# via sphinx
importlib-metadata==6.8.0
# via
+ # build
# keyring
# sphinx
# twine
@@ -144,6 +143,8 @@ newrelic==9.0.0
# via
# -r requirements/test.txt
# edx-django-utils
+nh3==0.2.14
+ # via readme-renderer
packaging==23.1
# via
# -r requirements/test.txt
@@ -189,7 +190,7 @@ pynacl==1.5.0
# edx-django-utils
pyproject-hooks==1.0.0
# via build
-pytest==7.4.0
+pytest==7.4.2
# via
# -r requirements/test.txt
# pytest-cov
@@ -198,15 +199,11 @@ pytest-cov==4.1.0
# via -r requirements/test.txt
pytest-django==4.5.2
# via -r requirements/test.txt
-python-dateutil==2.8.2
- # via
- # -r requirements/test.txt
- # edx-drf-extensions
python-slugify==8.0.1
# via
# -r requirements/test.txt
# code-annotations
-pytz==2023.3
+pytz==2023.3.post1
# via
# -r requirements/test.txt
# babel
@@ -217,7 +214,7 @@ pyyaml==6.0.1
# -r requirements/test.txt
# code-annotations
# responses
-readme-renderer==41.0
+readme-renderer==42.0
# via twine
requests==2.31.0
# via
@@ -235,7 +232,7 @@ restructuredtext-lint==1.4.0
# via doc8
rfc3986==2.0.0
# via twine
-rich==13.5.2
+rich==13.5.3
# via twine
secretstorage==3.3.3
# via keyring
@@ -243,12 +240,6 @@ semantic-version==2.10.0
# via
# -r requirements/test.txt
# edx-drf-extensions
-six==1.16.0
- # via
- # -r requirements/test.txt
- # bleach
- # edx-drf-extensions
- # python-dateutil
snowballstemmer==2.2.0
# via sphinx
sphinx==7.1.2
@@ -294,7 +285,7 @@ types-pyyaml==6.0.12.11
# via
# -r requirements/test.txt
# responses
-typing-extensions==4.7.1
+typing-extensions==4.8.0
# via
# -r requirements/test.txt
# asgiref
@@ -306,9 +297,7 @@ urllib3==2.0.4
# requests
# responses
# twine
-webencodings==0.5.1
- # via bleach
-zipp==3.16.2
+zipp==3.17.0
# via
# importlib-metadata
# importlib-resources
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 007ed38..894fa17 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -4,10 +4,12 @@
#
# make upgrade
#
-build==0.10.0
+build==1.0.3
# via pip-tools
click==8.1.7
# via pip-tools
+importlib-metadata==6.8.0
+ # via build
packaging==23.1
# via build
pip-tools==7.3.0
@@ -21,6 +23,8 @@ tomli==2.0.1
# pyproject-hooks
wheel==0.41.2
# via pip-tools
+zipp==3.17.0
+ # via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# pip
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 13c7e84..3e7d8f4 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -10,5 +10,5 @@ wheel==0.41.2
# The following packages are considered to be unsafe in a requirements file:
pip==23.2.1
# via -r requirements/pip.in
-setuptools==68.1.2
+setuptools==68.2.2
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 64c97cc..c76647c 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -38,7 +38,7 @@ code-annotations==1.5.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.3.0
+coverage[toml]==7.3.1
# via
# -r requirements/test.txt
# pytest-cov
@@ -50,7 +50,7 @@ ddt==1.6.0
# via -r requirements/test.txt
dill==0.3.7
# via pylint
-django==3.2.20
+django==3.2.21
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -85,7 +85,7 @@ edx-django-utils==5.7.0
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-drf-extensions==8.9.1
+edx-drf-extensions==8.10.0
# via -r requirements/test.txt
edx-lint==5.3.4
# via -r requirements/quality.in
@@ -178,7 +178,7 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==7.4.0
+pytest==7.4.2
# via
# -r requirements/test.txt
# pytest-cov
@@ -187,15 +187,11 @@ pytest-cov==4.1.0
# via -r requirements/test.txt
pytest-django==4.5.2
# via -r requirements/test.txt
-python-dateutil==2.8.2
- # via
- # -r requirements/test.txt
- # edx-drf-extensions
python-slugify==8.0.1
# via
# -r requirements/test.txt
# code-annotations
-pytz==2023.3
+pytz==2023.3.post1
# via
# -r requirements/test.txt
# django
@@ -217,11 +213,7 @@ semantic-version==2.10.0
# -r requirements/test.txt
# edx-drf-extensions
six==1.16.0
- # via
- # -r requirements/test.txt
- # edx-drf-extensions
- # edx-lint
- # python-dateutil
+ # via edx-lint
snowballstemmer==2.2.0
# via pydocstyle
sqlparse==0.4.4
@@ -250,7 +242,7 @@ types-pyyaml==6.0.12.11
# via
# -r requirements/test.txt
# responses
-typing-extensions==4.7.1
+typing-extensions==4.8.0
# via
# -r requirements/test.txt
# asgiref
diff --git a/requirements/test.txt b/requirements/test.txt
index a19c5f8..97b56fb 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -28,7 +28,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.5.0
# via -r requirements/test.in
-coverage[toml]==7.3.0
+coverage[toml]==7.3.1
# via pytest-cov
cryptography==41.0.3
# via
@@ -70,7 +70,7 @@ edx-django-utils==5.7.0
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-drf-extensions==8.9.1
+edx-drf-extensions==8.10.0
# via -r requirements/base.txt
edx-opaque-keys==2.5.0
# via
@@ -121,7 +121,7 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==7.4.0
+pytest==7.4.2
# via
# pytest-cov
# pytest-django
@@ -129,13 +129,9 @@ pytest-cov==4.1.0
# via -r requirements/test.in
pytest-django==4.5.2
# via -r requirements/test.in
-python-dateutil==2.8.2
- # via
- # -r requirements/base.txt
- # edx-drf-extensions
python-slugify==8.0.1
# via code-annotations
-pytz==2023.3
+pytz==2023.3.post1
# via
# -r requirements/base.txt
# django
@@ -155,11 +151,6 @@ semantic-version==2.10.0
# via
# -r requirements/base.txt
# edx-drf-extensions
-six==1.16.0
- # via
- # -r requirements/base.txt
- # edx-drf-extensions
- # python-dateutil
sqlparse==0.4.4
# via
# -r requirements/base.txt
@@ -178,7 +169,7 @@ tomli==2.0.1
# pytest
types-pyyaml==6.0.12.11
# via responses
-typing-extensions==4.7.1
+typing-extensions==4.8.0
# via
# -r requirements/base.txt
# asgiref
From 471b41b1dbf5abae726db3c21848dcd4abd449ae Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 29 Sep 2023 15:54:31 -0400
Subject: [PATCH 004/104] chore: Updating Python Requirements (#24)
---
requirements/base.txt | 8 ++++----
requirements/ci.txt | 2 +-
requirements/dev.txt | 14 +++++++-------
requirements/doc.txt | 12 ++++++------
requirements/quality.txt | 14 +++++++-------
requirements/test.txt | 10 +++++-----
6 files changed, 30 insertions(+), 30 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 9f65c9a..982b411 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -16,7 +16,7 @@ charset-normalizer==3.2.0
# via requests
click==8.1.7
# via edx-django-utils
-cryptography==41.0.3
+cryptography==41.0.4
# via pyjwt
django==3.2.21
# via
@@ -48,13 +48,13 @@ edx-django-utils==5.7.0
# via edx-drf-extensions
edx-drf-extensions==8.10.0
# via -r requirements/base.in
-edx-opaque-keys==2.5.0
+edx-opaque-keys==2.5.1
# via
# -r requirements/base.in
# edx-drf-extensions
idna==3.4
# via requests
-newrelic==9.0.0
+newrelic==9.1.0
# via edx-django-utils
pbr==5.11.1
# via stevedore
@@ -88,5 +88,5 @@ typing-extensions==4.8.0
# via
# asgiref
# edx-opaque-keys
-urllib3==2.0.4
+urllib3==2.0.5
# via requests
diff --git a/requirements/ci.txt b/requirements/ci.txt
index e1844ab..a457fa7 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -41,7 +41,7 @@ tox==3.28.0
# tox-battery
tox-battery==0.6.2
# via -r requirements/ci.in
-urllib3==2.0.4
+urllib3==2.0.5
# via requests
virtualenv==20.24.5
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index b69fcd5..dd4e2ee 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -8,7 +8,7 @@ asgiref==3.7.2
# via
# -r requirements/quality.txt
# django
-astroid==2.15.6
+astroid==2.15.8
# via
# -r requirements/quality.txt
# pylint
@@ -59,7 +59,7 @@ coverage[toml]==7.3.1
# -r requirements/quality.txt
# codecov
# pytest-cov
-cryptography==41.0.3
+cryptography==41.0.4
# via
# -r requirements/quality.txt
# pyjwt
@@ -117,7 +117,7 @@ edx-i18n-tools==1.2.0
# via -r requirements/dev.in
edx-lint==5.3.4
# via -r requirements/quality.txt
-edx-opaque-keys==2.5.0
+edx-opaque-keys==2.5.1
# via
# -r requirements/quality.txt
# edx-drf-extensions
@@ -164,7 +164,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.0.0
+newrelic==9.1.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -222,7 +222,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/quality.txt
# drf-jwt
# edx-drf-extensions
-pylint==2.17.5
+pylint==2.17.6
# via
# -r requirements/quality.txt
# edx-lint
@@ -338,7 +338,7 @@ tox==3.28.0
# tox-battery
tox-battery==0.6.2
# via -r requirements/ci.txt
-types-pyyaml==6.0.12.11
+types-pyyaml==6.0.12.12
# via
# -r requirements/quality.txt
# responses
@@ -349,7 +349,7 @@ typing-extensions==4.8.0
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.0.4
+urllib3==2.0.5
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 050409f..4be05b0 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -38,7 +38,7 @@ coverage[toml]==7.3.1
# via
# -r requirements/test.txt
# pytest-cov
-cryptography==41.0.3
+cryptography==41.0.4
# via
# -r requirements/test.txt
# pyjwt
@@ -90,7 +90,7 @@ edx-django-utils==5.7.0
# edx-drf-extensions
edx-drf-extensions==8.10.0
# via -r requirements/test.txt
-edx-opaque-keys==2.5.0
+edx-opaque-keys==2.5.1
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -110,7 +110,7 @@ importlib-metadata==6.8.0
# keyring
# sphinx
# twine
-importlib-resources==6.0.1
+importlib-resources==6.1.0
# via keyring
iniconfig==2.0.0
# via
@@ -139,7 +139,7 @@ mdurl==0.1.2
# via markdown-it-py
more-itertools==10.1.0
# via jaraco-classes
-newrelic==9.0.0
+newrelic==9.1.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -281,7 +281,7 @@ tomli==2.0.1
# pytest
twine==4.0.2
# via -r requirements/doc.in
-types-pyyaml==6.0.12.11
+types-pyyaml==6.0.12.12
# via
# -r requirements/test.txt
# responses
@@ -291,7 +291,7 @@ typing-extensions==4.8.0
# asgiref
# edx-opaque-keys
# rich
-urllib3==2.0.4
+urllib3==2.0.5
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/quality.txt b/requirements/quality.txt
index c76647c..bd6e276 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -8,7 +8,7 @@ asgiref==3.7.2
# via
# -r requirements/test.txt
# django
-astroid==2.15.6
+astroid==2.15.8
# via
# pylint
# pylint-celery
@@ -42,7 +42,7 @@ coverage[toml]==7.3.1
# via
# -r requirements/test.txt
# pytest-cov
-cryptography==41.0.3
+cryptography==41.0.4
# via
# -r requirements/test.txt
# pyjwt
@@ -89,7 +89,7 @@ edx-drf-extensions==8.10.0
# via -r requirements/test.txt
edx-lint==5.3.4
# via -r requirements/quality.in
-edx-opaque-keys==2.5.0
+edx-opaque-keys==2.5.1
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -121,7 +121,7 @@ markupsafe==2.1.3
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.0.0
+newrelic==9.1.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -156,7 +156,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/test.txt
# drf-jwt
# edx-drf-extensions
-pylint==2.17.5
+pylint==2.17.6
# via
# edx-lint
# pylint-celery
@@ -238,7 +238,7 @@ tomli==2.0.1
# pytest
tomlkit==0.12.1
# via pylint
-types-pyyaml==6.0.12.11
+types-pyyaml==6.0.12.12
# via
# -r requirements/test.txt
# responses
@@ -249,7 +249,7 @@ typing-extensions==4.8.0
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.0.4
+urllib3==2.0.5
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/test.txt b/requirements/test.txt
index 97b56fb..6a0a1a7 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -30,7 +30,7 @@ code-annotations==1.5.0
# via -r requirements/test.in
coverage[toml]==7.3.1
# via pytest-cov
-cryptography==41.0.3
+cryptography==41.0.4
# via
# -r requirements/base.txt
# pyjwt
@@ -72,7 +72,7 @@ edx-django-utils==5.7.0
# edx-drf-extensions
edx-drf-extensions==8.10.0
# via -r requirements/base.txt
-edx-opaque-keys==2.5.0
+edx-opaque-keys==2.5.1
# via
# -r requirements/base.txt
# edx-drf-extensions
@@ -88,7 +88,7 @@ jinja2==3.1.2
# via code-annotations
markupsafe==2.1.3
# via jinja2
-newrelic==9.0.0
+newrelic==9.1.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -167,14 +167,14 @@ tomli==2.0.1
# via
# coverage
# pytest
-types-pyyaml==6.0.12.11
+types-pyyaml==6.0.12.12
# via responses
typing-extensions==4.8.0
# via
# -r requirements/base.txt
# asgiref
# edx-opaque-keys
-urllib3==2.0.4
+urllib3==2.0.5
# via
# -r requirements/base.txt
# requests
From 4651c0c4f3953e1b149e971cfb21004f6388c3cc Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 3 Oct 2023 11:21:02 -0400
Subject: [PATCH 005/104] chore: Updating Python Requirements
---
requirements/base.txt | 6 +++---
requirements/ci.txt | 10 +++++-----
requirements/dev.txt | 14 +++++++-------
requirements/doc.txt | 14 +++++++-------
requirements/pip-tools.txt | 2 +-
requirements/quality.txt | 14 +++++++-------
requirements/test.txt | 10 +++++-----
7 files changed, 35 insertions(+), 35 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 982b411..7a7e31e 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -8,11 +8,11 @@ asgiref==3.7.2
# via django
certifi==2023.7.22
# via requests
-cffi==1.15.1
+cffi==1.16.0
# via
# cryptography
# pynacl
-charset-normalizer==3.2.0
+charset-normalizer==3.3.0
# via requests
click==8.1.7
# via edx-django-utils
@@ -88,5 +88,5 @@ typing-extensions==4.8.0
# via
# asgiref
# edx-opaque-keys
-urllib3==2.0.5
+urllib3==2.0.6
# via requests
diff --git a/requirements/ci.txt b/requirements/ci.txt
index a457fa7..ee3ed60 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -6,11 +6,11 @@
#
certifi==2023.7.22
# via requests
-charset-normalizer==3.2.0
+charset-normalizer==3.3.0
# via requests
codecov==2.1.13
# via -r requirements/ci.in
-coverage==7.3.1
+coverage==7.3.2
# via codecov
distlib==0.3.7
# via virtualenv
@@ -20,9 +20,9 @@ filelock==3.12.4
# virtualenv
idna==3.4
# via requests
-packaging==23.1
+packaging==23.2
# via tox
-platformdirs==3.10.0
+platformdirs==3.11.0
# via virtualenv
pluggy==1.3.0
# via tox
@@ -41,7 +41,7 @@ tox==3.28.0
# tox-battery
tox-battery==0.6.2
# via -r requirements/ci.in
-urllib3==2.0.5
+urllib3==2.0.6
# via requests
virtualenv==20.24.5
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index dd4e2ee..1cd5216 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -22,14 +22,14 @@ certifi==2023.7.22
# -r requirements/ci.txt
# -r requirements/quality.txt
# requests
-cffi==1.15.1
+cffi==1.16.0
# via
# -r requirements/quality.txt
# cryptography
# pynacl
chardet==5.2.0
# via diff-cover
-charset-normalizer==3.2.0
+charset-normalizer==3.3.0
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -53,7 +53,7 @@ code-annotations==1.5.0
# edx-lint
codecov==2.1.13
# via -r requirements/ci.txt
-coverage[toml]==7.3.1
+coverage[toml]==7.3.2
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -168,7 +168,7 @@ newrelic==9.1.0
# via
# -r requirements/quality.txt
# edx-django-utils
-packaging==23.1
+packaging==23.2
# via
# -r requirements/ci.txt
# -r requirements/pip-tools.txt
@@ -184,7 +184,7 @@ pbr==5.11.1
# stevedore
pip-tools==7.3.0
# via -r requirements/pip-tools.txt
-platformdirs==3.10.0
+platformdirs==3.11.0
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -222,7 +222,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/quality.txt
# drf-jwt
# edx-drf-extensions
-pylint==2.17.6
+pylint==2.17.7
# via
# -r requirements/quality.txt
# edx-lint
@@ -349,7 +349,7 @@ typing-extensions==4.8.0
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.0.5
+urllib3==2.0.6
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 4be05b0..d22ebab 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -10,7 +10,7 @@ asgiref==3.7.2
# via
# -r requirements/test.txt
# django
-babel==2.12.1
+babel==2.13.0
# via sphinx
build==1.0.3
# via -r requirements/doc.in
@@ -18,12 +18,12 @@ certifi==2023.7.22
# via
# -r requirements/test.txt
# requests
-cffi==1.15.1
+cffi==1.16.0
# via
# -r requirements/test.txt
# cryptography
# pynacl
-charset-normalizer==3.2.0
+charset-normalizer==3.3.0
# via
# -r requirements/test.txt
# requests
@@ -34,7 +34,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.5.0
# via -r requirements/test.txt
-coverage[toml]==7.3.1
+coverage[toml]==7.3.2
# via
# -r requirements/test.txt
# pytest-cov
@@ -145,7 +145,7 @@ newrelic==9.1.0
# edx-django-utils
nh3==0.2.14
# via readme-renderer
-packaging==23.1
+packaging==23.2
# via
# -r requirements/test.txt
# build
@@ -232,7 +232,7 @@ restructuredtext-lint==1.4.0
# via doc8
rfc3986==2.0.0
# via twine
-rich==13.5.3
+rich==13.6.0
# via twine
secretstorage==3.3.3
# via keyring
@@ -291,7 +291,7 @@ typing-extensions==4.8.0
# asgiref
# edx-opaque-keys
# rich
-urllib3==2.0.5
+urllib3==2.0.6
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 894fa17..50d35f2 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -10,7 +10,7 @@ click==8.1.7
# via pip-tools
importlib-metadata==6.8.0
# via build
-packaging==23.1
+packaging==23.2
# via build
pip-tools==7.3.0
# via -r requirements/pip-tools.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index bd6e276..80ac9d6 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -16,12 +16,12 @@ certifi==2023.7.22
# via
# -r requirements/test.txt
# requests
-cffi==1.15.1
+cffi==1.16.0
# via
# -r requirements/test.txt
# cryptography
# pynacl
-charset-normalizer==3.2.0
+charset-normalizer==3.3.0
# via
# -r requirements/test.txt
# requests
@@ -38,7 +38,7 @@ code-annotations==1.5.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.3.1
+coverage[toml]==7.3.2
# via
# -r requirements/test.txt
# pytest-cov
@@ -125,7 +125,7 @@ newrelic==9.1.0
# via
# -r requirements/test.txt
# edx-django-utils
-packaging==23.1
+packaging==23.2
# via
# -r requirements/test.txt
# pytest
@@ -133,7 +133,7 @@ pbr==5.11.1
# via
# -r requirements/test.txt
# stevedore
-platformdirs==3.10.0
+platformdirs==3.11.0
# via pylint
pluggy==1.3.0
# via
@@ -156,7 +156,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/test.txt
# drf-jwt
# edx-drf-extensions
-pylint==2.17.6
+pylint==2.17.7
# via
# edx-lint
# pylint-celery
@@ -249,7 +249,7 @@ typing-extensions==4.8.0
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.0.5
+urllib3==2.0.6
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/test.txt b/requirements/test.txt
index 6a0a1a7..7384cd6 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -12,12 +12,12 @@ certifi==2023.7.22
# via
# -r requirements/base.txt
# requests
-cffi==1.15.1
+cffi==1.16.0
# via
# -r requirements/base.txt
# cryptography
# pynacl
-charset-normalizer==3.2.0
+charset-normalizer==3.3.0
# via
# -r requirements/base.txt
# requests
@@ -28,7 +28,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.5.0
# via -r requirements/test.in
-coverage[toml]==7.3.1
+coverage[toml]==7.3.2
# via pytest-cov
cryptography==41.0.4
# via
@@ -92,7 +92,7 @@ newrelic==9.1.0
# via
# -r requirements/base.txt
# edx-django-utils
-packaging==23.1
+packaging==23.2
# via pytest
pbr==5.11.1
# via
@@ -174,7 +174,7 @@ typing-extensions==4.8.0
# -r requirements/base.txt
# asgiref
# edx-opaque-keys
-urllib3==2.0.5
+urllib3==2.0.6
# via
# -r requirements/base.txt
# requests
From d565f909c4ee242bf691dbde8975e09240c049b7 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 10 Oct 2023 11:21:04 -0400
Subject: [PATCH 006/104] chore: Updating Python Requirements
---
requirements/base.txt | 2 +-
requirements/dev.txt | 6 ++++--
requirements/doc.txt | 2 +-
requirements/quality.txt | 2 +-
4 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 7a7e31e..3c45598 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -18,7 +18,7 @@ click==8.1.7
# via edx-django-utils
cryptography==41.0.4
# via pyjwt
-django==3.2.21
+django==3.2.22
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/base.in
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 1cd5216..8ac027b 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -75,7 +75,7 @@ distlib==0.3.7
# via
# -r requirements/ci.txt
# virtualenv
-django==3.2.21
+django==3.2.22
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/quality.txt
@@ -113,7 +113,7 @@ edx-django-utils==5.7.0
# edx-drf-extensions
edx-drf-extensions==8.10.0
# via -r requirements/quality.txt
-edx-i18n-tools==1.2.0
+edx-i18n-tools==1.3.0
# via -r requirements/dev.in
edx-lint==5.3.4
# via -r requirements/quality.txt
@@ -156,6 +156,8 @@ lazy-object-proxy==1.9.0
# via
# -r requirements/quality.txt
# astroid
+lxml==4.9.3
+ # via edx-i18n-tools
markupsafe==2.1.3
# via
# -r requirements/quality.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index d22ebab..ee4e2ed 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -45,7 +45,7 @@ cryptography==41.0.4
# secretstorage
ddt==1.6.0
# via -r requirements/test.txt
-django==3.2.21
+django==3.2.22
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 80ac9d6..d8b2473 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -50,7 +50,7 @@ ddt==1.6.0
# via -r requirements/test.txt
dill==0.3.7
# via pylint
-django==3.2.21
+django==3.2.22
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
From 0fd14308963122170492367fbba7f535be7be6fd Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 17 Oct 2023 11:20:35 -0400
Subject: [PATCH 007/104] chore: Updating Python Requirements
---
requirements/base.txt | 5 +++--
requirements/dev.txt | 10 ++++++----
requirements/doc.txt | 6 ++++--
requirements/pip.txt | 2 +-
requirements/quality.txt | 8 +++++---
requirements/test.txt | 9 ++++++---
6 files changed, 25 insertions(+), 15 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 3c45598..ebff97b 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -46,7 +46,7 @@ drf-jwt==1.19.2
# via edx-drf-extensions
edx-django-utils==5.7.0
# via edx-drf-extensions
-edx-drf-extensions==8.10.0
+edx-drf-extensions==8.12.0
# via -r requirements/base.in
edx-opaque-keys==2.5.1
# via
@@ -58,7 +58,7 @@ newrelic==9.1.0
# via edx-django-utils
pbr==5.11.1
# via stevedore
-psutil==5.9.5
+psutil==5.9.6
# via edx-django-utils
pycparser==2.21
# via cffi
@@ -66,6 +66,7 @@ pyjwt[crypto]==2.8.0
# via
# drf-jwt
# edx-drf-extensions
+ # pyjwt
pymongo==3.13.0
# via edx-opaque-keys
pynacl==1.5.0
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 8ac027b..6643e02 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -58,6 +58,7 @@ coverage[toml]==7.3.2
# -r requirements/ci.txt
# -r requirements/quality.txt
# codecov
+ # coverage
# pytest-cov
cryptography==41.0.4
# via
@@ -65,7 +66,7 @@ cryptography==41.0.4
# pyjwt
ddt==1.6.0
# via -r requirements/quality.txt
-diff-cover==7.7.0
+diff-cover==8.0.0
# via -r requirements/dev.in
dill==0.3.7
# via
@@ -111,7 +112,7 @@ edx-django-utils==5.7.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-drf-extensions==8.10.0
+edx-drf-extensions==8.12.0
# via -r requirements/quality.txt
edx-i18n-tools==1.3.0
# via -r requirements/dev.in
@@ -201,7 +202,7 @@ pluggy==1.3.0
# tox
polib==1.2.0
# via edx-i18n-tools
-psutil==5.9.5
+psutil==5.9.6
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -209,7 +210,7 @@ py==1.11.0
# via
# -r requirements/ci.txt
# tox
-pycodestyle==2.11.0
+pycodestyle==2.11.1
# via -r requirements/quality.txt
pycparser==2.21
# via
@@ -224,6 +225,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/quality.txt
# drf-jwt
# edx-drf-extensions
+ # pyjwt
pylint==2.17.7
# via
# -r requirements/quality.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index ee4e2ed..16171e4 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -37,6 +37,7 @@ code-annotations==1.5.0
coverage[toml]==7.3.2
# via
# -r requirements/test.txt
+ # coverage
# pytest-cov
cryptography==41.0.4
# via
@@ -88,7 +89,7 @@ edx-django-utils==5.7.0
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-drf-extensions==8.10.0
+edx-drf-extensions==8.12.0
# via -r requirements/test.txt
edx-opaque-keys==2.5.1
# via
@@ -161,7 +162,7 @@ pluggy==1.3.0
# via
# -r requirements/test.txt
# pytest
-psutil==5.9.5
+psutil==5.9.6
# via
# -r requirements/test.txt
# edx-django-utils
@@ -180,6 +181,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/test.txt
# drf-jwt
# edx-drf-extensions
+ # pyjwt
pymongo==3.13.0
# via
# -r requirements/test.txt
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 3e7d8f4..2154d29 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -8,7 +8,7 @@ wheel==0.41.2
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
-pip==23.2.1
+pip==23.3
# via -r requirements/pip.in
setuptools==68.2.2
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index d8b2473..adb0130 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -41,6 +41,7 @@ code-annotations==1.5.0
coverage[toml]==7.3.2
# via
# -r requirements/test.txt
+ # coverage
# pytest-cov
cryptography==41.0.4
# via
@@ -85,7 +86,7 @@ edx-django-utils==5.7.0
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-drf-extensions==8.10.0
+edx-drf-extensions==8.12.0
# via -r requirements/test.txt
edx-lint==5.3.4
# via -r requirements/quality.in
@@ -139,11 +140,11 @@ pluggy==1.3.0
# via
# -r requirements/test.txt
# pytest
-psutil==5.9.5
+psutil==5.9.6
# via
# -r requirements/test.txt
# edx-django-utils
-pycodestyle==2.11.0
+pycodestyle==2.11.1
# via -r requirements/quality.in
pycparser==2.21
# via
@@ -156,6 +157,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/test.txt
# drf-jwt
# edx-drf-extensions
+ # pyjwt
pylint==2.17.7
# via
# edx-lint
diff --git a/requirements/test.txt b/requirements/test.txt
index 7384cd6..43759f1 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -29,7 +29,9 @@ click==8.1.7
code-annotations==1.5.0
# via -r requirements/test.in
coverage[toml]==7.3.2
- # via pytest-cov
+ # via
+ # coverage
+ # pytest-cov
cryptography==41.0.4
# via
# -r requirements/base.txt
@@ -70,7 +72,7 @@ edx-django-utils==5.7.0
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-drf-extensions==8.10.0
+edx-drf-extensions==8.12.0
# via -r requirements/base.txt
edx-opaque-keys==2.5.1
# via
@@ -100,7 +102,7 @@ pbr==5.11.1
# stevedore
pluggy==1.3.0
# via pytest
-psutil==5.9.5
+psutil==5.9.6
# via
# -r requirements/base.txt
# edx-django-utils
@@ -113,6 +115,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/base.txt
# drf-jwt
# edx-drf-extensions
+ # pyjwt
pymongo==3.13.0
# via
# -r requirements/base.txt
From 2d8d29d61f211e92317fbe97894cbb10be991bef Mon Sep 17 00:00:00 2001
From: Alie Langston
Date: Wed, 18 Oct 2023 10:30:36 -0400
Subject: [PATCH 008/104] feat: add management command for course prompts
---
CHANGELOG.rst | 4 +
learning_assistant/__init__.py | 2 +-
learning_assistant/management/__init__.py | 0
.../management/commands/__init__.py | 0
.../management/commands/set_course_prompts.py | 108 ++++++++++++++++++
.../management/commands/tests/__init__.py | 0
.../commands/tests/test_set_course_prompts.py | 75 ++++++++++++
requirements/base.in | 1 +
requirements/base.txt | 16 ++-
requirements/ci.txt | 2 +-
requirements/dev.txt | 12 +-
requirements/doc.txt | 47 ++++----
requirements/quality.txt | 12 +-
requirements/test.txt | 12 +-
test_settings.py | 5 +
15 files changed, 266 insertions(+), 30 deletions(-)
create mode 100644 learning_assistant/management/__init__.py
create mode 100644 learning_assistant/management/commands/__init__.py
create mode 100644 learning_assistant/management/commands/set_course_prompts.py
create mode 100644 learning_assistant/management/commands/tests/__init__.py
create mode 100644 learning_assistant/management/commands/tests/test_set_course_prompts.py
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6841ee5..9fdec11 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -11,6 +11,10 @@ Change Log
.. There should always be an "Unreleased" section for changes pending release.
+1.5.0 - 2023-10-18
+******************
+* Add management command to generate course prompts
+
1.4.0 - 2023-09-11
******************
* Send reduced message list if needed to avoid going over token limit
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 60ac235..d31819d 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '1.4.0'
+__version__ = '1.5.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/management/__init__.py b/learning_assistant/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/learning_assistant/management/commands/__init__.py b/learning_assistant/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/learning_assistant/management/commands/set_course_prompts.py b/learning_assistant/management/commands/set_course_prompts.py
new file mode 100644
index 0000000..284366f
--- /dev/null
+++ b/learning_assistant/management/commands/set_course_prompts.py
@@ -0,0 +1,108 @@
+"""
+Django management command to generate course prompts.
+"""
+import json
+import logging
+from posixpath import join as urljoin
+
+from django.conf import settings
+from django.core.management.base import BaseCommand
+from edx_rest_api_client.client import OAuthAPIClient
+from opaque_keys.edx.keys import CourseKey
+
+from learning_assistant.models import CoursePrompt
+
+logger = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """
+ Django Management command to create a set of course prompts
+ """
+
+ def add_arguments(self, parser):
+
+ # list of course ids
+ parser.add_argument(
+ '--course_ids',
+ dest='course_ids',
+ help='Comma separated list of course_ids to generate. Only newer style course ids can be supplied.',
+ )
+
+ # pre-message
+ parser.add_argument(
+ '--pre_message',
+ dest='pre_message',
+ help='Message to prepend to course topics',
+ )
+
+ parser.add_argument(
+ '--skills_descriptor',
+ dest='skills_descriptor',
+ help='Message that describes skill structure'
+ )
+
+ # post-message
+ parser.add_argument(
+ '--post_message',
+ dest='post_message',
+ help='Message to append to course topics',
+ )
+
+ @staticmethod
+ def _get_discovery_api_client():
+ """
+ Returns an API client which can be used to make Catalog API requests.
+ """
+ return OAuthAPIClient(
+ base_url=settings.DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL,
+ client_id=settings.DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_KEY,
+ client_secret=settings.DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_SECRET,
+ )
+
+ def handle(self, *args, **options):
+ """
+ Management command entry point.
+
+ This command is meant to generate a small (<500) set of course prompts. If a larger number of prompts
+ should be created, consider adding batching to this command.
+
+ As of now, this command supports a limited structure of course prompt, such that each prompt is composed of
+ three messages: the pre message, skills message, and post message. Should we need more messages in the future,
+ and want to use this management command, the structure of the command args should be updated.
+ """
+ course_ids = options['course_ids']
+ pre_message = options['pre_message']
+ skills_descriptor = options['skills_descriptor']
+ post_message = options['post_message']
+
+ client = self._get_discovery_api_client()
+
+ course_ids_list = course_ids.split(',')
+ for course_run_id in course_ids_list:
+ course_key = CourseKey.from_string(course_run_id)
+
+ # discovery API requires course keys, not course run keys
+ course_id = f'{course_key.org}+{course_key.course}'
+
+ url = urljoin(
+ settings.DISCOVERY_BASE_URL,
+ 'api/v1/courses/{course_id}'.format(course_id=course_id)
+ )
+ response_data = client.get(url).json()
+ title = response_data['title']
+ skill_names = response_data['skill_names']
+
+ # create restructured dictionary with data
+ course_dict = {'title': title, 'topics': skill_names}
+
+ # append descriptor message and decode json dict into a string
+ skills_message = skills_descriptor + json.dumps(course_dict)
+
+ # finally, create list of prompt messages and save
+ prompt_messages = [pre_message, skills_message, post_message]
+ CoursePrompt.objects.update_or_create(
+ course_id=course_run_id, defaults={'json_prompt_content': prompt_messages}
+ )
+
+ logger.info('Updated course prompt for course_run_id=%s', course_run_id)
diff --git a/learning_assistant/management/commands/tests/__init__.py b/learning_assistant/management/commands/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/learning_assistant/management/commands/tests/test_set_course_prompts.py b/learning_assistant/management/commands/tests/test_set_course_prompts.py
new file mode 100644
index 0000000..29df5bf
--- /dev/null
+++ b/learning_assistant/management/commands/tests/test_set_course_prompts.py
@@ -0,0 +1,75 @@
+"""
+Tests for the set_course_prompts management command.
+"""
+import json
+from posixpath import join as urljoin
+from unittest.mock import MagicMock, patch
+
+from django.conf import settings
+from django.core.management import call_command
+from django.test import TestCase
+
+from learning_assistant.models import CoursePrompt
+
+
+class SetCoursePromptsTests(TestCase):
+ """Test set_course_prompts command"""
+ command = 'set_course_prompts'
+
+ def setUp(self):
+ self.pre_message = 'This is the first message'
+ self.skills_descriptor = 'These are the skills: '
+ self.post_message = 'This message comes after'
+ self.course_ids = 'course-v1:edx+test+23,course-v1:edx+test+24'
+ self.course_title = 'Intro to Testing'
+ self.skill_names = ['Testing', 'Computers', 'Coding']
+
+ def get_mock_discovery_response(self):
+ """
+ Create scaled down mock of discovery response
+ """
+ response_data = {
+ 'title': self.course_title,
+ 'skill_names': self.skill_names
+ }
+ return response_data
+
+ @patch('learning_assistant.management.commands.set_course_prompts.Command._get_discovery_api_client')
+ def test_course_prompts_created(self, mock_get_discovery_client):
+ """
+ Assert that course prompts are created by calling management command.
+ """
+ mock_client = MagicMock()
+ mock_get_discovery_client.return_value = mock_client
+ mock_client.get.return_value = MagicMock(
+ status_code=200,
+ json=lambda: self.get_mock_discovery_response() # pylint: disable=unnecessary-lambda
+ )
+
+ call_command(
+ self.command,
+ course_ids=self.course_ids,
+ pre_message=self.pre_message,
+ skills_descriptor=self.skills_descriptor,
+ post_message=self.post_message,
+ )
+
+ # assert that discovery api was called with course id, not course run id
+ expected_url = urljoin(
+ settings.DISCOVERY_BASE_URL,
+ 'api/v1/courses/{course_id}'.format(course_id='edx+test')
+ )
+ mock_client.get.assert_any_call(expected_url)
+ mock_client.get.assert_called()
+
+ # assert that number of prompts created is equivalent to number of courses passed in to command
+ prompts = CoursePrompt.objects.filter()
+ self.assertEqual(len(prompts), len(self.course_ids.split(',')))
+
+ # assert structure of prompt
+ course_prompt = prompts[0].json_prompt_content
+ self.assertEqual(len(course_prompt), 3)
+
+ skills_message = self.skills_descriptor + json.dumps({'title': self.course_title, 'topics': self.skill_names})
+ expected_response = [self.pre_message, skills_message, self.post_message]
+ self.assertEqual(course_prompt, expected_response)
diff --git a/requirements/base.in b/requirements/base.in
index b11b471..9f9bb7e 100644
--- a/requirements/base.in
+++ b/requirements/base.in
@@ -5,4 +5,5 @@ Django # Web application framework
django-model-utils
djangorestframework
edx-drf-extensions
+edx-rest-api-client
edx-opaque-keys
diff --git a/requirements/base.txt b/requirements/base.txt
index ebff97b..aa583aa 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -45,13 +45,17 @@ djangorestframework==3.14.0
drf-jwt==1.19.2
# via edx-drf-extensions
edx-django-utils==5.7.0
- # via edx-drf-extensions
+ # via
+ # edx-drf-extensions
+ # edx-rest-api-client
edx-drf-extensions==8.12.0
# via -r requirements/base.in
edx-opaque-keys==2.5.1
# via
# -r requirements/base.in
# edx-drf-extensions
+edx-rest-api-client==5.6.1
+ # via -r requirements/base.in
idna==3.4
# via requests
newrelic==9.1.0
@@ -66,6 +70,7 @@ pyjwt[crypto]==2.8.0
# via
# drf-jwt
# edx-drf-extensions
+ # edx-rest-api-client
# pyjwt
pymongo==3.13.0
# via edx-opaque-keys
@@ -76,9 +81,14 @@ pytz==2023.3.post1
# django
# djangorestframework
requests==2.31.0
- # via edx-drf-extensions
+ # via
+ # edx-drf-extensions
+ # edx-rest-api-client
+ # slumber
semantic-version==2.10.0
# via edx-drf-extensions
+slumber==0.7.1
+ # via edx-rest-api-client
sqlparse==0.4.4
# via django
stevedore==5.1.0
@@ -89,5 +99,5 @@ typing-extensions==4.8.0
# via
# asgiref
# edx-opaque-keys
-urllib3==2.0.6
+urllib3==2.0.7
# via requests
diff --git a/requirements/ci.txt b/requirements/ci.txt
index ee3ed60..7f64397 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -41,7 +41,7 @@ tox==3.28.0
# tox-battery
tox-battery==0.6.2
# via -r requirements/ci.in
-urllib3==2.0.6
+urllib3==2.0.7
# via requests
virtualenv==20.24.5
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 6643e02..b2cd968 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -112,6 +112,7 @@ edx-django-utils==5.7.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
+ # edx-rest-api-client
edx-drf-extensions==8.12.0
# via -r requirements/quality.txt
edx-i18n-tools==1.3.0
@@ -122,6 +123,8 @@ edx-opaque-keys==2.5.1
# via
# -r requirements/quality.txt
# edx-drf-extensions
+edx-rest-api-client==5.6.1
+ # via -r requirements/quality.txt
exceptiongroup==1.1.3
# via
# -r requirements/quality.txt
@@ -225,6 +228,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/quality.txt
# drf-jwt
# edx-drf-extensions
+ # edx-rest-api-client
# pyjwt
pylint==2.17.7
# via
@@ -288,7 +292,9 @@ requests==2.31.0
# -r requirements/quality.txt
# codecov
# edx-drf-extensions
+ # edx-rest-api-client
# responses
+ # slumber
responses==0.23.3
# via -r requirements/quality.txt
semantic-version==2.10.0
@@ -301,6 +307,10 @@ six==1.16.0
# -r requirements/quality.txt
# edx-lint
# tox
+slumber==0.7.1
+ # via
+ # -r requirements/quality.txt
+ # edx-rest-api-client
snowballstemmer==2.2.0
# via
# -r requirements/quality.txt
@@ -353,7 +363,7 @@ typing-extensions==4.8.0
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.0.6
+urllib3==2.0.7
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 16171e4..f7b3d20 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -43,7 +43,6 @@ cryptography==41.0.4
# via
# -r requirements/test.txt
# pyjwt
- # secretstorage
ddt==1.6.0
# via -r requirements/test.txt
django==3.2.22
@@ -89,12 +88,15 @@ edx-django-utils==5.7.0
# via
# -r requirements/test.txt
# edx-drf-extensions
+ # edx-rest-api-client
edx-drf-extensions==8.12.0
# via -r requirements/test.txt
edx-opaque-keys==2.5.1
# via
# -r requirements/test.txt
# edx-drf-extensions
+edx-rest-api-client==5.6.1
+ # via -r requirements/test.txt
exceptiongroup==1.1.3
# via
# -r requirements/test.txt
@@ -111,18 +113,12 @@ importlib-metadata==6.8.0
# keyring
# sphinx
# twine
-importlib-resources==6.1.0
- # via keyring
iniconfig==2.0.0
# via
# -r requirements/test.txt
# pytest
jaraco-classes==3.3.0
# via keyring
-jeepney==0.8.0
- # via
- # keyring
- # secretstorage
jinja2==3.1.2
# via
# -r requirements/test.txt
@@ -181,6 +177,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/test.txt
# drf-jwt
# edx-drf-extensions
+ # edx-rest-api-client
# pyjwt
pymongo==3.13.0
# via
@@ -208,7 +205,6 @@ python-slugify==8.0.1
pytz==2023.3.post1
# via
# -r requirements/test.txt
- # babel
# django
# djangorestframework
pyyaml==6.0.1
@@ -222,8 +218,10 @@ requests==2.31.0
# via
# -r requirements/test.txt
# edx-drf-extensions
+ # edx-rest-api-client
# requests-toolbelt
# responses
+ # slumber
# sphinx
# twine
requests-toolbelt==1.0.0
@@ -236,27 +234,35 @@ rfc3986==2.0.0
# via twine
rich==13.6.0
# via twine
-secretstorage==3.3.3
- # via keyring
semantic-version==2.10.0
# via
# -r requirements/test.txt
# edx-drf-extensions
+slumber==0.7.1
+ # via
+ # -r requirements/test.txt
+ # edx-rest-api-client
snowballstemmer==2.2.0
# via sphinx
-sphinx==7.1.2
- # via -r requirements/doc.in
-sphinxcontrib-applehelp==1.0.4
+sphinx==7.2.6
+ # via
+ # -r requirements/doc.in
+ # sphinxcontrib-applehelp
+ # sphinxcontrib-devhelp
+ # sphinxcontrib-htmlhelp
+ # sphinxcontrib-qthelp
+ # sphinxcontrib-serializinghtml
+sphinxcontrib-applehelp==1.0.7
# via sphinx
-sphinxcontrib-devhelp==1.0.2
+sphinxcontrib-devhelp==1.0.5
# via sphinx
-sphinxcontrib-htmlhelp==2.0.1
+sphinxcontrib-htmlhelp==2.0.4
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
-sphinxcontrib-qthelp==1.0.3
+sphinxcontrib-qthelp==1.0.6
# via sphinx
-sphinxcontrib-serializinghtml==1.1.5
+sphinxcontrib-serializinghtml==1.1.9
# via sphinx
sqlparse==0.4.4
# via
@@ -292,14 +298,11 @@ typing-extensions==4.8.0
# -r requirements/test.txt
# asgiref
# edx-opaque-keys
- # rich
-urllib3==2.0.6
+urllib3==2.0.7
# via
# -r requirements/test.txt
# requests
# responses
# twine
zipp==3.17.0
- # via
- # importlib-metadata
- # importlib-resources
+ # via importlib-metadata
diff --git a/requirements/quality.txt b/requirements/quality.txt
index adb0130..4c7294b 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -86,6 +86,7 @@ edx-django-utils==5.7.0
# via
# -r requirements/test.txt
# edx-drf-extensions
+ # edx-rest-api-client
edx-drf-extensions==8.12.0
# via -r requirements/test.txt
edx-lint==5.3.4
@@ -94,6 +95,8 @@ edx-opaque-keys==2.5.1
# via
# -r requirements/test.txt
# edx-drf-extensions
+edx-rest-api-client==5.6.1
+ # via -r requirements/test.txt
exceptiongroup==1.1.3
# via
# -r requirements/test.txt
@@ -157,6 +160,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/test.txt
# drf-jwt
# edx-drf-extensions
+ # edx-rest-api-client
# pyjwt
pylint==2.17.7
# via
@@ -207,7 +211,9 @@ requests==2.31.0
# via
# -r requirements/test.txt
# edx-drf-extensions
+ # edx-rest-api-client
# responses
+ # slumber
responses==0.23.3
# via -r requirements/test.txt
semantic-version==2.10.0
@@ -216,6 +222,10 @@ semantic-version==2.10.0
# edx-drf-extensions
six==1.16.0
# via edx-lint
+slumber==0.7.1
+ # via
+ # -r requirements/test.txt
+ # edx-rest-api-client
snowballstemmer==2.2.0
# via pydocstyle
sqlparse==0.4.4
@@ -251,7 +261,7 @@ typing-extensions==4.8.0
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.0.6
+urllib3==2.0.7
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/test.txt b/requirements/test.txt
index 43759f1..fa4b4aa 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -72,12 +72,15 @@ edx-django-utils==5.7.0
# via
# -r requirements/base.txt
# edx-drf-extensions
+ # edx-rest-api-client
edx-drf-extensions==8.12.0
# via -r requirements/base.txt
edx-opaque-keys==2.5.1
# via
# -r requirements/base.txt
# edx-drf-extensions
+edx-rest-api-client==5.6.1
+ # via -r requirements/base.txt
exceptiongroup==1.1.3
# via pytest
idna==3.4
@@ -115,6 +118,7 @@ pyjwt[crypto]==2.8.0
# -r requirements/base.txt
# drf-jwt
# edx-drf-extensions
+ # edx-rest-api-client
# pyjwt
pymongo==3.13.0
# via
@@ -147,13 +151,19 @@ requests==2.31.0
# via
# -r requirements/base.txt
# edx-drf-extensions
+ # edx-rest-api-client
# responses
+ # slumber
responses==0.23.3
# via -r requirements/test.in
semantic-version==2.10.0
# via
# -r requirements/base.txt
# edx-drf-extensions
+slumber==0.7.1
+ # via
+ # -r requirements/base.txt
+ # edx-rest-api-client
sqlparse==0.4.4
# via
# -r requirements/base.txt
@@ -177,7 +187,7 @@ typing-extensions==4.8.0
# -r requirements/base.txt
# asgiref
# edx-opaque-keys
-urllib3==2.0.6
+urllib3==2.0.7
# via
# -r requirements/base.txt
# requests
diff --git a/test_settings.py b/test_settings.py
index d3d5955..ea6e856 100644
--- a/test_settings.py
+++ b/test_settings.py
@@ -64,3 +64,8 @@ def root(*args):
CHAT_COMPLETION_API_KEY = 'endpoint_key'
CHAT_COMPLETION_API_CONNECT_TIMEOUT = 0.5
CHAT_COMPLETION_API_READ_TIMEOUT = 10
+
+DISCOVERY_BASE_URL = 'http://edx.devstack.discovery:18381'
+DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL = 'http://edx.devstack.lms:18000/oauth2'
+DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_KEY = 'discovery-backend-service-key'
+DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_SECRET = 'discovery-backend-service-secret'
From 252c161817d45af02d5215b1ea7593549c4bc1d9 Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 27 Oct 2023 16:30:04 -0400
Subject: [PATCH 009/104] chore: Updating Python Requirements (#31)
---
requirements/base.txt | 4 ++--
requirements/ci.txt | 4 ++--
requirements/dev.txt | 22 +++++++--------------
requirements/doc.txt | 41 +++++++++++++++++++++++-----------------
requirements/pip.txt | 2 +-
requirements/quality.txt | 16 ++++++----------
requirements/test.txt | 4 ++--
7 files changed, 44 insertions(+), 49 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index aa583aa..26cb1cc 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -12,7 +12,7 @@ cffi==1.16.0
# via
# cryptography
# pynacl
-charset-normalizer==3.3.0
+charset-normalizer==3.3.1
# via requests
click==8.1.7
# via edx-django-utils
@@ -58,7 +58,7 @@ edx-rest-api-client==5.6.1
# via -r requirements/base.in
idna==3.4
# via requests
-newrelic==9.1.0
+newrelic==9.1.1
# via edx-django-utils
pbr==5.11.1
# via stevedore
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 7f64397..82f4a88 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -6,7 +6,7 @@
#
certifi==2023.7.22
# via requests
-charset-normalizer==3.3.0
+charset-normalizer==3.3.1
# via requests
codecov==2.1.13
# via -r requirements/ci.in
@@ -43,5 +43,5 @@ tox-battery==0.6.2
# via -r requirements/ci.in
urllib3==2.0.7
# via requests
-virtualenv==20.24.5
+virtualenv==20.24.6
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index b2cd968..bb2bf1c 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -8,7 +8,7 @@ asgiref==3.7.2
# via
# -r requirements/quality.txt
# django
-astroid==2.15.8
+astroid==3.0.1
# via
# -r requirements/quality.txt
# pylint
@@ -29,7 +29,7 @@ cffi==1.16.0
# pynacl
chardet==5.2.0
# via diff-cover
-charset-normalizer==3.3.0
+charset-normalizer==3.3.1
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -117,7 +117,7 @@ edx-drf-extensions==8.12.0
# via -r requirements/quality.txt
edx-i18n-tools==1.3.0
# via -r requirements/dev.in
-edx-lint==5.3.4
+edx-lint==5.3.6
# via -r requirements/quality.txt
edx-opaque-keys==2.5.1
# via
@@ -156,10 +156,6 @@ jinja2==3.1.2
# -r requirements/quality.txt
# code-annotations
# diff-cover
-lazy-object-proxy==1.9.0
- # via
- # -r requirements/quality.txt
- # astroid
lxml==4.9.3
# via edx-i18n-tools
markupsafe==2.1.3
@@ -170,7 +166,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.1.0
+newrelic==9.1.1
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -230,7 +226,7 @@ pyjwt[crypto]==2.8.0
# edx-drf-extensions
# edx-rest-api-client
# pyjwt
-pylint==2.17.7
+pylint==3.0.2
# via
# -r requirements/quality.txt
# edx-lint
@@ -241,7 +237,7 @@ pylint-celery==0.3
# via
# -r requirements/quality.txt
# edx-lint
-pylint-django==2.5.3
+pylint-django==2.5.5
# via
# -r requirements/quality.txt
# edx-lint
@@ -369,7 +365,7 @@ urllib3==2.0.7
# -r requirements/quality.txt
# requests
# responses
-virtualenv==20.24.5
+virtualenv==20.24.6
# via
# -r requirements/ci.txt
# tox
@@ -377,10 +373,6 @@ wheel==0.41.2
# via
# -r requirements/pip-tools.txt
# pip-tools
-wrapt==1.15.0
- # via
- # -r requirements/quality.txt
- # astroid
zipp==3.17.0
# via
# -r requirements/pip-tools.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index f7b3d20..1469d83 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -10,7 +10,7 @@ asgiref==3.7.2
# via
# -r requirements/test.txt
# django
-babel==2.13.0
+babel==2.13.1
# via sphinx
build==1.0.3
# via -r requirements/doc.in
@@ -23,7 +23,7 @@ cffi==1.16.0
# -r requirements/test.txt
# cryptography
# pynacl
-charset-normalizer==3.3.0
+charset-normalizer==3.3.1
# via
# -r requirements/test.txt
# requests
@@ -43,6 +43,7 @@ cryptography==41.0.4
# via
# -r requirements/test.txt
# pyjwt
+ # secretstorage
ddt==1.6.0
# via -r requirements/test.txt
django==3.2.22
@@ -113,12 +114,18 @@ importlib-metadata==6.8.0
# keyring
# sphinx
# twine
+importlib-resources==6.1.0
+ # via keyring
iniconfig==2.0.0
# via
# -r requirements/test.txt
# pytest
jaraco-classes==3.3.0
# via keyring
+jeepney==0.8.0
+ # via
+ # keyring
+ # secretstorage
jinja2==3.1.2
# via
# -r requirements/test.txt
@@ -136,7 +143,7 @@ mdurl==0.1.2
# via markdown-it-py
more-itertools==10.1.0
# via jaraco-classes
-newrelic==9.1.0
+newrelic==9.1.1
# via
# -r requirements/test.txt
# edx-django-utils
@@ -205,6 +212,7 @@ python-slugify==8.0.1
pytz==2023.3.post1
# via
# -r requirements/test.txt
+ # babel
# django
# djangorestframework
pyyaml==6.0.1
@@ -234,6 +242,8 @@ rfc3986==2.0.0
# via twine
rich==13.6.0
# via twine
+secretstorage==3.3.3
+ # via keyring
semantic-version==2.10.0
# via
# -r requirements/test.txt
@@ -244,25 +254,19 @@ slumber==0.7.1
# edx-rest-api-client
snowballstemmer==2.2.0
# via sphinx
-sphinx==7.2.6
- # via
- # -r requirements/doc.in
- # sphinxcontrib-applehelp
- # sphinxcontrib-devhelp
- # sphinxcontrib-htmlhelp
- # sphinxcontrib-qthelp
- # sphinxcontrib-serializinghtml
-sphinxcontrib-applehelp==1.0.7
+sphinx==7.1.2
+ # via -r requirements/doc.in
+sphinxcontrib-applehelp==1.0.4
# via sphinx
-sphinxcontrib-devhelp==1.0.5
+sphinxcontrib-devhelp==1.0.2
# via sphinx
-sphinxcontrib-htmlhelp==2.0.4
+sphinxcontrib-htmlhelp==2.0.1
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
-sphinxcontrib-qthelp==1.0.6
+sphinxcontrib-qthelp==1.0.3
# via sphinx
-sphinxcontrib-serializinghtml==1.1.9
+sphinxcontrib-serializinghtml==1.1.5
# via sphinx
sqlparse==0.4.4
# via
@@ -298,6 +302,7 @@ typing-extensions==4.8.0
# -r requirements/test.txt
# asgiref
# edx-opaque-keys
+ # rich
urllib3==2.0.7
# via
# -r requirements/test.txt
@@ -305,4 +310,6 @@ urllib3==2.0.7
# responses
# twine
zipp==3.17.0
- # via importlib-metadata
+ # via
+ # importlib-metadata
+ # importlib-resources
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 2154d29..0c788d6 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -8,7 +8,7 @@ wheel==0.41.2
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
-pip==23.3
+pip==23.3.1
# via -r requirements/pip.in
setuptools==68.2.2
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 4c7294b..eba43ef 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -8,7 +8,7 @@ asgiref==3.7.2
# via
# -r requirements/test.txt
# django
-astroid==2.15.8
+astroid==3.0.1
# via
# pylint
# pylint-celery
@@ -21,7 +21,7 @@ cffi==1.16.0
# -r requirements/test.txt
# cryptography
# pynacl
-charset-normalizer==3.3.0
+charset-normalizer==3.3.1
# via
# -r requirements/test.txt
# requests
@@ -89,7 +89,7 @@ edx-django-utils==5.7.0
# edx-rest-api-client
edx-drf-extensions==8.12.0
# via -r requirements/test.txt
-edx-lint==5.3.4
+edx-lint==5.3.6
# via -r requirements/quality.in
edx-opaque-keys==2.5.1
# via
@@ -117,15 +117,13 @@ jinja2==3.1.2
# via
# -r requirements/test.txt
# code-annotations
-lazy-object-proxy==1.9.0
- # via astroid
markupsafe==2.1.3
# via
# -r requirements/test.txt
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.1.0
+newrelic==9.1.1
# via
# -r requirements/test.txt
# edx-django-utils
@@ -162,7 +160,7 @@ pyjwt[crypto]==2.8.0
# edx-drf-extensions
# edx-rest-api-client
# pyjwt
-pylint==2.17.7
+pylint==3.0.2
# via
# edx-lint
# pylint-celery
@@ -170,7 +168,7 @@ pylint==2.17.7
# pylint-plugin-utils
pylint-celery==0.3
# via edx-lint
-pylint-django==2.5.3
+pylint-django==2.5.5
# via edx-lint
pylint-plugin-utils==0.8.2
# via
@@ -266,5 +264,3 @@ urllib3==2.0.7
# -r requirements/test.txt
# requests
# responses
-wrapt==1.15.0
- # via astroid
diff --git a/requirements/test.txt b/requirements/test.txt
index fa4b4aa..b3ba796 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -17,7 +17,7 @@ cffi==1.16.0
# -r requirements/base.txt
# cryptography
# pynacl
-charset-normalizer==3.3.0
+charset-normalizer==3.3.1
# via
# -r requirements/base.txt
# requests
@@ -93,7 +93,7 @@ jinja2==3.1.2
# via code-annotations
markupsafe==2.1.3
# via jinja2
-newrelic==9.1.0
+newrelic==9.1.1
# via
# -r requirements/base.txt
# edx-django-utils
From 8c0aa65ec437a86d26ff5c936ec005f58f6fc434 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 31 Oct 2023 11:20:35 -0400
Subject: [PATCH 010/104] chore: Updating Python Requirements
---
requirements/base.txt | 2 +-
requirements/ci.txt | 2 +-
requirements/dev.txt | 10 +++++-----
requirements/doc.txt | 6 +++---
requirements/pip-tools.txt | 2 +-
requirements/pip.txt | 2 +-
requirements/quality.txt | 6 +++---
requirements/test.txt | 6 +++---
8 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 26cb1cc..fc600c7 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -16,7 +16,7 @@ charset-normalizer==3.3.1
# via requests
click==8.1.7
# via edx-django-utils
-cryptography==41.0.4
+cryptography==41.0.5
# via pyjwt
django==3.2.22
# via
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 82f4a88..d4e8cc7 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -14,7 +14,7 @@ coverage==7.3.2
# via codecov
distlib==0.3.7
# via virtualenv
-filelock==3.12.4
+filelock==3.13.1
# via
# tox
# virtualenv
diff --git a/requirements/dev.txt b/requirements/dev.txt
index bb2bf1c..98c61e5 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -60,7 +60,7 @@ coverage[toml]==7.3.2
# codecov
# coverage
# pytest-cov
-cryptography==41.0.4
+cryptography==41.0.5
# via
# -r requirements/quality.txt
# pyjwt
@@ -129,7 +129,7 @@ exceptiongroup==1.1.3
# via
# -r requirements/quality.txt
# pytest
-filelock==3.12.4
+filelock==3.13.1
# via
# -r requirements/ci.txt
# tox
@@ -258,14 +258,14 @@ pyproject-hooks==1.0.0
# via
# -r requirements/pip-tools.txt
# build
-pytest==7.4.2
+pytest==7.4.3
# via
# -r requirements/quality.txt
# pytest-cov
# pytest-django
pytest-cov==4.1.0
# via -r requirements/quality.txt
-pytest-django==4.5.2
+pytest-django==4.6.0
# via -r requirements/quality.txt
python-slugify==8.0.1
# via
@@ -369,7 +369,7 @@ virtualenv==20.24.6
# via
# -r requirements/ci.txt
# tox
-wheel==0.41.2
+wheel==0.41.3
# via
# -r requirements/pip-tools.txt
# pip-tools
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 1469d83..cd31cc1 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -39,7 +39,7 @@ coverage[toml]==7.3.2
# -r requirements/test.txt
# coverage
# pytest-cov
-cryptography==41.0.4
+cryptography==41.0.5
# via
# -r requirements/test.txt
# pyjwt
@@ -196,14 +196,14 @@ pynacl==1.5.0
# edx-django-utils
pyproject-hooks==1.0.0
# via build
-pytest==7.4.2
+pytest==7.4.3
# via
# -r requirements/test.txt
# pytest-cov
# pytest-django
pytest-cov==4.1.0
# via -r requirements/test.txt
-pytest-django==4.5.2
+pytest-django==4.6.0
# via -r requirements/test.txt
python-slugify==8.0.1
# via
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 50d35f2..ea34731 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -21,7 +21,7 @@ tomli==2.0.1
# build
# pip-tools
# pyproject-hooks
-wheel==0.41.2
+wheel==0.41.3
# via pip-tools
zipp==3.17.0
# via importlib-metadata
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 0c788d6..9014f2c 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -4,7 +4,7 @@
#
# make upgrade
#
-wheel==0.41.2
+wheel==0.41.3
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
diff --git a/requirements/quality.txt b/requirements/quality.txt
index eba43ef..671d6f8 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -43,7 +43,7 @@ coverage[toml]==7.3.2
# -r requirements/test.txt
# coverage
# pytest-cov
-cryptography==41.0.4
+cryptography==41.0.5
# via
# -r requirements/test.txt
# pyjwt
@@ -182,14 +182,14 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==7.4.2
+pytest==7.4.3
# via
# -r requirements/test.txt
# pytest-cov
# pytest-django
pytest-cov==4.1.0
# via -r requirements/test.txt
-pytest-django==4.5.2
+pytest-django==4.6.0
# via -r requirements/test.txt
python-slugify==8.0.1
# via
diff --git a/requirements/test.txt b/requirements/test.txt
index b3ba796..5b473a6 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -32,7 +32,7 @@ coverage[toml]==7.3.2
# via
# coverage
# pytest-cov
-cryptography==41.0.4
+cryptography==41.0.5
# via
# -r requirements/base.txt
# pyjwt
@@ -128,13 +128,13 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==7.4.2
+pytest==7.4.3
# via
# pytest-cov
# pytest-django
pytest-cov==4.1.0
# via -r requirements/test.in
-pytest-django==4.5.2
+pytest-django==4.6.0
# via -r requirements/test.in
python-slugify==8.0.1
# via code-annotations
From faed1ec892d1dcc1006e7830136f7833c3ae6890 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 7 Nov 2023 10:20:10 -0500
Subject: [PATCH 011/104] chore: Updating Python Requirements
---
requirements/base.txt | 8 ++++----
requirements/ci.txt | 2 +-
requirements/dev.txt | 16 ++++++----------
requirements/doc.txt | 16 ++++++----------
requirements/quality.txt | 16 ++++++----------
requirements/test.txt | 10 ++++------
6 files changed, 27 insertions(+), 41 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index fc600c7..e4d3397 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -12,13 +12,13 @@ cffi==1.16.0
# via
# cryptography
# pynacl
-charset-normalizer==3.3.1
+charset-normalizer==3.3.2
# via requests
click==8.1.7
# via edx-django-utils
cryptography==41.0.5
# via pyjwt
-django==3.2.22
+django==3.2.23
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/base.in
@@ -44,11 +44,11 @@ djangorestframework==3.14.0
# edx-drf-extensions
drf-jwt==1.19.2
# via edx-drf-extensions
-edx-django-utils==5.7.0
+edx-django-utils==5.8.0
# via
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==8.12.0
+edx-drf-extensions==8.13.0
# via -r requirements/base.in
edx-opaque-keys==2.5.1
# via
diff --git a/requirements/ci.txt b/requirements/ci.txt
index d4e8cc7..db5d954 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -6,7 +6,7 @@
#
certifi==2023.7.22
# via requests
-charset-normalizer==3.3.1
+charset-normalizer==3.3.2
# via requests
codecov==2.1.13
# via -r requirements/ci.in
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 98c61e5..ad577d4 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -29,7 +29,7 @@ cffi==1.16.0
# pynacl
chardet==5.2.0
# via diff-cover
-charset-normalizer==3.3.1
+charset-normalizer==3.3.2
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -76,7 +76,7 @@ distlib==0.3.7
# via
# -r requirements/ci.txt
# virtualenv
-django==3.2.22
+django==3.2.23
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/quality.txt
@@ -108,12 +108,12 @@ drf-jwt==1.19.2
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-django-utils==5.7.0
+edx-django-utils==5.8.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==8.12.0
+edx-drf-extensions==8.13.0
# via -r requirements/quality.txt
edx-i18n-tools==1.3.0
# via -r requirements/dev.in
@@ -291,7 +291,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.23.3
+responses==0.24.0
# via -r requirements/quality.txt
semantic-version==2.10.0
# via
@@ -337,7 +337,7 @@ tomli==2.0.1
# pyproject-hooks
# pytest
# tox
-tomlkit==0.12.1
+tomlkit==0.12.2
# via
# -r requirements/quality.txt
# pylint
@@ -348,10 +348,6 @@ tox==3.28.0
# tox-battery
tox-battery==0.6.2
# via -r requirements/ci.txt
-types-pyyaml==6.0.12.12
- # via
- # -r requirements/quality.txt
- # responses
typing-extensions==4.8.0
# via
# -r requirements/quality.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index cd31cc1..12d47f8 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -23,7 +23,7 @@ cffi==1.16.0
# -r requirements/test.txt
# cryptography
# pynacl
-charset-normalizer==3.3.1
+charset-normalizer==3.3.2
# via
# -r requirements/test.txt
# requests
@@ -46,7 +46,7 @@ cryptography==41.0.5
# secretstorage
ddt==1.6.0
# via -r requirements/test.txt
-django==3.2.22
+django==3.2.23
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -85,12 +85,12 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.7.0
+edx-django-utils==5.8.0
# via
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==8.12.0
+edx-drf-extensions==8.13.0
# via -r requirements/test.txt
edx-opaque-keys==2.5.1
# via
@@ -114,7 +114,7 @@ importlib-metadata==6.8.0
# keyring
# sphinx
# twine
-importlib-resources==6.1.0
+importlib-resources==6.1.1
# via keyring
iniconfig==2.0.0
# via
@@ -234,7 +234,7 @@ requests==2.31.0
# twine
requests-toolbelt==1.0.0
# via twine
-responses==0.23.3
+responses==0.24.0
# via -r requirements/test.txt
restructuredtext-lint==1.4.0
# via doc8
@@ -293,10 +293,6 @@ tomli==2.0.1
# pytest
twine==4.0.2
# via -r requirements/doc.in
-types-pyyaml==6.0.12.12
- # via
- # -r requirements/test.txt
- # responses
typing-extensions==4.8.0
# via
# -r requirements/test.txt
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 671d6f8..76f74e8 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -21,7 +21,7 @@ cffi==1.16.0
# -r requirements/test.txt
# cryptography
# pynacl
-charset-normalizer==3.3.1
+charset-normalizer==3.3.2
# via
# -r requirements/test.txt
# requests
@@ -51,7 +51,7 @@ ddt==1.6.0
# via -r requirements/test.txt
dill==0.3.7
# via pylint
-django==3.2.22
+django==3.2.23
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -82,12 +82,12 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.7.0
+edx-django-utils==5.8.0
# via
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==8.12.0
+edx-drf-extensions==8.13.0
# via -r requirements/test.txt
edx-lint==5.3.6
# via -r requirements/quality.in
@@ -212,7 +212,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.23.3
+responses==0.24.0
# via -r requirements/test.txt
semantic-version==2.10.0
# via
@@ -246,12 +246,8 @@ tomli==2.0.1
# coverage
# pylint
# pytest
-tomlkit==0.12.1
+tomlkit==0.12.2
# via pylint
-types-pyyaml==6.0.12.12
- # via
- # -r requirements/test.txt
- # responses
typing-extensions==4.8.0
# via
# -r requirements/test.txt
diff --git a/requirements/test.txt b/requirements/test.txt
index 5b473a6..7f7ea2e 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -17,7 +17,7 @@ cffi==1.16.0
# -r requirements/base.txt
# cryptography
# pynacl
-charset-normalizer==3.3.1
+charset-normalizer==3.3.2
# via
# -r requirements/base.txt
# requests
@@ -68,12 +68,12 @@ drf-jwt==1.19.2
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-django-utils==5.7.0
+edx-django-utils==5.8.0
# via
# -r requirements/base.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==8.12.0
+edx-drf-extensions==8.13.0
# via -r requirements/base.txt
edx-opaque-keys==2.5.1
# via
@@ -154,7 +154,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.23.3
+responses==0.24.0
# via -r requirements/test.in
semantic-version==2.10.0
# via
@@ -180,8 +180,6 @@ tomli==2.0.1
# via
# coverage
# pytest
-types-pyyaml==6.0.12.12
- # via responses
typing-extensions==4.8.0
# via
# -r requirements/base.txt
From 7f9e101cc4672f98e468ab125255a96d0ae44d4c Mon Sep 17 00:00:00 2001
From: UsamaSadiq
Date: Fri, 10 Nov 2023 18:05:47 +0500
Subject: [PATCH 012/104] fix: update tox config
---
tox.ini | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tox.ini b/tox.ini
index 4a4dac3..b1237de 100644
--- a/tox.ini
+++ b/tox.ini
@@ -49,7 +49,7 @@ setenv =
PYTHONPATH = {toxinidir}
# Adding the option here instead of as a default in the docs Makefile because that Makefile is generated by shpinx.
SPHINXOPTS = -W
-whitelist_externals =
+allowlist_externals =
make
rm
deps =
@@ -64,7 +64,7 @@ commands =
twine check dist/*
[testenv:quality]
-whitelist_externals =
+allowlist_externals =
make
rm
touch
From 273e62d5aa5779772d09418355a2d34e9794961e Mon Sep 17 00:00:00 2001
From: UsamaSadiq
Date: Fri, 10 Nov 2023 18:06:46 +0500
Subject: [PATCH 013/104] fix: remove tox-battery package
---
requirements/ci.in | 1 -
requirements/ci.txt | 3 ---
requirements/dev.txt | 7 ++++---
3 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/requirements/ci.in b/requirements/ci.in
index a99051b..27389ff 100644
--- a/requirements/ci.in
+++ b/requirements/ci.in
@@ -4,4 +4,3 @@
codecov # Code coverage reporting
tox # Virtualenv management for tests
-tox-battery # Makes tox aware of requirements file changes
diff --git a/requirements/ci.txt b/requirements/ci.txt
index db5d954..2cb117b 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -38,9 +38,6 @@ tox==3.28.0
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/ci.in
- # tox-battery
-tox-battery==0.6.2
- # via -r requirements/ci.in
urllib3==2.0.7
# via requests
virtualenv==20.24.6
diff --git a/requirements/dev.txt b/requirements/dev.txt
index ad577d4..d7a3371 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -345,9 +345,10 @@ tox==3.28.0
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/ci.txt
- # tox-battery
-tox-battery==0.6.2
- # via -r requirements/ci.txt
+types-pyyaml==6.0.12.12
+ # via
+ # -r requirements/quality.txt
+ # responses
typing-extensions==4.8.0
# via
# -r requirements/quality.txt
From 16a12e8bdd7852574637864c0eedbf14ee50b318 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 21 Nov 2023 10:20:49 -0500
Subject: [PATCH 014/104] chore: Updating Python Requirements
---
requirements/base.txt | 10 +++----
requirements/ci.txt | 31 ++++++++++++--------
requirements/dev.txt | 61 ++++++++++++++++++++++------------------
requirements/doc.txt | 24 ++++++++--------
requirements/pip.txt | 2 +-
requirements/quality.txt | 24 ++++++++--------
requirements/test.txt | 18 ++++++------
7 files changed, 94 insertions(+), 76 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index e4d3397..d5b64f8 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -6,7 +6,7 @@
#
asgiref==3.7.2
# via django
-certifi==2023.7.22
+certifi==2023.11.17
# via requests
cffi==1.16.0
# via
@@ -48,7 +48,7 @@ edx-django-utils==5.8.0
# via
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==8.13.0
+edx-drf-extensions==8.13.1
# via -r requirements/base.in
edx-opaque-keys==2.5.1
# via
@@ -58,9 +58,9 @@ edx-rest-api-client==5.6.1
# via -r requirements/base.in
idna==3.4
# via requests
-newrelic==9.1.1
+newrelic==9.2.0
# via edx-django-utils
-pbr==5.11.1
+pbr==6.0.0
# via stevedore
psutil==5.9.6
# via edx-django-utils
@@ -99,5 +99,5 @@ typing-extensions==4.8.0
# via
# asgiref
# edx-opaque-keys
-urllib3==2.0.7
+urllib3==2.1.0
# via requests
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 2cb117b..04b193f 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -4,12 +4,18 @@
#
# make upgrade
#
-certifi==2023.7.22
+cachetools==5.3.2
+ # via tox
+certifi==2023.11.17
# via requests
+chardet==5.2.0
+ # via tox
charset-normalizer==3.3.2
# via requests
codecov==2.1.13
# via -r requirements/ci.in
+colorama==0.4.6
+ # via tox
coverage==7.3.2
# via codecov
distlib==0.3.7
@@ -21,24 +27,27 @@ filelock==3.13.1
idna==3.4
# via requests
packaging==23.2
- # via tox
+ # via
+ # pyproject-api
+ # tox
platformdirs==3.11.0
- # via virtualenv
+ # via
+ # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
+ # tox
+ # virtualenv
pluggy==1.3.0
# via tox
-py==1.11.0
+pyproject-api==1.6.1
# via tox
requests==2.31.0
# via codecov
-six==1.16.0
- # via tox
tomli==2.0.1
- # via tox
-tox==3.28.0
# via
- # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
- # -r requirements/ci.in
-urllib3==2.0.7
+ # pyproject-api
+ # tox
+tox==4.11.3
+ # via -r requirements/ci.in
+urllib3==2.1.0
# via requests
virtualenv==20.24.6
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index d7a3371..9a5893c 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -17,7 +17,11 @@ build==1.0.3
# via
# -r requirements/pip-tools.txt
# pip-tools
-certifi==2023.7.22
+cachetools==5.3.2
+ # via
+ # -r requirements/ci.txt
+ # tox
+certifi==2023.11.17
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -28,7 +32,10 @@ cffi==1.16.0
# cryptography
# pynacl
chardet==5.2.0
- # via diff-cover
+ # via
+ # -r requirements/ci.txt
+ # diff-cover
+ # tox
charset-normalizer==3.3.2
# via
# -r requirements/ci.txt
@@ -53,6 +60,10 @@ code-annotations==1.5.0
# edx-lint
codecov==2.1.13
# via -r requirements/ci.txt
+colorama==0.4.6
+ # via
+ # -r requirements/ci.txt
+ # tox
coverage[toml]==7.3.2
# via
# -r requirements/ci.txt
@@ -64,9 +75,9 @@ cryptography==41.0.5
# via
# -r requirements/quality.txt
# pyjwt
-ddt==1.6.0
+ddt==1.7.0
# via -r requirements/quality.txt
-diff-cover==8.0.0
+diff-cover==8.0.1
# via -r requirements/dev.in
dill==0.3.7
# via
@@ -113,7 +124,7 @@ edx-django-utils==5.8.0
# -r requirements/quality.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==8.13.0
+edx-drf-extensions==8.13.1
# via -r requirements/quality.txt
edx-i18n-tools==1.3.0
# via -r requirements/dev.in
@@ -125,7 +136,7 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/quality.txt
-exceptiongroup==1.1.3
+exceptiongroup==1.2.0
# via
# -r requirements/quality.txt
# pytest
@@ -166,7 +177,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.1.1
+newrelic==9.2.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -176,11 +187,12 @@ packaging==23.2
# -r requirements/pip-tools.txt
# -r requirements/quality.txt
# build
+ # pyproject-api
# pytest
# tox
path==16.7.1
# via edx-i18n-tools
-pbr==5.11.1
+pbr==6.0.0
# via
# -r requirements/quality.txt
# stevedore
@@ -188,9 +200,11 @@ pip-tools==7.3.0
# via -r requirements/pip-tools.txt
platformdirs==3.11.0
# via
+ # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/ci.txt
# -r requirements/quality.txt
# pylint
+ # tox
# virtualenv
pluggy==1.3.0
# via
@@ -205,10 +219,6 @@ psutil==5.9.6
# via
# -r requirements/quality.txt
# edx-django-utils
-py==1.11.0
- # via
- # -r requirements/ci.txt
- # tox
pycodestyle==2.11.1
# via -r requirements/quality.txt
pycparser==2.21
@@ -217,7 +227,7 @@ pycparser==2.21
# cffi
pydocstyle==6.3.0
# via -r requirements/quality.txt
-pygments==2.16.1
+pygments==2.17.1
# via diff-cover
pyjwt[crypto]==2.8.0
# via
@@ -254,6 +264,10 @@ pynacl==1.5.0
# via
# -r requirements/quality.txt
# edx-django-utils
+pyproject-api==1.6.1
+ # via
+ # -r requirements/ci.txt
+ # tox
pyproject-hooks==1.0.0
# via
# -r requirements/pip-tools.txt
@@ -265,7 +279,7 @@ pytest==7.4.3
# pytest-django
pytest-cov==4.1.0
# via -r requirements/quality.txt
-pytest-django==4.6.0
+pytest-django==4.7.0
# via -r requirements/quality.txt
python-slugify==8.0.1
# via
@@ -291,7 +305,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.24.0
+responses==0.24.1
# via -r requirements/quality.txt
semantic-version==2.10.0
# via
@@ -299,10 +313,8 @@ semantic-version==2.10.0
# edx-drf-extensions
six==1.16.0
# via
- # -r requirements/ci.txt
# -r requirements/quality.txt
# edx-lint
- # tox
slumber==0.7.1
# via
# -r requirements/quality.txt
@@ -334,21 +346,16 @@ tomli==2.0.1
# coverage
# pip-tools
# pylint
+ # pyproject-api
# pyproject-hooks
# pytest
# tox
-tomlkit==0.12.2
+tomlkit==0.12.3
# via
# -r requirements/quality.txt
# pylint
-tox==3.28.0
- # via
- # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
- # -r requirements/ci.txt
-types-pyyaml==6.0.12.12
- # via
- # -r requirements/quality.txt
- # responses
+tox==4.11.3
+ # via -r requirements/ci.txt
typing-extensions==4.8.0
# via
# -r requirements/quality.txt
@@ -356,7 +363,7 @@ typing-extensions==4.8.0
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.0.7
+urllib3==2.1.0
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 12d47f8..4c8f896 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -14,7 +14,7 @@ babel==2.13.1
# via sphinx
build==1.0.3
# via -r requirements/doc.in
-certifi==2023.7.22
+certifi==2023.11.17
# via
# -r requirements/test.txt
# requests
@@ -44,7 +44,7 @@ cryptography==41.0.5
# -r requirements/test.txt
# pyjwt
# secretstorage
-ddt==1.6.0
+ddt==1.7.0
# via -r requirements/test.txt
django==3.2.23
# via
@@ -90,7 +90,7 @@ edx-django-utils==5.8.0
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==8.13.0
+edx-drf-extensions==8.13.1
# via -r requirements/test.txt
edx-opaque-keys==2.5.1
# via
@@ -98,7 +98,7 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/test.txt
-exceptiongroup==1.1.3
+exceptiongroup==1.2.0
# via
# -r requirements/test.txt
# pytest
@@ -131,7 +131,7 @@ jinja2==3.1.2
# -r requirements/test.txt
# code-annotations
# sphinx
-keyring==24.2.0
+keyring==24.3.0
# via twine
markdown-it-py==3.0.0
# via rich
@@ -143,7 +143,7 @@ mdurl==0.1.2
# via markdown-it-py
more-itertools==10.1.0
# via jaraco-classes
-newrelic==9.1.1
+newrelic==9.2.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -155,7 +155,7 @@ packaging==23.2
# build
# pytest
# sphinx
-pbr==5.11.1
+pbr==6.0.0
# via
# -r requirements/test.txt
# stevedore
@@ -173,7 +173,7 @@ pycparser==2.21
# via
# -r requirements/test.txt
# cffi
-pygments==2.16.1
+pygments==2.17.1
# via
# doc8
# readme-renderer
@@ -203,7 +203,7 @@ pytest==7.4.3
# pytest-django
pytest-cov==4.1.0
# via -r requirements/test.txt
-pytest-django==4.6.0
+pytest-django==4.7.0
# via -r requirements/test.txt
python-slugify==8.0.1
# via
@@ -234,13 +234,13 @@ requests==2.31.0
# twine
requests-toolbelt==1.0.0
# via twine
-responses==0.24.0
+responses==0.24.1
# via -r requirements/test.txt
restructuredtext-lint==1.4.0
# via doc8
rfc3986==2.0.0
# via twine
-rich==13.6.0
+rich==13.7.0
# via twine
secretstorage==3.3.3
# via keyring
@@ -299,7 +299,7 @@ typing-extensions==4.8.0
# asgiref
# edx-opaque-keys
# rich
-urllib3==2.0.7
+urllib3==2.1.0
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 9014f2c..9465bd4 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -10,5 +10,5 @@ wheel==0.41.3
# The following packages are considered to be unsafe in a requirements file:
pip==23.3.1
# via -r requirements/pip.in
-setuptools==68.2.2
+setuptools==69.0.1
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 76f74e8..e308ffc 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -12,7 +12,7 @@ astroid==3.0.1
# via
# pylint
# pylint-celery
-certifi==2023.7.22
+certifi==2023.11.17
# via
# -r requirements/test.txt
# requests
@@ -47,7 +47,7 @@ cryptography==41.0.5
# via
# -r requirements/test.txt
# pyjwt
-ddt==1.6.0
+ddt==1.7.0
# via -r requirements/test.txt
dill==0.3.7
# via pylint
@@ -87,7 +87,7 @@ edx-django-utils==5.8.0
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==8.13.0
+edx-drf-extensions==8.13.1
# via -r requirements/test.txt
edx-lint==5.3.6
# via -r requirements/quality.in
@@ -97,7 +97,7 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/test.txt
-exceptiongroup==1.1.3
+exceptiongroup==1.2.0
# via
# -r requirements/test.txt
# pytest
@@ -123,7 +123,7 @@ markupsafe==2.1.3
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.1.1
+newrelic==9.2.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -131,12 +131,14 @@ packaging==23.2
# via
# -r requirements/test.txt
# pytest
-pbr==5.11.1
+pbr==6.0.0
# via
# -r requirements/test.txt
# stevedore
platformdirs==3.11.0
- # via pylint
+ # via
+ # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
+ # pylint
pluggy==1.3.0
# via
# -r requirements/test.txt
@@ -189,7 +191,7 @@ pytest==7.4.3
# pytest-django
pytest-cov==4.1.0
# via -r requirements/test.txt
-pytest-django==4.6.0
+pytest-django==4.7.0
# via -r requirements/test.txt
python-slugify==8.0.1
# via
@@ -212,7 +214,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.24.0
+responses==0.24.1
# via -r requirements/test.txt
semantic-version==2.10.0
# via
@@ -246,7 +248,7 @@ tomli==2.0.1
# coverage
# pylint
# pytest
-tomlkit==0.12.2
+tomlkit==0.12.3
# via pylint
typing-extensions==4.8.0
# via
@@ -255,7 +257,7 @@ typing-extensions==4.8.0
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.0.7
+urllib3==2.1.0
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/test.txt b/requirements/test.txt
index 7f7ea2e..030e8e7 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -8,7 +8,7 @@ asgiref==3.7.2
# via
# -r requirements/base.txt
# django
-certifi==2023.7.22
+certifi==2023.11.17
# via
# -r requirements/base.txt
# requests
@@ -36,7 +36,7 @@ cryptography==41.0.5
# via
# -r requirements/base.txt
# pyjwt
-ddt==1.6.0
+ddt==1.7.0
# via -r requirements/test.in
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
@@ -73,7 +73,7 @@ edx-django-utils==5.8.0
# -r requirements/base.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==8.13.0
+edx-drf-extensions==8.13.1
# via -r requirements/base.txt
edx-opaque-keys==2.5.1
# via
@@ -81,7 +81,7 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/base.txt
-exceptiongroup==1.1.3
+exceptiongroup==1.2.0
# via pytest
idna==3.4
# via
@@ -93,13 +93,13 @@ jinja2==3.1.2
# via code-annotations
markupsafe==2.1.3
# via jinja2
-newrelic==9.1.1
+newrelic==9.2.0
# via
# -r requirements/base.txt
# edx-django-utils
packaging==23.2
# via pytest
-pbr==5.11.1
+pbr==6.0.0
# via
# -r requirements/base.txt
# stevedore
@@ -134,7 +134,7 @@ pytest==7.4.3
# pytest-django
pytest-cov==4.1.0
# via -r requirements/test.in
-pytest-django==4.6.0
+pytest-django==4.7.0
# via -r requirements/test.in
python-slugify==8.0.1
# via code-annotations
@@ -154,7 +154,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.24.0
+responses==0.24.1
# via -r requirements/test.in
semantic-version==2.10.0
# via
@@ -185,7 +185,7 @@ typing-extensions==4.8.0
# -r requirements/base.txt
# asgiref
# edx-opaque-keys
-urllib3==2.0.7
+urllib3==2.1.0
# via
# -r requirements/base.txt
# requests
From 285e0e287363b0d8a921c0b0d0aa5ecdb1f2a7fd Mon Sep 17 00:00:00 2001
From: Alie Langston
Date: Tue, 28 Nov 2023 10:19:15 -0500
Subject: [PATCH 015/104] feat: expose functionality to extract unit content
---
learning_assistant/api.py | 75 ++++++++++++
learning_assistant/constants.py | 6 +
learning_assistant/platform_imports.py | 47 ++++++++
learning_assistant/text_utils.py | 62 ++++++++++
tests/test_api.py | 155 ++++++++++++++++++++++++-
tests/test_text_utils.py | 57 +++++++++
6 files changed, 401 insertions(+), 1 deletion(-)
create mode 100644 learning_assistant/platform_imports.py
create mode 100644 learning_assistant/text_utils.py
create mode 100644 tests/test_text_utils.py
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index fbc9bf3..17bb9f4 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -1,7 +1,16 @@
"""
Library for the learning_assistant app.
"""
+from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, CATEGORY_TYPE_MAP
from learning_assistant.models import CoursePrompt
+from learning_assistant.platform_imports import (
+ block_get_children,
+ block_leaf_filter,
+ get_single_block,
+ get_text_transcript,
+ traverse_block_pre_order,
+)
+from learning_assistant.text_utils import html_to_text
def get_deserialized_prompt_content_by_course_id(course_id):
@@ -23,3 +32,69 @@ def get_setup_messages(course_id):
setup_messages = [{'role': 'system', 'content': x} for x in message_content]
return setup_messages
return None
+
+
+def _extract_block_contents(child, category):
+ """
+ Process the child contents based on its category.
+
+ Returns a string or None if there are no contents available.
+ """
+ if category == 'html':
+ content_html = child.get_html()
+ text = html_to_text(content_html)
+ return text
+
+ if category == 'video':
+ transcript = get_text_transcript(child) # may be None
+ return transcript
+
+ return None
+
+
+def _leaf_filter(block):
+ """
+ Return only leaf nodes of a particular category.
+ """
+ is_leaf = block_leaf_filter(block)
+ category = block.category
+
+ return is_leaf and category in ACCEPTED_CATEGORY_TYPES
+
+
+def _get_children_contents(block):
+ """
+ Given a specific block, return the content type and text of a pre-order traversal of the blocks children.
+ """
+ leaf_nodes = traverse_block_pre_order(block, block_get_children, _leaf_filter)
+
+ length = 0
+ items = []
+
+ for node in leaf_nodes:
+ category = node.category
+ content = _extract_block_contents(node, category)
+
+ if content:
+ length += len(content)
+ items.append({
+ 'content_type': CATEGORY_TYPE_MAP.get(category),
+ 'content_text': content,
+ })
+
+ return length, items
+
+
+def get_block_content(request, user_id, course_id, unit_usage_key):
+ """
+ Public wrapper for retrieving the content of a given block's children.
+
+ Returns
+ length - the cummulative length of a block's children's content
+ items - a list of dictionaries containing the content type and text for each child
+ """
+ block = get_single_block(request, user_id, course_id, unit_usage_key)
+
+ length, items = _get_children_contents(block)
+
+ return length, items
diff --git a/learning_assistant/constants.py b/learning_assistant/constants.py
index 580616c..06097ff 100644
--- a/learning_assistant/constants.py
+++ b/learning_assistant/constants.py
@@ -8,3 +8,9 @@
EXTERNAL_COURSE_KEY_PATTERN = r'([A-Za-z0-9-_:]+)'
COURSE_ID_PATTERN = rf'(?P({INTERNAL_COURSE_KEY_PATTERN}|{EXTERNAL_COURSE_KEY_PATTERN}))'
+
+ACCEPTED_CATEGORY_TYPES = ['html', 'video']
+CATEGORY_TYPE_MAP = {
+ "html": "TEXT",
+ "video": "VIDEO",
+}
diff --git a/learning_assistant/platform_imports.py b/learning_assistant/platform_imports.py
new file mode 100644
index 0000000..d29de31
--- /dev/null
+++ b/learning_assistant/platform_imports.py
@@ -0,0 +1,47 @@
+"""
+Contain all imported functions coming out of the platform.
+
+We know these functions will be available at run time, but they
+cannot be imported normally.
+"""
+
+
+def get_text_transcript(video_block):
+ """Get the transcript for a video block in text format, or None."""
+ # pylint: disable=import-error, import-outside-toplevel
+ from xmodule.exceptions import NotFoundError
+ from xmodule.video_block.transcripts_utils import get_transcript
+ try:
+ transcript, _, _ = get_transcript(video_block, output_format='txt')
+ except NotFoundError:
+ # some old videos have no transcripts, just accept that reality
+ return None
+ return transcript
+
+
+def get_single_block(request, user_id, course_id, usage_key_string, course=None):
+ """Load a single xblock."""
+ # pylint: disable=import-error, import-outside-toplevel
+ from lms.djangoapps.courseware.block_renderer import load_single_xblock
+ return load_single_xblock(request, user_id, course_id, usage_key_string, course)
+
+
+def traverse_block_pre_order(start_node, get_children, filter_func=None):
+ """Traverse a DAG or tree in pre-order."""
+ # pylint: disable=import-error, import-outside-toplevel
+ from openedx.core.lib.graph_traversals import traverse_pre_order
+ return traverse_pre_order(start_node, get_children, filter_func)
+
+
+def block_leaf_filter(block):
+ """Return only leaf nodes."""
+ # pylint: disable=import-error, import-outside-toplevel
+ from openedx.core.lib.graph_traversals import leaf_filter
+ return leaf_filter(block)
+
+
+def block_get_children(block):
+ """Return children of a given block."""
+ # pylint: disable=import-error, import-outside-toplevel
+ from openedx.core.lib.graph_traversals import get_children
+ return get_children(block)
diff --git a/learning_assistant/text_utils.py b/learning_assistant/text_utils.py
new file mode 100644
index 0000000..728fd14
--- /dev/null
+++ b/learning_assistant/text_utils.py
@@ -0,0 +1,62 @@
+"""
+Text manipulation utils. This has been copied from the ai-aside repository.
+"""
+
+from html.parser import HTMLParser
+from re import sub
+
+from django.conf import settings
+
+
+def cleanup_text(text):
+ """
+ Remove litter from replacing or manipulating text.
+ """
+ stripped = sub(r'[^\S\r\n]+', ' ', text) # Removing extra spaces
+ stripped = sub(r'\n{2,}', '\n', stripped) # Removing extra new lines
+ stripped = sub(r'(\s+)?\n(\s+)?', '\n', stripped) # Removing starting extra spacesbetween new lines
+ stripped = sub(r'(^(\s+)\n?)|(\n(\s+)?$)', '', stripped) # Trim
+
+ return stripped
+
+
+class _HTMLToTextHelper(HTMLParser): # lint-amnesty, pylint: disable=abstract-method
+ """
+ Helper function for html_to_text below.
+ """
+
+ _is_content = True
+
+ def __init__(self):
+ HTMLParser.__init__(self)
+ self.reset()
+ self.fed = []
+
+ def handle_starttag(self, tag, _):
+ """On each tag, check whether this is a tag we think is content."""
+ tags_to_filter = getattr(settings, 'HTML_TAGS_TO_REMOVE', None)
+ self._is_content = not (tags_to_filter and tag in tags_to_filter)
+
+ def handle_data(self, data):
+ """Handle tag data by appending text we think is content."""
+ if self._is_content:
+ self.fed.append(data)
+
+ def handle_entityref(self, name):
+ """If there is an entity, append the reference to the text."""
+ if self._is_content:
+ self.fed.append('&%s;' % name)
+
+ def get_data(self):
+ """Join together the separate data chunks into one cohesive string."""
+ return ''.join(self.fed)
+
+
+def html_to_text(html):
+ """Strip the html tags off of the text to return plaintext."""
+ htmlstripper = _HTMLToTextHelper()
+ htmlstripper.feed(html)
+ text = htmlstripper.get_data()
+ text = cleanup_text(text)
+
+ return text
diff --git a/tests/test_api.py b/tests/test_api.py
index 3316f98..bf198d7 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,11 +1,55 @@
"""
Test cases for the learning-assistant api module.
"""
+from unittest.mock import MagicMock, patch
+
+import ddt
from django.test import TestCase
+from opaque_keys.edx.keys import UsageKey
-from learning_assistant.api import get_deserialized_prompt_content_by_course_id, get_setup_messages
+from learning_assistant.api import (
+ _extract_block_contents,
+ _get_children_contents,
+ _leaf_filter,
+ get_block_content,
+ get_deserialized_prompt_content_by_course_id,
+ get_setup_messages,
+)
from learning_assistant.models import CoursePrompt
+fake_transcript = 'This is the text version from the transcript'
+
+
+class FakeChild:
+ """Fake child block for testing"""
+ transcript_download_format = 'txt'
+
+ def __init__(self, category, test_id='test-id', test_html='This is a test
'):
+ self.category = category
+ self.published_on = 'published-on-{}'.format(test_id)
+ self.edited_on = 'edited-on-{}'.format(test_id)
+ self.scope_ids = lambda: None
+ self.scope_ids.def_id = 'def-id-{}'.format(test_id)
+ self.html = test_html
+ self.transcript = fake_transcript
+
+ def get_html(self):
+ if self.category == 'html':
+ return self.html
+
+ return None
+
+
+class FakeBlock:
+ "Fake block for testing, returns given children"
+ def __init__(self, children):
+ self.children = children
+ self.scope_ids = lambda: None
+ self.scope_ids.usage_id = UsageKey.from_string('block-v1:edX+A+B+type@vertical+block@verticalD')
+
+ def get_children(self):
+ return self.children
+
class LearningAssistantAPITests(TestCase):
"""
@@ -38,3 +82,112 @@ def test_get_setup_messages(self):
def test_get_setup_messages_invalid_course_id(self):
setup_messages = get_setup_messages('course-v1:edx+fake+19')
self.assertIsNone(setup_messages)
+
+
+@ddt.ddt
+class GetBlockContentAPITests(TestCase):
+ """
+ Test suite for the get_block_content api function.
+ """
+
+ def setUp(self):
+ self.children = [
+ FakeChild('html', '01', '''
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Vivamus dapibus elit lacus, at vehicula arcu vehicula in.
+ In id felis arcu. Maecenas elit quam, volutpat cursus pharetra vel, tempor at lorem.
+ Fusce luctus orci quis tempor aliquet.
+
'''),
+ FakeChild('html', '02', '''
+
+ Nothing
+ '''),
+ FakeChild('video', '03'),
+ FakeChild('unknown', '04')
+ ]
+ self.block = FakeBlock(self.children)
+
+ self.course_id = 'course-v1:edx+test+23'
+
+ @ddt.data(
+ ('video', True),
+ ('html', True),
+ ('unknown', False)
+ )
+ @ddt.unpack
+ @patch('learning_assistant.api.block_leaf_filter')
+ def test_block_leaf_filter(self, category, expected_value, mock_leaf_filter):
+ mock_leaf_filter.return_value = True
+
+ block = FakeChild(category)
+
+ is_leaf = _leaf_filter(block)
+ self.assertEqual(is_leaf, expected_value)
+
+ @ddt.data(
+ 'video',
+ 'html',
+ 'unknown'
+ )
+ @patch('learning_assistant.api.html_to_text')
+ @patch('learning_assistant.api.get_text_transcript')
+ def test_extract_block_contents(self, category, mock_html, mock_transcript):
+ mock_return = 'This is the block content'
+ mock_html.return_value = mock_return
+ mock_transcript.return_value = mock_return
+
+ block = FakeChild(category)
+
+ block_content = _extract_block_contents(block, category)
+
+ if category in ['html', 'video']:
+ self.assertEqual(block_content, mock_return)
+ else:
+ self.assertIsNone(block_content)
+
+ @patch('learning_assistant.api.traverse_block_pre_order')
+ @patch('learning_assistant.api.html_to_text')
+ @patch('learning_assistant.api.get_text_transcript')
+ def test_get_children_contents(self, mock_transcript, mock_html, mock_traversal):
+ mock_traversal.return_value = self.children
+ block_content = 'This is the block content'
+ mock_html.return_value = block_content
+ mock_transcript.return_value = block_content
+
+ length, items = _get_children_contents(self.block)
+
+ expected_items = [
+ {'content_type': 'TEXT', 'content_text': block_content},
+ {'content_type': 'TEXT', 'content_text': block_content},
+ {'content_type': 'VIDEO', 'content_text': block_content}
+ ]
+
+ # expected length should be equivalent to the sum of the content length in each of the 3 child blocks
+ # that are either video or html
+ self.assertEqual(length, len(block_content) * 3)
+ self.assertEqual(len(items), 3)
+ self.assertEqual(items, expected_items)
+
+ @patch('learning_assistant.api.get_single_block')
+ @patch('learning_assistant.api._get_children_contents')
+ def test_get_block_content(self, mock_get_children_contents, mock_get_single_block):
+ mock_get_single_block.return_value = self.block
+
+ block_content = 'This is the block content'
+ content_items = [{'content_type': 'TEXT', 'content_text': block_content}]
+ mock_get_children_contents.return_value = (len(block_content), content_items)
+
+ # mock arguments that are passed through to `get_single_block` function. the value of these
+ # args does not matter for this test right now, as the `get_single_block` function is entirely mocked.
+ request = MagicMock()
+ user_id = 1
+ course_id = self.course_id
+ unit_usage_key = 'block-v1:edX+A+B+type@vertical+block@verticalD'
+
+ length, items = get_block_content(request, user_id, course_id, unit_usage_key)
+
+ mock_get_children_contents.assert_called_with(self.block)
+
+ self.assertEqual(length, len(block_content))
+ self.assertEqual(items, content_items)
diff --git a/tests/test_text_utils.py b/tests/test_text_utils.py
new file mode 100644
index 0000000..3fbb244
--- /dev/null
+++ b/tests/test_text_utils.py
@@ -0,0 +1,57 @@
+"""Tests for text utils used by the blocks"""
+import unittest
+from textwrap import dedent
+
+from learning_assistant.text_utils import html_to_text
+
+
+class TestSummaryHookAside(unittest.TestCase):
+ """Tests of text utils as used by the summary hook"""
+ def test_html_to_text(self):
+ html_content = '''\
+
+
Lorem Ipsum
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+
Sed volutpat velit sed dui fringilla fermentum.
+ Nullam quis velit at turpis lacinia convallis.
+ '''
+ expected_text = dedent('''\
+ Lorem Ipsum
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Sed volutpat velit sed dui fringilla fermentum.
+ Nullam quis velit at turpis lacinia convallis.''')
+ text = html_to_text(html_content)
+ self.assertEqual(text, expected_text)
+
+ def test_html_to_text_messy(self):
+ html_content = '''\
+
+ Lorem Ipsum
+ > Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ > Sed volutpat velit sed dui fringilla fermentum.42>
+ > Nullam quis velit at turpis lacinia convallis.
'''
+ expected_text = dedent('''\
+ Lorem Ipsum
+ > Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ > Sed volutpat velit sed dui fringilla fermentum.
+ > Nullam quis velit at turpis lacinia convallis.''')
+ text = html_to_text(html_content)
+ self.assertEqual(text, expected_text)
+
+ def test_html_to_text_iframe(self):
+ html_content = '''\
+
Date: Mon, 4 Dec 2023 08:31:18 -0500
Subject: [PATCH 017/104] feat: cache unit content
---
CHANGELOG.rst | 4 ++++
learning_assistant/api.py | 24 +++++++++++++++++++-----
learning_assistant/text_utils.py | 2 +-
test_settings.py | 6 ++++++
tests/test_api.py | 13 +++++++++++++
5 files changed, 43 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 9fdec11..097e50d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -11,6 +11,10 @@ Change Log
.. There should always be an "Unreleased" section for changes pending release.
+Unreleased
+**********
+* Add content cache
+
1.5.0 - 2023-10-18
******************
* Add management command to generate course prompts
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 17bb9f4..0a44a7f 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -1,6 +1,10 @@
"""
Library for the learning_assistant app.
"""
+from django.conf import settings
+from django.core.cache import cache
+from edx_django_utils.cache import get_cache_key
+
from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, CATEGORY_TYPE_MAP
from learning_assistant.models import CoursePrompt
from learning_assistant.platform_imports import (
@@ -93,8 +97,18 @@ def get_block_content(request, user_id, course_id, unit_usage_key):
length - the cummulative length of a block's children's content
items - a list of dictionaries containing the content type and text for each child
"""
- block = get_single_block(request, user_id, course_id, unit_usage_key)
-
- length, items = _get_children_contents(block)
-
- return length, items
+ cache_key = get_cache_key(
+ resource='learning_assistant',
+ user_id=user_id,
+ course_id=course_id,
+ unit_usage_key=unit_usage_key
+ )
+ cache_data = cache.get(cache_key)
+
+ if not isinstance(cache_data, dict):
+ block = get_single_block(request, user_id, course_id, unit_usage_key)
+ length, items = _get_children_contents(block)
+ cache_data = {'content_length': length, 'content_items': items}
+ cache.set(cache_key, cache_data, getattr(settings, 'LEARNING_ASSISTANT_CACHE_TIMEOUT', 360))
+
+ return cache_data['content_length'], cache_data['content_items']
diff --git a/learning_assistant/text_utils.py b/learning_assistant/text_utils.py
index 728fd14..b97872c 100644
--- a/learning_assistant/text_utils.py
+++ b/learning_assistant/text_utils.py
@@ -34,7 +34,7 @@ def __init__(self):
def handle_starttag(self, tag, _):
"""On each tag, check whether this is a tag we think is content."""
- tags_to_filter = getattr(settings, 'HTML_TAGS_TO_REMOVE', None)
+ tags_to_filter = getattr(settings, 'LEARNING_ASSISTANT_HTML_TAGS_TO_REMOVE', None)
self._is_content = not (tags_to_filter and tag in tags_to_filter)
def handle_data(self, data):
diff --git a/test_settings.py b/test_settings.py
index ea6e856..282b3db 100644
--- a/test_settings.py
+++ b/test_settings.py
@@ -69,3 +69,9 @@ def root(*args):
DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL = 'http://edx.devstack.lms:18000/oauth2'
DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_KEY = 'discovery-backend-service-key'
DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_SECRET = 'discovery-backend-service-secret'
+
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+ }
+}
diff --git a/tests/test_api.py b/tests/test_api.py
index bf198d7..ad145e8 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -4,6 +4,7 @@
from unittest.mock import MagicMock, patch
import ddt
+from django.core.cache import cache
from django.test import TestCase
from opaque_keys.edx.keys import UsageKey
@@ -91,6 +92,8 @@ class GetBlockContentAPITests(TestCase):
"""
def setUp(self):
+ cache.clear()
+
self.children = [
FakeChild('html', '01', '''
@@ -187,7 +190,17 @@ def test_get_block_content(self, mock_get_children_contents, mock_get_single_blo
length, items = get_block_content(request, user_id, course_id, unit_usage_key)
+ mock_get_children_contents.assert_called_once()
mock_get_children_contents.assert_called_with(self.block)
self.assertEqual(length, len(block_content))
self.assertEqual(items, content_items)
+
+ # call get_block_content again with same args to assert that cache is used
+ length, items = get_block_content(request, user_id, course_id, unit_usage_key)
+
+ # assert that the mock for _get_children_contents has not been called again,
+ # as subsequent calls should hit the cache
+ mock_get_children_contents.assert_called_once()
+ self.assertEqual(length, len(block_content))
+ self.assertEqual(items, content_items)
From b4f5f49da07ecc8603081882511214f01c74951f Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 12 Dec 2023 10:20:58 -0500
Subject: [PATCH 018/104] chore: Updating Python Requirements
---
requirements/base.txt | 8 ++++----
requirements/ci.txt | 7 +++----
requirements/dev.txt | 25 ++++++++++++-------------
requirements/doc.txt | 14 +++++++-------
requirements/pip-tools.txt | 2 +-
requirements/quality.txt | 20 +++++++++-----------
requirements/test.txt | 8 ++++----
7 files changed, 40 insertions(+), 44 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 437fba7..52daf80 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -33,7 +33,7 @@ django-crum==0.7.9
# via edx-django-utils
django-model-utils==4.3.1
# via -r requirements/base.in
-django-waffle==4.0.0
+django-waffle==4.1.0
# via
# edx-django-utils
# edx-drf-extensions
@@ -48,7 +48,7 @@ edx-django-utils==5.9.0
# via
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.0.0
+edx-drf-extensions==9.0.1
# via -r requirements/base.in
edx-opaque-keys==2.5.1
# via
@@ -58,7 +58,7 @@ edx-rest-api-client==5.6.1
# via -r requirements/base.in
idna==3.6
# via requests
-newrelic==9.2.0
+newrelic==9.3.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
@@ -95,7 +95,7 @@ stevedore==5.1.0
# via
# edx-django-utils
# edx-opaque-keys
-typing-extensions==4.8.0
+typing-extensions==4.9.0
# via
# asgiref
# edx-opaque-keys
diff --git a/requirements/ci.txt b/requirements/ci.txt
index fe4aa1f..49736d5 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -18,7 +18,7 @@ colorama==0.4.6
# via tox
coverage==7.3.2
# via codecov
-distlib==0.3.7
+distlib==0.3.8
# via virtualenv
filelock==3.13.1
# via
@@ -30,9 +30,8 @@ packaging==23.2
# via
# pyproject-api
# tox
-platformdirs==3.11.0
+platformdirs==4.1.0
# via
- # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# tox
# virtualenv
pluggy==1.3.0
@@ -49,5 +48,5 @@ tox==4.11.4
# via -r requirements/ci.in
urllib3==2.1.0
# via requests
-virtualenv==20.24.7
+virtualenv==20.25.0
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 202708a..2bf84ac 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -8,7 +8,7 @@ asgiref==3.7.2
# via
# -r requirements/quality.txt
# django
-astroid==3.0.1
+astroid==3.0.2
# via
# -r requirements/quality.txt
# pylint
@@ -83,7 +83,7 @@ dill==0.3.7
# via
# -r requirements/quality.txt
# pylint
-distlib==0.3.7
+distlib==0.3.8
# via
# -r requirements/ci.txt
# virtualenv
@@ -105,7 +105,7 @@ django-crum==0.7.9
# edx-django-utils
django-model-utils==4.3.1
# via -r requirements/quality.txt
-django-waffle==4.0.0
+django-waffle==4.1.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -124,7 +124,7 @@ edx-django-utils==5.9.0
# -r requirements/quality.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.0.0
+edx-drf-extensions==9.0.1
# via -r requirements/quality.txt
edx-i18n-tools==1.3.0
# via -r requirements/dev.in
@@ -150,7 +150,7 @@ idna==3.6
# -r requirements/ci.txt
# -r requirements/quality.txt
# requests
-importlib-metadata==6.8.0
+importlib-metadata==7.0.0
# via
# -r requirements/pip-tools.txt
# build
@@ -158,7 +158,7 @@ iniconfig==2.0.0
# via
# -r requirements/quality.txt
# pytest
-isort==5.12.0
+isort==5.13.1
# via
# -r requirements/quality.txt
# pylint
@@ -177,7 +177,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.2.0
+newrelic==9.3.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -190,7 +190,7 @@ packaging==23.2
# pyproject-api
# pytest
# tox
-path==16.7.1
+path==16.9.0
# via edx-i18n-tools
pbr==6.0.0
# via
@@ -198,9 +198,8 @@ pbr==6.0.0
# stevedore
pip-tools==7.3.0
# via -r requirements/pip-tools.txt
-platformdirs==3.11.0
+platformdirs==4.1.0
# via
- # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/ci.txt
# -r requirements/quality.txt
# pylint
@@ -236,7 +235,7 @@ pyjwt[crypto]==2.8.0
# edx-drf-extensions
# edx-rest-api-client
# pyjwt
-pylint==3.0.2
+pylint==3.0.3
# via
# -r requirements/quality.txt
# edx-lint
@@ -356,7 +355,7 @@ tomlkit==0.12.3
# pylint
tox==4.11.4
# via -r requirements/ci.txt
-typing-extensions==4.8.0
+typing-extensions==4.9.0
# via
# -r requirements/quality.txt
# asgiref
@@ -369,7 +368,7 @@ urllib3==2.1.0
# -r requirements/quality.txt
# requests
# responses
-virtualenv==20.24.7
+virtualenv==20.25.0
# via
# -r requirements/ci.txt
# tox
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 5d7f8a1..f1115fb 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -10,7 +10,7 @@ asgiref==3.7.2
# via
# -r requirements/test.txt
# django
-babel==2.13.1
+babel==2.14.0
# via sphinx
build==1.0.3
# via -r requirements/doc.in
@@ -63,7 +63,7 @@ django-crum==0.7.9
# edx-django-utils
django-model-utils==4.3.1
# via -r requirements/test.txt
-django-waffle==4.0.0
+django-waffle==4.1.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -90,7 +90,7 @@ edx-django-utils==5.9.0
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.0.0
+edx-drf-extensions==9.0.1
# via -r requirements/test.txt
edx-opaque-keys==2.5.1
# via
@@ -108,7 +108,7 @@ idna==3.6
# requests
imagesize==1.4.1
# via sphinx
-importlib-metadata==6.8.0
+importlib-metadata==7.0.0
# via
# build
# keyring
@@ -143,11 +143,11 @@ mdurl==0.1.2
# via markdown-it-py
more-itertools==10.1.0
# via jaraco-classes
-newrelic==9.2.0
+newrelic==9.3.0
# via
# -r requirements/test.txt
# edx-django-utils
-nh3==0.2.14
+nh3==0.2.15
# via readme-renderer
packaging==23.2
# via
@@ -293,7 +293,7 @@ tomli==2.0.1
# pytest
twine==4.0.2
# via -r requirements/doc.in
-typing-extensions==4.8.0
+typing-extensions==4.9.0
# via
# -r requirements/test.txt
# asgiref
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 41203fd..93a9cee 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -8,7 +8,7 @@ build==1.0.3
# via pip-tools
click==8.1.7
# via pip-tools
-importlib-metadata==6.8.0
+importlib-metadata==7.0.0
# via build
packaging==23.2
# via build
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 91ca32e..e22838c 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -8,7 +8,7 @@ asgiref==3.7.2
# via
# -r requirements/test.txt
# django
-astroid==3.0.1
+astroid==3.0.2
# via
# pylint
# pylint-celery
@@ -68,7 +68,7 @@ django-crum==0.7.9
# edx-django-utils
django-model-utils==4.3.1
# via -r requirements/test.txt
-django-waffle==4.0.0
+django-waffle==4.1.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -87,7 +87,7 @@ edx-django-utils==5.9.0
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.0.0
+edx-drf-extensions==9.0.1
# via -r requirements/test.txt
edx-lint==5.3.6
# via -r requirements/quality.in
@@ -109,7 +109,7 @@ iniconfig==2.0.0
# via
# -r requirements/test.txt
# pytest
-isort==5.12.0
+isort==5.13.1
# via
# -r requirements/quality.in
# pylint
@@ -123,7 +123,7 @@ markupsafe==2.1.3
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.2.0
+newrelic==9.3.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -135,10 +135,8 @@ pbr==6.0.0
# via
# -r requirements/test.txt
# stevedore
-platformdirs==3.11.0
- # via
- # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
- # pylint
+platformdirs==4.1.0
+ # via pylint
pluggy==1.3.0
# via
# -r requirements/test.txt
@@ -162,7 +160,7 @@ pyjwt[crypto]==2.8.0
# edx-drf-extensions
# edx-rest-api-client
# pyjwt
-pylint==3.0.2
+pylint==3.0.3
# via
# edx-lint
# pylint-celery
@@ -250,7 +248,7 @@ tomli==2.0.1
# pytest
tomlkit==0.12.3
# via pylint
-typing-extensions==4.8.0
+typing-extensions==4.9.0
# via
# -r requirements/test.txt
# asgiref
diff --git a/requirements/test.txt b/requirements/test.txt
index 56f20ad..083ef5f 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -54,7 +54,7 @@ django-crum==0.7.9
# edx-django-utils
django-model-utils==4.3.1
# via -r requirements/base.txt
-django-waffle==4.0.0
+django-waffle==4.1.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -73,7 +73,7 @@ edx-django-utils==5.9.0
# -r requirements/base.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.0.0
+edx-drf-extensions==9.0.1
# via -r requirements/base.txt
edx-opaque-keys==2.5.1
# via
@@ -93,7 +93,7 @@ jinja2==3.1.2
# via code-annotations
markupsafe==2.1.3
# via jinja2
-newrelic==9.2.0
+newrelic==9.3.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -180,7 +180,7 @@ tomli==2.0.1
# via
# coverage
# pytest
-typing-extensions==4.8.0
+typing-extensions==4.9.0
# via
# -r requirements/base.txt
# asgiref
From 5b11adbefd1fca5440c89f82d32c5549a4ba153a Mon Sep 17 00:00:00 2001
From: Michael Roytman
Date: Thu, 14 Dec 2023 09:41:31 -0500
Subject: [PATCH 019/104] docs: document system prompt design changes (#41)
This commit adds documentation for changes to the system prompt design. It includes an Architecture Decision Record (ADR) detailing our proposed changes to the way we store the system prompt as well as instructions for how to properly modify the system prompt with this new design.
---
.../0002-system-prompt-design-changes.rst | 96 +++++++++++++++++++
docs/modifying-system-prompt-template.rst | 48 ++++++++++
2 files changed, 144 insertions(+)
create mode 100644 docs/decisions/0002-system-prompt-design-changes.rst
create mode 100644 docs/modifying-system-prompt-template.rst
diff --git a/docs/decisions/0002-system-prompt-design-changes.rst b/docs/decisions/0002-system-prompt-design-changes.rst
new file mode 100644
index 0000000..0f59c55
--- /dev/null
+++ b/docs/decisions/0002-system-prompt-design-changes.rst
@@ -0,0 +1,96 @@
+2. System Prompt Design Changes
+###############################
+
+Status
+******
+
+**Accepted** *2023-12-13*
+
+Context
+*******
+
+A system prompt in the Learning Assistant context refers to the text-based instructions provided to the large language
+model (LLM) that describe the objective and proper behavior of the Learning Assistant. This prompt is provided to the
+LLM via 2U's Xpert Platform generic chat completion endpoint as the first set of elements in the ``message_list``
+payload key.
+
+Currently, the system prompt is stored in the `CoursePrompt model`_ on a per-course basis. For each course in which the
+Learning Assistant is enabled, a system prompt must be stored in the associated database table.
+
+The original intention behind storing the system prompt in this way was to enable an expedited release, a greater degree
+of per-course customization, and a flexibility to modify the system prompt quickly in the early stages of the project.
+
+The process of releasing the Learning Assistant to a new course involves either the manual creation of the model
+instance via the Django admin or the use of the `set_course_prompts management command`_. The latter requires that a
+member of 2U's Site Reliability Engineering (SRE) team runs this command in the proper environment.
+
+The next iteration of the Learning Assistant will enable the integration of unit content into the system prompt to
+provide the LLM with more information about the context in which the learner is asking a question. This will require a
+change to the system prompt to accommodate the unit content, and will, thus, require manual work on the part of an
+engineer to update the existing system prompts and to create new system prompts as we approach a full roll out. This
+presents an opportunity to reconsider the way that we store and process the system prompt.
+
+Decision
+********
+
+* We will store a system prompt template in a Django setting in the form of a Jinja template.
+* We will use Jinja constructs, such as variables and control structures, to implement a single system prompt template
+ for all courses.
+* The system prompt template will be rendered in two steps.
+
+ * The first render step will be performed by the `learning-assistant`_ code. This step will interpolate any variables
+ for which this code has a value (e.g. unit content).
+ * The second render step will be performed by the 2U Xpert Platform generic chat completion endpoint. This step will
+ interpolate any variable for which the platform has a value (e.g. course title and skill names).
+
+* After the first render step, the resulting value will be a Jinja template that has been partially rendered. This
+ template will be sent to 2U's Xpert Platform generic chat completion endpoint to be completely rendered.
+* We will remove the `CoursePrompt model`_ following the instructions documented in
+ `Everything About Database Migrations`_.
+
+Consequences
+************
+
+* Changes to the system prompt will require pull requests to the appropriate repository in which the prompt is stored.
+* Anyone with access to the appropriate repository in which the prompt is stored can change the system prompt. Members
+ of the team will no longer require assistance from the SRE team.
+* The transition to a system prompt template required the 2U Xpert Platform team to provide support for accepting and
+ rendering a system prompt template and the integration of Discovery ``skill_names`` into their index.
+* The use of a system prompt template will require cross-team collaboration to ensure that the same variable names are
+ use in the system prompt template, in the `learning-assistant`_ code, and the 2U Xpert Platform generic chat
+ completion endpoint.
+* Because the system prompt template will be stored in a Django setting in a private repository, and the code that
+ renders the template is stored here, changes to the template will require careful coordination to ensure that the
+ template is rendered properly.
+
+ * For example, if a new variable is added to the template, the code must be modified and deployed in advanced of
+ changes to the template. Otherwise, if changes to template are deployed before the related code changes, then the
+ rendered template will contain uninterpolated variables.
+
+* It will become more difficult to enable per-course customizations because all courses will be served by a single
+ system prompt template.
+
+Rejected Alternatives
+*********************
+
+* Status Quo
+
+ * The main alternative to this change is to continue to use manual entry and the `set_course_prompts management command`_
+ to manage system prompts.
+ * To enable unit content integration, we would need to modify the JSON string stored in the `CoursePrompt model`_ to
+ store a JSON string with format string variable for the content, which would be interpolated at runtime.
+ * The main advantage of this alternative is that it will require less engineering work.
+ * The main disadvantage of this alternative is that it will become challenging to manage which prompt a course should
+ use. For example, to do a stage released of unit content integration, we would be required to manage different
+ templates manually. Later, a full release would require additional changes. This would make management of the
+ templates even more tedious.
+ * Managing system prompts in a full release would become intractable.
+ * Additionally, in order to better operationalize the use of the management command and to reduce our reliance on the
+ SRE team, we would likely want to invest time in setting up a Jenkins job to run the management command ad-hoc. If
+ we will be investing engineering resources in this area anyway, we felt it was a more future-proof approach to
+ pursue the solution described above.
+
+.. _set_course_prompts management command: https://github.com/edx/learning-assistant/blob/main/learning_assistant/management/commands/set_course_prompts.py
+.. _CoursePrompt model: https://github.com/edx/learning-assistant/blob/34604a0775f7bd79adb465e0ca51c7759197bfa9/learning_assistant/models.py
+.. _Everything About Database Migrations: https://openedx.atlassian.net/wiki/spaces/AC/pages/23003228/Everything+About+Database+Migrations#EverythingAboutDatabaseMigrations-Howtodropatable
+.. _learning-assistant: https://github.com/edx/learning-assistant
\ No newline at end of file
diff --git a/docs/modifying-system-prompt-template.rst b/docs/modifying-system-prompt-template.rst
new file mode 100644
index 0000000..f95e43e
--- /dev/null
+++ b/docs/modifying-system-prompt-template.rst
@@ -0,0 +1,48 @@
+Modifying the System Prompt Template
+####################################
+
+Context
+*******
+
+Because the system prompt template will be stored in a Django setting in a private repository, and the code that
+renders the template is stored here, changes to the template will require careful coordination to ensure that the
+template is rendered properly.
+
+For example, if a new variable is added to the template, the code must be modified and deployed in advanced of
+changes to the template. Otherwise, if changes to template are deployed before the related code changes, then the
+rendered template will contain uninterpolated variables.
+
+This document describes how to properly modify the system prompt template and the code that renders it. These steps
+are only required when your change introduces a new dependency between the template and the code. For example, the
+introductin of a new variable introduces a new dependency, because the code must provide the value for this variable
+when the template is rendered for the variable to be interpolated properly. Additionally, renaming a variable or
+removing a variable would also require you to follow these steps. On the other hand, using an existing variable in a new
+way or changing static text in the template would not require you to follow these steps, because these changes would not
+require a related change to the code.
+
+Adding to the Template
+**********************
+
+If you are adding to the template, then you must follow these steps.
+
+#. Modify the code to supply the correct values to the function that renders the template.
+#. Merge and deploy the code changes.
+#. Modify the template.
+#. Merge and deploy the template changes.
+
+Removing From the Template
+**************************
+
+If you are removing from the template, then you must follow these steps.
+
+#. Modify the template.
+#. Merge and deploy the template changes.
+#. Modify the code to supply the correct values to the function that renders the template.
+#. Merge and deploy the code changes.
+
+Adding to and Removing From the Template
+****************************************
+
+Combination changes will require that the changes are divided into additions and removals. Divide your changes into
+additions and removals and follow the above steps for adding to the template and removing from the template,
+respectively.
From 23648dcfca968c4ed60adeb84491b0b1b0ddb9cb Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 5 Jan 2024 09:59:21 -0500
Subject: [PATCH 020/104] chore: Updating Python Requirements (#46)
---
requirements/base.txt | 2 +-
requirements/ci.txt | 2 +-
requirements/dev.txt | 14 +++++++-------
requirements/doc.txt | 8 ++++----
requirements/pip-tools.txt | 2 +-
requirements/pip.txt | 4 ++--
requirements/quality.txt | 8 ++++----
requirements/test.txt | 6 +++---
8 files changed, 23 insertions(+), 23 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 52daf80..a14fa9a 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -62,7 +62,7 @@ newrelic==9.3.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
-psutil==5.9.6
+psutil==5.9.7
# via edx-django-utils
pycparser==2.21
# via cffi
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 49736d5..533de31 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -16,7 +16,7 @@ codecov==2.1.13
# via -r requirements/ci.in
colorama==0.4.6
# via tox
-coverage==7.3.2
+coverage==7.4.0
# via codecov
distlib==0.3.8
# via virtualenv
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 2bf84ac..a977133 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -64,7 +64,7 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.3.2
+coverage[toml]==7.4.0
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -77,7 +77,7 @@ cryptography==41.0.7
# pyjwt
ddt==1.7.0
# via -r requirements/quality.txt
-diff-cover==8.0.1
+diff-cover==8.0.2
# via -r requirements/dev.in
dill==0.3.7
# via
@@ -150,7 +150,7 @@ idna==3.6
# -r requirements/ci.txt
# -r requirements/quality.txt
# requests
-importlib-metadata==7.0.0
+importlib-metadata==7.0.1
# via
# -r requirements/pip-tools.txt
# build
@@ -158,7 +158,7 @@ iniconfig==2.0.0
# via
# -r requirements/quality.txt
# pytest
-isort==5.13.1
+isort==5.13.2
# via
# -r requirements/quality.txt
# pylint
@@ -167,7 +167,7 @@ jinja2==3.1.2
# -r requirements/quality.txt
# code-annotations
# diff-cover
-lxml==4.9.3
+lxml==5.0.0
# via edx-i18n-tools
markupsafe==2.1.3
# via
@@ -214,7 +214,7 @@ pluggy==1.3.0
# tox
polib==1.2.0
# via edx-i18n-tools
-psutil==5.9.6
+psutil==5.9.7
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -271,7 +271,7 @@ pyproject-hooks==1.0.0
# via
# -r requirements/pip-tools.txt
# build
-pytest==7.4.3
+pytest==7.4.4
# via
# -r requirements/quality.txt
# pytest-cov
diff --git a/requirements/doc.txt b/requirements/doc.txt
index f1115fb..65d4147 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -34,7 +34,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.5.0
# via -r requirements/test.txt
-coverage[toml]==7.3.2
+coverage[toml]==7.4.0
# via
# -r requirements/test.txt
# coverage
@@ -108,7 +108,7 @@ idna==3.6
# requests
imagesize==1.4.1
# via sphinx
-importlib-metadata==7.0.0
+importlib-metadata==7.0.1
# via
# build
# keyring
@@ -165,7 +165,7 @@ pluggy==1.3.0
# via
# -r requirements/test.txt
# pytest
-psutil==5.9.6
+psutil==5.9.7
# via
# -r requirements/test.txt
# edx-django-utils
@@ -196,7 +196,7 @@ pynacl==1.5.0
# edx-django-utils
pyproject-hooks==1.0.0
# via build
-pytest==7.4.3
+pytest==7.4.4
# via
# -r requirements/test.txt
# pytest-cov
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 93a9cee..0e88226 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -8,7 +8,7 @@ build==1.0.3
# via pip-tools
click==8.1.7
# via pip-tools
-importlib-metadata==7.0.0
+importlib-metadata==7.0.1
# via build
packaging==23.2
# via build
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 14cb99c..a4cf530 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -8,7 +8,7 @@ wheel==0.42.0
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
-pip==23.3.1
+pip==23.3.2
# via -r requirements/pip.in
-setuptools==69.0.2
+setuptools==69.0.3
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index e22838c..672fc8e 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -38,7 +38,7 @@ code-annotations==1.5.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.3.2
+coverage[toml]==7.4.0
# via
# -r requirements/test.txt
# coverage
@@ -109,7 +109,7 @@ iniconfig==2.0.0
# via
# -r requirements/test.txt
# pytest
-isort==5.13.1
+isort==5.13.2
# via
# -r requirements/quality.in
# pylint
@@ -141,7 +141,7 @@ pluggy==1.3.0
# via
# -r requirements/test.txt
# pytest
-psutil==5.9.6
+psutil==5.9.7
# via
# -r requirements/test.txt
# edx-django-utils
@@ -182,7 +182,7 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==7.4.3
+pytest==7.4.4
# via
# -r requirements/test.txt
# pytest-cov
diff --git a/requirements/test.txt b/requirements/test.txt
index 083ef5f..bc8ad53 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -28,7 +28,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.5.0
# via -r requirements/test.in
-coverage[toml]==7.3.2
+coverage[toml]==7.4.0
# via
# coverage
# pytest-cov
@@ -105,7 +105,7 @@ pbr==6.0.0
# stevedore
pluggy==1.3.0
# via pytest
-psutil==5.9.6
+psutil==5.9.7
# via
# -r requirements/base.txt
# edx-django-utils
@@ -128,7 +128,7 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==7.4.3
+pytest==7.4.4
# via
# pytest-cov
# pytest-django
From 79c38b7213c768e94bde247e33767d126def1956 Mon Sep 17 00:00:00 2001
From: Michael Roytman
Date: Fri, 5 Jan 2024 11:26:02 -0500
Subject: [PATCH 021/104] feat: add LearningAssistantCourseEnabled model to
store course overrides (#48)
This commit adds a LearningAssistantCourseEnabled model to store overrides for whether the Learning Assistant feature is enabled. Although whether the feature is enabled will first be controlled by a CourseWaffleFlag, eventually, this model will allow course team members to selectively disable or re-enable the feature themselves.
---
.../0005_learningassistantcourseenabled.py | 29 +++++++++++++++++++
learning_assistant/models.py | 17 +++++++++++
2 files changed, 46 insertions(+)
create mode 100644 learning_assistant/migrations/0005_learningassistantcourseenabled.py
diff --git a/learning_assistant/migrations/0005_learningassistantcourseenabled.py b/learning_assistant/migrations/0005_learningassistantcourseenabled.py
new file mode 100644
index 0000000..c98d3bf
--- /dev/null
+++ b/learning_assistant/migrations/0005_learningassistantcourseenabled.py
@@ -0,0 +1,29 @@
+# Generated by Django 3.2.23 on 2024-01-04 15:02
+
+from django.db import migrations, models
+import django.utils.timezone
+import model_utils.fields
+import opaque_keys.edx.django.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('learning_assistant', '0004_remove_courseprompt_prompt'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='LearningAssistantCourseEnabled',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
+ ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
+ ('course_id', opaque_keys.edx.django.models.CourseKeyField(db_index=True, max_length=255, unique=True)),
+ ('enabled', models.BooleanField()),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ ]
diff --git a/learning_assistant/models.py b/learning_assistant/models.py
index f4313f0..f16e0a4 100644
--- a/learning_assistant/models.py
+++ b/learning_assistant/models.py
@@ -30,3 +30,20 @@ def get_json_prompt_content_by_course_id(cls, course_id):
except cls.DoesNotExist:
json_prompt_content = None
return json_prompt_content
+
+
+class LearningAssistantCourseEnabled(TimeStampedModel):
+ """
+ This model stores whether the Learning Assistant is enabled for a particular course ID.
+
+ For now, the purpose of this model is to store overrides added by course team members. By default, the Learning
+ Assistant will be enabled via a CourseWaffleFlag. This model will store whether course team members have manually
+ disabled the Learning Assistant.
+
+ .. no_pii: This model has no PII.
+ """
+
+ # course ID with for the course in which the Learning Assistant is enabled or disabled
+ course_id = CourseKeyField(max_length=255, db_index=True, unique=True)
+
+ enabled = models.BooleanField()
From 585979831dc0ef03a19a7902597e048943bc689f Mon Sep 17 00:00:00 2001
From: Michael Roytman
Date: Tue, 9 Jan 2024 09:49:01 -0500
Subject: [PATCH 022/104] docs: add ADR for using the edx-platform CourseApp
Django app (#49)
This commit adds an ADR to document our decision to use the edx-platform CourseApp Django app to register a Learning Assistant CourseApp plugin with the platform. The impact of this is that this feature will be exposed via the CourseApp REST API, and a card for this feature will appear on the Pages & Resources page.
---
docs/decisions/0003-courseapp-use.rst | 58 +++++++++++++++++++++++++++
1 file changed, 58 insertions(+)
create mode 100644 docs/decisions/0003-courseapp-use.rst
diff --git a/docs/decisions/0003-courseapp-use.rst b/docs/decisions/0003-courseapp-use.rst
new file mode 100644
index 0000000..b19b1a7
--- /dev/null
+++ b/docs/decisions/0003-courseapp-use.rst
@@ -0,0 +1,58 @@
+3. CourseApp Use
+################
+
+Status
+******
+
+**Accepted** *2024-01-08*
+
+Context
+*******
+
+We are adding the ability for course teams to manually enable and disable the Learning Assistant in their courses via
+a card in the `Pages & Resources page`_ of Studio, provided that the Learning Assistant is enabled and available in
+a given course.
+
+The `Pages & Resources page`_ is powered on the backend by the edX Platform `CourseApp Django app`_. With one
+exception in the case of Xpert Unit Summaries, each card on the `Pages & Resources page`_ has a corresponding instance
+of the `CourseApp plugin`_ configured in the `edx-platform repository`_. The `Pages & Resources page`_ can then make
+calls to the `backend CourseApp REST API`_ to get necessary information about the feature described by the plugin.
+
+The one exception to this behavior is the ``Xpert unit summaries card``, which uses a `Javascript object`_ stored in a
+static file in the `frontend-app-course-authoring repository`_ to describe the options that would otherwise be returned
+by the `backend CourseApp REST API`_ using a corresponding ``CourseApp`` plugin.
+
+Currently, the only ``CourseApp`` plugins registered in the platfrom are those that are defined within the
+`edx-platform repository`_. There are currently no plugins that are defined outside of the `edx-platform repository`_.
+
+Decision
+********
+
+* We will define an instance of the `CourseApp plugin`_ in this repository to describe the Learning Assistant feature.
+* We will register the `CourseApp plugin`_ as an entrypoint with the ``openedx.course_app`` key so that the `CourseApp
+ Django app`_ will be able to pick up the plugin if the Learning Assistant plugin is installed into ``edx-platform``.
+
+Consequences
+************
+
+* The Learning Assistant ``CourseApp`` plugin will only be registered with the `CourseApp Django app`_ if, and,
+ therefore, the Learning Assistant card on the `Pages & Resources page`_ will only be visible if, the Learning
+ Assistant plugin is installed into ``edx-platform``.
+* It will become slightly more difficult to know which ``CourseApps`` are available simply by reading the code, because
+ this ``CourseApp`` plugin is not stored in the `edx-platform repository`_.
+
+Rejected Alternatives
+*********************
+
+* We decided not to add in a custom backend REST API for exposing the ability to introspect the Learning Assistant
+ feature and enable and disable it. There already exists the `CourseApp Django app`_ for this purpose, and using it
+ allows us to avoid writing a lot of ad hoc code.
+
+.. _backend CourseApp REST API: https://github.com/openedx/edx-platform/blob/master/openedx/core/djangoapps/course_apps/rest_api/v1/views.py#L80
+.. _CourseApp Django app: https://github.com/openedx/edx-platform/tree/master/openedx/core/djangoapps/course_apps
+.. _CourseApp plugin: https://github.com/openedx/edx-platform/blob/master/openedx/core/djangoapps/course_apps/plugins.py#L15
+.. _edx-platform: https://github.com/openedx/edx-platform
+.. _edx-platform repository: https://github.com/openedx/edx-platform
+.. _frontend-app-course-authoring repository: https://github.com/openedx/frontend-app-course-authoring/tree/master
+.. _Javascript object: https://github.com/openedx/frontend-app-course-authoring/blob/master/src/pages-and-resources/xpert-unit-summary/appInfo.js
+.. _Pages & Resources page: https://github.com/openedx/frontend-app-course-authoring/tree/master/src/pages-and-resources
From 6fb879b23fe606a7ccef5eb62f4fa3b75e57dde0 Mon Sep 17 00:00:00 2001
From: alangsto <46360176+alangsto@users.noreply.github.com>
Date: Wed, 10 Jan 2024 13:52:02 -0500
Subject: [PATCH 023/104] feat: integrate system prompt (#47)
* feat: integrate system prompt
* feat: add course waffle flag for content integration
---
CHANGELOG.rst | 8 ++++
learning_assistant/__init__.py | 2 +-
learning_assistant/api.py | 23 ++++++++++
learning_assistant/platform_imports.py | 14 +++++-
learning_assistant/toggles.py | 34 ++++++++++++++
learning_assistant/utils.py | 53 +++++++++++++++++++---
learning_assistant/views.py | 25 +++++------
requirements/base.in | 1 +
requirements/base.txt | 8 ++--
requirements/ci.txt | 4 --
requirements/dev.txt | 24 ----------
requirements/doc.txt | 50 ++++++---------------
requirements/pip-tools.txt | 7 ---
requirements/quality.txt | 13 ------
requirements/test.txt | 15 +++----
test_settings.py | 9 ++++
tests/test_api.py | 28 ++++++++++++
tests/test_utils.py | 61 ++++++++++++++++----------
tests/test_views.py | 19 ++++----
19 files changed, 243 insertions(+), 155 deletions(-)
create mode 100644 learning_assistant/toggles.py
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 097e50d..5337a3d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -13,7 +13,15 @@ Change Log
Unreleased
**********
+
+2.0.1 - 2021-01-08
+******************
+* Gate content integration with waffle flag
+
+2.0.0 - 2024-01-03
+******************
* Add content cache
+* Integrate system prompt setting
1.5.0 - 2023-10-18
******************
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index d31819d..9af5cd8 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '1.5.0'
+__version__ = '2.0.1'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 0a44a7f..e5cfac5 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -1,9 +1,13 @@
"""
Library for the learning_assistant app.
"""
+import logging
+
from django.conf import settings
from django.core.cache import cache
from edx_django_utils.cache import get_cache_key
+from jinja2 import BaseLoader, Environment
+from opaque_keys.edx.keys import CourseKey
from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, CATEGORY_TYPE_MAP
from learning_assistant.models import CoursePrompt
@@ -15,6 +19,9 @@
traverse_block_pre_order,
)
from learning_assistant.text_utils import html_to_text
+from learning_assistant.toggles import course_content_enabled
+
+log = logging.getLogger(__name__)
def get_deserialized_prompt_content_by_course_id(course_id):
@@ -112,3 +119,19 @@ def get_block_content(request, user_id, course_id, unit_usage_key):
cache.set(cache_key, cache_data, getattr(settings, 'LEARNING_ASSISTANT_CACHE_TIMEOUT', 360))
return cache_data['content_length'], cache_data['content_items']
+
+
+def render_prompt_template(request, user_id, course_id, unit_usage_key):
+ """
+ Return a rendered prompt template, specified by the LEARNING_ASSISTANT_PROMPT_TEMPLATE setting.
+ """
+ unit_content = ''
+
+ course_run_key = CourseKey.from_string(course_id)
+ if unit_usage_key and course_content_enabled(course_run_key):
+ _, unit_content = get_block_content(request, user_id, course_id, unit_usage_key)
+
+ template_string = getattr(settings, 'LEARNING_ASSISTANT_PROMPT_TEMPLATE', '')
+ template = Environment(loader=BaseLoader).from_string(template_string)
+ data = template.render(unit_content=unit_content)
+ return data
diff --git a/learning_assistant/platform_imports.py b/learning_assistant/platform_imports.py
index d29de31..2641f52 100644
--- a/learning_assistant/platform_imports.py
+++ b/learning_assistant/platform_imports.py
@@ -22,7 +22,7 @@ def get_text_transcript(video_block):
def get_single_block(request, user_id, course_id, usage_key_string, course=None):
"""Load a single xblock."""
# pylint: disable=import-error, import-outside-toplevel
- from lms.djangoapps.courseware.block_renderer import load_single_xblock
+ from lms.djangoapps.courseware.block_render import load_single_xblock
return load_single_xblock(request, user_id, course_id, usage_key_string, course)
@@ -45,3 +45,15 @@ def block_get_children(block):
# pylint: disable=import-error, import-outside-toplevel
from openedx.core.lib.graph_traversals import get_children
return get_children(block)
+
+
+def get_cache_course_run_data(course_run_id, fields):
+ """
+ Return course run related data given a course run id.
+
+ This function makes use of the discovery course run cache, which is necessary because
+ only the discovery service stores the relation between courseruns and courses.
+ """
+ # pylint: disable=import-error, import-outside-toplevel
+ from openedx.core.djangoapps.catalog.utils import get_course_run_data
+ return get_course_run_data(course_run_id, fields)
diff --git a/learning_assistant/toggles.py b/learning_assistant/toggles.py
new file mode 100644
index 0000000..88d5ae6
--- /dev/null
+++ b/learning_assistant/toggles.py
@@ -0,0 +1,34 @@
+"""
+Toggles for learning-assistant app.
+"""
+
+WAFFLE_NAMESPACE = 'learning_assistant'
+
+# .. toggle_name: learning_assistant.enable_course_content
+# .. toggle_implementation: CourseWaffleFlag
+# .. toggle_default: False
+# .. toggle_description: Waffle flag to enable the course content integration with the learning assistant
+# .. toggle_use_cases: temporary
+# .. toggle_creation_date: 2024-01-08
+# .. toggle_target_removal_date: 2024-01-31
+# .. toggle_tickets: COSMO-80
+ENABLE_COURSE_CONTENT = 'enable_course_content'
+
+
+def _is_learning_assistant_waffle_flag_enabled(flag_name, course_key):
+ """
+ Import and return Waffle flag for enabling the summary hook.
+ """
+ # pylint: disable=import-outside-toplevel
+ try:
+ from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
+ return CourseWaffleFlag(f'{WAFFLE_NAMESPACE}.{flag_name}', __name__).is_enabled(course_key)
+ except ImportError:
+ return False
+
+
+def course_content_enabled(course_key):
+ """
+ Return whether the learning_assistant.enable_course_content WaffleFlag is on.
+ """
+ return _is_learning_assistant_waffle_flag_enabled(ENABLE_COURSE_CONTENT, course_key)
diff --git a/learning_assistant/utils.py b/learning_assistant/utils.py
index 110f449..0acc04c 100644
--- a/learning_assistant/utils.py
+++ b/learning_assistant/utils.py
@@ -1,6 +1,7 @@
"""
Utils file for learning-assistant.
"""
+import copy
import json
import logging
@@ -9,6 +10,8 @@
from requests.exceptions import ConnectTimeout
from rest_framework import status as http_status
+from learning_assistant.platform_imports import get_cache_course_run_data
+
log = logging.getLogger(__name__)
@@ -22,21 +25,31 @@ def _estimated_message_tokens(message):
return int((len(message) - message.count(' ')) / chars_per_token) + json_padding
-def get_reduced_message_list(system_list, message_list):
+def get_reduced_message_list(prompt_template, message_list):
"""
If messages are larger than allotted token amount, return a smaller list of messages.
"""
- total_system_tokens = sum(_estimated_message_tokens(system_message['content']) for system_message in system_list)
+ # the total number of system tokens is a sum of estimated tokens that includes the prompt template, the
+ # course title, and the course skills. It is necessary to include estimations for the course title and
+ # course skills, as the chat endpoint the prompt is being passed to is responsible for filling in the values
+ # for both of those variables.
+ total_system_tokens = (
+ _estimated_message_tokens(prompt_template)
+ + _estimated_message_tokens('.' * 40) # average number of characters per course name is 40
+ + _estimated_message_tokens('.' * 116) # average number of characters for skill names is 116
+ )
max_tokens = getattr(settings, 'CHAT_COMPLETION_MAX_TOKENS', 16385)
response_tokens = getattr(settings, 'CHAT_COMPLETION_RESPONSE_TOKENS', 1000)
remaining_tokens = max_tokens - response_tokens - total_system_tokens
new_message_list = []
+ # use copy of list, as it is modified as part of the reduction
+ message_list_copy = copy.deepcopy(message_list)
total_message_tokens = 0
- while total_message_tokens < remaining_tokens and len(message_list) != 0:
- new_message = message_list.pop()
+ while total_message_tokens < remaining_tokens and len(message_list_copy) != 0:
+ new_message = message_list_copy.pop()
total_message_tokens += _estimated_message_tokens(new_message['content'])
if total_message_tokens >= remaining_tokens:
break
@@ -47,7 +60,34 @@ def get_reduced_message_list(system_list, message_list):
return new_message_list
-def get_chat_response(system_list, message_list):
+def get_course_id(course_run_id):
+ """
+ Given a course run id (str), return the associated course key.
+ """
+ course_data = get_cache_course_run_data(course_run_id, ['course'])
+ course_key = course_data['course']
+ return course_key
+
+
+def create_request_body(prompt_template, message_list, courserun_id):
+ """
+ Form request body to be passed to the chat endpoint.
+ """
+ response_body = {
+ 'context': {
+ 'content': prompt_template,
+ 'render': {
+ 'doc_id': get_course_id(courserun_id),
+ 'fields': ['skillNames', 'title']
+ }
+ },
+ 'message_list': get_reduced_message_list(prompt_template, message_list)
+ }
+
+ return response_body
+
+
+def get_chat_response(prompt_template, message_list, courserun_id):
"""
Pass message list to chat endpoint, as defined by the CHAT_COMPLETION_API setting.
"""
@@ -58,8 +98,7 @@ def get_chat_response(system_list, message_list):
connect_timeout = getattr(settings, 'CHAT_COMPLETION_API_CONNECT_TIMEOUT', 1)
read_timeout = getattr(settings, 'CHAT_COMPLETION_API_READ_TIMEOUT', 15)
- reduced_messages = get_reduced_message_list(system_list, message_list)
- body = {'message_list': system_list + reduced_messages}
+ body = create_request_body(prompt_template, message_list, courserun_id)
try:
response = requests.post(
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 4749008..85fce4b 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -20,7 +20,7 @@
# If the waffle flag is false, the endpoint will force an early return.
learning_assistant_is_active = False
-from learning_assistant.api import get_setup_messages
+from learning_assistant.api import render_prompt_template
from learning_assistant.serializers import MessageSerializer
from learning_assistant.utils import get_chat_response
@@ -46,16 +46,16 @@ def post(self, request, course_id):
]
}
"""
- course_key = CourseKey.from_string(course_id)
- if not learning_assistant_is_active(course_key):
+ courserun_key = CourseKey.from_string(course_id)
+ if not learning_assistant_is_active(courserun_key):
return Response(
status=http_status.HTTP_403_FORBIDDEN,
data={'detail': 'Learning assistant not enabled for course.'}
)
# If user does not have an enrollment record, or is not staff, they should not have access
- user_role = get_user_role(request.user, course_key)
- enrollment_object = CourseEnrollment.get_enrollment(request.user, course_key)
+ user_role = get_user_role(request.user, courserun_key)
+ enrollment_object = CourseEnrollment.get_enrollment(request.user, courserun_key)
enrollment_mode = enrollment_object.mode if enrollment_object else None
if (
(enrollment_mode not in CourseMode.ALL_MODES)
@@ -66,12 +66,7 @@ def post(self, request, course_id):
data={'detail': 'Must be staff or have valid enrollment.'}
)
- prompt_messages = get_setup_messages(course_id)
- if not prompt_messages:
- return Response(
- status=http_status.HTTP_404_NOT_FOUND,
- data={'detail': 'Learning assistant not enabled for course.'}
- )
+ unit_id = request.query_params.get('unit_id')
message_list = request.data
serializer = MessageSerializer(data=message_list, many=True)
@@ -84,9 +79,6 @@ def post(self, request, course_id):
data={'detail': 'Invalid data', 'errors': serializer.errors}
)
- # append system message to beginning of message list
- message_setup = prompt_messages
-
log.info(
'Attempting to retrieve chat response for user_id=%(user_id)s in course_id=%(course_id)s',
{
@@ -94,6 +86,9 @@ def post(self, request, course_id):
'course_id': course_id
}
)
- status_code, message = get_chat_response(message_setup, message_list)
+
+ prompt_template = render_prompt_template(request, request.user.id, course_id, unit_id)
+
+ status_code, message = get_chat_response(prompt_template, message_list, course_id)
return Response(status=status_code, data=message)
diff --git a/requirements/base.in b/requirements/base.in
index 9f9bb7e..52fe316 100644
--- a/requirements/base.in
+++ b/requirements/base.in
@@ -7,3 +7,4 @@ djangorestframework
edx-drf-extensions
edx-rest-api-client
edx-opaque-keys
+jinja2
diff --git a/requirements/base.txt b/requirements/base.txt
index a14fa9a..a6f3404 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -58,6 +58,10 @@ edx-rest-api-client==5.6.1
# via -r requirements/base.in
idna==3.6
# via requests
+jinja2==3.1.2
+ # via -r requirements/base.in
+markupsafe==2.1.3
+ # via jinja2
newrelic==9.3.0
# via edx-django-utils
pbr==6.0.0
@@ -96,8 +100,6 @@ stevedore==5.1.0
# edx-django-utils
# edx-opaque-keys
typing-extensions==4.9.0
- # via
- # asgiref
- # edx-opaque-keys
+ # via edx-opaque-keys
urllib3==2.1.0
# via requests
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 533de31..b9ff286 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -40,10 +40,6 @@ pyproject-api==1.6.1
# via tox
requests==2.31.0
# via codecov
-tomli==2.0.1
- # via
- # pyproject-api
- # tox
tox==4.11.4
# via -r requirements/ci.in
urllib3==2.1.0
diff --git a/requirements/dev.txt b/requirements/dev.txt
index a977133..b108338 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -136,10 +136,6 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/quality.txt
-exceptiongroup==1.2.0
- # via
- # -r requirements/quality.txt
- # pytest
filelock==3.13.1
# via
# -r requirements/ci.txt
@@ -336,19 +332,6 @@ text-unidecode==1.3
# via
# -r requirements/quality.txt
# python-slugify
-tomli==2.0.1
- # via
- # -r requirements/ci.txt
- # -r requirements/pip-tools.txt
- # -r requirements/quality.txt
- # build
- # coverage
- # pip-tools
- # pylint
- # pyproject-api
- # pyproject-hooks
- # pytest
- # tox
tomlkit==0.12.3
# via
# -r requirements/quality.txt
@@ -358,10 +341,7 @@ tox==4.11.4
typing-extensions==4.9.0
# via
# -r requirements/quality.txt
- # asgiref
- # astroid
# edx-opaque-keys
- # pylint
urllib3==2.1.0
# via
# -r requirements/ci.txt
@@ -376,10 +356,6 @@ wheel==0.42.0
# via
# -r requirements/pip-tools.txt
# pip-tools
-zipp==3.17.0
- # via
- # -r requirements/pip-tools.txt
- # importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# pip
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 65d4147..7cd0158 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -43,7 +43,6 @@ cryptography==41.0.7
# via
# -r requirements/test.txt
# pyjwt
- # secretstorage
ddt==1.7.0
# via -r requirements/test.txt
django==3.2.23
@@ -98,10 +97,6 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/test.txt
-exceptiongroup==1.2.0
- # via
- # -r requirements/test.txt
- # pytest
idna==3.6
# via
# -r requirements/test.txt
@@ -110,22 +105,14 @@ imagesize==1.4.1
# via sphinx
importlib-metadata==7.0.1
# via
- # build
# keyring
- # sphinx
# twine
-importlib-resources==6.1.1
- # via keyring
iniconfig==2.0.0
# via
# -r requirements/test.txt
# pytest
jaraco-classes==3.3.0
# via keyring
-jeepney==0.8.0
- # via
- # keyring
- # secretstorage
jinja2==3.1.2
# via
# -r requirements/test.txt
@@ -212,7 +199,6 @@ python-slugify==8.0.1
pytz==2023.3.post1
# via
# -r requirements/test.txt
- # babel
# django
# djangorestframework
pyyaml==6.0.1
@@ -242,8 +228,6 @@ rfc3986==2.0.0
# via twine
rich==13.7.0
# via twine
-secretstorage==3.3.3
- # via keyring
semantic-version==2.10.0
# via
# -r requirements/test.txt
@@ -254,19 +238,25 @@ slumber==0.7.1
# edx-rest-api-client
snowballstemmer==2.2.0
# via sphinx
-sphinx==7.1.2
- # via -r requirements/doc.in
-sphinxcontrib-applehelp==1.0.4
+sphinx==7.2.6
+ # via
+ # -r requirements/doc.in
+ # sphinxcontrib-applehelp
+ # sphinxcontrib-devhelp
+ # sphinxcontrib-htmlhelp
+ # sphinxcontrib-qthelp
+ # sphinxcontrib-serializinghtml
+sphinxcontrib-applehelp==1.0.7
# via sphinx
-sphinxcontrib-devhelp==1.0.2
+sphinxcontrib-devhelp==1.0.5
# via sphinx
-sphinxcontrib-htmlhelp==2.0.1
+sphinxcontrib-htmlhelp==2.0.4
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
-sphinxcontrib-qthelp==1.0.3
+sphinxcontrib-qthelp==1.0.6
# via sphinx
-sphinxcontrib-serializinghtml==1.1.5
+sphinxcontrib-serializinghtml==1.1.9
# via sphinx
sqlparse==0.4.4
# via
@@ -283,22 +273,12 @@ text-unidecode==1.3
# via
# -r requirements/test.txt
# python-slugify
-tomli==2.0.1
- # via
- # -r requirements/test.txt
- # build
- # coverage
- # doc8
- # pyproject-hooks
- # pytest
twine==4.0.2
# via -r requirements/doc.in
typing-extensions==4.9.0
# via
# -r requirements/test.txt
- # asgiref
# edx-opaque-keys
- # rich
urllib3==2.1.0
# via
# -r requirements/test.txt
@@ -306,6 +286,4 @@ urllib3==2.1.0
# responses
# twine
zipp==3.17.0
- # via
- # importlib-metadata
- # importlib-resources
+ # via importlib-metadata
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 0e88226..194fea6 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -16,15 +16,8 @@ pip-tools==7.3.0
# via -r requirements/pip-tools.in
pyproject-hooks==1.0.0
# via build
-tomli==2.0.1
- # via
- # build
- # pip-tools
- # pyproject-hooks
wheel==0.42.0
# via pip-tools
-zipp==3.17.0
- # via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# pip
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 672fc8e..88bb0ac 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -97,10 +97,6 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/test.txt
-exceptiongroup==1.2.0
- # via
- # -r requirements/test.txt
- # pytest
idna==3.6
# via
# -r requirements/test.txt
@@ -240,21 +236,12 @@ text-unidecode==1.3
# via
# -r requirements/test.txt
# python-slugify
-tomli==2.0.1
- # via
- # -r requirements/test.txt
- # coverage
- # pylint
- # pytest
tomlkit==0.12.3
# via pylint
typing-extensions==4.9.0
# via
# -r requirements/test.txt
- # asgiref
- # astroid
# edx-opaque-keys
- # pylint
urllib3==2.1.0
# via
# -r requirements/test.txt
diff --git a/requirements/test.txt b/requirements/test.txt
index bc8ad53..71bbe65 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -81,8 +81,6 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/base.txt
-exceptiongroup==1.2.0
- # via pytest
idna==3.6
# via
# -r requirements/base.txt
@@ -90,9 +88,13 @@ idna==3.6
iniconfig==2.0.0
# via pytest
jinja2==3.1.2
- # via code-annotations
+ # via
+ # -r requirements/base.txt
+ # code-annotations
markupsafe==2.1.3
- # via jinja2
+ # via
+ # -r requirements/base.txt
+ # jinja2
newrelic==9.3.0
# via
# -r requirements/base.txt
@@ -176,14 +178,9 @@ stevedore==5.1.0
# edx-opaque-keys
text-unidecode==1.3
# via python-slugify
-tomli==2.0.1
- # via
- # coverage
- # pytest
typing-extensions==4.9.0
# via
# -r requirements/base.txt
- # asgiref
# edx-opaque-keys
urllib3==2.1.0
# via
diff --git a/test_settings.py b/test_settings.py
index 282b3db..f201fd2 100644
--- a/test_settings.py
+++ b/test_settings.py
@@ -75,3 +75,12 @@ def root(*args):
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}
+
+LEARNING_ASSISTANT_PROMPT_TEMPLATE = (
+ "This is a prompt. {% if unit_content %}"
+ "The following text is useful."
+ "\""
+ "{{ unit_content }}"
+ "\""
+ "{% endif %}"
+)
diff --git a/tests/test_api.py b/tests/test_api.py
index ad145e8..51397b0 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -15,6 +15,7 @@
get_block_content,
get_deserialized_prompt_content_by_course_id,
get_setup_messages,
+ render_prompt_template,
)
from learning_assistant.models import CoursePrompt
@@ -204,3 +205,30 @@ def test_get_block_content(self, mock_get_children_contents, mock_get_single_blo
mock_get_children_contents.assert_called_once()
self.assertEqual(length, len(block_content))
self.assertEqual(items, content_items)
+
+ @ddt.data(
+ ('This is content.', True),
+ ('', True),
+ ('This is content.', False),
+ ('', False),
+ )
+ @ddt.unpack
+ @patch('learning_assistant.toggles._is_learning_assistant_waffle_flag_enabled')
+ @patch('learning_assistant.api.get_block_content')
+ def test_render_prompt_template(self, unit_content, flag_enabled, mock_get_content, mock_is_flag_enabled):
+ mock_get_content.return_value = (len(unit_content), unit_content)
+ mock_is_flag_enabled.return_value = flag_enabled
+
+ # mock arguments that are passed through to `get_block_content` function. the value of these
+ # args does not matter for this test right now, as the `get_block_content` function is entirely mocked.
+ request = MagicMock()
+ user_id = 1
+ course_id = self.course_id
+ unit_usage_key = 'block-v1:edX+A+B+type@vertical+block@verticalD'
+
+ prompt_text = render_prompt_template(request, user_id, course_id, unit_usage_key)
+
+ if unit_content and flag_enabled:
+ self.assertIn(unit_content, prompt_text)
+ else:
+ self.assertNotIn('The following text is useful.', prompt_text)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 27afa8e..4adcf76 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,7 +1,6 @@
"""
Tests for the utils functions
"""
-import copy
import json
from unittest.mock import MagicMock, patch
@@ -21,24 +20,31 @@ class GetChatResponseTests(TestCase):
"""
def setUp(self):
super().setUp()
- self.system_message = [
- {'role': 'system', 'content': 'Do this'},
- {'role': 'system', 'content': 'Do that'},
- ]
- self.message_list = [
- {'role': 'assistant', 'content': 'Hello'},
- {'role': 'user', 'content': 'Goodbye'},
- ]
+
+ self.prompt_template = 'This is a prompt.'
+
+ self.message_list = [{'role': 'assistant', 'content': 'Hello'}, {'role': 'user', 'content': 'Goodbye'}]
+ self.course_id = 'course-v1:edx+test+23'
+ self.course_key = 'edx-test'
+
+ self.patcher = patch(
+ 'learning_assistant.utils.get_cache_course_run_data',
+ return_value={'course': self.course_key}
+ )
+ self.patcher.start()
+
+ def get_response(self):
+ return get_chat_response(self.prompt_template, self.message_list, self.course_id)
@override_settings(CHAT_COMPLETION_API=None)
def test_no_endpoint_setting(self):
- status_code, message = get_chat_response(self.system_message, self.message_list)
+ status_code, message = self.get_response()
self.assertEqual(status_code, 404)
self.assertEqual(message, 'Completion endpoint is not defined.')
@override_settings(CHAT_COMPLETION_API_KEY=None)
def test_no_endpoint_key_setting(self):
- status_code, message = get_chat_response(self.system_message, self.message_list)
+ status_code, message = self.get_response()
self.assertEqual(status_code, 404)
self.assertEqual(message, 'Completion endpoint is not defined.')
@@ -52,7 +58,7 @@ def test_200_response(self):
body=json.dumps(message_response),
)
- status_code, message = get_chat_response(self.system_message, self.message_list)
+ status_code, message = self.get_response()
self.assertEqual(status_code, 200)
self.assertEqual(message, message_response)
@@ -66,7 +72,7 @@ def test_non_200_response(self):
body=json.dumps(message_response),
)
- status_code, message = get_chat_response(self.system_message, self.message_list)
+ status_code, message = self.get_response()
self.assertEqual(status_code, 500)
self.assertEqual(message, message_response)
@@ -77,7 +83,7 @@ def test_non_200_response(self):
@patch('learning_assistant.utils.requests')
def test_timeout(self, exception, mock_requests):
mock_requests.post = MagicMock(side_effect=exception())
- status_code, _ = get_chat_response(self.system_message, self.message_list)
+ status_code, _ = self.get_response()
self.assertEqual(status_code, 502)
@patch('learning_assistant.utils.requests')
@@ -88,13 +94,23 @@ def test_post_request_structure(self, mock_requests):
connect_timeout = settings.CHAT_COMPLETION_API_CONNECT_TIMEOUT
read_timeout = settings.CHAT_COMPLETION_API_READ_TIMEOUT
headers = {'Content-Type': 'application/json', 'x-api-key': settings.CHAT_COMPLETION_API_KEY}
- body = json.dumps({'message_list': self.system_message + self.message_list})
- get_chat_response(self.system_message, self.message_list)
+ response_body = {
+ 'context': {
+ 'content': self.prompt_template,
+ 'render': {
+ 'doc_id': self.course_key,
+ 'fields': ['skillNames', 'title']
+ }
+ },
+ 'message_list': self.message_list
+ }
+
+ self.get_response()
mock_requests.post.assert_called_with(
completion_endpoint,
headers=headers,
- data=body,
+ data=json.dumps(response_body),
timeout=(connect_timeout, read_timeout)
)
@@ -105,27 +121,24 @@ class GetReducedMessageListTests(TestCase):
"""
def setUp(self):
super().setUp()
- self.system_message = [
- {'role': 'system', 'content': 'Do this'},
- {'role': 'system', 'content': 'Do that'},
- ]
+ self.prompt_template = 'This is a prompt.'
self.message_list = [
{'role': 'assistant', 'content': 'Hello'},
{'role': 'user', 'content': 'Goodbye'},
]
- @override_settings(CHAT_COMPLETION_MAX_TOKENS=30)
+ @override_settings(CHAT_COMPLETION_MAX_TOKENS=90)
@override_settings(CHAT_COMPLETION_RESPONSE_TOKENS=1)
def test_message_list_reduced(self):
"""
If the number of tokens in the message list is greater than allowed, assert that messages are removed
"""
# pass in copy of list, as it is modified as part of the reduction
- reduced_message_list = get_reduced_message_list(self.system_message, copy.deepcopy(self.message_list))
+ reduced_message_list = get_reduced_message_list(self.prompt_template, self.message_list)
self.assertEqual(len(reduced_message_list), 1)
self.assertEqual(reduced_message_list, self.message_list[-1:])
def test_message_list(self):
- reduced_message_list = get_reduced_message_list(self.system_message, copy.deepcopy(self.message_list))
+ reduced_message_list = get_reduced_message_list(self.prompt_template, self.message_list)
self.assertEqual(len(reduced_message_list), 2)
self.assertEqual(reduced_message_list, self.message_list)
diff --git a/tests/test_views.py b/tests/test_views.py
index a8d08db..129f3b6 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -102,15 +102,6 @@ def test_user_no_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_rol
response = self.client.post(reverse('chat', kwargs={'course_id': self.course_id}))
self.assertEqual(response.status_code, 403)
- @patch('learning_assistant.views.learning_assistant_is_active')
- @patch('learning_assistant.views.get_user_role')
- def test_no_prompt(self, mock_role, mock_waffle):
- mock_waffle.return_value = True
- mock_role.return_value = 'staff'
-
- response = self.client.post(reverse('chat', kwargs={'course_id': self.course_id}))
- self.assertEqual(response.status_code, 404)
-
@patch('learning_assistant.views.learning_assistant_is_active')
@patch('learning_assistant.views.get_user_role')
def test_invalid_messages(self, mock_role, mock_waffle):
@@ -134,17 +125,20 @@ def test_invalid_messages(self, mock_role, mock_waffle):
)
self.assertEqual(response.status_code, 400)
+ @patch('learning_assistant.views.render_prompt_template')
@patch('learning_assistant.views.get_chat_response')
@patch('learning_assistant.views.learning_assistant_is_active')
@patch('learning_assistant.views.get_user_role')
@patch('learning_assistant.views.CourseEnrollment.get_enrollment')
@patch('learning_assistant.views.CourseMode')
- def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle, mock_chat_response):
+ def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle, mock_chat_response, mock_render):
mock_waffle.return_value = True
mock_role.return_value = 'student'
mock_mode.ALL_MODES = ['verified']
mock_enrollment.return_value = MagicMock(mode='verified')
mock_chat_response.return_value = (200, {'role': 'assistant', 'content': 'Something else'})
+ mock_render.return_value = 'This is a template'
+ test_unit_id = 'test-unit-id'
CoursePrompt.objects.create(
course_id=self.course_id,
@@ -157,8 +151,11 @@ def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle,
]
response = self.client.post(
- reverse('chat', kwargs={'course_id': self.course_id}),
+ reverse('chat', kwargs={'course_id': self.course_id})+f'?unit_id={test_unit_id}',
data=json.dumps(test_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 200)
+
+ render_args = mock_render.call_args.args
+ self.assertIn(test_unit_id, render_args)
From b3310aa51c3079c0ee16291fdf3b853e1faab64c Mon Sep 17 00:00:00 2001
From: Michael Roytman
Date: Tue, 16 Jan 2024 11:13:23 -0500
Subject: [PATCH 024/104] feat: add admin model form for
LearningAssistantCourseEnabled model (#56)
---
learning_assistant/admin.py | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/learning_assistant/admin.py b/learning_assistant/admin.py
index a513a90..bb44a59 100644
--- a/learning_assistant/admin.py
+++ b/learning_assistant/admin.py
@@ -3,7 +3,7 @@
"""
from django.contrib import admin
-from learning_assistant.models import CoursePrompt
+from learning_assistant.models import CoursePrompt, LearningAssistantCourseEnabled
@admin.register(CoursePrompt)
@@ -13,3 +13,10 @@ class CoursePromptAdmin(admin.ModelAdmin):
"""
list_display = ('id', 'course_id')
+
+
+@admin.register(LearningAssistantCourseEnabled)
+class LearningAssistantCourseEnabledAdmin(admin.ModelAdmin):
+ """
+ Admin panel for the LearningAssistantCourseEnabled model.
+ """
From 9292fee9a0ee15c504890e6a722c6ad3289ba87f Mon Sep 17 00:00:00 2001
From: Michael Roytman
Date: Fri, 19 Jan 2024 09:44:23 -0500
Subject: [PATCH 025/104] feat: remove references to the CoursePrompt model in
preparation for its removal (#53)
This commit removes references to the CoursePrompt model. Because we no longer have plans to store system prompts in a model, we plan to remove this model and drop the associated table. In preparation for this step, we need to remove references to the model.
---
learning_assistant/admin.py | 11 +-
learning_assistant/api.py | 22 ----
.../management/commands/set_course_prompts.py | 108 ------------------
.../commands/tests/test_set_course_prompts.py | 75 ------------
tests/test_api.py | 36 ------
5 files changed, 1 insertion(+), 251 deletions(-)
delete mode 100644 learning_assistant/management/commands/set_course_prompts.py
delete mode 100644 learning_assistant/management/commands/tests/test_set_course_prompts.py
diff --git a/learning_assistant/admin.py b/learning_assistant/admin.py
index bb44a59..207d98d 100644
--- a/learning_assistant/admin.py
+++ b/learning_assistant/admin.py
@@ -3,16 +3,7 @@
"""
from django.contrib import admin
-from learning_assistant.models import CoursePrompt, LearningAssistantCourseEnabled
-
-
-@admin.register(CoursePrompt)
-class CoursePromptAdmin(admin.ModelAdmin):
- """
- Admin panel for course prompts.
- """
-
- list_display = ('id', 'course_id')
+from learning_assistant.models import LearningAssistantCourseEnabled
@admin.register(LearningAssistantCourseEnabled)
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index e5cfac5..73bcee1 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -10,7 +10,6 @@
from opaque_keys.edx.keys import CourseKey
from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, CATEGORY_TYPE_MAP
-from learning_assistant.models import CoursePrompt
from learning_assistant.platform_imports import (
block_get_children,
block_leaf_filter,
@@ -24,27 +23,6 @@
log = logging.getLogger(__name__)
-def get_deserialized_prompt_content_by_course_id(course_id):
- """
- Return a deserialized prompt given a course_id.
- """
- json_prompt = CoursePrompt.get_json_prompt_content_by_course_id(course_id)
- if json_prompt:
- return json_prompt
- return None
-
-
-def get_setup_messages(course_id):
- """
- Return a list of setup messages given a course id.
- """
- message_content = get_deserialized_prompt_content_by_course_id(course_id)
- if message_content:
- setup_messages = [{'role': 'system', 'content': x} for x in message_content]
- return setup_messages
- return None
-
-
def _extract_block_contents(child, category):
"""
Process the child contents based on its category.
diff --git a/learning_assistant/management/commands/set_course_prompts.py b/learning_assistant/management/commands/set_course_prompts.py
deleted file mode 100644
index 284366f..0000000
--- a/learning_assistant/management/commands/set_course_prompts.py
+++ /dev/null
@@ -1,108 +0,0 @@
-"""
-Django management command to generate course prompts.
-"""
-import json
-import logging
-from posixpath import join as urljoin
-
-from django.conf import settings
-from django.core.management.base import BaseCommand
-from edx_rest_api_client.client import OAuthAPIClient
-from opaque_keys.edx.keys import CourseKey
-
-from learning_assistant.models import CoursePrompt
-
-logger = logging.getLogger(__name__)
-
-
-class Command(BaseCommand):
- """
- Django Management command to create a set of course prompts
- """
-
- def add_arguments(self, parser):
-
- # list of course ids
- parser.add_argument(
- '--course_ids',
- dest='course_ids',
- help='Comma separated list of course_ids to generate. Only newer style course ids can be supplied.',
- )
-
- # pre-message
- parser.add_argument(
- '--pre_message',
- dest='pre_message',
- help='Message to prepend to course topics',
- )
-
- parser.add_argument(
- '--skills_descriptor',
- dest='skills_descriptor',
- help='Message that describes skill structure'
- )
-
- # post-message
- parser.add_argument(
- '--post_message',
- dest='post_message',
- help='Message to append to course topics',
- )
-
- @staticmethod
- def _get_discovery_api_client():
- """
- Returns an API client which can be used to make Catalog API requests.
- """
- return OAuthAPIClient(
- base_url=settings.DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL,
- client_id=settings.DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_KEY,
- client_secret=settings.DISCOVERY_BACKEND_SERVICE_EDX_OAUTH2_SECRET,
- )
-
- def handle(self, *args, **options):
- """
- Management command entry point.
-
- This command is meant to generate a small (<500) set of course prompts. If a larger number of prompts
- should be created, consider adding batching to this command.
-
- As of now, this command supports a limited structure of course prompt, such that each prompt is composed of
- three messages: the pre message, skills message, and post message. Should we need more messages in the future,
- and want to use this management command, the structure of the command args should be updated.
- """
- course_ids = options['course_ids']
- pre_message = options['pre_message']
- skills_descriptor = options['skills_descriptor']
- post_message = options['post_message']
-
- client = self._get_discovery_api_client()
-
- course_ids_list = course_ids.split(',')
- for course_run_id in course_ids_list:
- course_key = CourseKey.from_string(course_run_id)
-
- # discovery API requires course keys, not course run keys
- course_id = f'{course_key.org}+{course_key.course}'
-
- url = urljoin(
- settings.DISCOVERY_BASE_URL,
- 'api/v1/courses/{course_id}'.format(course_id=course_id)
- )
- response_data = client.get(url).json()
- title = response_data['title']
- skill_names = response_data['skill_names']
-
- # create restructured dictionary with data
- course_dict = {'title': title, 'topics': skill_names}
-
- # append descriptor message and decode json dict into a string
- skills_message = skills_descriptor + json.dumps(course_dict)
-
- # finally, create list of prompt messages and save
- prompt_messages = [pre_message, skills_message, post_message]
- CoursePrompt.objects.update_or_create(
- course_id=course_run_id, defaults={'json_prompt_content': prompt_messages}
- )
-
- logger.info('Updated course prompt for course_run_id=%s', course_run_id)
diff --git a/learning_assistant/management/commands/tests/test_set_course_prompts.py b/learning_assistant/management/commands/tests/test_set_course_prompts.py
deleted file mode 100644
index 29df5bf..0000000
--- a/learning_assistant/management/commands/tests/test_set_course_prompts.py
+++ /dev/null
@@ -1,75 +0,0 @@
-"""
-Tests for the set_course_prompts management command.
-"""
-import json
-from posixpath import join as urljoin
-from unittest.mock import MagicMock, patch
-
-from django.conf import settings
-from django.core.management import call_command
-from django.test import TestCase
-
-from learning_assistant.models import CoursePrompt
-
-
-class SetCoursePromptsTests(TestCase):
- """Test set_course_prompts command"""
- command = 'set_course_prompts'
-
- def setUp(self):
- self.pre_message = 'This is the first message'
- self.skills_descriptor = 'These are the skills: '
- self.post_message = 'This message comes after'
- self.course_ids = 'course-v1:edx+test+23,course-v1:edx+test+24'
- self.course_title = 'Intro to Testing'
- self.skill_names = ['Testing', 'Computers', 'Coding']
-
- def get_mock_discovery_response(self):
- """
- Create scaled down mock of discovery response
- """
- response_data = {
- 'title': self.course_title,
- 'skill_names': self.skill_names
- }
- return response_data
-
- @patch('learning_assistant.management.commands.set_course_prompts.Command._get_discovery_api_client')
- def test_course_prompts_created(self, mock_get_discovery_client):
- """
- Assert that course prompts are created by calling management command.
- """
- mock_client = MagicMock()
- mock_get_discovery_client.return_value = mock_client
- mock_client.get.return_value = MagicMock(
- status_code=200,
- json=lambda: self.get_mock_discovery_response() # pylint: disable=unnecessary-lambda
- )
-
- call_command(
- self.command,
- course_ids=self.course_ids,
- pre_message=self.pre_message,
- skills_descriptor=self.skills_descriptor,
- post_message=self.post_message,
- )
-
- # assert that discovery api was called with course id, not course run id
- expected_url = urljoin(
- settings.DISCOVERY_BASE_URL,
- 'api/v1/courses/{course_id}'.format(course_id='edx+test')
- )
- mock_client.get.assert_any_call(expected_url)
- mock_client.get.assert_called()
-
- # assert that number of prompts created is equivalent to number of courses passed in to command
- prompts = CoursePrompt.objects.filter()
- self.assertEqual(len(prompts), len(self.course_ids.split(',')))
-
- # assert structure of prompt
- course_prompt = prompts[0].json_prompt_content
- self.assertEqual(len(course_prompt), 3)
-
- skills_message = self.skills_descriptor + json.dumps({'title': self.course_title, 'topics': self.skill_names})
- expected_response = [self.pre_message, skills_message, self.post_message]
- self.assertEqual(course_prompt, expected_response)
diff --git a/tests/test_api.py b/tests/test_api.py
index 51397b0..15aee07 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -13,11 +13,8 @@
_get_children_contents,
_leaf_filter,
get_block_content,
- get_deserialized_prompt_content_by_course_id,
- get_setup_messages,
render_prompt_template,
)
-from learning_assistant.models import CoursePrompt
fake_transcript = 'This is the text version from the transcript'
@@ -53,39 +50,6 @@ def get_children(self):
return self.children
-class LearningAssistantAPITests(TestCase):
- """
- Test suite for the api module
- """
-
- def setUp(self):
- self.course_id = 'course-v1:edx+test+23'
- self.prompt = ["This is a Prompt", "This is another Prompt"]
- self.course_prompt = CoursePrompt.objects.create(
- course_id=self.course_id,
- json_prompt_content=self.prompt,
- )
- return super().setUp()
-
- def test_get_deserialized_prompt_valid_course_id(self):
- prompt_content = get_deserialized_prompt_content_by_course_id(self.course_id)
- expected_content = self.prompt
- self.assertEqual(prompt_content, expected_content)
-
- def test_get_deserialized_prompt_invalid_course_id(self):
- prompt_content = get_deserialized_prompt_content_by_course_id('course-v1:edx+fake+19')
- self.assertIsNone(prompt_content)
-
- def test_get_setup_messages(self):
- setup_messages = get_setup_messages(self.course_id)
- expected_messages = [{'role': 'system', 'content': x} for x in self.prompt]
- self.assertEqual(setup_messages, expected_messages)
-
- def test_get_setup_messages_invalid_course_id(self):
- setup_messages = get_setup_messages('course-v1:edx+fake+19')
- self.assertIsNone(setup_messages)
-
-
@ddt.ddt
class GetBlockContentAPITests(TestCase):
"""
From 45fde4931fc6aa7d809d3dd4d38534e208d051fa Mon Sep 17 00:00:00 2001
From: michaelroytman
Date: Fri, 19 Jan 2024 14:50:38 -0500
Subject: [PATCH 026/104] fix: update package version number
The version number was omitted in the previous commit by mistake. This commit updates the version number. Because a release for version 2.0.2 was already created and published, while the package version was set to `2.0.1`, version 2.0.2 can no longer be used, because it's tied to that commit. For this reason, this commit skips version 2.0.2 and goes straight to 2.0.3.
---
learning_assistant/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 9af5cd8..705cd96 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '2.0.1'
+__version__ = '2.0.3'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
From 7876b9e42f95c485e2f3e015a608a77d3e2fa35e Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 23 Jan 2024 10:21:01 -0500
Subject: [PATCH 027/104] chore: Updating Python Requirements
---
requirements/base.txt | 18 +++++-----
requirements/ci.txt | 6 +++-
requirements/dev.txt | 46 ++++++++++++++++++++------
requirements/doc.txt | 68 +++++++++++++++++++++++++-------------
requirements/pip-tools.txt | 7 ++++
requirements/quality.txt | 29 +++++++++++-----
requirements/test.txt | 23 ++++++++-----
7 files changed, 138 insertions(+), 59 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index a6f3404..f04682e 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -16,7 +16,7 @@ charset-normalizer==3.3.2
# via requests
click==8.1.7
# via edx-django-utils
-cryptography==41.0.7
+cryptography==42.0.0
# via pyjwt
django==3.2.23
# via
@@ -44,11 +44,11 @@ djangorestframework==3.14.0
# edx-drf-extensions
drf-jwt==1.19.2
# via edx-drf-extensions
-edx-django-utils==5.9.0
+edx-django-utils==5.10.1
# via
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.0.1
+edx-drf-extensions==9.1.2
# via -r requirements/base.in
edx-opaque-keys==2.5.1
# via
@@ -58,15 +58,15 @@ edx-rest-api-client==5.6.1
# via -r requirements/base.in
idna==3.6
# via requests
-jinja2==3.1.2
+jinja2==3.1.3
# via -r requirements/base.in
-markupsafe==2.1.3
+markupsafe==2.1.4
# via jinja2
-newrelic==9.3.0
+newrelic==9.5.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
-psutil==5.9.7
+psutil==5.9.8
# via edx-django-utils
pycparser==2.21
# via cffi
@@ -100,6 +100,8 @@ stevedore==5.1.0
# edx-django-utils
# edx-opaque-keys
typing-extensions==4.9.0
- # via edx-opaque-keys
+ # via
+ # asgiref
+ # edx-opaque-keys
urllib3==2.1.0
# via requests
diff --git a/requirements/ci.txt b/requirements/ci.txt
index b9ff286..ec7f1dc 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -40,7 +40,11 @@ pyproject-api==1.6.1
# via tox
requests==2.31.0
# via codecov
-tox==4.11.4
+tomli==2.0.1
+ # via
+ # pyproject-api
+ # tox
+tox==4.12.1
# via -r requirements/ci.in
urllib3==2.1.0
# via requests
diff --git a/requirements/dev.txt b/requirements/dev.txt
index b108338..d4b8c47 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -71,13 +71,13 @@ coverage[toml]==7.4.0
# codecov
# coverage
# pytest-cov
-cryptography==41.0.7
+cryptography==42.0.0
# via
# -r requirements/quality.txt
# pyjwt
-ddt==1.7.0
+ddt==1.7.1
# via -r requirements/quality.txt
-diff-cover==8.0.2
+diff-cover==8.0.3
# via -r requirements/dev.in
dill==0.3.7
# via
@@ -119,12 +119,12 @@ drf-jwt==1.19.2
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-django-utils==5.9.0
+edx-django-utils==5.10.1
# via
# -r requirements/quality.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.0.1
+edx-drf-extensions==9.1.2
# via -r requirements/quality.txt
edx-i18n-tools==1.3.0
# via -r requirements/dev.in
@@ -136,6 +136,10 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/quality.txt
+exceptiongroup==1.2.0
+ # via
+ # -r requirements/quality.txt
+ # pytest
filelock==3.13.1
# via
# -r requirements/ci.txt
@@ -158,14 +162,14 @@ isort==5.13.2
# via
# -r requirements/quality.txt
# pylint
-jinja2==3.1.2
+jinja2==3.1.3
# via
# -r requirements/quality.txt
# code-annotations
# diff-cover
-lxml==5.0.0
+lxml==5.1.0
# via edx-i18n-tools
-markupsafe==2.1.3
+markupsafe==2.1.4
# via
# -r requirements/quality.txt
# jinja2
@@ -173,7 +177,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.3.0
+newrelic==9.5.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -210,7 +214,7 @@ pluggy==1.3.0
# tox
polib==1.2.0
# via edx-i18n-tools
-psutil==5.9.7
+psutil==5.9.8
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -332,16 +336,32 @@ text-unidecode==1.3
# via
# -r requirements/quality.txt
# python-slugify
+tomli==2.0.1
+ # via
+ # -r requirements/ci.txt
+ # -r requirements/pip-tools.txt
+ # -r requirements/quality.txt
+ # build
+ # coverage
+ # pip-tools
+ # pylint
+ # pyproject-api
+ # pyproject-hooks
+ # pytest
+ # tox
tomlkit==0.12.3
# via
# -r requirements/quality.txt
# pylint
-tox==4.11.4
+tox==4.12.1
# via -r requirements/ci.txt
typing-extensions==4.9.0
# via
# -r requirements/quality.txt
+ # asgiref
+ # astroid
# edx-opaque-keys
+ # pylint
urllib3==2.1.0
# via
# -r requirements/ci.txt
@@ -356,6 +376,10 @@ wheel==0.42.0
# via
# -r requirements/pip-tools.txt
# pip-tools
+zipp==3.17.0
+ # via
+ # -r requirements/pip-tools.txt
+ # importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# pip
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 7cd0158..238d728 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -39,11 +39,12 @@ coverage[toml]==7.4.0
# -r requirements/test.txt
# coverage
# pytest-cov
-cryptography==41.0.7
+cryptography==42.0.0
# via
# -r requirements/test.txt
# pyjwt
-ddt==1.7.0
+ # secretstorage
+ddt==1.7.1
# via -r requirements/test.txt
django==3.2.23
# via
@@ -84,12 +85,12 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.9.0
+edx-django-utils==5.10.1
# via
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.0.1
+edx-drf-extensions==9.1.2
# via -r requirements/test.txt
edx-opaque-keys==2.5.1
# via
@@ -97,6 +98,10 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/test.txt
+exceptiongroup==1.2.0
+ # via
+ # -r requirements/test.txt
+ # pytest
idna==3.6
# via
# -r requirements/test.txt
@@ -105,15 +110,23 @@ imagesize==1.4.1
# via sphinx
importlib-metadata==7.0.1
# via
+ # build
# keyring
+ # sphinx
# twine
+importlib-resources==6.1.1
+ # via keyring
iniconfig==2.0.0
# via
# -r requirements/test.txt
# pytest
jaraco-classes==3.3.0
# via keyring
-jinja2==3.1.2
+jeepney==0.8.0
+ # via
+ # keyring
+ # secretstorage
+jinja2==3.1.3
# via
# -r requirements/test.txt
# code-annotations
@@ -122,15 +135,15 @@ keyring==24.3.0
# via twine
markdown-it-py==3.0.0
# via rich
-markupsafe==2.1.3
+markupsafe==2.1.4
# via
# -r requirements/test.txt
# jinja2
mdurl==0.1.2
# via markdown-it-py
-more-itertools==10.1.0
+more-itertools==10.2.0
# via jaraco-classes
-newrelic==9.3.0
+newrelic==9.5.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -152,7 +165,7 @@ pluggy==1.3.0
# via
# -r requirements/test.txt
# pytest
-psutil==5.9.7
+psutil==5.9.8
# via
# -r requirements/test.txt
# edx-django-utils
@@ -199,6 +212,7 @@ python-slugify==8.0.1
pytz==2023.3.post1
# via
# -r requirements/test.txt
+ # babel
# django
# djangorestframework
pyyaml==6.0.1
@@ -228,6 +242,8 @@ rfc3986==2.0.0
# via twine
rich==13.7.0
# via twine
+secretstorage==3.3.3
+ # via keyring
semantic-version==2.10.0
# via
# -r requirements/test.txt
@@ -238,25 +254,19 @@ slumber==0.7.1
# edx-rest-api-client
snowballstemmer==2.2.0
# via sphinx
-sphinx==7.2.6
- # via
- # -r requirements/doc.in
- # sphinxcontrib-applehelp
- # sphinxcontrib-devhelp
- # sphinxcontrib-htmlhelp
- # sphinxcontrib-qthelp
- # sphinxcontrib-serializinghtml
-sphinxcontrib-applehelp==1.0.7
+sphinx==7.1.2
+ # via -r requirements/doc.in
+sphinxcontrib-applehelp==1.0.4
# via sphinx
-sphinxcontrib-devhelp==1.0.5
+sphinxcontrib-devhelp==1.0.2
# via sphinx
-sphinxcontrib-htmlhelp==2.0.4
+sphinxcontrib-htmlhelp==2.0.1
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
-sphinxcontrib-qthelp==1.0.6
+sphinxcontrib-qthelp==1.0.3
# via sphinx
-sphinxcontrib-serializinghtml==1.1.9
+sphinxcontrib-serializinghtml==1.1.5
# via sphinx
sqlparse==0.4.4
# via
@@ -273,12 +283,22 @@ text-unidecode==1.3
# via
# -r requirements/test.txt
# python-slugify
+tomli==2.0.1
+ # via
+ # -r requirements/test.txt
+ # build
+ # coverage
+ # doc8
+ # pyproject-hooks
+ # pytest
twine==4.0.2
# via -r requirements/doc.in
typing-extensions==4.9.0
# via
# -r requirements/test.txt
+ # asgiref
# edx-opaque-keys
+ # rich
urllib3==2.1.0
# via
# -r requirements/test.txt
@@ -286,4 +306,6 @@ urllib3==2.1.0
# responses
# twine
zipp==3.17.0
- # via importlib-metadata
+ # via
+ # importlib-metadata
+ # importlib-resources
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 194fea6..0e88226 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -16,8 +16,15 @@ pip-tools==7.3.0
# via -r requirements/pip-tools.in
pyproject-hooks==1.0.0
# via build
+tomli==2.0.1
+ # via
+ # build
+ # pip-tools
+ # pyproject-hooks
wheel==0.42.0
# via pip-tools
+zipp==3.17.0
+ # via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# pip
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 88bb0ac..1e6c27b 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -43,11 +43,11 @@ coverage[toml]==7.4.0
# -r requirements/test.txt
# coverage
# pytest-cov
-cryptography==41.0.7
+cryptography==42.0.0
# via
# -r requirements/test.txt
# pyjwt
-ddt==1.7.0
+ddt==1.7.1
# via -r requirements/test.txt
dill==0.3.7
# via pylint
@@ -82,12 +82,12 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.9.0
+edx-django-utils==5.10.1
# via
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.0.1
+edx-drf-extensions==9.1.2
# via -r requirements/test.txt
edx-lint==5.3.6
# via -r requirements/quality.in
@@ -97,6 +97,10 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/test.txt
+exceptiongroup==1.2.0
+ # via
+ # -r requirements/test.txt
+ # pytest
idna==3.6
# via
# -r requirements/test.txt
@@ -109,17 +113,17 @@ isort==5.13.2
# via
# -r requirements/quality.in
# pylint
-jinja2==3.1.2
+jinja2==3.1.3
# via
# -r requirements/test.txt
# code-annotations
-markupsafe==2.1.3
+markupsafe==2.1.4
# via
# -r requirements/test.txt
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.3.0
+newrelic==9.5.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -137,7 +141,7 @@ pluggy==1.3.0
# via
# -r requirements/test.txt
# pytest
-psutil==5.9.7
+psutil==5.9.8
# via
# -r requirements/test.txt
# edx-django-utils
@@ -236,12 +240,21 @@ text-unidecode==1.3
# via
# -r requirements/test.txt
# python-slugify
+tomli==2.0.1
+ # via
+ # -r requirements/test.txt
+ # coverage
+ # pylint
+ # pytest
tomlkit==0.12.3
# via pylint
typing-extensions==4.9.0
# via
# -r requirements/test.txt
+ # asgiref
+ # astroid
# edx-opaque-keys
+ # pylint
urllib3==2.1.0
# via
# -r requirements/test.txt
diff --git a/requirements/test.txt b/requirements/test.txt
index 71bbe65..5afd614 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -32,11 +32,11 @@ coverage[toml]==7.4.0
# via
# coverage
# pytest-cov
-cryptography==41.0.7
+cryptography==42.0.0
# via
# -r requirements/base.txt
# pyjwt
-ddt==1.7.0
+ddt==1.7.1
# via -r requirements/test.in
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
@@ -68,12 +68,12 @@ drf-jwt==1.19.2
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-django-utils==5.9.0
+edx-django-utils==5.10.1
# via
# -r requirements/base.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.0.1
+edx-drf-extensions==9.1.2
# via -r requirements/base.txt
edx-opaque-keys==2.5.1
# via
@@ -81,21 +81,23 @@ edx-opaque-keys==2.5.1
# edx-drf-extensions
edx-rest-api-client==5.6.1
# via -r requirements/base.txt
+exceptiongroup==1.2.0
+ # via pytest
idna==3.6
# via
# -r requirements/base.txt
# requests
iniconfig==2.0.0
# via pytest
-jinja2==3.1.2
+jinja2==3.1.3
# via
# -r requirements/base.txt
# code-annotations
-markupsafe==2.1.3
+markupsafe==2.1.4
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.3.0
+newrelic==9.5.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -107,7 +109,7 @@ pbr==6.0.0
# stevedore
pluggy==1.3.0
# via pytest
-psutil==5.9.7
+psutil==5.9.8
# via
# -r requirements/base.txt
# edx-django-utils
@@ -178,9 +180,14 @@ stevedore==5.1.0
# edx-opaque-keys
text-unidecode==1.3
# via python-slugify
+tomli==2.0.1
+ # via
+ # coverage
+ # pytest
typing-extensions==4.9.0
# via
# -r requirements/base.txt
+ # asgiref
# edx-opaque-keys
urllib3==2.1.0
# via
From 92b820c72d9d5fa982c297e6dbe77fd4870c6614 Mon Sep 17 00:00:00 2001
From: Michael Roytman
Date: Tue, 23 Jan 2024 12:02:56 -0500
Subject: [PATCH 028/104] feat!: remove and drop the CoursePrompt model (#54)
This commit removes and drops the CoursePrompt model. Because we no longer have plans to store system prompts in a model, we have removed and dropped this model.
BREAKING CHANGE:
This commit removes the CoursePrompt model, which means that this model will no longer be supported for specifying the system prompt to use for a course. In lieu of the CoursePrompt model, please use the LEARNING_ASSISTANT_PROMPT_TEMPLATE Django setting to store a system prompt as a Jinja2 query template. Please view the associated architecture decision record (ADR), "2. System Prompt Design Changes", here for more details: https://github.com/edx/learning-assistant/blob/main/docs/decisions/0002-system-prompt-design-changes.rst.
---
learning_assistant/__init__.py | 2 +-
.../migrations/0006_delete_courseprompt.py | 16 ++++++++++
learning_assistant/models.py | 26 ---------------
tests/test_models.py | 32 -------------------
tests/test_views.py | 18 +++--------
5 files changed, 22 insertions(+), 72 deletions(-)
create mode 100644 learning_assistant/migrations/0006_delete_courseprompt.py
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 705cd96..c1c6a64 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '2.0.3'
+__version__ = '3.0.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/migrations/0006_delete_courseprompt.py b/learning_assistant/migrations/0006_delete_courseprompt.py
new file mode 100644
index 0000000..596d506
--- /dev/null
+++ b/learning_assistant/migrations/0006_delete_courseprompt.py
@@ -0,0 +1,16 @@
+# Generated by Django 3.2.23 on 2024-01-12 13:02
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('learning_assistant', '0005_learningassistantcourseenabled'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='CoursePrompt',
+ ),
+ ]
diff --git a/learning_assistant/models.py b/learning_assistant/models.py
index f16e0a4..55b3335 100644
--- a/learning_assistant/models.py
+++ b/learning_assistant/models.py
@@ -6,32 +6,6 @@
from opaque_keys.edx.django.models import CourseKeyField
-class CoursePrompt(TimeStampedModel):
- """
- This model represents a mapping between a particular course ID and a text prompt associated with the course ID.
-
- .. no_pii: This model has no PII.
- """
-
- # course ID with which the text prompt is associated
- course_id = CourseKeyField(max_length=255, db_index=True, unique=True)
-
- # a json representation of the prompt message content
- json_prompt_content = models.JSONField(null=True)
-
- @classmethod
- def get_json_prompt_content_by_course_id(cls, course_id):
- """
- Return a json representation of a prompt for a given course id.
- """
- try:
- prompt_object = cls.objects.get(course_id=course_id)
- json_prompt_content = prompt_object.json_prompt_content
- except cls.DoesNotExist:
- json_prompt_content = None
- return json_prompt_content
-
-
class LearningAssistantCourseEnabled(TimeStampedModel):
"""
This model stores whether the Learning Assistant is enabled for a particular course ID.
diff --git a/tests/test_models.py b/tests/test_models.py
index 5d0ceb4..be870eb 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -2,35 +2,3 @@
"""
Tests for the `learning-assistant` models module.
"""
-from django.test import TestCase
-
-from learning_assistant.models import CoursePrompt
-
-
-class CoursePromptTests(TestCase):
- """
- Test suite for the CoursePrompt model
- """
-
- def setUp(self):
- self.course_id = 'course-v1:edx+test+23'
- self.prompt = ["This is a Prompt", "This is another Prompt"]
- self.course_prompt = CoursePrompt.objects.create(
- course_id=self.course_id,
- json_prompt_content=self.prompt,
- )
- return super().setUp()
-
- def test_get_prompt_by_course_id(self):
- """
- Test that a prompt can be retrieved by course ID
- """
- prompt = CoursePrompt.get_json_prompt_content_by_course_id(self.course_id)
- self.assertEqual(prompt, self.prompt)
-
- def test_get_prompt_by_course_id_invalid(self):
- """
- Test that None is returned if the given course ID does not exist
- """
- prompt = CoursePrompt.get_json_prompt_content_by_course_id('course-v1:edx+fake+19')
- self.assertIsNone(prompt)
diff --git a/tests/test_views.py b/tests/test_views.py
index 129f3b6..909d9c2 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -13,8 +13,6 @@
from django.test.client import Client
from django.urls import reverse
-from learning_assistant.models import CoursePrompt
-
User = get_user_model()
@@ -102,16 +100,15 @@ def test_user_no_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_rol
response = self.client.post(reverse('chat', kwargs={'course_id': self.course_id}))
self.assertEqual(response.status_code, 403)
+ @patch('learning_assistant.views.render_prompt_template')
@patch('learning_assistant.views.learning_assistant_is_active')
@patch('learning_assistant.views.get_user_role')
- def test_invalid_messages(self, mock_role, mock_waffle):
+ def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
mock_waffle.return_value = True
mock_role.return_value = 'staff'
- CoursePrompt.objects.create(
- course_id=self.course_id,
- json_prompt_content=["This is a Prompt", "This is another Prompt"]
- )
+ mock_render.return_value = 'This is a template'
+ test_unit_id = 'test-unit-id'
test_data = [
{'role': 'user', 'content': 'What is 2+2?'},
@@ -119,7 +116,7 @@ def test_invalid_messages(self, mock_role, mock_waffle):
]
response = self.client.post(
- reverse('chat', kwargs={'course_id': self.course_id}),
+ reverse('chat', kwargs={'course_id': self.course_id})+f'?unit_id={test_unit_id}',
data=json.dumps(test_data),
content_type='application/json'
)
@@ -140,11 +137,6 @@ def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle,
mock_render.return_value = 'This is a template'
test_unit_id = 'test-unit-id'
- CoursePrompt.objects.create(
- course_id=self.course_id,
- json_prompt_content=["This is a Prompt", "This is another Prompt"]
- )
-
test_data = [
{'role': 'user', 'content': 'What is 2+2?'},
{'role': 'assistant', 'content': 'It is 4'}
From 8a8737aef228f1dd21b0f04afc7534a4acb0dea2 Mon Sep 17 00:00:00 2001
From: Michael Roytman
Date: Mon, 29 Jan 2024 14:18:19 -0500
Subject: [PATCH 029/104] feat: return 403 if Learning Assistant feature toggle
or instructor toggle disabled (#60)
This commit modifies the Learning Assistant access logic.
Previously, the chat view would return a 403 response code if the learning_assistant_available function returned False (i.e. the COURSEWARE_LEARNING_ASSISTANT CourseWaffleFlag was disabled).
Now, the chat view will return a 403 response code if either the learning_assistant_available function returns False or if there is a False override in the LearningAssistantCourseEnabled model corresponding to the course in which the chat is being requested.
---
codecov.yml | 3 ++
learning_assistant/__init__.py | 2 +-
learning_assistant/api.py | 26 ++++++++++++++
learning_assistant/platform_imports.py | 20 +++++++++++
learning_assistant/views.py | 9 +++--
tests/test_api.py | 47 +++++++++++++++++++++++++-
tests/test_views.py | 8 ++---
7 files changed, 104 insertions(+), 11 deletions(-)
diff --git a/codecov.yml b/codecov.yml
index 4da4768..681e43e 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -8,5 +8,8 @@ coverage:
default:
enabled: yes
target: 100%
+ignore:
+ - "learning_assistant/platform_imports.py"
+
comment: false
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index c1c6a64..787d2c3 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '3.0.0'
+__version__ = '3.1.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 73bcee1..7a41996 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -10,11 +10,13 @@
from opaque_keys.edx.keys import CourseKey
from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, CATEGORY_TYPE_MAP
+from learning_assistant.models import LearningAssistantCourseEnabled
from learning_assistant.platform_imports import (
block_get_children,
block_leaf_filter,
get_single_block,
get_text_transcript,
+ learning_assistant_available,
traverse_block_pre_order,
)
from learning_assistant.text_utils import html_to_text
@@ -113,3 +115,27 @@ def render_prompt_template(request, user_id, course_id, unit_usage_key):
template = Environment(loader=BaseLoader).from_string(template_string)
data = template.render(unit_content=unit_content)
return data
+
+
+def learning_assistant_enabled(course_key):
+ """
+ Return whether the Learning Assistant is enabled in the course represented by the course_key.
+
+ The Learning Assistant is enabled if the feature is available (i.e. appropriate CourseWaffleFlag is enabled) and
+ either there is no override in the LearningAssistantCourseEnabled table or there is an enabled value in the
+ LearningAssistantCourseEnabled table.
+
+ Arguments:
+ * course_key: (CourseKey): the course's key
+
+ Returns:
+ * bool: whether the Learning Assistant is enabled
+ """
+ try:
+ obj = LearningAssistantCourseEnabled.objects.get(course_id=course_key)
+ enabled = obj.enabled
+ except LearningAssistantCourseEnabled.DoesNotExist:
+ # Currently, the Learning Assistant defaults to enabled if there is no override.
+ enabled = True
+
+ return learning_assistant_available(course_key) and enabled
diff --git a/learning_assistant/platform_imports.py b/learning_assistant/platform_imports.py
index 2641f52..2c98d3d 100644
--- a/learning_assistant/platform_imports.py
+++ b/learning_assistant/platform_imports.py
@@ -57,3 +57,23 @@ def get_cache_course_run_data(course_run_id, fields):
# pylint: disable=import-error, import-outside-toplevel
from openedx.core.djangoapps.catalog.utils import get_course_run_data
return get_course_run_data(course_run_id, fields)
+
+
+def learning_assistant_available(course_key):
+ """
+ Return whether the Learning Assistant is available in the course represented by the course_key.
+
+ Note that this may be different than whether the Learning Assistant is enabled in the course. The value returned by
+ this fuction represents whether the Learning Assistant is available in the course and, perhaps, whether it is
+ enabled. Course teams can disable the Learning Assistant via the LearningAssistantCourseEnabled model, so, in those
+ cases, the Learning Assistant may be available and disabled.
+
+ Arguments:
+ * course_key (CourseKey): the course's key
+
+ Returns:
+ * bool: whether the Learning Assistant feature is available
+ """
+ # pylint: disable=import-error, import-outside-toplevel
+ from lms.djangoapps.courseware.toggles import learning_assistant_is_active
+ return learning_assistant_is_active(course_key)
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 85fce4b..0bcbedb 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -15,12 +15,10 @@
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.models import CourseEnrollment
from lms.djangoapps.courseware.access import get_user_role
- from lms.djangoapps.courseware.toggles import learning_assistant_is_active
except ImportError:
- # If the waffle flag is false, the endpoint will force an early return.
- learning_assistant_is_active = False
+ pass
-from learning_assistant.api import render_prompt_template
+from learning_assistant.api import learning_assistant_enabled, render_prompt_template
from learning_assistant.serializers import MessageSerializer
from learning_assistant.utils import get_chat_response
@@ -47,7 +45,8 @@ def post(self, request, course_id):
}
"""
courserun_key = CourseKey.from_string(course_id)
- if not learning_assistant_is_active(courserun_key):
+
+ if not learning_assistant_enabled(courserun_key):
return Response(
status=http_status.HTTP_403_FORBIDDEN,
data={'detail': 'Learning assistant not enabled for course.'}
diff --git a/tests/test_api.py b/tests/test_api.py
index 15aee07..1ddab3f 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -6,15 +6,17 @@
import ddt
from django.core.cache import cache
from django.test import TestCase
-from opaque_keys.edx.keys import UsageKey
+from opaque_keys.edx.keys import CourseKey, UsageKey
from learning_assistant.api import (
_extract_block_contents,
_get_children_contents,
_leaf_filter,
get_block_content,
+ learning_assistant_enabled,
render_prompt_template,
)
+from learning_assistant.models import LearningAssistantCourseEnabled
fake_transcript = 'This is the text version from the transcript'
@@ -196,3 +198,46 @@ def test_render_prompt_template(self, unit_content, flag_enabled, mock_get_conte
self.assertIn(unit_content, prompt_text)
else:
self.assertNotIn('The following text is useful.', prompt_text)
+
+
+@ddt.ddt
+class LearningAssistantCourseEnabledApiTests(TestCase):
+ """
+ Test suite for the learning_assistant_enabled and set_learning_assistant_enalbed api functions.
+ """
+ def setUp(self):
+ super().setUp()
+ self.course_key = CourseKey.from_string('course-v1:edx+fake+1')
+
+ @ddt.data(
+ (True, True, True, True),
+ (True, True, False, False),
+ (True, False, True, False),
+ (True, False, False, False),
+ (False, True, True, True),
+ (False, False, True, True),
+ (False, True, False, False),
+ (False, False, False, False),
+ )
+ @ddt.unpack
+ @patch('learning_assistant.api.learning_assistant_available')
+ def test_learning_assistant_enabled(
+ self,
+ obj_exists,
+ obj_value,
+ learning_assistant_available_value,
+ expected_value,
+ learning_assistant_available_mock,
+ ):
+ learning_assistant_available_mock.return_value = learning_assistant_available_value
+
+ if obj_exists:
+ LearningAssistantCourseEnabled.objects.update_or_create(
+ course_id=self.course_key,
+ defaults={'enabled': obj_value}
+ )
+
+ self.assertEqual(
+ learning_assistant_enabled(self.course_key),
+ expected_value
+ )
diff --git a/tests/test_views.py b/tests/test_views.py
index 909d9c2..fdb0fb6 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -81,13 +81,13 @@ def setUp(self):
super().setUp()
self.course_id = 'course-v1:edx+test+23'
- @patch('learning_assistant.views.learning_assistant_is_active')
+ @patch('learning_assistant.views.learning_assistant_enabled')
def test_course_waffle_inactive(self, mock_waffle):
mock_waffle.return_value = False
response = self.client.post(reverse('chat', kwargs={'course_id': self.course_id}))
self.assertEqual(response.status_code, 403)
- @patch('learning_assistant.views.learning_assistant_is_active')
+ @patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
@patch('learning_assistant.views.CourseEnrollment.get_enrollment')
@patch('learning_assistant.views.CourseMode')
@@ -101,7 +101,7 @@ def test_user_no_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_rol
self.assertEqual(response.status_code, 403)
@patch('learning_assistant.views.render_prompt_template')
- @patch('learning_assistant.views.learning_assistant_is_active')
+ @patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
mock_waffle.return_value = True
@@ -124,7 +124,7 @@ def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
@patch('learning_assistant.views.render_prompt_template')
@patch('learning_assistant.views.get_chat_response')
- @patch('learning_assistant.views.learning_assistant_is_active')
+ @patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
@patch('learning_assistant.views.CourseEnrollment.get_enrollment')
@patch('learning_assistant.views.CourseMode')
From 07a714dec28e5338cdc5cf2439231730ca192418 Mon Sep 17 00:00:00 2001
From: alangsto <46360176+alangsto@users.noreply.github.com>
Date: Tue, 30 Jan 2024 13:07:00 -0500
Subject: [PATCH 030/104] feat: remove audit access to learning assistant chat
view (#62)
---
CHANGELOG.rst | 18 +++++++++++++++++-
learning_assistant/__init__.py | 2 +-
learning_assistant/views.py | 2 +-
tests/test_views.py | 17 +++++++++++++++--
4 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 5337a3d..e9ce472 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,7 +14,23 @@ Change Log
Unreleased
**********
-2.0.1 - 2021-01-08
+3.2.0 - 2024-01-30
+******************
+* Remove audit access to chat view.
+
+3.0.1 - 2024-01-29
+******************
+* Modify gating of learning assistant based on waffle flag and enabled value.
+
+3.0.0 - 2024-01-23
+******************
+* Remove and drop the course prompt model.
+
+2.0.3 - 2024-01-22
+******************
+* Remove references to the course prompt model.
+
+2.0.1 - 2024-01-08
******************
* Gate content integration with waffle flag
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 787d2c3..cf622ab 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '3.1.0'
+__version__ = '3.2.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 0bcbedb..00a5edf 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -57,7 +57,7 @@ def post(self, request, course_id):
enrollment_object = CourseEnrollment.get_enrollment(request.user, courserun_key)
enrollment_mode = enrollment_object.mode if enrollment_object else None
if (
- (enrollment_mode not in CourseMode.ALL_MODES)
+ (enrollment_mode not in CourseMode.VERIFIED_MODES)
and user_role not in ('staff', 'instructor')
):
return Response(
diff --git a/tests/test_views.py b/tests/test_views.py
index fdb0fb6..9f3edc7 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -94,12 +94,25 @@ def test_course_waffle_inactive(self, mock_waffle):
def test_user_no_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_role, mock_waffle):
mock_waffle.return_value = True
mock_role.return_value = 'student'
- mock_mode.ALL_MODES = ['verified']
+ mock_mode.VERIFIED_MODES = ['verified']
mock_enrollment.return_value = None
response = self.client.post(reverse('chat', kwargs={'course_id': self.course_id}))
self.assertEqual(response.status_code, 403)
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ @patch('learning_assistant.views.get_user_role')
+ @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseMode')
+ def test_user_audit_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_role, mock_waffle):
+ mock_waffle.return_value = True
+ mock_role.return_value = 'student'
+ mock_mode.VERIFIED_MODES = ['verified']
+ mock_enrollment.return_value = MagicMock(mode='audit')
+
+ response = self.client.post(reverse('chat', kwargs={'course_id': self.course_id}))
+ self.assertEqual(response.status_code, 403)
+
@patch('learning_assistant.views.render_prompt_template')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
@@ -131,7 +144,7 @@ def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle, mock_chat_response, mock_render):
mock_waffle.return_value = True
mock_role.return_value = 'student'
- mock_mode.ALL_MODES = ['verified']
+ mock_mode.VERIFIED_MODES = ['verified']
mock_enrollment.return_value = MagicMock(mode='verified')
mock_chat_response.return_value = (200, {'role': 'assistant', 'content': 'Something else'})
mock_render.return_value = 'This is a template'
From 7da0c8bc8e29fea20fb410b25351e3acda8af091 Mon Sep 17 00:00:00 2001
From: michaelroytman
Date: Tue, 30 Jan 2024 15:07:57 -0500
Subject: [PATCH 031/104] feat: add GET endpoint to retrieve whether Learning
Assistant is enabled
This commit adds a new GET endpoint to retrieve whether the Learning Assistant feature is enabled in a course, given a course ID. This endpoint will introspect all data that is relevant to determining whether the feature is enabled (i.e. the courseware.learning_assistant CourseWaffleFlag and the LearningAssistantCourseEnabled model).
This endpoint was added so that the frontend can determine whether or not to show the Learning Assistant feature without requiring that the edx-platform read from the LearningAssistantCourseEnabled model in this plugin.
---
CHANGELOG.rst | 4 ++++
learning_assistant/__init__.py | 2 +-
learning_assistant/urls.py | 11 +++++++---
learning_assistant/views.py | 38 ++++++++++++++++++++++++++++++++++
tests/test_views.py | 29 ++++++++++++++++++++++++++
5 files changed, 80 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index e9ce472..e43ff2d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,10 @@ Change Log
Unreleased
**********
+3.3.0 - 2024-01-30
+******************
+* Add new GET endpoint to retrieve whether Learning Assistant is enabled in a given course.
+
3.2.0 - 2024-01-30
******************
* Remove audit access to chat view.
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index cf622ab..cc2e729 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '3.2.0'
+__version__ = '3.3.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/urls.py b/learning_assistant/urls.py
index 169d816..e08b413 100644
--- a/learning_assistant/urls.py
+++ b/learning_assistant/urls.py
@@ -4,14 +4,19 @@
from django.urls import re_path
from learning_assistant.constants import COURSE_ID_PATTERN
-from learning_assistant.views import CourseChatView
+from learning_assistant.views import CourseChatView, LearningAssistantEnabledView
app_name = 'learning_assistant'
urlpatterns = [
re_path(
- fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}',
+ fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}$',
CourseChatView.as_view(),
- name='chat'
+ name='chat',
+ ),
+ re_path(
+ fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}/enabled',
+ LearningAssistantEnabledView.as_view(),
+ name='enabled',
),
]
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 00a5edf..a7d87ce 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -91,3 +91,41 @@ def post(self, request, course_id):
status_code, message = get_chat_response(prompt_template, message_list, course_id)
return Response(status=status_code, data=message)
+
+
+class LearningAssistantEnabledView(APIView):
+ """
+ View to retrieve whether the Learning Assistant is enabled for a course.
+
+ This endpoint returns a boolean representing whether the Learning Assistant feature is enabled in a course
+ represented by the course_key, which is provided in the URL.
+
+ Accepts: [GET]
+
+ Path: /learning_assistant/v1/course_id/{course_key}/enabled
+
+ Parameters:
+ * course_key: the ID of the course
+
+ Responses:
+ * 200: OK
+ """
+
+ authentication_classes = (SessionAuthentication, JwtAuthentication,)
+ permission_classes = (IsAuthenticated,)
+
+ def get(self, request, course_id):
+ """
+ Given a course ID, retrieve whether the Learning Assistant is enabled for the corresponding course.
+
+ The response will be in the following format.
+
+ {'enabled': }
+ """
+ courserun_key = CourseKey.from_string(course_id)
+
+ data = {
+ 'enabled': learning_assistant_enabled(courserun_key),
+ }
+
+ return Response(status=http_status.HTTP_200_OK, data=data)
diff --git a/tests/test_views.py b/tests/test_views.py
index 9f3edc7..5a9bb38 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -6,6 +6,7 @@
from importlib import import_module
from unittest.mock import MagicMock, patch
+import ddt
from django.conf import settings
from django.contrib.auth import get_user_model, login
from django.http import HttpRequest
@@ -164,3 +165,31 @@ def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle,
render_args = mock_render.call_args.args
self.assertIn(test_unit_id, render_args)
+
+
+@ddt.ddt
+class LearningAssistantEnabledViewTests(LoggedInTestCase):
+ """
+ Test for the LearningAssistantEnabledView
+ """
+ sys.modules['lms.djangoapps.courseware.access'] = MagicMock()
+ sys.modules['lms.djangoapps.courseware.toggles'] = MagicMock()
+ sys.modules['common.djangoapps.course_modes.models'] = MagicMock()
+ sys.modules['common.djangoapps.student.models'] = MagicMock()
+
+ def setUp(self):
+ super().setUp()
+ self.course_id = 'course-v1:edx+test+23'
+
+ @ddt.data(
+ (True, True),
+ (False, False),
+ )
+ @ddt.unpack
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ def test_learning_assistant_enabled(self, mock_value, expected_value, mock_learning_assistant_enabled):
+ mock_learning_assistant_enabled.return_value = mock_value
+ response = self.client.get(reverse('enabled', kwargs={'course_id': self.course_id}))
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.data, {'enabled': expected_value})
From 840dfcfd812d7b370fc41afc1f97fc78956e737a Mon Sep 17 00:00:00 2001
From: michaelroytman
Date: Tue, 30 Jan 2024 15:14:16 -0500
Subject: [PATCH 032/104] Revert "feat: add GET endpoint to retrieve whether
Learning Assistant is enabled"
This reverts commit 7da0c8bc8e29fea20fb410b25351e3acda8af091.
This commit was accidentally pushed to the main branch, due to insufficiently restrictive branch protections. This commit will be reverted, the main branch protections will be improved, and then a pull request will be opened with the contents of the original commit.
---
CHANGELOG.rst | 4 ----
learning_assistant/__init__.py | 2 +-
learning_assistant/urls.py | 11 +++-------
learning_assistant/views.py | 38 ----------------------------------
tests/test_views.py | 29 --------------------------
5 files changed, 4 insertions(+), 80 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index e43ff2d..e9ce472 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,10 +14,6 @@ Change Log
Unreleased
**********
-3.3.0 - 2024-01-30
-******************
-* Add new GET endpoint to retrieve whether Learning Assistant is enabled in a given course.
-
3.2.0 - 2024-01-30
******************
* Remove audit access to chat view.
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index cc2e729..cf622ab 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '3.3.0'
+__version__ = '3.2.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/urls.py b/learning_assistant/urls.py
index e08b413..169d816 100644
--- a/learning_assistant/urls.py
+++ b/learning_assistant/urls.py
@@ -4,19 +4,14 @@
from django.urls import re_path
from learning_assistant.constants import COURSE_ID_PATTERN
-from learning_assistant.views import CourseChatView, LearningAssistantEnabledView
+from learning_assistant.views import CourseChatView
app_name = 'learning_assistant'
urlpatterns = [
re_path(
- fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}$',
+ fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}',
CourseChatView.as_view(),
- name='chat',
- ),
- re_path(
- fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}/enabled',
- LearningAssistantEnabledView.as_view(),
- name='enabled',
+ name='chat'
),
]
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index a7d87ce..00a5edf 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -91,41 +91,3 @@ def post(self, request, course_id):
status_code, message = get_chat_response(prompt_template, message_list, course_id)
return Response(status=status_code, data=message)
-
-
-class LearningAssistantEnabledView(APIView):
- """
- View to retrieve whether the Learning Assistant is enabled for a course.
-
- This endpoint returns a boolean representing whether the Learning Assistant feature is enabled in a course
- represented by the course_key, which is provided in the URL.
-
- Accepts: [GET]
-
- Path: /learning_assistant/v1/course_id/{course_key}/enabled
-
- Parameters:
- * course_key: the ID of the course
-
- Responses:
- * 200: OK
- """
-
- authentication_classes = (SessionAuthentication, JwtAuthentication,)
- permission_classes = (IsAuthenticated,)
-
- def get(self, request, course_id):
- """
- Given a course ID, retrieve whether the Learning Assistant is enabled for the corresponding course.
-
- The response will be in the following format.
-
- {'enabled': }
- """
- courserun_key = CourseKey.from_string(course_id)
-
- data = {
- 'enabled': learning_assistant_enabled(courserun_key),
- }
-
- return Response(status=http_status.HTTP_200_OK, data=data)
diff --git a/tests/test_views.py b/tests/test_views.py
index 5a9bb38..9f3edc7 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -6,7 +6,6 @@
from importlib import import_module
from unittest.mock import MagicMock, patch
-import ddt
from django.conf import settings
from django.contrib.auth import get_user_model, login
from django.http import HttpRequest
@@ -165,31 +164,3 @@ def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle,
render_args = mock_render.call_args.args
self.assertIn(test_unit_id, render_args)
-
-
-@ddt.ddt
-class LearningAssistantEnabledViewTests(LoggedInTestCase):
- """
- Test for the LearningAssistantEnabledView
- """
- sys.modules['lms.djangoapps.courseware.access'] = MagicMock()
- sys.modules['lms.djangoapps.courseware.toggles'] = MagicMock()
- sys.modules['common.djangoapps.course_modes.models'] = MagicMock()
- sys.modules['common.djangoapps.student.models'] = MagicMock()
-
- def setUp(self):
- super().setUp()
- self.course_id = 'course-v1:edx+test+23'
-
- @ddt.data(
- (True, True),
- (False, False),
- )
- @ddt.unpack
- @patch('learning_assistant.views.learning_assistant_enabled')
- def test_learning_assistant_enabled(self, mock_value, expected_value, mock_learning_assistant_enabled):
- mock_learning_assistant_enabled.return_value = mock_value
- response = self.client.get(reverse('enabled', kwargs={'course_id': self.course_id}))
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.data, {'enabled': expected_value})
From 423b175b3b92e9e9c812bad9fca5d52dda3a2a85 Mon Sep 17 00:00:00 2001
From: alangsto <46360176+alangsto@users.noreply.github.com>
Date: Tue, 30 Jan 2024 15:53:04 -0500
Subject: [PATCH 033/104] feat: update release version (#64)
---
CHANGELOG.rst | 4 ++++
learning_assistant/__init__.py | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index e9ce472..5544d2a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,10 @@ Change Log
Unreleased
**********
+3.3.0 - 2024-01-30
+******************
+* Fix release version
+
3.2.0 - 2024-01-30
******************
* Remove audit access to chat view.
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index cf622ab..cc2e729 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '3.2.0'
+__version__ = '3.3.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
From 8a25061b1afc855f1f578965797bb7c4864eb08e Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Wed, 31 Jan 2024 08:00:56 -0500
Subject: [PATCH 034/104] chore: Updating Python Requirements (#61)
---
requirements/base.txt | 10 +++++-----
requirements/ci.txt | 6 +++---
requirements/dev.txt | 22 +++++++++++-----------
requirements/doc.txt | 20 ++++++++++----------
requirements/quality.txt | 22 +++++++++++-----------
requirements/test.txt | 20 ++++++++++----------
6 files changed, 50 insertions(+), 50 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index f04682e..f9c9994 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -16,7 +16,7 @@ charset-normalizer==3.3.2
# via requests
click==8.1.7
# via edx-django-utils
-cryptography==42.0.0
+cryptography==42.0.1
# via pyjwt
django==3.2.23
# via
@@ -48,7 +48,7 @@ edx-django-utils==5.10.1
# via
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.1.2
+edx-drf-extensions==10.1.0
# via -r requirements/base.in
edx-opaque-keys==2.5.1
# via
@@ -62,7 +62,7 @@ jinja2==3.1.3
# via -r requirements/base.in
markupsafe==2.1.4
# via jinja2
-newrelic==9.5.0
+newrelic==9.6.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
@@ -80,7 +80,7 @@ pymongo==3.13.0
# via edx-opaque-keys
pynacl==1.5.0
# via edx-django-utils
-pytz==2023.3.post1
+pytz==2023.4
# via
# django
# djangorestframework
@@ -103,5 +103,5 @@ typing-extensions==4.9.0
# via
# asgiref
# edx-opaque-keys
-urllib3==2.1.0
+urllib3==2.2.0
# via requests
diff --git a/requirements/ci.txt b/requirements/ci.txt
index ec7f1dc..1a344f5 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -16,7 +16,7 @@ codecov==2.1.13
# via -r requirements/ci.in
colorama==0.4.6
# via tox
-coverage==7.4.0
+coverage==7.4.1
# via codecov
distlib==0.3.8
# via virtualenv
@@ -34,7 +34,7 @@ platformdirs==4.1.0
# via
# tox
# virtualenv
-pluggy==1.3.0
+pluggy==1.4.0
# via tox
pyproject-api==1.6.1
# via tox
@@ -46,7 +46,7 @@ tomli==2.0.1
# tox
tox==4.12.1
# via -r requirements/ci.in
-urllib3==2.1.0
+urllib3==2.2.0
# via requests
virtualenv==20.25.0
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index d4b8c47..fc302b2 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -64,14 +64,14 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.4.0
+coverage[toml]==7.4.1
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
# codecov
# coverage
# pytest-cov
-cryptography==42.0.0
+cryptography==42.0.1
# via
# -r requirements/quality.txt
# pyjwt
@@ -79,7 +79,7 @@ ddt==1.7.1
# via -r requirements/quality.txt
diff-cover==8.0.3
# via -r requirements/dev.in
-dill==0.3.7
+dill==0.3.8
# via
# -r requirements/quality.txt
# pylint
@@ -124,7 +124,7 @@ edx-django-utils==5.10.1
# -r requirements/quality.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.1.2
+edx-drf-extensions==10.1.0
# via -r requirements/quality.txt
edx-i18n-tools==1.3.0
# via -r requirements/dev.in
@@ -177,7 +177,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.5.0
+newrelic==9.6.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -205,7 +205,7 @@ platformdirs==4.1.0
# pylint
# tox
# virtualenv
-pluggy==1.3.0
+pluggy==1.4.0
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -271,20 +271,20 @@ pyproject-hooks==1.0.0
# via
# -r requirements/pip-tools.txt
# build
-pytest==7.4.4
+pytest==8.0.0
# via
# -r requirements/quality.txt
# pytest-cov
# pytest-django
pytest-cov==4.1.0
# via -r requirements/quality.txt
-pytest-django==4.7.0
+pytest-django==4.8.0
# via -r requirements/quality.txt
-python-slugify==8.0.1
+python-slugify==8.0.2
# via
# -r requirements/quality.txt
# code-annotations
-pytz==2023.3.post1
+pytz==2023.4
# via
# -r requirements/quality.txt
# django
@@ -362,7 +362,7 @@ typing-extensions==4.9.0
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.1.0
+urllib3==2.2.0
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 238d728..0d69262 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -34,12 +34,12 @@ click==8.1.7
# edx-django-utils
code-annotations==1.5.0
# via -r requirements/test.txt
-coverage[toml]==7.4.0
+coverage[toml]==7.4.1
# via
# -r requirements/test.txt
# coverage
# pytest-cov
-cryptography==42.0.0
+cryptography==42.0.1
# via
# -r requirements/test.txt
# pyjwt
@@ -90,7 +90,7 @@ edx-django-utils==5.10.1
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.1.2
+edx-drf-extensions==10.1.0
# via -r requirements/test.txt
edx-opaque-keys==2.5.1
# via
@@ -143,7 +143,7 @@ mdurl==0.1.2
# via markdown-it-py
more-itertools==10.2.0
# via jaraco-classes
-newrelic==9.5.0
+newrelic==9.6.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -161,7 +161,7 @@ pbr==6.0.0
# stevedore
pkginfo==1.9.6
# via twine
-pluggy==1.3.0
+pluggy==1.4.0
# via
# -r requirements/test.txt
# pytest
@@ -196,20 +196,20 @@ pynacl==1.5.0
# edx-django-utils
pyproject-hooks==1.0.0
# via build
-pytest==7.4.4
+pytest==8.0.0
# via
# -r requirements/test.txt
# pytest-cov
# pytest-django
pytest-cov==4.1.0
# via -r requirements/test.txt
-pytest-django==4.7.0
+pytest-django==4.8.0
# via -r requirements/test.txt
-python-slugify==8.0.1
+python-slugify==8.0.2
# via
# -r requirements/test.txt
# code-annotations
-pytz==2023.3.post1
+pytz==2023.4
# via
# -r requirements/test.txt
# babel
@@ -299,7 +299,7 @@ typing-extensions==4.9.0
# asgiref
# edx-opaque-keys
# rich
-urllib3==2.1.0
+urllib3==2.2.0
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 1e6c27b..cca8464 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -38,18 +38,18 @@ code-annotations==1.5.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.4.0
+coverage[toml]==7.4.1
# via
# -r requirements/test.txt
# coverage
# pytest-cov
-cryptography==42.0.0
+cryptography==42.0.1
# via
# -r requirements/test.txt
# pyjwt
ddt==1.7.1
# via -r requirements/test.txt
-dill==0.3.7
+dill==0.3.8
# via pylint
django==3.2.23
# via
@@ -87,7 +87,7 @@ edx-django-utils==5.10.1
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.1.2
+edx-drf-extensions==10.1.0
# via -r requirements/test.txt
edx-lint==5.3.6
# via -r requirements/quality.in
@@ -123,7 +123,7 @@ markupsafe==2.1.4
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.5.0
+newrelic==9.6.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -137,7 +137,7 @@ pbr==6.0.0
# stevedore
platformdirs==4.1.0
# via pylint
-pluggy==1.3.0
+pluggy==1.4.0
# via
# -r requirements/test.txt
# pytest
@@ -182,20 +182,20 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==7.4.4
+pytest==8.0.0
# via
# -r requirements/test.txt
# pytest-cov
# pytest-django
pytest-cov==4.1.0
# via -r requirements/test.txt
-pytest-django==4.7.0
+pytest-django==4.8.0
# via -r requirements/test.txt
-python-slugify==8.0.1
+python-slugify==8.0.2
# via
# -r requirements/test.txt
# code-annotations
-pytz==2023.3.post1
+pytz==2023.4
# via
# -r requirements/test.txt
# django
@@ -255,7 +255,7 @@ typing-extensions==4.9.0
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.1.0
+urllib3==2.2.0
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/test.txt b/requirements/test.txt
index 5afd614..0e03b66 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -28,11 +28,11 @@ click==8.1.7
# edx-django-utils
code-annotations==1.5.0
# via -r requirements/test.in
-coverage[toml]==7.4.0
+coverage[toml]==7.4.1
# via
# coverage
# pytest-cov
-cryptography==42.0.0
+cryptography==42.0.1
# via
# -r requirements/base.txt
# pyjwt
@@ -73,7 +73,7 @@ edx-django-utils==5.10.1
# -r requirements/base.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==9.1.2
+edx-drf-extensions==10.1.0
# via -r requirements/base.txt
edx-opaque-keys==2.5.1
# via
@@ -97,7 +97,7 @@ markupsafe==2.1.4
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.5.0
+newrelic==9.6.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -107,7 +107,7 @@ pbr==6.0.0
# via
# -r requirements/base.txt
# stevedore
-pluggy==1.3.0
+pluggy==1.4.0
# via pytest
psutil==5.9.8
# via
@@ -132,17 +132,17 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==7.4.4
+pytest==8.0.0
# via
# pytest-cov
# pytest-django
pytest-cov==4.1.0
# via -r requirements/test.in
-pytest-django==4.7.0
+pytest-django==4.8.0
# via -r requirements/test.in
-python-slugify==8.0.1
+python-slugify==8.0.2
# via code-annotations
-pytz==2023.3.post1
+pytz==2023.4
# via
# -r requirements/base.txt
# django
@@ -189,7 +189,7 @@ typing-extensions==4.9.0
# -r requirements/base.txt
# asgiref
# edx-opaque-keys
-urllib3==2.1.0
+urllib3==2.2.0
# via
# -r requirements/base.txt
# requests
From ab8b9698ad3aa9f642ecbcf0bfbae29d4a1f5c3d Mon Sep 17 00:00:00 2001
From: Michael Roytman
Date: Thu, 1 Feb 2024 11:22:29 -0500
Subject: [PATCH 035/104] feat: add GET endpoint to retrieve whether Learning
Assistant is enabled (#63)
This commit adds a new GET endpoint to retrieve whether the Learning Assistant feature is enabled in a course, given a course ID. This endpoint will introspect all data that is relevant to determining whether the feature is enabled (i.e. the courseware.learning_assistant CourseWaffleFlag and the LearningAssistantCourseEnabled model).
This endpoint was added so that the frontend can determine whether or not to show the Learning Assistant feature without requiring that the edx-platform read from the LearningAssistantCourseEnabled model in this plugin.
---
CHANGELOG.rst | 4 +++
learning_assistant/__init__.py | 2 +-
learning_assistant/urls.py | 9 ++++--
learning_assistant/views.py | 54 +++++++++++++++++++++++++++++++++-
tests/test_views.py | 43 +++++++++++++++++++++++++++
5 files changed, 108 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 5544d2a..122d15b 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,10 @@ Change Log
Unreleased
**********
+3.4.0 - 2024-01-30
+******************
+* Add new GET endpoint to retrieve whether Learning Assistant is enabled in a given course.
+
3.3.0 - 2024-01-30
******************
* Fix release version
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index cc2e729..23a04a2 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '3.3.0'
+__version__ = '3.4.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/urls.py b/learning_assistant/urls.py
index 169d816..a5d3bbe 100644
--- a/learning_assistant/urls.py
+++ b/learning_assistant/urls.py
@@ -4,14 +4,19 @@
from django.urls import re_path
from learning_assistant.constants import COURSE_ID_PATTERN
-from learning_assistant.views import CourseChatView
+from learning_assistant.views import CourseChatView, LearningAssistantEnabledView
app_name = 'learning_assistant'
urlpatterns = [
re_path(
- fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}',
+ fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}$',
CourseChatView.as_view(),
name='chat'
),
+ re_path(
+ fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}/enabled',
+ LearningAssistantEnabledView.as_view(),
+ name='enabled',
+ ),
]
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 00a5edf..360a5a2 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -4,6 +4,7 @@
import logging
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
+from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework import status as http_status
from rest_framework.authentication import SessionAuthentication
@@ -44,7 +45,13 @@ def post(self, request, course_id):
]
}
"""
- courserun_key = CourseKey.from_string(course_id)
+ try:
+ courserun_key = CourseKey.from_string(course_id)
+ except InvalidKeyError:
+ return Response(
+ status=http_status.HTTP_400_BAD_REQUEST,
+ data={'detail': 'Course ID is not a valid course ID.'}
+ )
if not learning_assistant_enabled(courserun_key):
return Response(
@@ -91,3 +98,48 @@ def post(self, request, course_id):
status_code, message = get_chat_response(prompt_template, message_list, course_id)
return Response(status=status_code, data=message)
+
+
+class LearningAssistantEnabledView(APIView):
+ """
+ View to retrieve whether the Learning Assistant is enabled for a course.
+
+ This endpoint returns a boolean representing whether the Learning Assistant feature is enabled in a course
+ represented by the course_key, which is provided in the URL.
+
+ Accepts: [GET]
+
+ Path: /learning_assistant/v1/course_id/{course_key}/enabled
+
+ Parameters:
+ * course_key: the ID of the course
+
+ Responses:
+ * 200: OK
+ * 400: Malformed Request - Course ID is not a valid course ID.
+ """
+
+ authentication_classes = (SessionAuthentication, JwtAuthentication,)
+ permission_classes = (IsAuthenticated,)
+
+ def get(self, request, course_id):
+ """
+ Given a course ID, retrieve whether the Learning Assistant is enabled for the corresponding course.
+
+ The response will be in the following format.
+
+ {'enabled': }
+ """
+ try:
+ courserun_key = CourseKey.from_string(course_id)
+ except InvalidKeyError:
+ return Response(
+ status=http_status.HTTP_400_BAD_REQUEST,
+ data={'detail': 'Course ID is not a valid course ID.'}
+ )
+
+ data = {
+ 'enabled': learning_assistant_enabled(courserun_key),
+ }
+
+ return Response(status=http_status.HTTP_200_OK, data=data)
diff --git a/tests/test_views.py b/tests/test_views.py
index 9f3edc7..24bf4b7 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -6,6 +6,7 @@
from importlib import import_module
from unittest.mock import MagicMock, patch
+import ddt
from django.conf import settings
from django.contrib.auth import get_user_model, login
from django.http import HttpRequest
@@ -81,6 +82,13 @@ def setUp(self):
super().setUp()
self.course_id = 'course-v1:edx+test+23'
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ def test_invalid_course_id(self, mock_learning_assistant_enabled):
+ mock_learning_assistant_enabled.return_value = True
+ response = self.client.get(reverse('enabled', kwargs={'course_id': self.course_id+'+invalid'}))
+
+ self.assertEqual(response.status_code, 400)
+
@patch('learning_assistant.views.learning_assistant_enabled')
def test_course_waffle_inactive(self, mock_waffle):
mock_waffle.return_value = False
@@ -164,3 +172,38 @@ def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle,
render_args = mock_render.call_args.args
self.assertIn(test_unit_id, render_args)
+
+
+@ddt.ddt
+class LearningAssistantEnabledViewTests(LoggedInTestCase):
+ """
+ Test for the LearningAssistantEnabledView
+ """
+ sys.modules['lms.djangoapps.courseware.access'] = MagicMock()
+ sys.modules['lms.djangoapps.courseware.toggles'] = MagicMock()
+ sys.modules['common.djangoapps.course_modes.models'] = MagicMock()
+ sys.modules['common.djangoapps.student.models'] = MagicMock()
+
+ def setUp(self):
+ super().setUp()
+ self.course_id = 'course-v1:edx+test+23'
+
+ @ddt.data(
+ (True, True),
+ (False, False),
+ )
+ @ddt.unpack
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ def test_learning_assistant_enabled(self, mock_value, expected_value, mock_learning_assistant_enabled):
+ mock_learning_assistant_enabled.return_value = mock_value
+ response = self.client.get(reverse('enabled', kwargs={'course_id': self.course_id}))
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.data, {'enabled': expected_value})
+
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ def test_invalid_course_id(self, mock_learning_assistant_enabled):
+ mock_learning_assistant_enabled.return_value = True
+ response = self.client.get(reverse('enabled', kwargs={'course_id': self.course_id+'+invalid'}))
+
+ self.assertEqual(response.status_code, 400)
From b64608fce095e56dd8af08d9f4cb4a1e72a56c56 Mon Sep 17 00:00:00 2001
From: Michael Roytman
Date: Wed, 7 Feb 2024 08:55:12 -0500
Subject: [PATCH 036/104] feat: add CourseApp for Learning Assistant feature
and register as entrypoint (#52)
This commit adds a concrete implementation of the CourseApp ABC from the CourseApps edx-platform Django app. This enables the Learning Assistant to be represented by a card on the Studio Pages & Resources pages. Please see the associated ADR for more details.
This commit also registers this CourseApp class as an entrypoint to the CourseApps edx-platfrom Django app. Because the CourseApps REST API is a part of the CMS, the Learning Assistant plugin was also added as entrypoint to the CMS.
---
.coveragerc | 1 +
docs/decisions/0003-courseapp-use.rst | 4 ++
learning_assistant/__init__.py | 2 +-
learning_assistant/api.py | 23 +++++++
learning_assistant/data.py | 15 +++++
learning_assistant/platform_imports.py | 16 +++++
learning_assistant/plugins.py | 90 ++++++++++++++++++++++++++
learning_assistant/plugins_api.py | 88 +++++++++++++++++++++++++
learning_assistant/utils.py | 13 ++++
learning_assistant/views.py | 4 +-
requirements/base.in | 1 +
requirements/base.txt | 2 +
requirements/dev.txt | 2 +
requirements/doc.txt | 2 +
requirements/quality.txt | 2 +
requirements/test.txt | 2 +
setup.py | 6 ++
tests/test_api.py | 33 ++++++++--
tests/test_plugins_api.py | 85 ++++++++++++++++++++++++
tests/test_utils.py | 13 +++-
20 files changed, 396 insertions(+), 8 deletions(-)
create mode 100644 learning_assistant/data.py
create mode 100644 learning_assistant/plugins.py
create mode 100644 learning_assistant/plugins_api.py
create mode 100644 tests/test_plugins_api.py
diff --git a/.coveragerc b/.coveragerc
index fdefc7e..a519e2a 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -8,3 +8,4 @@ omit =
*admin.py
*static*
*templates*
+ learning_assistant/plugins.py
diff --git a/docs/decisions/0003-courseapp-use.rst b/docs/decisions/0003-courseapp-use.rst
index b19b1a7..eedf67e 100644
--- a/docs/decisions/0003-courseapp-use.rst
+++ b/docs/decisions/0003-courseapp-use.rst
@@ -40,6 +40,10 @@ Consequences
Assistant plugin is installed into ``edx-platform``.
* It will become slightly more difficult to know which ``CourseApps`` are available simply by reading the code, because
this ``CourseApp`` plugin is not stored in the `edx-platform repository`_.
+* Because the `CourseApps` API is registered under the CMS application, the Learning Assistant needs to be registered as
+ a plugin to the CMS as well. Otherwise, the plugin is not included in the CMS application's ``INSTALLED_APPS`` list.
+ This causes a runtime error, because the Learning Assistant CourseApp plugin will refer to the Learning Assistant's
+ models, and this are not available in the CMS if the Learning Assistant plugin is not installed.
Rejected Alternatives
*********************
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 23a04a2..c900846 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '3.4.0'
+__version__ = '3.5.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 7a41996..b5e4a29 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -10,6 +10,7 @@
from opaque_keys.edx.keys import CourseKey
from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, CATEGORY_TYPE_MAP
+from learning_assistant.data import LearningAssistantCourseEnabledData
from learning_assistant.models import LearningAssistantCourseEnabled
from learning_assistant.platform_imports import (
block_get_children,
@@ -139,3 +140,25 @@ def learning_assistant_enabled(course_key):
enabled = True
return learning_assistant_available(course_key) and enabled
+
+
+def set_learning_assistant_enabled(course_key, enabled):
+ """
+ Set whether the Learning Assistant is enabled and return a representation of the created data.
+
+ Arguments:
+ * course_key: (CourseKey): the course's key
+ * enabled (bool): whether the Learning Assistant should be enabled
+
+ Returns:
+ * bool: whether the Learning Assistant is enabled
+ """
+ obj, _ = LearningAssistantCourseEnabled.objects.update_or_create(
+ course_id=course_key,
+ defaults={'enabled': enabled}
+ )
+
+ return LearningAssistantCourseEnabledData(
+ course_key=obj.course_id,
+ enabled=obj.enabled
+ )
diff --git a/learning_assistant/data.py b/learning_assistant/data.py
new file mode 100644
index 0000000..2e89c27
--- /dev/null
+++ b/learning_assistant/data.py
@@ -0,0 +1,15 @@
+"""
+Data classes for the Learning Assistant application.
+"""
+from attrs import field, frozen, validators
+from opaque_keys.edx.keys import CourseKey
+
+
+@frozen
+class LearningAssistantCourseEnabledData:
+ """
+ Data class representing whether Learning Assistant is enabled in a course.
+ """
+
+ course_key: CourseKey = field(validator=validators.instance_of(CourseKey))
+ enabled: bool = field(validator=validators.instance_of(bool))
diff --git a/learning_assistant/platform_imports.py b/learning_assistant/platform_imports.py
index 2c98d3d..d9958fd 100644
--- a/learning_assistant/platform_imports.py
+++ b/learning_assistant/platform_imports.py
@@ -77,3 +77,19 @@ def learning_assistant_available(course_key):
# pylint: disable=import-error, import-outside-toplevel
from lms.djangoapps.courseware.toggles import learning_assistant_is_active
return learning_assistant_is_active(course_key)
+
+
+def get_user_role(user, course_key):
+ """
+ Return the role of the user on the edX platform.
+
+ Arguments:
+ * user (User): the user who's role to get
+ * course_key (CourseKey): the key of the course in which to get the user's role
+
+ Returns:
+ * str: the user's role
+ """
+ # pylint: disable=import-error, import-outside-toplevel
+ from lms.djangoapps.courseware.access import get_user_role as platform_get_user_role
+ return platform_get_user_role(user, course_key)
diff --git a/learning_assistant/plugins.py b/learning_assistant/plugins.py
new file mode 100644
index 0000000..c2f3bfe
--- /dev/null
+++ b/learning_assistant/plugins.py
@@ -0,0 +1,90 @@
+"""
+Plugins for the Learning Assistant application.
+"""
+# pylint: disable=import-error
+from openedx.core.djangoapps.course_apps.plugins import CourseApp
+
+from learning_assistant import plugins_api
+
+
+class LearningAssistantCourseApp(CourseApp):
+ """
+ A CourseApp plugin representing the Learning Assistant feature.
+
+ Please see the associated ADR for more details.
+ """
+
+ app_id = 'learning_assistant'
+ name = 'Learning Assistant'
+ description = 'Use generative AI to power a Learning Assistant using course content.'
+ documentation_links = {
+ 'learn_more_openai': 'https://openai.com/',
+ 'learn_more_openai_data_privacy': 'https://openai.com/api-data-privacy',
+ }
+
+ @classmethod
+ def is_available(cls, course_key):
+ """
+ Return a boolean indicating this course app's availability for a given course.
+
+ If an app is not available, it will not show up in the UI at all for that course,
+ and it will not be possible to enable/disable/configure it.
+
+ Args:
+ course_key (CourseKey): Course key for course whose availability is being checked.
+
+ Returns:
+ bool: Availability status of app.
+ """
+ return plugins_api.is_available(course_key)
+
+ @classmethod
+ def is_enabled(cls, course_key):
+ """
+ Return if this course app is enabled for the provided course.
+
+ Args:
+ course_key (CourseKey): The course key for the course you
+ want to check the status of.
+
+ Returns:
+ bool: The status of the course app for the specified course.
+ """
+ return plugins_api.is_enabled(course_key)
+
+ @classmethod
+ def set_enabled(cls, course_key, enabled, user):
+ """
+ Update the status of this app for the provided course and return the new status.
+
+ Args:
+ course_key (CourseKey): The course key for the course for which the app should be enabled.
+ enabled (bool): The new status of the app.
+ user (User): The user performing this operation.
+
+ Returns:
+ bool: The new status of the course app.
+ """
+ return plugins_api.set_enabled(course_key, enabled, user)
+
+ @classmethod
+ def get_allowed_operations(cls, course_key, user=None):
+ """
+ Return a dictionary of available operations for this app.
+
+ Not all apps will support being configured, and some may support
+ other operations via the UI. This will list, the minimum whether
+ the app can be enabled/disabled and whether it can be configured.
+
+ Args:
+ course_key (CourseKey): The course key for a course.
+ user (User): The user for which the operation is to be tested.
+
+ Returns:
+ A dictionary that has keys like 'enable', 'configure' etc
+ with values indicating whether those operations are allowed.
+
+ get_allowed_operations: function that returns a dictionary of the form
+ {'enable': , 'configure': }.
+ """
+ return plugins_api.get_allowed_operations(course_key, user)
diff --git a/learning_assistant/plugins_api.py b/learning_assistant/plugins_api.py
new file mode 100644
index 0000000..eb334c9
--- /dev/null
+++ b/learning_assistant/plugins_api.py
@@ -0,0 +1,88 @@
+"""
+Concrete implementations of abstract methods of the CourseApp plugin ABC, for use by the LearningAssistantCourseApp.
+
+Because the LearningAssistantCourseApp plugin inherits from the CourseApp class, which is imported from the
+edx-platform, we cannot test that plugin directly, because pytest will run outside the platform context.
+Instead, the CourseApp abstract methods are implemented here and
+imported into and used by the LearningAssistantCourseApp. This way, these implementations can be tested.
+"""
+
+from learning_assistant.api import learning_assistant_enabled, set_learning_assistant_enabled
+from learning_assistant.platform_imports import get_user_role, learning_assistant_available
+from learning_assistant.utils import user_role_is_staff
+
+
+def is_available(course_key):
+ """
+ Return a boolean indicating this course app's availability for a given course.
+
+ If an app is not available, it will not show up in the UI at all for that course,
+ and it will not be possible to enable/disable/configure it.
+
+ Args:
+ course_key (CourseKey): Course key for course whose availability is being checked.
+
+ Returns:
+ bool: Availability status of app.
+ """
+ return learning_assistant_available(course_key)
+
+
+def is_enabled(course_key):
+ """
+ Return if this course app is enabled for the provided course.
+
+ Args:
+ course_key (CourseKey): The course key for the course you
+ want to check the status of.
+
+ Returns:
+ bool: The status of the course app for the specified course.
+ """
+ return learning_assistant_enabled(course_key)
+
+
+# pylint: disable=unused-argument
+def set_enabled(course_key, enabled, user):
+ """
+ Update the status of this app for the provided course and return the new status.
+
+ Args:
+ course_key (CourseKey): The course key for the course for which the app should be enabled.
+ enabled (bool): The new status of the app.
+ user (User): The user performing this operation.
+
+ Returns:
+ bool: The new status of the course app.
+ """
+ obj = set_learning_assistant_enabled(course_key, enabled)
+
+ return obj.enabled
+
+
+def get_allowed_operations(course_key, user=None):
+ """
+ Return a dictionary of available operations for this app.
+
+ Not all apps will support being configured, and some may support
+ other operations via the UI. This will list, the minimum whether
+ the app can be enabled/disabled and whether it can be configured.
+
+ Args:
+ course_key (CourseKey): The course key for a course.
+ user (User): The user for which the operation is to be tested.
+
+ Returns:
+ A dictionary that has keys like 'enable', 'configure' etc
+ with values indicating whether those operations are allowed.
+
+ get_allowed_operations: function that returns a dictionary of the form
+ {'enable': , 'configure': }.
+ """
+ if not user:
+ return {'configure': False, 'enable': False}
+ else:
+ user_role = get_user_role(user, course_key)
+ is_staff = user_role_is_staff(user_role)
+
+ return {'configure': False, 'enable': is_staff}
diff --git a/learning_assistant/utils.py b/learning_assistant/utils.py
index 0acc04c..518fb01 100644
--- a/learning_assistant/utils.py
+++ b/learning_assistant/utils.py
@@ -123,3 +123,16 @@ def get_chat_response(prompt_template, message_list, courserun_id):
chat = 'Completion endpoint is not defined.'
return response_status, chat
+
+
+def user_role_is_staff(role):
+ """
+ Return whether the user role parameter represents that of a staff member.
+
+ Arguments:
+ * role (str): the user's role
+
+ Returns:
+ * bool: whether the user's role is that of a staff member
+ """
+ return role in ('staff', 'instructor')
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 360a5a2..5db882a 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -21,7 +21,7 @@
from learning_assistant.api import learning_assistant_enabled, render_prompt_template
from learning_assistant.serializers import MessageSerializer
-from learning_assistant.utils import get_chat_response
+from learning_assistant.utils import get_chat_response, user_role_is_staff
log = logging.getLogger(__name__)
@@ -65,7 +65,7 @@ def post(self, request, course_id):
enrollment_mode = enrollment_object.mode if enrollment_object else None
if (
(enrollment_mode not in CourseMode.VERIFIED_MODES)
- and user_role not in ('staff', 'instructor')
+ and not user_role_is_staff(user_role)
):
return Response(
status=http_status.HTTP_403_FORBIDDEN,
diff --git a/requirements/base.in b/requirements/base.in
index 52fe316..0fc7801 100644
--- a/requirements/base.in
+++ b/requirements/base.in
@@ -1,6 +1,7 @@
# Core requirements for using this application
-c constraints.txt
+attrs
Django # Web application framework
django-model-utils
djangorestframework
diff --git a/requirements/base.txt b/requirements/base.txt
index f9c9994..f6d14c2 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -6,6 +6,8 @@
#
asgiref==3.7.2
# via django
+attrs==23.2.0
+ # via -r requirements/base.in
certifi==2023.11.17
# via requests
cffi==1.16.0
diff --git a/requirements/dev.txt b/requirements/dev.txt
index fc302b2..1473fcf 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -13,6 +13,8 @@ astroid==3.0.2
# -r requirements/quality.txt
# pylint
# pylint-celery
+attrs==23.2.0
+ # via -r requirements/quality.txt
build==1.0.3
# via
# -r requirements/pip-tools.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 0d69262..39f0fc2 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -10,6 +10,8 @@ asgiref==3.7.2
# via
# -r requirements/test.txt
# django
+attrs==23.2.0
+ # via -r requirements/test.txt
babel==2.14.0
# via sphinx
build==1.0.3
diff --git a/requirements/quality.txt b/requirements/quality.txt
index cca8464..6bfae66 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -12,6 +12,8 @@ astroid==3.0.2
# via
# pylint
# pylint-celery
+attrs==23.2.0
+ # via -r requirements/test.txt
certifi==2023.11.17
# via
# -r requirements/test.txt
diff --git a/requirements/test.txt b/requirements/test.txt
index 0e03b66..33b6add 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -8,6 +8,8 @@ asgiref==3.7.2
# via
# -r requirements/base.txt
# django
+attrs==23.2.0
+ # via -r requirements/base.txt
certifi==2023.11.17
# via
# -r requirements/base.txt
diff --git a/setup.py b/setup.py
index 80b41a2..817fad1 100755
--- a/setup.py
+++ b/setup.py
@@ -131,6 +131,12 @@ def is_requirement(line):
entry_points={
'lms.djangoapp': [
"learning_assistant = learning_assistant.apps:LearningAssistantConfig"
+ ],
+ 'cms.djangoapp': [
+ "learning_assistant = learning_assistant.apps:LearningAssistantConfig"
+ ],
+ 'openedx.course_app': [
+ "learning_assistant = learning_assistant.plugins:LearningAssistantCourseApp",
]
}
)
diff --git a/tests/test_api.py b/tests/test_api.py
index 1ddab3f..3dbadc4 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,6 +1,7 @@
"""
Test cases for the learning-assistant api module.
"""
+import itertools
from unittest.mock import MagicMock, patch
import ddt
@@ -15,7 +16,9 @@
get_block_content,
learning_assistant_enabled,
render_prompt_template,
+ set_learning_assistant_enabled,
)
+from learning_assistant.data import LearningAssistantCourseEnabledData
from learning_assistant.models import LearningAssistantCourseEnabled
fake_transcript = 'This is the text version from the transcript'
@@ -232,12 +235,34 @@ def test_learning_assistant_enabled(
learning_assistant_available_mock.return_value = learning_assistant_available_value
if obj_exists:
- LearningAssistantCourseEnabled.objects.update_or_create(
- course_id=self.course_key,
- defaults={'enabled': obj_value}
- )
+ set_learning_assistant_enabled(self.course_key, obj_value)
self.assertEqual(
learning_assistant_enabled(self.course_key),
expected_value
)
+
+ @ddt.idata(itertools.product((True, False), (True, False)))
+ @ddt.unpack
+ def test_set_learning_assistant_enabled(self, obj_exists, obj_value):
+ if obj_exists:
+ LearningAssistantCourseEnabled.objects.create(
+ course_id=self.course_key,
+ # Set the opposite of the desired end value to test that it is changed properly.
+ enabled=not obj_value,
+ )
+
+ expected_value = LearningAssistantCourseEnabledData(
+ self.course_key,
+ obj_value,
+ )
+
+ return_value = set_learning_assistant_enabled(self.course_key, obj_value)
+
+ self.assertEqual(
+ return_value,
+ expected_value,
+ )
+
+ obj = LearningAssistantCourseEnabled.objects.get(course_id=self.course_key)
+ self.assertEqual(obj.enabled, obj_value)
diff --git a/tests/test_plugins_api.py b/tests/test_plugins_api.py
new file mode 100644
index 0000000..f7f14a2
--- /dev/null
+++ b/tests/test_plugins_api.py
@@ -0,0 +1,85 @@
+"""
+Test cases for the learning-assistant plugins module.
+"""
+from unittest.mock import patch
+
+import ddt
+from django.contrib.auth import get_user_model
+from django.test import TestCase
+from opaque_keys.edx.keys import CourseKey
+
+from learning_assistant.models import LearningAssistantCourseEnabled
+from learning_assistant.plugins_api import get_allowed_operations, is_available, is_enabled, set_enabled
+
+User = get_user_model()
+
+
+@ddt.ddt
+class PluginApiTests(TestCase):
+ """
+ Test suite for the plugins_api module.
+ """
+ def setUp(self):
+ super().setUp()
+ self.course_key = CourseKey.from_string('course-v1:edx+fake+1')
+ self.user = User(username='tester', email='tester@test.com')
+
+ @ddt.data(True, False)
+ @patch('learning_assistant.plugins_api.learning_assistant_available')
+ def test_is_available(self, is_available_value, learning_assistant_available_mock):
+ """
+ Test the is_available function of the plugins_api module.
+ """
+ learning_assistant_available_mock.return_value = is_available_value
+ self.assertEqual(is_available(self.course_key), is_available_value)
+
+ @ddt.data(True, False)
+ @patch('learning_assistant.plugins_api.learning_assistant_enabled')
+ def test_is_enabled(self, is_enabled_value, learning_assistant_enabled_mock):
+ """
+ Test the is_enabled function of the plugins_api module.
+ """
+ learning_assistant_enabled_mock.return_value = is_enabled_value
+ self.assertEqual(is_enabled(self.course_key), is_enabled_value)
+
+ @ddt.data(True, False)
+ def test_set_enabled_create(self, enabled_value):
+ """
+ Test the set_enabled function of the plugins_api module when a create should occur.
+ """
+ self.assertEqual(set_enabled(self.course_key, enabled_value, self.user), enabled_value)
+
+ @ddt.data(True, False)
+ def test_set_enabled_update(self, enabled_value):
+ """
+ Test the set_enabled function of the plugins_api module when an update should occur.
+ """
+ LearningAssistantCourseEnabled.objects.create(
+ course_id=self.course_key,
+ enabled=enabled_value
+ )
+
+ self.assertEqual(set_enabled(self.course_key, enabled_value, self.user), enabled_value)
+
+ def test_get_allowed_operations_no_user(self):
+ """
+ Test the get_allowed_operations function of the plugins_api module when no user is passed as an argument.
+ """
+ self.assertEqual(
+ get_allowed_operations(self.course_key),
+ {'configure': False, 'enable': False}
+ )
+
+ @ddt.unpack
+ @ddt.data(('instructor', True), ('staff', True), ('student', False))
+ @patch('learning_assistant.plugins_api.get_user_role')
+ def test_get_allowed_operations(self, role_value, is_staff_value, get_user_role_mock):
+ """
+ Test the get_allowed_operations function of the plugins_api module.
+ """
+ get_user_role_mock.return_value = role_value
+
+ self.assertEqual(
+ get_allowed_operations(self.course_key, self.user),
+ {'configure': False, 'enable': is_staff_value}
+ )
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 4adcf76..a410c1f 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -10,7 +10,7 @@
from django.test import TestCase, override_settings
from requests.exceptions import ConnectTimeout
-from learning_assistant.utils import get_chat_response, get_reduced_message_list
+from learning_assistant.utils import get_chat_response, get_reduced_message_list, user_role_is_staff
@ddt.ddt
@@ -142,3 +142,14 @@ def test_message_list(self):
reduced_message_list = get_reduced_message_list(self.prompt_template, self.message_list)
self.assertEqual(len(reduced_message_list), 2)
self.assertEqual(reduced_message_list, self.message_list)
+
+
+@ddt.ddt
+class UserRoleIsStaffTests(TestCase):
+ """
+ Tests for the user_role_is_staff helper function.
+ """
+ @ddt.data(('instructor', True), ('staff', True), ('student', False))
+ @ddt.unpack
+ def test_user_role_is_staff(self, role, expected_value):
+ self.assertEqual(user_role_is_staff(role), expected_value)
From 9ed1be30659d44578e6bca082fbe052fc062eecd Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Wed, 7 Feb 2024 11:26:40 -0500
Subject: [PATCH 037/104] chore: Updating Python Requirements (#65)
---
requirements/base.txt | 12 ++++++------
requirements/ci.txt | 4 ++--
requirements/dev.txt | 22 +++++++++++-----------
requirements/doc.txt | 16 ++++++++--------
requirements/pip.txt | 2 +-
requirements/quality.txt | 20 ++++++++++----------
requirements/test.txt | 14 +++++++-------
7 files changed, 45 insertions(+), 45 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index f6d14c2..65dd86a 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -8,7 +8,7 @@ asgiref==3.7.2
# via django
attrs==23.2.0
# via -r requirements/base.in
-certifi==2023.11.17
+certifi==2024.2.2
# via requests
cffi==1.16.0
# via
@@ -18,9 +18,9 @@ charset-normalizer==3.3.2
# via requests
click==8.1.7
# via edx-django-utils
-cryptography==42.0.1
+cryptography==42.0.2
# via pyjwt
-django==3.2.23
+django==3.2.24
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/base.in
@@ -50,7 +50,7 @@ edx-django-utils==5.10.1
# via
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.1.0
+edx-drf-extensions==10.2.0
# via -r requirements/base.in
edx-opaque-keys==2.5.1
# via
@@ -62,7 +62,7 @@ idna==3.6
# via requests
jinja2==3.1.3
# via -r requirements/base.in
-markupsafe==2.1.4
+markupsafe==2.1.5
# via jinja2
newrelic==9.6.0
# via edx-django-utils
@@ -82,7 +82,7 @@ pymongo==3.13.0
# via edx-opaque-keys
pynacl==1.5.0
# via edx-django-utils
-pytz==2023.4
+pytz==2024.1
# via
# django
# djangorestframework
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 1a344f5..3501245 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -6,7 +6,7 @@
#
cachetools==5.3.2
# via tox
-certifi==2023.11.17
+certifi==2024.2.2
# via requests
chardet==5.2.0
# via tox
@@ -30,7 +30,7 @@ packaging==23.2
# via
# pyproject-api
# tox
-platformdirs==4.1.0
+platformdirs==4.2.0
# via
# tox
# virtualenv
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 1473fcf..eec9fd2 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -8,7 +8,7 @@ asgiref==3.7.2
# via
# -r requirements/quality.txt
# django
-astroid==3.0.2
+astroid==3.0.3
# via
# -r requirements/quality.txt
# pylint
@@ -23,7 +23,7 @@ cachetools==5.3.2
# via
# -r requirements/ci.txt
# tox
-certifi==2023.11.17
+certifi==2024.2.2
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -56,7 +56,7 @@ click-log==0.4.0
# via
# -r requirements/quality.txt
# edx-lint
-code-annotations==1.5.0
+code-annotations==1.6.0
# via
# -r requirements/quality.txt
# edx-lint
@@ -73,7 +73,7 @@ coverage[toml]==7.4.1
# codecov
# coverage
# pytest-cov
-cryptography==42.0.1
+cryptography==42.0.2
# via
# -r requirements/quality.txt
# pyjwt
@@ -89,7 +89,7 @@ distlib==0.3.8
# via
# -r requirements/ci.txt
# virtualenv
-django==3.2.23
+django==3.2.24
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/quality.txt
@@ -126,7 +126,7 @@ edx-django-utils==5.10.1
# -r requirements/quality.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.1.0
+edx-drf-extensions==10.2.0
# via -r requirements/quality.txt
edx-i18n-tools==1.3.0
# via -r requirements/dev.in
@@ -171,7 +171,7 @@ jinja2==3.1.3
# diff-cover
lxml==5.1.0
# via edx-i18n-tools
-markupsafe==2.1.4
+markupsafe==2.1.5
# via
# -r requirements/quality.txt
# jinja2
@@ -192,7 +192,7 @@ packaging==23.2
# pyproject-api
# pytest
# tox
-path==16.9.0
+path==16.10.0
# via edx-i18n-tools
pbr==6.0.0
# via
@@ -200,7 +200,7 @@ pbr==6.0.0
# stevedore
pip-tools==7.3.0
# via -r requirements/pip-tools.txt
-platformdirs==4.1.0
+platformdirs==4.2.0
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -282,11 +282,11 @@ pytest-cov==4.1.0
# via -r requirements/quality.txt
pytest-django==4.8.0
# via -r requirements/quality.txt
-python-slugify==8.0.2
+python-slugify==8.0.3
# via
# -r requirements/quality.txt
# code-annotations
-pytz==2023.4
+pytz==2024.1
# via
# -r requirements/quality.txt
# django
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 39f0fc2..b939995 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -16,7 +16,7 @@ babel==2.14.0
# via sphinx
build==1.0.3
# via -r requirements/doc.in
-certifi==2023.11.17
+certifi==2024.2.2
# via
# -r requirements/test.txt
# requests
@@ -34,21 +34,21 @@ click==8.1.7
# -r requirements/test.txt
# code-annotations
# edx-django-utils
-code-annotations==1.5.0
+code-annotations==1.6.0
# via -r requirements/test.txt
coverage[toml]==7.4.1
# via
# -r requirements/test.txt
# coverage
# pytest-cov
-cryptography==42.0.1
+cryptography==42.0.2
# via
# -r requirements/test.txt
# pyjwt
# secretstorage
ddt==1.7.1
# via -r requirements/test.txt
-django==3.2.23
+django==3.2.24
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -92,7 +92,7 @@ edx-django-utils==5.10.1
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.1.0
+edx-drf-extensions==10.2.0
# via -r requirements/test.txt
edx-opaque-keys==2.5.1
# via
@@ -137,7 +137,7 @@ keyring==24.3.0
# via twine
markdown-it-py==3.0.0
# via rich
-markupsafe==2.1.4
+markupsafe==2.1.5
# via
# -r requirements/test.txt
# jinja2
@@ -207,11 +207,11 @@ pytest-cov==4.1.0
# via -r requirements/test.txt
pytest-django==4.8.0
# via -r requirements/test.txt
-python-slugify==8.0.2
+python-slugify==8.0.3
# via
# -r requirements/test.txt
# code-annotations
-pytz==2023.4
+pytz==2024.1
# via
# -r requirements/test.txt
# babel
diff --git a/requirements/pip.txt b/requirements/pip.txt
index a4cf530..dfa2b77 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -8,7 +8,7 @@ wheel==0.42.0
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
-pip==23.3.2
+pip==24.0
# via -r requirements/pip.in
setuptools==69.0.3
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 6bfae66..7ef7483 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -8,13 +8,13 @@ asgiref==3.7.2
# via
# -r requirements/test.txt
# django
-astroid==3.0.2
+astroid==3.0.3
# via
# pylint
# pylint-celery
attrs==23.2.0
# via -r requirements/test.txt
-certifi==2023.11.17
+certifi==2024.2.2
# via
# -r requirements/test.txt
# requests
@@ -36,7 +36,7 @@ click==8.1.7
# edx-lint
click-log==0.4.0
# via edx-lint
-code-annotations==1.5.0
+code-annotations==1.6.0
# via
# -r requirements/test.txt
# edx-lint
@@ -45,7 +45,7 @@ coverage[toml]==7.4.1
# -r requirements/test.txt
# coverage
# pytest-cov
-cryptography==42.0.1
+cryptography==42.0.2
# via
# -r requirements/test.txt
# pyjwt
@@ -53,7 +53,7 @@ ddt==1.7.1
# via -r requirements/test.txt
dill==0.3.8
# via pylint
-django==3.2.23
+django==3.2.24
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -89,7 +89,7 @@ edx-django-utils==5.10.1
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.1.0
+edx-drf-extensions==10.2.0
# via -r requirements/test.txt
edx-lint==5.3.6
# via -r requirements/quality.in
@@ -119,7 +119,7 @@ jinja2==3.1.3
# via
# -r requirements/test.txt
# code-annotations
-markupsafe==2.1.4
+markupsafe==2.1.5
# via
# -r requirements/test.txt
# jinja2
@@ -137,7 +137,7 @@ pbr==6.0.0
# via
# -r requirements/test.txt
# stevedore
-platformdirs==4.1.0
+platformdirs==4.2.0
# via pylint
pluggy==1.4.0
# via
@@ -193,11 +193,11 @@ pytest-cov==4.1.0
# via -r requirements/test.txt
pytest-django==4.8.0
# via -r requirements/test.txt
-python-slugify==8.0.2
+python-slugify==8.0.3
# via
# -r requirements/test.txt
# code-annotations
-pytz==2023.4
+pytz==2024.1
# via
# -r requirements/test.txt
# django
diff --git a/requirements/test.txt b/requirements/test.txt
index 33b6add..9391959 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -10,7 +10,7 @@ asgiref==3.7.2
# django
attrs==23.2.0
# via -r requirements/base.txt
-certifi==2023.11.17
+certifi==2024.2.2
# via
# -r requirements/base.txt
# requests
@@ -28,13 +28,13 @@ click==8.1.7
# -r requirements/base.txt
# code-annotations
# edx-django-utils
-code-annotations==1.5.0
+code-annotations==1.6.0
# via -r requirements/test.in
coverage[toml]==7.4.1
# via
# coverage
# pytest-cov
-cryptography==42.0.1
+cryptography==42.0.2
# via
# -r requirements/base.txt
# pyjwt
@@ -75,7 +75,7 @@ edx-django-utils==5.10.1
# -r requirements/base.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.1.0
+edx-drf-extensions==10.2.0
# via -r requirements/base.txt
edx-opaque-keys==2.5.1
# via
@@ -95,7 +95,7 @@ jinja2==3.1.3
# via
# -r requirements/base.txt
# code-annotations
-markupsafe==2.1.4
+markupsafe==2.1.5
# via
# -r requirements/base.txt
# jinja2
@@ -142,9 +142,9 @@ pytest-cov==4.1.0
# via -r requirements/test.in
pytest-django==4.8.0
# via -r requirements/test.in
-python-slugify==8.0.2
+python-slugify==8.0.3
# via code-annotations
-pytz==2023.4
+pytz==2024.1
# via
# -r requirements/base.txt
# django
From 9c016d639cc76eddd3bf3cfa06da08dd1e95f120 Mon Sep 17 00:00:00 2001
From: Alison Langston <46360176+alangsto@users.noreply.github.com>
Date: Wed, 14 Feb 2024 16:33:44 -0500
Subject: [PATCH 038/104] feat: replace waffle flag with setting (#67)
---
CHANGELOG.rst | 4 ++++
learning_assistant/__init__.py | 2 +-
learning_assistant/api.py | 9 ++++++++-
learning_assistant/platform_imports.py | 2 +-
learning_assistant/plugins_api.py | 11 ++++++++---
test_settings.py | 2 ++
tests/test_api.py | 21 +++++++++++++++++++--
7 files changed, 43 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 122d15b..7ecd9b8 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,10 @@ Change Log
Unreleased
**********
+3.6.0 - 2024-02-13
+******************
+* Enable backend access by course waffle flag or django setting.
+
3.4.0 - 2024-01-30
******************
* Add new GET endpoint to retrieve whether Learning Assistant is enabled in a given course.
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index c900846..67b85a7 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '3.5.0'
+__version__ = '3.6.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index b5e4a29..ed85d6c 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -17,7 +17,7 @@
block_leaf_filter,
get_single_block,
get_text_transcript,
- learning_assistant_available,
+ learning_assistant_available_flag,
traverse_block_pre_order,
)
from learning_assistant.text_utils import html_to_text
@@ -118,6 +118,13 @@ def render_prompt_template(request, user_id, course_id, unit_usage_key):
return data
+def learning_assistant_available(course_key):
+ """
+ Return whether or not the learning assistant is available via django setting or course waffle flag.
+ """
+ return getattr(settings, 'LEARNING_ASSISTANT_AVAILABLE', False) or learning_assistant_available_flag(course_key)
+
+
def learning_assistant_enabled(course_key):
"""
Return whether the Learning Assistant is enabled in the course represented by the course_key.
diff --git a/learning_assistant/platform_imports.py b/learning_assistant/platform_imports.py
index d9958fd..2e18d89 100644
--- a/learning_assistant/platform_imports.py
+++ b/learning_assistant/platform_imports.py
@@ -59,7 +59,7 @@ def get_cache_course_run_data(course_run_id, fields):
return get_course_run_data(course_run_id, fields)
-def learning_assistant_available(course_key):
+def learning_assistant_available_flag(course_key):
"""
Return whether the Learning Assistant is available in the course represented by the course_key.
diff --git a/learning_assistant/plugins_api.py b/learning_assistant/plugins_api.py
index eb334c9..fc03e41 100644
--- a/learning_assistant/plugins_api.py
+++ b/learning_assistant/plugins_api.py
@@ -7,8 +7,12 @@
imported into and used by the LearningAssistantCourseApp. This way, these implementations can be tested.
"""
-from learning_assistant.api import learning_assistant_enabled, set_learning_assistant_enabled
-from learning_assistant.platform_imports import get_user_role, learning_assistant_available
+from learning_assistant.api import (
+ learning_assistant_available,
+ learning_assistant_enabled,
+ set_learning_assistant_enabled,
+)
+from learning_assistant.platform_imports import get_user_role
from learning_assistant.utils import user_role_is_staff
@@ -17,7 +21,8 @@ def is_available(course_key):
Return a boolean indicating this course app's availability for a given course.
If an app is not available, it will not show up in the UI at all for that course,
- and it will not be possible to enable/disable/configure it.
+ and it will not be possible to enable/disable/configure it, unless the platform wide setting
+ LEARNING_ASSISTANT_AVAILABLE is set to True.
Args:
course_key (CourseKey): Course key for course whose availability is being checked.
diff --git a/test_settings.py b/test_settings.py
index f201fd2..0302e54 100644
--- a/test_settings.py
+++ b/test_settings.py
@@ -84,3 +84,5 @@ def root(*args):
"\""
"{% endif %}"
)
+
+LEARNING_ASSISTANT_AVAILABLE = True
diff --git a/tests/test_api.py b/tests/test_api.py
index 3dbadc4..121f488 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -6,7 +6,7 @@
import ddt
from django.core.cache import cache
-from django.test import TestCase
+from django.test import TestCase, override_settings
from opaque_keys.edx.keys import CourseKey, UsageKey
from learning_assistant.api import (
@@ -14,6 +14,7 @@
_get_children_contents,
_leaf_filter,
get_block_content,
+ learning_assistant_available,
learning_assistant_enabled,
render_prompt_template,
set_learning_assistant_enabled,
@@ -206,7 +207,7 @@ def test_render_prompt_template(self, unit_content, flag_enabled, mock_get_conte
@ddt.ddt
class LearningAssistantCourseEnabledApiTests(TestCase):
"""
- Test suite for the learning_assistant_enabled and set_learning_assistant_enalbed api functions.
+ Test suite for learning_assistant_available, learning_assistant_enabled, and set_learning_assistant_enabled.
"""
def setUp(self):
super().setUp()
@@ -266,3 +267,19 @@ def test_set_learning_assistant_enabled(self, obj_exists, obj_value):
obj = LearningAssistantCourseEnabled.objects.get(course_id=self.course_key)
self.assertEqual(obj.enabled, obj_value)
+
+ @ddt.idata(itertools.product((True, False), (True, False)))
+ @ddt.unpack
+ @patch('learning_assistant.api.learning_assistant_available_flag')
+ def test_learning_assistant_available(
+ self,
+ learning_assistant_available_flag_value,
+ learning_assistant_available_setting_value,
+ learning_assistant_available_flag_mock
+ ):
+ learning_assistant_available_flag_mock.return_value = learning_assistant_available_flag_value
+ with override_settings(LEARNING_ASSISTANT_AVAILABLE=learning_assistant_available_setting_value):
+ return_value = learning_assistant_available(self.course_key)
+
+ expected_value = learning_assistant_available_setting_value or learning_assistant_available_flag_value
+ self.assertEqual(return_value, expected_value)
From 58cec8875b92b4eb96edc5169ba97723105d9e4b Mon Sep 17 00:00:00 2001
From: zubairshakoorarbisoft
Date: Mon, 18 Dec 2023 21:17:37 +0500
Subject: [PATCH 039/104] fix: codecov pkg removed
---
requirements/ci.in | 1 -
1 file changed, 1 deletion(-)
diff --git a/requirements/ci.in b/requirements/ci.in
index 27389ff..3586cbe 100644
--- a/requirements/ci.in
+++ b/requirements/ci.in
@@ -2,5 +2,4 @@
-c constraints.txt
-codecov # Code coverage reporting
tox # Virtualenv management for tests
From c69e96bb2e7e91a3f08d7ecc330e7b6db66b36a5 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Thu, 15 Feb 2024 10:25:10 -0500
Subject: [PATCH 040/104] chore: Updating Python Requirements
---
requirements/base.txt | 2 +-
requirements/ci.txt | 14 --------------
requirements/dev.txt | 16 +++-------------
requirements/doc.txt | 10 +++++-----
requirements/pip.txt | 2 +-
requirements/quality.txt | 6 +++---
requirements/test.txt | 6 +++---
7 files changed, 16 insertions(+), 40 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 65dd86a..88a79bf 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -33,7 +33,7 @@ django==3.2.24
# edx-drf-extensions
django-crum==0.7.9
# via edx-django-utils
-django-model-utils==4.3.1
+django-model-utils==4.4.0
# via -r requirements/base.in
django-waffle==4.1.0
# via
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 3501245..742e39c 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -6,26 +6,16 @@
#
cachetools==5.3.2
# via tox
-certifi==2024.2.2
- # via requests
chardet==5.2.0
# via tox
-charset-normalizer==3.3.2
- # via requests
-codecov==2.1.13
- # via -r requirements/ci.in
colorama==0.4.6
# via tox
-coverage==7.4.1
- # via codecov
distlib==0.3.8
# via virtualenv
filelock==3.13.1
# via
# tox
# virtualenv
-idna==3.6
- # via requests
packaging==23.2
# via
# pyproject-api
@@ -38,15 +28,11 @@ pluggy==1.4.0
# via tox
pyproject-api==1.6.1
# via tox
-requests==2.31.0
- # via codecov
tomli==2.0.1
# via
# pyproject-api
# tox
tox==4.12.1
# via -r requirements/ci.in
-urllib3==2.2.0
- # via requests
virtualenv==20.25.0
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index eec9fd2..48a017f 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -25,7 +25,6 @@ cachetools==5.3.2
# tox
certifi==2024.2.2
# via
- # -r requirements/ci.txt
# -r requirements/quality.txt
# requests
cffi==1.16.0
@@ -40,7 +39,6 @@ chardet==5.2.0
# tox
charset-normalizer==3.3.2
# via
- # -r requirements/ci.txt
# -r requirements/quality.txt
# requests
click==8.1.7
@@ -60,17 +58,13 @@ code-annotations==1.6.0
# via
# -r requirements/quality.txt
# edx-lint
-codecov==2.1.13
- # via -r requirements/ci.txt
colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
coverage[toml]==7.4.1
# via
- # -r requirements/ci.txt
# -r requirements/quality.txt
- # codecov
# coverage
# pytest-cov
cryptography==42.0.2
@@ -105,7 +99,7 @@ django-crum==0.7.9
# via
# -r requirements/quality.txt
# edx-django-utils
-django-model-utils==4.3.1
+django-model-utils==4.4.0
# via -r requirements/quality.txt
django-waffle==4.1.0
# via
@@ -149,7 +143,6 @@ filelock==3.13.1
# virtualenv
idna==3.6
# via
- # -r requirements/ci.txt
# -r requirements/quality.txt
# requests
importlib-metadata==7.0.1
@@ -282,7 +275,7 @@ pytest-cov==4.1.0
# via -r requirements/quality.txt
pytest-django==4.8.0
# via -r requirements/quality.txt
-python-slugify==8.0.3
+python-slugify==8.0.4
# via
# -r requirements/quality.txt
# code-annotations
@@ -299,14 +292,12 @@ pyyaml==6.0.1
# responses
requests==2.31.0
# via
- # -r requirements/ci.txt
# -r requirements/quality.txt
- # codecov
# edx-drf-extensions
# edx-rest-api-client
# responses
# slumber
-responses==0.24.1
+responses==0.25.0
# via -r requirements/quality.txt
semantic-version==2.10.0
# via
@@ -366,7 +357,6 @@ typing-extensions==4.9.0
# pylint
urllib3==2.2.0
# via
- # -r requirements/ci.txt
# -r requirements/quality.txt
# requests
# responses
diff --git a/requirements/doc.txt b/requirements/doc.txt
index b939995..20a6b4b 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -63,7 +63,7 @@ django-crum==0.7.9
# via
# -r requirements/test.txt
# edx-django-utils
-django-model-utils==4.3.1
+django-model-utils==4.4.0
# via -r requirements/test.txt
django-waffle==4.1.0
# via
@@ -122,7 +122,7 @@ iniconfig==2.0.0
# via
# -r requirements/test.txt
# pytest
-jaraco-classes==3.3.0
+jaraco-classes==3.3.1
# via keyring
jeepney==0.8.0
# via
@@ -207,7 +207,7 @@ pytest-cov==4.1.0
# via -r requirements/test.txt
pytest-django==4.8.0
# via -r requirements/test.txt
-python-slugify==8.0.3
+python-slugify==8.0.4
# via
# -r requirements/test.txt
# code-annotations
@@ -236,7 +236,7 @@ requests==2.31.0
# twine
requests-toolbelt==1.0.0
# via twine
-responses==0.24.1
+responses==0.25.0
# via -r requirements/test.txt
restructuredtext-lint==1.4.0
# via doc8
@@ -293,7 +293,7 @@ tomli==2.0.1
# doc8
# pyproject-hooks
# pytest
-twine==4.0.2
+twine==5.0.0
# via -r requirements/doc.in
typing-extensions==4.9.0
# via
diff --git a/requirements/pip.txt b/requirements/pip.txt
index dfa2b77..71954cc 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -10,5 +10,5 @@ wheel==0.42.0
# The following packages are considered to be unsafe in a requirements file:
pip==24.0
# via -r requirements/pip.in
-setuptools==69.0.3
+setuptools==69.1.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 7ef7483..0c70399 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -68,7 +68,7 @@ django-crum==0.7.9
# via
# -r requirements/test.txt
# edx-django-utils
-django-model-utils==4.3.1
+django-model-utils==4.4.0
# via -r requirements/test.txt
django-waffle==4.1.0
# via
@@ -193,7 +193,7 @@ pytest-cov==4.1.0
# via -r requirements/test.txt
pytest-django==4.8.0
# via -r requirements/test.txt
-python-slugify==8.0.3
+python-slugify==8.0.4
# via
# -r requirements/test.txt
# code-annotations
@@ -214,7 +214,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.24.1
+responses==0.25.0
# via -r requirements/test.txt
semantic-version==2.10.0
# via
diff --git a/requirements/test.txt b/requirements/test.txt
index 9391959..ad06fe2 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -54,7 +54,7 @@ django-crum==0.7.9
# via
# -r requirements/base.txt
# edx-django-utils
-django-model-utils==4.3.1
+django-model-utils==4.4.0
# via -r requirements/base.txt
django-waffle==4.1.0
# via
@@ -142,7 +142,7 @@ pytest-cov==4.1.0
# via -r requirements/test.in
pytest-django==4.8.0
# via -r requirements/test.in
-python-slugify==8.0.3
+python-slugify==8.0.4
# via code-annotations
pytz==2024.1
# via
@@ -160,7 +160,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.24.1
+responses==0.25.0
# via -r requirements/test.in
semantic-version==2.10.0
# via
From fddf0bc27016bd4a1cabf82de7bcb80b51f3763b Mon Sep 17 00:00:00 2001
From: Alison Langston <46360176+alangsto@users.noreply.github.com>
Date: Wed, 21 Feb 2024 15:20:54 -0500
Subject: [PATCH 041/104] feat: remove use of course waffle flag (#70)
---
CHANGELOG.rst | 5 +++++
learning_assistant/__init__.py | 2 +-
learning_assistant/api.py | 7 +++----
learning_assistant/platform_imports.py | 20 --------------------
learning_assistant/plugins.py | 4 ++--
learning_assistant/plugins_api.py | 4 ++--
tests/test_api.py | 19 +++++++------------
tests/test_plugins_api.py | 2 +-
8 files changed, 21 insertions(+), 42 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 7ecd9b8..0c3b823 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,11 @@ Change Log
Unreleased
**********
+4.0.0 - 2024-02-21
+******************
+* Remove use of course waffle flag. Use the django setting LEARNING_ASSISTANT_AVAILABLE
+ to enable the learning assistant feature.
+
3.6.0 - 2024-02-13
******************
* Enable backend access by course waffle flag or django setting.
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 67b85a7..b83582f 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '3.6.0'
+__version__ = '4.0.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index ed85d6c..8b1a2f5 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -17,7 +17,6 @@
block_leaf_filter,
get_single_block,
get_text_transcript,
- learning_assistant_available_flag,
traverse_block_pre_order,
)
from learning_assistant.text_utils import html_to_text
@@ -118,11 +117,11 @@ def render_prompt_template(request, user_id, course_id, unit_usage_key):
return data
-def learning_assistant_available(course_key):
+def learning_assistant_available():
"""
Return whether or not the learning assistant is available via django setting or course waffle flag.
"""
- return getattr(settings, 'LEARNING_ASSISTANT_AVAILABLE', False) or learning_assistant_available_flag(course_key)
+ return getattr(settings, 'LEARNING_ASSISTANT_AVAILABLE', False)
def learning_assistant_enabled(course_key):
@@ -146,7 +145,7 @@ def learning_assistant_enabled(course_key):
# Currently, the Learning Assistant defaults to enabled if there is no override.
enabled = True
- return learning_assistant_available(course_key) and enabled
+ return learning_assistant_available() and enabled
def set_learning_assistant_enabled(course_key, enabled):
diff --git a/learning_assistant/platform_imports.py b/learning_assistant/platform_imports.py
index 2e18d89..1d18664 100644
--- a/learning_assistant/platform_imports.py
+++ b/learning_assistant/platform_imports.py
@@ -59,26 +59,6 @@ def get_cache_course_run_data(course_run_id, fields):
return get_course_run_data(course_run_id, fields)
-def learning_assistant_available_flag(course_key):
- """
- Return whether the Learning Assistant is available in the course represented by the course_key.
-
- Note that this may be different than whether the Learning Assistant is enabled in the course. The value returned by
- this fuction represents whether the Learning Assistant is available in the course and, perhaps, whether it is
- enabled. Course teams can disable the Learning Assistant via the LearningAssistantCourseEnabled model, so, in those
- cases, the Learning Assistant may be available and disabled.
-
- Arguments:
- * course_key (CourseKey): the course's key
-
- Returns:
- * bool: whether the Learning Assistant feature is available
- """
- # pylint: disable=import-error, import-outside-toplevel
- from lms.djangoapps.courseware.toggles import learning_assistant_is_active
- return learning_assistant_is_active(course_key)
-
-
def get_user_role(user, course_key):
"""
Return the role of the user on the edX platform.
diff --git a/learning_assistant/plugins.py b/learning_assistant/plugins.py
index c2f3bfe..87a4e6b 100644
--- a/learning_assistant/plugins.py
+++ b/learning_assistant/plugins.py
@@ -23,7 +23,7 @@ class LearningAssistantCourseApp(CourseApp):
}
@classmethod
- def is_available(cls, course_key):
+ def is_available(cls, course_key): # pylint: disable=unused-argument
"""
Return a boolean indicating this course app's availability for a given course.
@@ -36,7 +36,7 @@ def is_available(cls, course_key):
Returns:
bool: Availability status of app.
"""
- return plugins_api.is_available(course_key)
+ return plugins_api.is_available()
@classmethod
def is_enabled(cls, course_key):
diff --git a/learning_assistant/plugins_api.py b/learning_assistant/plugins_api.py
index fc03e41..906ccdd 100644
--- a/learning_assistant/plugins_api.py
+++ b/learning_assistant/plugins_api.py
@@ -16,7 +16,7 @@
from learning_assistant.utils import user_role_is_staff
-def is_available(course_key):
+def is_available():
"""
Return a boolean indicating this course app's availability for a given course.
@@ -30,7 +30,7 @@ def is_available(course_key):
Returns:
bool: Availability status of app.
"""
- return learning_assistant_available(course_key)
+ return learning_assistant_available()
def is_enabled(course_key):
diff --git a/tests/test_api.py b/tests/test_api.py
index 121f488..7042b0a 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -268,18 +268,13 @@ def test_set_learning_assistant_enabled(self, obj_exists, obj_value):
obj = LearningAssistantCourseEnabled.objects.get(course_id=self.course_key)
self.assertEqual(obj.enabled, obj_value)
- @ddt.idata(itertools.product((True, False), (True, False)))
- @ddt.unpack
- @patch('learning_assistant.api.learning_assistant_available_flag')
- def test_learning_assistant_available(
- self,
- learning_assistant_available_flag_value,
- learning_assistant_available_setting_value,
- learning_assistant_available_flag_mock
- ):
- learning_assistant_available_flag_mock.return_value = learning_assistant_available_flag_value
+ @ddt.data(
+ True,
+ False
+ )
+ def test_learning_assistant_available(self, learning_assistant_available_setting_value):
with override_settings(LEARNING_ASSISTANT_AVAILABLE=learning_assistant_available_setting_value):
- return_value = learning_assistant_available(self.course_key)
+ return_value = learning_assistant_available()
- expected_value = learning_assistant_available_setting_value or learning_assistant_available_flag_value
+ expected_value = learning_assistant_available_setting_value
self.assertEqual(return_value, expected_value)
diff --git a/tests/test_plugins_api.py b/tests/test_plugins_api.py
index f7f14a2..076335d 100644
--- a/tests/test_plugins_api.py
+++ b/tests/test_plugins_api.py
@@ -31,7 +31,7 @@ def test_is_available(self, is_available_value, learning_assistant_available_moc
Test the is_available function of the plugins_api module.
"""
learning_assistant_available_mock.return_value = is_available_value
- self.assertEqual(is_available(self.course_key), is_available_value)
+ self.assertEqual(is_available(), is_available_value)
@ddt.data(True, False)
@patch('learning_assistant.plugins_api.learning_assistant_enabled')
From dc9179cbb1fdeaffa4f2b1b7e28e4b4abc479051 Mon Sep 17 00:00:00 2001
From: Alison Langston <46360176+alangsto@users.noreply.github.com>
Date: Wed, 28 Feb 2024 11:20:13 -0500
Subject: [PATCH 042/104] feat: use course cache to retrieve course data (#72)
---
CHANGELOG.rst | 4 ++
docs/decisions/0004-course-cache-use.rst | 50 ++++++++++++++++++++++++
learning_assistant/__init__.py | 2 +-
learning_assistant/api.py | 23 +++++++++--
learning_assistant/constants.py | 2 +-
learning_assistant/platform_imports.py | 16 +++++++-
learning_assistant/utils.py | 19 ++-------
learning_assistant/views.py | 20 +++++-----
test_settings.py | 2 +
tests/test_api.py | 19 ++++++---
tests/test_utils.py | 11 +-----
tests/test_views.py | 22 +++++++----
12 files changed, 136 insertions(+), 54 deletions(-)
create mode 100644 docs/decisions/0004-course-cache-use.rst
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0c3b823..b4c7a19 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,10 @@ Change Log
Unreleased
**********
+4.1.0 - 2024-02-26
+******************
+* Use course cache to inject course title and course skill names into prompt template.
+
4.0.0 - 2024-02-21
******************
* Remove use of course waffle flag. Use the django setting LEARNING_ASSISTANT_AVAILABLE
diff --git a/docs/decisions/0004-course-cache-use.rst b/docs/decisions/0004-course-cache-use.rst
new file mode 100644
index 0000000..13db245
--- /dev/null
+++ b/docs/decisions/0004-course-cache-use.rst
@@ -0,0 +1,50 @@
+4. Use of the LMS course caches
+###############################
+
+Status
+******
+
+**Accepted** *2024-02-26*
+
+Context
+*******
+Each course run ID in edx-platform is associated with another course ID. While for many courses, the mapping between
+course run ID and course ID is straight forward, i.e. edX+testX+2019 -> edX+testX, this is not the case for every
+course on edX. The discovery service is the source of truth for mappings between course run ID and course IDs, and
+string manipulation cannot be relied on as an accurate way to map between the two forms of ID.
+
+The learning-assistant `CourseChatView`_ accepts a course run ID as a path parameter, but a number of API functions
+in the learning assistant backend also require the course ID associated with a given course.
+
+In our initial release, we also found that the current courses available in 2U's Xpert Platform team's index, which was
+being used to inject course skill names and course titles into the system prompt (see `System Prompt Design Changes`_ for
+original details), were too limited. Courses included in that index were conditionally added depending on course
+enrollment dates and additional fields from the discovery course API. While the 2U Xpert Platform team may work to address
+the gap in product needs for their current course index, an alternate method for retrieving course skills and title should
+be considered.
+
+Decision
+********
+In order to determine the mapping between a course run ID and course ID in the learning-assistant app, we will make
+use of an `existing course run cache that is defined in edx-platform`_. Similarly, to retrieve the skill names and title of a course, we will also use
+an `existing course cache`_. Both caches store data from the discovery API for course runs and courses, respectively.
+These are long term caches with a TTL of 24 hours, and on a cache miss the discovery API will be called.
+
+Consequences
+************
+* If the caches were to be removed, code in the learning-assistant repository would no longer function as expected.
+* On a cache miss, the learning-assistant backend will incur additional performance cost on calls to the discovery API.
+
+Rejected Alternatives
+*********************
+* Calling the discovery API directly from the learning-assistant backend
+ * This would require building a custom solution in the learning-assistant app to call the discovery service directly.
+ * Without a cache, this would impact performance on every call to the learning-assistant backend.
+* Using string manipulation to map course run ID to course ID.
+ * If we do not use the discovery service as our source of truth for course run ID to course ID mappings,
+ we run the risk of being unable to support courses that do not fit the usual pattern mapping.
+
+.. _existing course run cache that is defined in edx-platform: https://github.com/openedx/edx-platform/blob/c61df904c1d2a5f523f1da44460c21e17ec087ee/openedx/core/djangoapps/catalog/utils.py#L801
+.. _CourseChatView: https://github.com/edx/learning-assistant/blob/fddf0bc27016bd4a1cabf82de7bcb80b51f3763b/learning_assistant/views.py#L29
+.. _System Prompt Design Changes: https://github.com/edx/learning-assistant/blob/main/docs/decisions/0002-system-prompt-design-changes.rst
+.. _existing course cache: https://github.com/openedx/edx-platform/blob/3a2b6dd8fcc909fd9128f81750f52650ba8ff906/openedx/core/djangoapps/catalog/utils.py#L767
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index b83582f..b46d757 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.0.0'
+__version__ = '4.1.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 8b1a2f5..38ae1c0 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -15,6 +15,8 @@
from learning_assistant.platform_imports import (
block_get_children,
block_leaf_filter,
+ get_cache_course_data,
+ get_cache_course_run_data,
get_single_block,
get_text_transcript,
traverse_block_pre_order,
@@ -101,19 +103,23 @@ def get_block_content(request, user_id, course_id, unit_usage_key):
return cache_data['content_length'], cache_data['content_items']
-def render_prompt_template(request, user_id, course_id, unit_usage_key):
+def render_prompt_template(request, user_id, course_run_id, unit_usage_key, course_id):
"""
Return a rendered prompt template, specified by the LEARNING_ASSISTANT_PROMPT_TEMPLATE setting.
"""
unit_content = ''
- course_run_key = CourseKey.from_string(course_id)
+ course_run_key = CourseKey.from_string(course_run_id)
if unit_usage_key and course_content_enabled(course_run_key):
- _, unit_content = get_block_content(request, user_id, course_id, unit_usage_key)
+ _, unit_content = get_block_content(request, user_id, course_run_id, unit_usage_key)
+
+ course_data = get_cache_course_data(course_id, ['skill_names', 'title'])
+ skill_names = course_data['skill_names']
+ title = course_data['title']
template_string = getattr(settings, 'LEARNING_ASSISTANT_PROMPT_TEMPLATE', '')
template = Environment(loader=BaseLoader).from_string(template_string)
- data = template.render(unit_content=unit_content)
+ data = template.render(unit_content=unit_content, skill_names=skill_names, title=title)
return data
@@ -168,3 +174,12 @@ def set_learning_assistant_enabled(course_key, enabled):
course_key=obj.course_id,
enabled=obj.enabled
)
+
+
+def get_course_id(course_run_id):
+ """
+ Given a course run id (str), return the associated course key.
+ """
+ course_data = get_cache_course_run_data(course_run_id, ['course'])
+ course_key = course_data['course']
+ return course_key
diff --git a/learning_assistant/constants.py b/learning_assistant/constants.py
index 06097ff..7027a28 100644
--- a/learning_assistant/constants.py
+++ b/learning_assistant/constants.py
@@ -7,7 +7,7 @@
EXTERNAL_COURSE_KEY_PATTERN = r'([A-Za-z0-9-_:]+)'
-COURSE_ID_PATTERN = rf'(?P({INTERNAL_COURSE_KEY_PATTERN}|{EXTERNAL_COURSE_KEY_PATTERN}))'
+COURSE_ID_PATTERN = rf'(?P({INTERNAL_COURSE_KEY_PATTERN}|{EXTERNAL_COURSE_KEY_PATTERN}))'
ACCEPTED_CATEGORY_TYPES = ['html', 'video']
CATEGORY_TYPE_MAP = {
diff --git a/learning_assistant/platform_imports.py b/learning_assistant/platform_imports.py
index 1d18664..8c2411c 100644
--- a/learning_assistant/platform_imports.py
+++ b/learning_assistant/platform_imports.py
@@ -51,14 +51,26 @@ def get_cache_course_run_data(course_run_id, fields):
"""
Return course run related data given a course run id.
- This function makes use of the discovery course run cache, which is necessary because
- only the discovery service stores the relation between courseruns and courses.
+ This function makes use of the course run cache in the LMS, which caches data from the discovery service. This is
+ necessary because only the discovery service stores the relation between courseruns and courses.
"""
# pylint: disable=import-error, import-outside-toplevel
from openedx.core.djangoapps.catalog.utils import get_course_run_data
return get_course_run_data(course_run_id, fields)
+def get_cache_course_data(course_id, fields):
+ """
+ Return course related data given a course id.
+
+ This function makes use of the course cache in the LMS, which caches data from the discovery service. This is
+ necessary because only the discovery service stores course skills data.
+ """
+ # pylint: disable=import-error, import-outside-toplevel
+ from openedx.core.djangoapps.catalog.utils import get_course_data
+ return get_course_data(course_id, fields)
+
+
def get_user_role(user, course_key):
"""
Return the role of the user on the edX platform.
diff --git a/learning_assistant/utils.py b/learning_assistant/utils.py
index 518fb01..7bd3fb8 100644
--- a/learning_assistant/utils.py
+++ b/learning_assistant/utils.py
@@ -10,8 +10,6 @@
from requests.exceptions import ConnectTimeout
from rest_framework import status as http_status
-from learning_assistant.platform_imports import get_cache_course_run_data
-
log = logging.getLogger(__name__)
@@ -60,16 +58,7 @@ def get_reduced_message_list(prompt_template, message_list):
return new_message_list
-def get_course_id(course_run_id):
- """
- Given a course run id (str), return the associated course key.
- """
- course_data = get_cache_course_run_data(course_run_id, ['course'])
- course_key = course_data['course']
- return course_key
-
-
-def create_request_body(prompt_template, message_list, courserun_id):
+def create_request_body(prompt_template, message_list, course_id):
"""
Form request body to be passed to the chat endpoint.
"""
@@ -77,7 +66,7 @@ def create_request_body(prompt_template, message_list, courserun_id):
'context': {
'content': prompt_template,
'render': {
- 'doc_id': get_course_id(courserun_id),
+ 'doc_id': course_id,
'fields': ['skillNames', 'title']
}
},
@@ -87,7 +76,7 @@ def create_request_body(prompt_template, message_list, courserun_id):
return response_body
-def get_chat_response(prompt_template, message_list, courserun_id):
+def get_chat_response(prompt_template, message_list, course_id):
"""
Pass message list to chat endpoint, as defined by the CHAT_COMPLETION_API setting.
"""
@@ -98,7 +87,7 @@ def get_chat_response(prompt_template, message_list, courserun_id):
connect_timeout = getattr(settings, 'CHAT_COMPLETION_API_CONNECT_TIMEOUT', 1)
read_timeout = getattr(settings, 'CHAT_COMPLETION_API_READ_TIMEOUT', 15)
- body = create_request_body(prompt_template, message_list, courserun_id)
+ body = create_request_body(prompt_template, message_list, course_id)
try:
response = requests.post(
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 5db882a..ed76ce6 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -19,7 +19,7 @@
except ImportError:
pass
-from learning_assistant.api import learning_assistant_enabled, render_prompt_template
+from learning_assistant.api import get_course_id, learning_assistant_enabled, render_prompt_template
from learning_assistant.serializers import MessageSerializer
from learning_assistant.utils import get_chat_response, user_role_is_staff
@@ -34,9 +34,9 @@ class CourseChatView(APIView):
authentication_classes = (SessionAuthentication, JwtAuthentication,)
permission_classes = (IsAuthenticated,)
- def post(self, request, course_id):
+ def post(self, request, course_run_id):
"""
- Given a course ID, retrieve a chat response for that course.
+ Given a course run ID, retrieve a chat response for that course.
Expected POST data: {
[
@@ -46,7 +46,7 @@ def post(self, request, course_id):
}
"""
try:
- courserun_key = CourseKey.from_string(course_id)
+ courserun_key = CourseKey.from_string(course_run_id)
except InvalidKeyError:
return Response(
status=http_status.HTTP_400_BAD_REQUEST,
@@ -89,11 +89,13 @@ def post(self, request, course_id):
'Attempting to retrieve chat response for user_id=%(user_id)s in course_id=%(course_id)s',
{
'user_id': request.user.id,
- 'course_id': course_id
+ 'course_id': course_run_id
}
)
- prompt_template = render_prompt_template(request, request.user.id, course_id, unit_id)
+ course_id = get_course_id(course_run_id)
+
+ prompt_template = render_prompt_template(request, request.user.id, course_run_id, unit_id, course_id)
status_code, message = get_chat_response(prompt_template, message_list, course_id)
@@ -122,16 +124,16 @@ class LearningAssistantEnabledView(APIView):
authentication_classes = (SessionAuthentication, JwtAuthentication,)
permission_classes = (IsAuthenticated,)
- def get(self, request, course_id):
+ def get(self, request, course_run_id):
"""
- Given a course ID, retrieve whether the Learning Assistant is enabled for the corresponding course.
+ Given a course run ID, retrieve whether the Learning Assistant is enabled for the corresponding course.
The response will be in the following format.
{'enabled': }
"""
try:
- courserun_key = CourseKey.from_string(course_id)
+ courserun_key = CourseKey.from_string(course_run_id)
except InvalidKeyError:
return Response(
status=http_status.HTTP_400_BAD_REQUEST,
diff --git a/test_settings.py b/test_settings.py
index 0302e54..78d99fc 100644
--- a/test_settings.py
+++ b/test_settings.py
@@ -83,6 +83,8 @@ def root(*args):
"{{ unit_content }}"
"\""
"{% endif %}"
+ "{{ skill_names }}"
+ "{{ title }}"
)
LEARNING_ASSISTANT_AVAILABLE = True
diff --git a/tests/test_api.py b/tests/test_api.py
index 7042b0a..1f18d5f 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -82,7 +82,7 @@ def setUp(self):
]
self.block = FakeBlock(self.children)
- self.course_id = 'course-v1:edx+test+23'
+ self.course_run_id = 'course-v1:edx+test+23'
@ddt.data(
('video', True),
@@ -156,7 +156,7 @@ def test_get_block_content(self, mock_get_children_contents, mock_get_single_blo
# args does not matter for this test right now, as the `get_single_block` function is entirely mocked.
request = MagicMock()
user_id = 1
- course_id = self.course_id
+ course_id = self.course_run_id
unit_usage_key = 'block-v1:edX+A+B+type@vertical+block@verticalD'
length, items = get_block_content(request, user_id, course_id, unit_usage_key)
@@ -183,25 +183,34 @@ def test_get_block_content(self, mock_get_children_contents, mock_get_single_blo
('', False),
)
@ddt.unpack
+ @patch('learning_assistant.api.get_cache_course_data')
@patch('learning_assistant.toggles._is_learning_assistant_waffle_flag_enabled')
@patch('learning_assistant.api.get_block_content')
- def test_render_prompt_template(self, unit_content, flag_enabled, mock_get_content, mock_is_flag_enabled):
+ def test_render_prompt_template(
+ self, unit_content, flag_enabled, mock_get_content, mock_is_flag_enabled, mock_cache
+ ):
mock_get_content.return_value = (len(unit_content), unit_content)
mock_is_flag_enabled.return_value = flag_enabled
+ skills_content = ['skills']
+ title = 'title'
+ mock_cache.return_value = {'skill_names': skills_content, 'title': title}
# mock arguments that are passed through to `get_block_content` function. the value of these
# args does not matter for this test right now, as the `get_block_content` function is entirely mocked.
request = MagicMock()
user_id = 1
- course_id = self.course_id
+ course_run_id = self.course_run_id
unit_usage_key = 'block-v1:edX+A+B+type@vertical+block@verticalD'
+ course_id = 'edx+test'
- prompt_text = render_prompt_template(request, user_id, course_id, unit_usage_key)
+ prompt_text = render_prompt_template(request, user_id, course_run_id, unit_usage_key, course_id)
if unit_content and flag_enabled:
self.assertIn(unit_content, prompt_text)
else:
self.assertNotIn('The following text is useful.', prompt_text)
+ self.assertIn(str(skills_content), prompt_text)
+ self.assertIn(title, prompt_text)
@ddt.ddt
diff --git a/tests/test_utils.py b/tests/test_utils.py
index a410c1f..359f7ff 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -24,14 +24,7 @@ def setUp(self):
self.prompt_template = 'This is a prompt.'
self.message_list = [{'role': 'assistant', 'content': 'Hello'}, {'role': 'user', 'content': 'Goodbye'}]
- self.course_id = 'course-v1:edx+test+23'
- self.course_key = 'edx-test'
-
- self.patcher = patch(
- 'learning_assistant.utils.get_cache_course_run_data',
- return_value={'course': self.course_key}
- )
- self.patcher.start()
+ self.course_id = 'edx+test'
def get_response(self):
return get_chat_response(self.prompt_template, self.message_list, self.course_id)
@@ -99,7 +92,7 @@ def test_post_request_structure(self, mock_requests):
'context': {
'content': self.prompt_template,
'render': {
- 'doc_id': self.course_key,
+ 'doc_id': self.course_id,
'fields': ['skillNames', 'title']
}
},
diff --git a/tests/test_views.py b/tests/test_views.py
index 24bf4b7..fbbe8b5 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -82,17 +82,23 @@ def setUp(self):
super().setUp()
self.course_id = 'course-v1:edx+test+23'
+ self.patcher = patch(
+ 'learning_assistant.api.get_cache_course_run_data',
+ return_value={'course': 'edx+test'}
+ )
+ self.patcher.start()
+
@patch('learning_assistant.views.learning_assistant_enabled')
def test_invalid_course_id(self, mock_learning_assistant_enabled):
mock_learning_assistant_enabled.return_value = True
- response = self.client.get(reverse('enabled', kwargs={'course_id': self.course_id+'+invalid'}))
+ response = self.client.get(reverse('enabled', kwargs={'course_run_id': self.course_id+'+invalid'}))
self.assertEqual(response.status_code, 400)
@patch('learning_assistant.views.learning_assistant_enabled')
def test_course_waffle_inactive(self, mock_waffle):
mock_waffle.return_value = False
- response = self.client.post(reverse('chat', kwargs={'course_id': self.course_id}))
+ response = self.client.post(reverse('chat', kwargs={'course_run_id': self.course_id}))
self.assertEqual(response.status_code, 403)
@patch('learning_assistant.views.learning_assistant_enabled')
@@ -105,7 +111,7 @@ def test_user_no_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_rol
mock_mode.VERIFIED_MODES = ['verified']
mock_enrollment.return_value = None
- response = self.client.post(reverse('chat', kwargs={'course_id': self.course_id}))
+ response = self.client.post(reverse('chat', kwargs={'course_run_id': self.course_id}))
self.assertEqual(response.status_code, 403)
@patch('learning_assistant.views.learning_assistant_enabled')
@@ -118,7 +124,7 @@ def test_user_audit_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_
mock_mode.VERIFIED_MODES = ['verified']
mock_enrollment.return_value = MagicMock(mode='audit')
- response = self.client.post(reverse('chat', kwargs={'course_id': self.course_id}))
+ response = self.client.post(reverse('chat', kwargs={'course_run_id': self.course_id}))
self.assertEqual(response.status_code, 403)
@patch('learning_assistant.views.render_prompt_template')
@@ -137,7 +143,7 @@ def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
]
response = self.client.post(
- reverse('chat', kwargs={'course_id': self.course_id})+f'?unit_id={test_unit_id}',
+ reverse('chat', kwargs={'course_run_id': self.course_id})+f'?unit_id={test_unit_id}',
data=json.dumps(test_data),
content_type='application/json'
)
@@ -164,7 +170,7 @@ def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle,
]
response = self.client.post(
- reverse('chat', kwargs={'course_id': self.course_id})+f'?unit_id={test_unit_id}',
+ reverse('chat', kwargs={'course_run_id': self.course_id})+f'?unit_id={test_unit_id}',
data=json.dumps(test_data),
content_type='application/json'
)
@@ -196,7 +202,7 @@ def setUp(self):
@patch('learning_assistant.views.learning_assistant_enabled')
def test_learning_assistant_enabled(self, mock_value, expected_value, mock_learning_assistant_enabled):
mock_learning_assistant_enabled.return_value = mock_value
- response = self.client.get(reverse('enabled', kwargs={'course_id': self.course_id}))
+ response = self.client.get(reverse('enabled', kwargs={'course_run_id': self.course_id}))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, {'enabled': expected_value})
@@ -204,6 +210,6 @@ def test_learning_assistant_enabled(self, mock_value, expected_value, mock_learn
@patch('learning_assistant.views.learning_assistant_enabled')
def test_invalid_course_id(self, mock_learning_assistant_enabled):
mock_learning_assistant_enabled.return_value = True
- response = self.client.get(reverse('enabled', kwargs={'course_id': self.course_id+'+invalid'}))
+ response = self.client.get(reverse('enabled', kwargs={'course_run_id': self.course_id+'+invalid'}))
self.assertEqual(response.status_code, 400)
From ec8cff5d467bf7c266d2e2fcb561b498dacb72c9 Mon Sep 17 00:00:00 2001
From: Alison Langston <46360176+alangsto@users.noreply.github.com>
Date: Thu, 29 Feb 2024 09:18:24 -0500
Subject: [PATCH 043/104] feat: update request to xpert backend (#74)
---
CHANGELOG.rst | 4 ++++
learning_assistant/__init__.py | 2 +-
learning_assistant/utils.py | 27 +++++++--------------------
learning_assistant/views.py | 2 +-
tests/test_utils.py | 27 +++++++++++++--------------
5 files changed, 26 insertions(+), 36 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index b4c7a19..411165d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,10 @@ Change Log
Unreleased
**********
+4.2.0 - 2024-02-28
+******************
+* Modify call to Xpert backend to prevent use of course index.
+
4.1.0 - 2024-02-26
******************
* Use course cache to inject course title and course skill names into prompt template.
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index b46d757..590f745 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.1.0'
+__version__ = '4.2.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/utils.py b/learning_assistant/utils.py
index 7bd3fb8..fc72daf 100644
--- a/learning_assistant/utils.py
+++ b/learning_assistant/utils.py
@@ -27,15 +27,7 @@ def get_reduced_message_list(prompt_template, message_list):
"""
If messages are larger than allotted token amount, return a smaller list of messages.
"""
- # the total number of system tokens is a sum of estimated tokens that includes the prompt template, the
- # course title, and the course skills. It is necessary to include estimations for the course title and
- # course skills, as the chat endpoint the prompt is being passed to is responsible for filling in the values
- # for both of those variables.
- total_system_tokens = (
- _estimated_message_tokens(prompt_template)
- + _estimated_message_tokens('.' * 40) # average number of characters per course name is 40
- + _estimated_message_tokens('.' * 116) # average number of characters for skill names is 116
- )
+ total_system_tokens = _estimated_message_tokens(prompt_template)
max_tokens = getattr(settings, 'CHAT_COMPLETION_MAX_TOKENS', 16385)
response_tokens = getattr(settings, 'CHAT_COMPLETION_RESPONSE_TOKENS', 1000)
@@ -55,28 +47,23 @@ def get_reduced_message_list(prompt_template, message_list):
# insert message at beginning of list, because we are traversing the message list from most recent to oldest
new_message_list.insert(0, new_message)
- return new_message_list
+ system_message = {'role': 'system', 'content': prompt_template}
+ return [system_message] + new_message_list
-def create_request_body(prompt_template, message_list, course_id):
+
+def create_request_body(prompt_template, message_list):
"""
Form request body to be passed to the chat endpoint.
"""
response_body = {
- 'context': {
- 'content': prompt_template,
- 'render': {
- 'doc_id': course_id,
- 'fields': ['skillNames', 'title']
- }
- },
'message_list': get_reduced_message_list(prompt_template, message_list)
}
return response_body
-def get_chat_response(prompt_template, message_list, course_id):
+def get_chat_response(prompt_template, message_list):
"""
Pass message list to chat endpoint, as defined by the CHAT_COMPLETION_API setting.
"""
@@ -87,7 +74,7 @@ def get_chat_response(prompt_template, message_list, course_id):
connect_timeout = getattr(settings, 'CHAT_COMPLETION_API_CONNECT_TIMEOUT', 1)
read_timeout = getattr(settings, 'CHAT_COMPLETION_API_READ_TIMEOUT', 15)
- body = create_request_body(prompt_template, message_list, course_id)
+ body = create_request_body(prompt_template, message_list)
try:
response = requests.post(
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index ed76ce6..52ad248 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -97,7 +97,7 @@ def post(self, request, course_run_id):
prompt_template = render_prompt_template(request, request.user.id, course_run_id, unit_id, course_id)
- status_code, message = get_chat_response(prompt_template, message_list, course_id)
+ status_code, message = get_chat_response(prompt_template, message_list)
return Response(status=status_code, data=message)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 359f7ff..21b23c4 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -27,7 +27,7 @@ def setUp(self):
self.course_id = 'edx+test'
def get_response(self):
- return get_chat_response(self.prompt_template, self.message_list, self.course_id)
+ return get_chat_response(self.prompt_template, self.message_list)
@override_settings(CHAT_COMPLETION_API=None)
def test_no_endpoint_setting(self):
@@ -89,14 +89,7 @@ def test_post_request_structure(self, mock_requests):
headers = {'Content-Type': 'application/json', 'x-api-key': settings.CHAT_COMPLETION_API_KEY}
response_body = {
- 'context': {
- 'content': self.prompt_template,
- 'render': {
- 'doc_id': self.course_id,
- 'fields': ['skillNames', 'title']
- }
- },
- 'message_list': self.message_list
+ 'message_list': [{'role': 'system', 'content': self.prompt_template}] + self.message_list
}
self.get_response()
@@ -120,7 +113,7 @@ def setUp(self):
{'role': 'user', 'content': 'Goodbye'},
]
- @override_settings(CHAT_COMPLETION_MAX_TOKENS=90)
+ @override_settings(CHAT_COMPLETION_MAX_TOKENS=30)
@override_settings(CHAT_COMPLETION_RESPONSE_TOKENS=1)
def test_message_list_reduced(self):
"""
@@ -128,13 +121,19 @@ def test_message_list_reduced(self):
"""
# pass in copy of list, as it is modified as part of the reduction
reduced_message_list = get_reduced_message_list(self.prompt_template, self.message_list)
- self.assertEqual(len(reduced_message_list), 1)
- self.assertEqual(reduced_message_list, self.message_list[-1:])
+ self.assertEqual(len(reduced_message_list), 2)
+ self.assertEqual(
+ reduced_message_list,
+ [{'role': 'system', 'content': self.prompt_template}] + self.message_list[-1:]
+ )
def test_message_list(self):
reduced_message_list = get_reduced_message_list(self.prompt_template, self.message_list)
- self.assertEqual(len(reduced_message_list), 2)
- self.assertEqual(reduced_message_list, self.message_list)
+ self.assertEqual(len(reduced_message_list), 3)
+ self.assertEqual(
+ reduced_message_list,
+ [{'role': 'system', 'content': self.prompt_template}] + self.message_list
+ )
@ddt.ddt
From 38e434b518b9d77a119555e071c73e6c51a2068f Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 8 Mar 2024 14:16:19 -0500
Subject: [PATCH 044/104] chore: Updating Python Requirements (#75)
---
requirements/base.txt | 19 ++++++++---------
requirements/ci.txt | 6 +++---
requirements/dev.txt | 42 ++++++++++++++++++++------------------
requirements/doc.txt | 37 +++++++++++++++++----------------
requirements/pip-tools.txt | 8 +++++---
requirements/pip.txt | 2 +-
requirements/quality.txt | 31 ++++++++++++++--------------
requirements/test.txt | 26 +++++++++++------------
8 files changed, 88 insertions(+), 83 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 88a79bf..5994205 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -8,6 +8,8 @@ asgiref==3.7.2
# via django
attrs==23.2.0
# via -r requirements/base.in
+backports-zoneinfo==0.2.1
+ # via django
certifi==2024.2.2
# via requests
cffi==1.16.0
@@ -18,9 +20,9 @@ charset-normalizer==3.3.2
# via requests
click==8.1.7
# via edx-django-utils
-cryptography==42.0.2
+cryptography==42.0.5
# via pyjwt
-django==3.2.24
+django==4.2.11
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/base.in
@@ -64,7 +66,7 @@ jinja2==3.1.3
# via -r requirements/base.in
markupsafe==2.1.5
# via jinja2
-newrelic==9.6.0
+newrelic==9.7.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
@@ -77,15 +79,12 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
- # pyjwt
pymongo==3.13.0
# via edx-opaque-keys
pynacl==1.5.0
# via edx-django-utils
pytz==2024.1
- # via
- # django
- # djangorestframework
+ # via djangorestframework
requests==2.31.0
# via
# edx-drf-extensions
@@ -97,13 +96,13 @@ slumber==0.7.1
# via edx-rest-api-client
sqlparse==0.4.4
# via django
-stevedore==5.1.0
+stevedore==5.2.0
# via
# edx-django-utils
# edx-opaque-keys
-typing-extensions==4.9.0
+typing-extensions==4.10.0
# via
# asgiref
# edx-opaque-keys
-urllib3==2.2.0
+urllib3==2.2.1
# via requests
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 742e39c..88a39ff 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -4,7 +4,7 @@
#
# make upgrade
#
-cachetools==5.3.2
+cachetools==5.3.3
# via tox
chardet==5.2.0
# via tox
@@ -32,7 +32,7 @@ tomli==2.0.1
# via
# pyproject-api
# tox
-tox==4.12.1
+tox==4.13.0
# via -r requirements/ci.in
-virtualenv==20.25.0
+virtualenv==20.25.1
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 48a017f..fa5a428 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -8,18 +8,22 @@ asgiref==3.7.2
# via
# -r requirements/quality.txt
# django
-astroid==3.0.3
+astroid==3.1.0
# via
# -r requirements/quality.txt
# pylint
# pylint-celery
attrs==23.2.0
# via -r requirements/quality.txt
-build==1.0.3
+backports-zoneinfo==0.2.1
+ # via
+ # -r requirements/quality.txt
+ # django
+build==1.1.1
# via
# -r requirements/pip-tools.txt
# pip-tools
-cachetools==5.3.2
+cachetools==5.3.3
# via
# -r requirements/ci.txt
# tox
@@ -62,16 +66,15 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.4.1
+coverage[toml]==7.4.3
# via
# -r requirements/quality.txt
- # coverage
# pytest-cov
-cryptography==42.0.2
+cryptography==42.0.5
# via
# -r requirements/quality.txt
# pyjwt
-ddt==1.7.1
+ddt==1.7.2
# via -r requirements/quality.txt
diff-cover==8.0.3
# via -r requirements/dev.in
@@ -83,7 +86,7 @@ distlib==0.3.8
# via
# -r requirements/ci.txt
# virtualenv
-django==3.2.24
+django==4.2.11
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/quality.txt
@@ -172,7 +175,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.6.0
+newrelic==9.7.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -191,7 +194,7 @@ pbr==6.0.0
# via
# -r requirements/quality.txt
# stevedore
-pip-tools==7.3.0
+pip-tools==7.4.0
# via -r requirements/pip-tools.txt
platformdirs==4.2.0
# via
@@ -229,8 +232,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
- # pyjwt
-pylint==3.0.3
+pylint==3.1.0
# via
# -r requirements/quality.txt
# edx-lint
@@ -266,7 +268,8 @@ pyproject-hooks==1.0.0
# via
# -r requirements/pip-tools.txt
# build
-pytest==8.0.0
+ # pip-tools
+pytest==8.0.2
# via
# -r requirements/quality.txt
# pytest-cov
@@ -282,7 +285,6 @@ python-slugify==8.0.4
pytz==2024.1
# via
# -r requirements/quality.txt
- # django
# djangorestframework
pyyaml==6.0.1
# via
@@ -319,7 +321,7 @@ sqlparse==0.4.4
# via
# -r requirements/quality.txt
# django
-stevedore==5.1.0
+stevedore==5.2.0
# via
# -r requirements/quality.txt
# code-annotations
@@ -342,25 +344,25 @@ tomli==2.0.1
# pyproject-hooks
# pytest
# tox
-tomlkit==0.12.3
+tomlkit==0.12.4
# via
# -r requirements/quality.txt
# pylint
-tox==4.12.1
+tox==4.13.0
# via -r requirements/ci.txt
-typing-extensions==4.9.0
+typing-extensions==4.10.0
# via
# -r requirements/quality.txt
# asgiref
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.2.0
+urllib3==2.2.1
# via
# -r requirements/quality.txt
# requests
# responses
-virtualenv==20.25.0
+virtualenv==20.25.1
# via
# -r requirements/ci.txt
# tox
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 20a6b4b..cf8a36f 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -14,7 +14,11 @@ attrs==23.2.0
# via -r requirements/test.txt
babel==2.14.0
# via sphinx
-build==1.0.3
+backports-zoneinfo==0.2.1
+ # via
+ # -r requirements/test.txt
+ # django
+build==1.1.1
# via -r requirements/doc.in
certifi==2024.2.2
# via
@@ -36,19 +40,18 @@ click==8.1.7
# edx-django-utils
code-annotations==1.6.0
# via -r requirements/test.txt
-coverage[toml]==7.4.1
+coverage[toml]==7.4.3
# via
# -r requirements/test.txt
- # coverage
# pytest-cov
-cryptography==42.0.2
+cryptography==42.0.5
# via
# -r requirements/test.txt
# pyjwt
# secretstorage
-ddt==1.7.1
+ddt==1.7.2
# via -r requirements/test.txt
-django==3.2.24
+django==4.2.11
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -116,7 +119,7 @@ importlib-metadata==7.0.1
# keyring
# sphinx
# twine
-importlib-resources==6.1.1
+importlib-resources==6.1.2
# via keyring
iniconfig==2.0.0
# via
@@ -133,7 +136,7 @@ jinja2==3.1.3
# -r requirements/test.txt
# code-annotations
# sphinx
-keyring==24.3.0
+keyring==24.3.1
# via twine
markdown-it-py==3.0.0
# via rich
@@ -145,7 +148,7 @@ mdurl==0.1.2
# via markdown-it-py
more-itertools==10.2.0
# via jaraco-classes
-newrelic==9.6.0
+newrelic==9.7.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -161,7 +164,7 @@ pbr==6.0.0
# via
# -r requirements/test.txt
# stevedore
-pkginfo==1.9.6
+pkginfo==1.10.0
# via twine
pluggy==1.4.0
# via
@@ -187,7 +190,6 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
- # pyjwt
pymongo==3.13.0
# via
# -r requirements/test.txt
@@ -198,7 +200,7 @@ pynacl==1.5.0
# edx-django-utils
pyproject-hooks==1.0.0
# via build
-pytest==8.0.0
+pytest==8.0.2
# via
# -r requirements/test.txt
# pytest-cov
@@ -215,14 +217,13 @@ pytz==2024.1
# via
# -r requirements/test.txt
# babel
- # django
# djangorestframework
pyyaml==6.0.1
# via
# -r requirements/test.txt
# code-annotations
# responses
-readme-renderer==42.0
+readme-renderer==43.0
# via twine
requests==2.31.0
# via
@@ -242,7 +243,7 @@ restructuredtext-lint==1.4.0
# via doc8
rfc3986==2.0.0
# via twine
-rich==13.7.0
+rich==13.7.1
# via twine
secretstorage==3.3.3
# via keyring
@@ -274,7 +275,7 @@ sqlparse==0.4.4
# via
# -r requirements/test.txt
# django
-stevedore==5.1.0
+stevedore==5.2.0
# via
# -r requirements/test.txt
# code-annotations
@@ -295,13 +296,13 @@ tomli==2.0.1
# pytest
twine==5.0.0
# via -r requirements/doc.in
-typing-extensions==4.9.0
+typing-extensions==4.10.0
# via
# -r requirements/test.txt
# asgiref
# edx-opaque-keys
# rich
-urllib3==2.2.0
+urllib3==2.2.1
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 0e88226..8528adb 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -4,7 +4,7 @@
#
# make upgrade
#
-build==1.0.3
+build==1.1.1
# via pip-tools
click==8.1.7
# via pip-tools
@@ -12,10 +12,12 @@ importlib-metadata==7.0.1
# via build
packaging==23.2
# via build
-pip-tools==7.3.0
+pip-tools==7.4.0
# via -r requirements/pip-tools.in
pyproject-hooks==1.0.0
- # via build
+ # via
+ # build
+ # pip-tools
tomli==2.0.1
# via
# build
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 71954cc..6665603 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -10,5 +10,5 @@ wheel==0.42.0
# The following packages are considered to be unsafe in a requirements file:
pip==24.0
# via -r requirements/pip.in
-setuptools==69.1.0
+setuptools==69.1.1
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 0c70399..90d5325 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -8,12 +8,16 @@ asgiref==3.7.2
# via
# -r requirements/test.txt
# django
-astroid==3.0.3
+astroid==3.1.0
# via
# pylint
# pylint-celery
attrs==23.2.0
# via -r requirements/test.txt
+backports-zoneinfo==0.2.1
+ # via
+ # -r requirements/test.txt
+ # django
certifi==2024.2.2
# via
# -r requirements/test.txt
@@ -40,20 +44,19 @@ code-annotations==1.6.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.4.1
+coverage[toml]==7.4.3
# via
# -r requirements/test.txt
- # coverage
# pytest-cov
-cryptography==42.0.2
+cryptography==42.0.5
# via
# -r requirements/test.txt
# pyjwt
-ddt==1.7.1
+ddt==1.7.2
# via -r requirements/test.txt
dill==0.3.8
# via pylint
-django==3.2.24
+django==4.2.11
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -125,7 +128,7 @@ markupsafe==2.1.5
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.6.0
+newrelic==9.7.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -161,8 +164,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
- # pyjwt
-pylint==3.0.3
+pylint==3.1.0
# via
# edx-lint
# pylint-celery
@@ -184,7 +186,7 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==8.0.0
+pytest==8.0.2
# via
# -r requirements/test.txt
# pytest-cov
@@ -200,7 +202,6 @@ python-slugify==8.0.4
pytz==2024.1
# via
# -r requirements/test.txt
- # django
# djangorestframework
pyyaml==6.0.1
# via
@@ -232,7 +233,7 @@ sqlparse==0.4.4
# via
# -r requirements/test.txt
# django
-stevedore==5.1.0
+stevedore==5.2.0
# via
# -r requirements/test.txt
# code-annotations
@@ -248,16 +249,16 @@ tomli==2.0.1
# coverage
# pylint
# pytest
-tomlkit==0.12.3
+tomlkit==0.12.4
# via pylint
-typing-extensions==4.9.0
+typing-extensions==4.10.0
# via
# -r requirements/test.txt
# asgiref
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.2.0
+urllib3==2.2.1
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/test.txt b/requirements/test.txt
index ad06fe2..bef6f37 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -10,6 +10,10 @@ asgiref==3.7.2
# django
attrs==23.2.0
# via -r requirements/base.txt
+backports-zoneinfo==0.2.1
+ # via
+ # -r requirements/base.txt
+ # django
certifi==2024.2.2
# via
# -r requirements/base.txt
@@ -30,15 +34,13 @@ click==8.1.7
# edx-django-utils
code-annotations==1.6.0
# via -r requirements/test.in
-coverage[toml]==7.4.1
- # via
- # coverage
- # pytest-cov
-cryptography==42.0.2
+coverage[toml]==7.4.3
+ # via pytest-cov
+cryptography==42.0.5
# via
# -r requirements/base.txt
# pyjwt
-ddt==1.7.1
+ddt==1.7.2
# via -r requirements/test.in
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
@@ -99,7 +101,7 @@ markupsafe==2.1.5
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.6.0
+newrelic==9.7.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -125,7 +127,6 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
- # pyjwt
pymongo==3.13.0
# via
# -r requirements/base.txt
@@ -134,7 +135,7 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==8.0.0
+pytest==8.0.2
# via
# pytest-cov
# pytest-django
@@ -147,7 +148,6 @@ python-slugify==8.0.4
pytz==2024.1
# via
# -r requirements/base.txt
- # django
# djangorestframework
pyyaml==6.0.1
# via
@@ -174,7 +174,7 @@ sqlparse==0.4.4
# via
# -r requirements/base.txt
# django
-stevedore==5.1.0
+stevedore==5.2.0
# via
# -r requirements/base.txt
# code-annotations
@@ -186,12 +186,12 @@ tomli==2.0.1
# via
# coverage
# pytest
-typing-extensions==4.9.0
+typing-extensions==4.10.0
# via
# -r requirements/base.txt
# asgiref
# edx-opaque-keys
-urllib3==2.2.0
+urllib3==2.2.1
# via
# -r requirements/base.txt
# requests
From 523823005d64cd7d7095b956bfdfa76a8119e8a9 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 19 Mar 2024 11:20:51 -0400
Subject: [PATCH 045/104] chore: Updating Python Requirements
---
requirements/base.txt | 12 ++++++------
requirements/ci.txt | 4 ++--
requirements/dev.txt | 30 ++++++++++++++----------------
requirements/doc.txt | 27 +++++++++++++--------------
requirements/pip-tools.txt | 14 ++++++++------
requirements/pip.txt | 4 ++--
requirements/quality.txt | 19 ++++++++-----------
requirements/test.txt | 19 ++++++++-----------
8 files changed, 61 insertions(+), 68 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 5994205..c77bba2 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -9,7 +9,9 @@ asgiref==3.7.2
attrs==23.2.0
# via -r requirements/base.in
backports-zoneinfo==0.2.1
- # via django
+ # via
+ # django
+ # djangorestframework
certifi==2024.2.2
# via requests
cffi==1.16.0
@@ -41,14 +43,14 @@ django-waffle==4.1.0
# via
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.14.0
+djangorestframework==3.15.0
# via
# -r requirements/base.in
# drf-jwt
# edx-drf-extensions
drf-jwt==1.19.2
# via edx-drf-extensions
-edx-django-utils==5.10.1
+edx-django-utils==5.11.0
# via
# edx-drf-extensions
# edx-rest-api-client
@@ -66,7 +68,7 @@ jinja2==3.1.3
# via -r requirements/base.in
markupsafe==2.1.5
# via jinja2
-newrelic==9.7.0
+newrelic==9.7.1
# via edx-django-utils
pbr==6.0.0
# via stevedore
@@ -83,8 +85,6 @@ pymongo==3.13.0
# via edx-opaque-keys
pynacl==1.5.0
# via edx-django-utils
-pytz==2024.1
- # via djangorestframework
requests==2.31.0
# via
# edx-drf-extensions
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 88a39ff..9f335db 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -16,7 +16,7 @@ filelock==3.13.1
# via
# tox
# virtualenv
-packaging==23.2
+packaging==24.0
# via
# pyproject-api
# tox
@@ -32,7 +32,7 @@ tomli==2.0.1
# via
# pyproject-api
# tox
-tox==4.13.0
+tox==4.14.1
# via -r requirements/ci.in
virtualenv==20.25.1
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index fa5a428..819335c 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -19,6 +19,7 @@ backports-zoneinfo==0.2.1
# via
# -r requirements/quality.txt
# django
+ # djangorestframework
build==1.1.1
# via
# -r requirements/pip-tools.txt
@@ -58,7 +59,7 @@ click-log==0.4.0
# via
# -r requirements/quality.txt
# edx-lint
-code-annotations==1.6.0
+code-annotations==1.7.0
# via
# -r requirements/quality.txt
# edx-lint
@@ -66,7 +67,7 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.4.3
+coverage[toml]==7.4.4
# via
# -r requirements/quality.txt
# pytest-cov
@@ -109,7 +110,7 @@ django-waffle==4.1.0
# -r requirements/quality.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.14.0
+djangorestframework==3.15.0
# via
# -r requirements/quality.txt
# drf-jwt
@@ -118,7 +119,7 @@ drf-jwt==1.19.2
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-django-utils==5.10.1
+edx-django-utils==5.11.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
@@ -148,8 +149,9 @@ idna==3.6
# via
# -r requirements/quality.txt
# requests
-importlib-metadata==7.0.1
+importlib-metadata==6.11.0
# via
+ # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/pip-tools.txt
# build
iniconfig==2.0.0
@@ -175,11 +177,11 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.7.0
+newrelic==9.7.1
# via
# -r requirements/quality.txt
# edx-django-utils
-packaging==23.2
+packaging==24.0
# via
# -r requirements/ci.txt
# -r requirements/pip-tools.txt
@@ -194,7 +196,7 @@ pbr==6.0.0
# via
# -r requirements/quality.txt
# stevedore
-pip-tools==7.4.0
+pip-tools==7.4.1
# via -r requirements/pip-tools.txt
platformdirs==4.2.0
# via
@@ -269,7 +271,7 @@ pyproject-hooks==1.0.0
# -r requirements/pip-tools.txt
# build
# pip-tools
-pytest==8.0.2
+pytest==8.1.1
# via
# -r requirements/quality.txt
# pytest-cov
@@ -282,10 +284,6 @@ python-slugify==8.0.4
# via
# -r requirements/quality.txt
# code-annotations
-pytz==2024.1
- # via
- # -r requirements/quality.txt
- # djangorestframework
pyyaml==6.0.1
# via
# -r requirements/quality.txt
@@ -348,7 +346,7 @@ tomlkit==0.12.4
# via
# -r requirements/quality.txt
# pylint
-tox==4.13.0
+tox==4.14.1
# via -r requirements/ci.txt
typing-extensions==4.10.0
# via
@@ -366,11 +364,11 @@ virtualenv==20.25.1
# via
# -r requirements/ci.txt
# tox
-wheel==0.42.0
+wheel==0.43.0
# via
# -r requirements/pip-tools.txt
# pip-tools
-zipp==3.17.0
+zipp==3.18.1
# via
# -r requirements/pip-tools.txt
# importlib-metadata
diff --git a/requirements/doc.txt b/requirements/doc.txt
index cf8a36f..9f1dada 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -18,6 +18,7 @@ backports-zoneinfo==0.2.1
# via
# -r requirements/test.txt
# django
+ # djangorestframework
build==1.1.1
# via -r requirements/doc.in
certifi==2024.2.2
@@ -38,9 +39,9 @@ click==8.1.7
# -r requirements/test.txt
# code-annotations
# edx-django-utils
-code-annotations==1.6.0
+code-annotations==1.7.0
# via -r requirements/test.txt
-coverage[toml]==7.4.3
+coverage[toml]==7.4.4
# via
# -r requirements/test.txt
# pytest-cov
@@ -73,7 +74,7 @@ django-waffle==4.1.0
# -r requirements/test.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.14.0
+djangorestframework==3.15.0
# via
# -r requirements/test.txt
# drf-jwt
@@ -90,7 +91,7 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.10.1
+edx-django-utils==5.11.0
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -113,13 +114,14 @@ idna==3.6
# requests
imagesize==1.4.1
# via sphinx
-importlib-metadata==7.0.1
+importlib-metadata==6.11.0
# via
+ # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# build
# keyring
# sphinx
# twine
-importlib-resources==6.1.2
+importlib-resources==6.3.1
# via keyring
iniconfig==2.0.0
# via
@@ -148,13 +150,13 @@ mdurl==0.1.2
# via markdown-it-py
more-itertools==10.2.0
# via jaraco-classes
-newrelic==9.7.0
+newrelic==9.7.1
# via
# -r requirements/test.txt
# edx-django-utils
nh3==0.2.15
# via readme-renderer
-packaging==23.2
+packaging==24.0
# via
# -r requirements/test.txt
# build
@@ -200,7 +202,7 @@ pynacl==1.5.0
# edx-django-utils
pyproject-hooks==1.0.0
# via build
-pytest==8.0.2
+pytest==8.1.1
# via
# -r requirements/test.txt
# pytest-cov
@@ -214,10 +216,7 @@ python-slugify==8.0.4
# -r requirements/test.txt
# code-annotations
pytz==2024.1
- # via
- # -r requirements/test.txt
- # babel
- # djangorestframework
+ # via babel
pyyaml==6.0.1
# via
# -r requirements/test.txt
@@ -308,7 +307,7 @@ urllib3==2.2.1
# requests
# responses
# twine
-zipp==3.17.0
+zipp==3.18.1
# via
# importlib-metadata
# importlib-resources
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 8528adb..aad9d38 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -8,11 +8,13 @@ build==1.1.1
# via pip-tools
click==8.1.7
# via pip-tools
-importlib-metadata==7.0.1
- # via build
-packaging==23.2
+importlib-metadata==6.11.0
+ # via
+ # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
+ # build
+packaging==24.0
# via build
-pip-tools==7.4.0
+pip-tools==7.4.1
# via -r requirements/pip-tools.in
pyproject-hooks==1.0.0
# via
@@ -23,9 +25,9 @@ tomli==2.0.1
# build
# pip-tools
# pyproject-hooks
-wheel==0.42.0
+wheel==0.43.0
# via pip-tools
-zipp==3.17.0
+zipp==3.18.1
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 6665603..cf44902 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -4,11 +4,11 @@
#
# make upgrade
#
-wheel==0.42.0
+wheel==0.43.0
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
pip==24.0
# via -r requirements/pip.in
-setuptools==69.1.1
+setuptools==69.2.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 90d5325..c83d133 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -18,6 +18,7 @@ backports-zoneinfo==0.2.1
# via
# -r requirements/test.txt
# django
+ # djangorestframework
certifi==2024.2.2
# via
# -r requirements/test.txt
@@ -40,11 +41,11 @@ click==8.1.7
# edx-lint
click-log==0.4.0
# via edx-lint
-code-annotations==1.6.0
+code-annotations==1.7.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.4.3
+coverage[toml]==7.4.4
# via
# -r requirements/test.txt
# pytest-cov
@@ -78,7 +79,7 @@ django-waffle==4.1.0
# -r requirements/test.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.14.0
+djangorestframework==3.15.0
# via
# -r requirements/test.txt
# drf-jwt
@@ -87,7 +88,7 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.10.1
+edx-django-utils==5.11.0
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -128,11 +129,11 @@ markupsafe==2.1.5
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.7.0
+newrelic==9.7.1
# via
# -r requirements/test.txt
# edx-django-utils
-packaging==23.2
+packaging==24.0
# via
# -r requirements/test.txt
# pytest
@@ -186,7 +187,7 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==8.0.2
+pytest==8.1.1
# via
# -r requirements/test.txt
# pytest-cov
@@ -199,10 +200,6 @@ python-slugify==8.0.4
# via
# -r requirements/test.txt
# code-annotations
-pytz==2024.1
- # via
- # -r requirements/test.txt
- # djangorestframework
pyyaml==6.0.1
# via
# -r requirements/test.txt
diff --git a/requirements/test.txt b/requirements/test.txt
index bef6f37..5437471 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -14,6 +14,7 @@ backports-zoneinfo==0.2.1
# via
# -r requirements/base.txt
# django
+ # djangorestframework
certifi==2024.2.2
# via
# -r requirements/base.txt
@@ -32,9 +33,9 @@ click==8.1.7
# -r requirements/base.txt
# code-annotations
# edx-django-utils
-code-annotations==1.6.0
+code-annotations==1.7.0
# via -r requirements/test.in
-coverage[toml]==7.4.3
+coverage[toml]==7.4.4
# via pytest-cov
cryptography==42.0.5
# via
@@ -63,7 +64,7 @@ django-waffle==4.1.0
# -r requirements/base.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.14.0
+djangorestframework==3.15.0
# via
# -r requirements/base.txt
# drf-jwt
@@ -72,7 +73,7 @@ drf-jwt==1.19.2
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-django-utils==5.10.1
+edx-django-utils==5.11.0
# via
# -r requirements/base.txt
# edx-drf-extensions
@@ -101,11 +102,11 @@ markupsafe==2.1.5
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.7.0
+newrelic==9.7.1
# via
# -r requirements/base.txt
# edx-django-utils
-packaging==23.2
+packaging==24.0
# via pytest
pbr==6.0.0
# via
@@ -135,7 +136,7 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==8.0.2
+pytest==8.1.1
# via
# pytest-cov
# pytest-django
@@ -145,10 +146,6 @@ pytest-django==4.8.0
# via -r requirements/test.in
python-slugify==8.0.4
# via code-annotations
-pytz==2024.1
- # via
- # -r requirements/base.txt
- # djangorestframework
pyyaml==6.0.1
# via
# code-annotations
From ae347485a1181ffe010759638251d78f89a60e0b Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 26 Mar 2024 11:19:43 -0400
Subject: [PATCH 046/104] chore: Updating Python Requirements
---
requirements/base.txt | 4 ++--
requirements/ci.txt | 4 ++--
requirements/dev.txt | 10 +++++-----
requirements/doc.txt | 20 +++++++++++++-------
requirements/quality.txt | 6 +++---
requirements/test.txt | 6 +++---
6 files changed, 28 insertions(+), 22 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index c77bba2..d46b530 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -4,7 +4,7 @@
#
# make upgrade
#
-asgiref==3.7.2
+asgiref==3.8.1
# via django
attrs==23.2.0
# via -r requirements/base.in
@@ -43,7 +43,7 @@ django-waffle==4.1.0
# via
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.15.0
+djangorestframework==3.15.1
# via
# -r requirements/base.in
# drf-jwt
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 9f335db..c085cbc 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -12,7 +12,7 @@ colorama==0.4.6
# via tox
distlib==0.3.8
# via virtualenv
-filelock==3.13.1
+filelock==3.13.3
# via
# tox
# virtualenv
@@ -32,7 +32,7 @@ tomli==2.0.1
# via
# pyproject-api
# tox
-tox==4.14.1
+tox==4.14.2
# via -r requirements/ci.in
virtualenv==20.25.1
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 819335c..671113b 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -4,7 +4,7 @@
#
# make upgrade
#
-asgiref==3.7.2
+asgiref==3.8.1
# via
# -r requirements/quality.txt
# django
@@ -110,7 +110,7 @@ django-waffle==4.1.0
# -r requirements/quality.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.15.0
+djangorestframework==3.15.1
# via
# -r requirements/quality.txt
# drf-jwt
@@ -140,7 +140,7 @@ exceptiongroup==1.2.0
# via
# -r requirements/quality.txt
# pytest
-filelock==3.13.1
+filelock==3.13.3
# via
# -r requirements/ci.txt
# tox
@@ -276,7 +276,7 @@ pytest==8.1.1
# -r requirements/quality.txt
# pytest-cov
# pytest-django
-pytest-cov==4.1.0
+pytest-cov==5.0.0
# via -r requirements/quality.txt
pytest-django==4.8.0
# via -r requirements/quality.txt
@@ -346,7 +346,7 @@ tomlkit==0.12.4
# via
# -r requirements/quality.txt
# pylint
-tox==4.14.1
+tox==4.14.2
# via -r requirements/ci.txt
typing-extensions==4.10.0
# via
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 9f1dada..80366fa 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -6,7 +6,7 @@
#
alabaster==0.7.13
# via sphinx
-asgiref==3.7.2
+asgiref==3.8.1
# via
# -r requirements/test.txt
# django
@@ -74,7 +74,7 @@ django-waffle==4.1.0
# -r requirements/test.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.15.0
+djangorestframework==3.15.1
# via
# -r requirements/test.txt
# drf-jwt
@@ -121,7 +121,7 @@ importlib-metadata==6.11.0
# keyring
# sphinx
# twine
-importlib-resources==6.3.1
+importlib-resources==6.4.0
# via keyring
iniconfig==2.0.0
# via
@@ -129,6 +129,10 @@ iniconfig==2.0.0
# pytest
jaraco-classes==3.3.1
# via keyring
+jaraco-context==4.3.0
+ # via keyring
+jaraco-functools==4.0.0
+ # via keyring
jeepney==0.8.0
# via
# keyring
@@ -138,7 +142,7 @@ jinja2==3.1.3
# -r requirements/test.txt
# code-annotations
# sphinx
-keyring==24.3.1
+keyring==25.0.0
# via twine
markdown-it-py==3.0.0
# via rich
@@ -149,12 +153,14 @@ markupsafe==2.1.5
mdurl==0.1.2
# via markdown-it-py
more-itertools==10.2.0
- # via jaraco-classes
+ # via
+ # jaraco-classes
+ # jaraco-functools
newrelic==9.7.1
# via
# -r requirements/test.txt
# edx-django-utils
-nh3==0.2.15
+nh3==0.2.17
# via readme-renderer
packaging==24.0
# via
@@ -207,7 +213,7 @@ pytest==8.1.1
# -r requirements/test.txt
# pytest-cov
# pytest-django
-pytest-cov==4.1.0
+pytest-cov==5.0.0
# via -r requirements/test.txt
pytest-django==4.8.0
# via -r requirements/test.txt
diff --git a/requirements/quality.txt b/requirements/quality.txt
index c83d133..aff7fc2 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -4,7 +4,7 @@
#
# make upgrade
#
-asgiref==3.7.2
+asgiref==3.8.1
# via
# -r requirements/test.txt
# django
@@ -79,7 +79,7 @@ django-waffle==4.1.0
# -r requirements/test.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.15.0
+djangorestframework==3.15.1
# via
# -r requirements/test.txt
# drf-jwt
@@ -192,7 +192,7 @@ pytest==8.1.1
# -r requirements/test.txt
# pytest-cov
# pytest-django
-pytest-cov==4.1.0
+pytest-cov==5.0.0
# via -r requirements/test.txt
pytest-django==4.8.0
# via -r requirements/test.txt
diff --git a/requirements/test.txt b/requirements/test.txt
index 5437471..0e717e9 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -4,7 +4,7 @@
#
# make upgrade
#
-asgiref==3.7.2
+asgiref==3.8.1
# via
# -r requirements/base.txt
# django
@@ -64,7 +64,7 @@ django-waffle==4.1.0
# -r requirements/base.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.15.0
+djangorestframework==3.15.1
# via
# -r requirements/base.txt
# drf-jwt
@@ -140,7 +140,7 @@ pytest==8.1.1
# via
# pytest-cov
# pytest-django
-pytest-cov==4.1.0
+pytest-cov==5.0.0
# via -r requirements/test.in
pytest-django==4.8.0
# via -r requirements/test.in
From 4fb22f44aa44c40375990f96ed0e29125cefdf66 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 2 Apr 2024 11:20:34 -0400
Subject: [PATCH 047/104] chore: Updating Python Requirements
---
requirements/base.txt | 10 +++++-----
requirements/dev.txt | 14 +++++++-------
requirements/doc.txt | 16 ++++++++--------
requirements/pip-tools.txt | 2 +-
requirements/quality.txt | 10 +++++-----
requirements/test.txt | 10 +++++-----
6 files changed, 31 insertions(+), 31 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index d46b530..a5abdaf 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -37,7 +37,7 @@ django==4.2.11
# edx-drf-extensions
django-crum==0.7.9
# via edx-django-utils
-django-model-utils==4.4.0
+django-model-utils==4.5.0
# via -r requirements/base.in
django-waffle==4.1.0
# via
@@ -50,11 +50,11 @@ djangorestframework==3.15.1
# edx-drf-extensions
drf-jwt==1.19.2
# via edx-drf-extensions
-edx-django-utils==5.11.0
+edx-django-utils==5.12.0
# via
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.2.0
+edx-drf-extensions==10.3.0
# via -r requirements/base.in
edx-opaque-keys==2.5.1
# via
@@ -68,13 +68,13 @@ jinja2==3.1.3
# via -r requirements/base.in
markupsafe==2.1.5
# via jinja2
-newrelic==9.7.1
+newrelic==9.8.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
psutil==5.9.8
# via edx-django-utils
-pycparser==2.21
+pycparser==2.22
# via cffi
pyjwt[crypto]==2.8.0
# via
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 671113b..3547bbc 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -20,7 +20,7 @@ backports-zoneinfo==0.2.1
# -r requirements/quality.txt
# django
# djangorestframework
-build==1.1.1
+build==1.2.1
# via
# -r requirements/pip-tools.txt
# pip-tools
@@ -103,7 +103,7 @@ django-crum==0.7.9
# via
# -r requirements/quality.txt
# edx-django-utils
-django-model-utils==4.4.0
+django-model-utils==4.5.0
# via -r requirements/quality.txt
django-waffle==4.1.0
# via
@@ -119,12 +119,12 @@ drf-jwt==1.19.2
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-django-utils==5.11.0
+edx-django-utils==5.12.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.2.0
+edx-drf-extensions==10.3.0
# via -r requirements/quality.txt
edx-i18n-tools==1.3.0
# via -r requirements/dev.in
@@ -167,7 +167,7 @@ jinja2==3.1.3
# -r requirements/quality.txt
# code-annotations
# diff-cover
-lxml==5.1.0
+lxml==5.2.0
# via edx-i18n-tools
markupsafe==2.1.5
# via
@@ -177,7 +177,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.7.1
+newrelic==9.8.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -220,7 +220,7 @@ psutil==5.9.8
# edx-django-utils
pycodestyle==2.11.1
# via -r requirements/quality.txt
-pycparser==2.21
+pycparser==2.22
# via
# -r requirements/quality.txt
# cffi
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 80366fa..76a579a 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -19,7 +19,7 @@ backports-zoneinfo==0.2.1
# -r requirements/test.txt
# django
# djangorestframework
-build==1.1.1
+build==1.2.1
# via -r requirements/doc.in
certifi==2024.2.2
# via
@@ -67,7 +67,7 @@ django-crum==0.7.9
# via
# -r requirements/test.txt
# edx-django-utils
-django-model-utils==4.4.0
+django-model-utils==4.5.0
# via -r requirements/test.txt
django-waffle==4.1.0
# via
@@ -91,12 +91,12 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.11.0
+edx-django-utils==5.12.0
# via
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.2.0
+edx-drf-extensions==10.3.0
# via -r requirements/test.txt
edx-opaque-keys==2.5.1
# via
@@ -127,7 +127,7 @@ iniconfig==2.0.0
# via
# -r requirements/test.txt
# pytest
-jaraco-classes==3.3.1
+jaraco-classes==3.4.0
# via keyring
jaraco-context==4.3.0
# via keyring
@@ -142,7 +142,7 @@ jinja2==3.1.3
# -r requirements/test.txt
# code-annotations
# sphinx
-keyring==25.0.0
+keyring==25.1.0
# via twine
markdown-it-py==3.0.0
# via rich
@@ -156,7 +156,7 @@ more-itertools==10.2.0
# via
# jaraco-classes
# jaraco-functools
-newrelic==9.7.1
+newrelic==9.8.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -182,7 +182,7 @@ psutil==5.9.8
# via
# -r requirements/test.txt
# edx-django-utils
-pycparser==2.21
+pycparser==2.22
# via
# -r requirements/test.txt
# cffi
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index aad9d38..748bf44 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -4,7 +4,7 @@
#
# make upgrade
#
-build==1.1.1
+build==1.2.1
# via pip-tools
click==8.1.7
# via pip-tools
diff --git a/requirements/quality.txt b/requirements/quality.txt
index aff7fc2..587826e 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -72,7 +72,7 @@ django-crum==0.7.9
# via
# -r requirements/test.txt
# edx-django-utils
-django-model-utils==4.4.0
+django-model-utils==4.5.0
# via -r requirements/test.txt
django-waffle==4.1.0
# via
@@ -88,12 +88,12 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.11.0
+edx-django-utils==5.12.0
# via
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.2.0
+edx-drf-extensions==10.3.0
# via -r requirements/test.txt
edx-lint==5.3.6
# via -r requirements/quality.in
@@ -129,7 +129,7 @@ markupsafe==2.1.5
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.7.1
+newrelic==9.8.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -153,7 +153,7 @@ psutil==5.9.8
# edx-django-utils
pycodestyle==2.11.1
# via -r requirements/quality.in
-pycparser==2.21
+pycparser==2.22
# via
# -r requirements/test.txt
# cffi
diff --git a/requirements/test.txt b/requirements/test.txt
index 0e717e9..3f580e7 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -57,7 +57,7 @@ django-crum==0.7.9
# via
# -r requirements/base.txt
# edx-django-utils
-django-model-utils==4.4.0
+django-model-utils==4.5.0
# via -r requirements/base.txt
django-waffle==4.1.0
# via
@@ -73,12 +73,12 @@ drf-jwt==1.19.2
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-django-utils==5.11.0
+edx-django-utils==5.12.0
# via
# -r requirements/base.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.2.0
+edx-drf-extensions==10.3.0
# via -r requirements/base.txt
edx-opaque-keys==2.5.1
# via
@@ -102,7 +102,7 @@ markupsafe==2.1.5
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.7.1
+newrelic==9.8.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -118,7 +118,7 @@ psutil==5.9.8
# via
# -r requirements/base.txt
# edx-django-utils
-pycparser==2.21
+pycparser==2.22
# via
# -r requirements/base.txt
# cffi
From 6cf63d10b1140b74a60b622346676ec3a17d11ef Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 19 Apr 2024 14:58:41 -0400
Subject: [PATCH 048/104] chore: Updating Python Requirements (#81)
---
requirements/base.txt | 8 ++++----
requirements/ci.txt | 2 +-
requirements/dev.txt | 20 ++++++++++----------
requirements/doc.txt | 14 ++++++++------
requirements/pip.txt | 2 +-
requirements/quality.txt | 10 +++++-----
requirements/test.txt | 10 +++++-----
7 files changed, 34 insertions(+), 32 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index a5abdaf..5473460 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -60,9 +60,9 @@ edx-opaque-keys==2.5.1
# via
# -r requirements/base.in
# edx-drf-extensions
-edx-rest-api-client==5.6.1
+edx-rest-api-client==5.7.0
# via -r requirements/base.in
-idna==3.6
+idna==3.7
# via requests
jinja2==3.1.3
# via -r requirements/base.in
@@ -94,13 +94,13 @@ semantic-version==2.10.0
# via edx-drf-extensions
slumber==0.7.1
# via edx-rest-api-client
-sqlparse==0.4.4
+sqlparse==0.5.0
# via django
stevedore==5.2.0
# via
# edx-django-utils
# edx-opaque-keys
-typing-extensions==4.10.0
+typing-extensions==4.11.0
# via
# asgiref
# edx-opaque-keys
diff --git a/requirements/ci.txt b/requirements/ci.txt
index c085cbc..641296f 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -12,7 +12,7 @@ colorama==0.4.6
# via tox
distlib==0.3.8
# via virtualenv
-filelock==3.13.3
+filelock==3.13.4
# via
# tox
# virtualenv
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 3547bbc..e710757 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -59,7 +59,7 @@ click-log==0.4.0
# via
# -r requirements/quality.txt
# edx-lint
-code-annotations==1.7.0
+code-annotations==1.8.0
# via
# -r requirements/quality.txt
# edx-lint
@@ -77,7 +77,7 @@ cryptography==42.0.5
# pyjwt
ddt==1.7.2
# via -r requirements/quality.txt
-diff-cover==8.0.3
+diff-cover==9.0.0
# via -r requirements/dev.in
dill==0.3.8
# via
@@ -126,7 +126,7 @@ edx-django-utils==5.12.0
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/quality.txt
-edx-i18n-tools==1.3.0
+edx-i18n-tools==1.5.0
# via -r requirements/dev.in
edx-lint==5.3.6
# via -r requirements/quality.txt
@@ -134,18 +134,18 @@ edx-opaque-keys==2.5.1
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-rest-api-client==5.6.1
+edx-rest-api-client==5.7.0
# via -r requirements/quality.txt
exceptiongroup==1.2.0
# via
# -r requirements/quality.txt
# pytest
-filelock==3.13.3
+filelock==3.13.4
# via
# -r requirements/ci.txt
# tox
# virtualenv
-idna==3.6
+idna==3.7
# via
# -r requirements/quality.txt
# requests
@@ -167,7 +167,7 @@ jinja2==3.1.3
# -r requirements/quality.txt
# code-annotations
# diff-cover
-lxml==5.2.0
+lxml==5.2.1
# via edx-i18n-tools
markupsafe==2.1.5
# via
@@ -190,7 +190,7 @@ packaging==24.0
# pyproject-api
# pytest
# tox
-path==16.10.0
+path==16.14.0
# via edx-i18n-tools
pbr==6.0.0
# via
@@ -315,7 +315,7 @@ snowballstemmer==2.2.0
# via
# -r requirements/quality.txt
# pydocstyle
-sqlparse==0.4.4
+sqlparse==0.5.0
# via
# -r requirements/quality.txt
# django
@@ -348,7 +348,7 @@ tomlkit==0.12.4
# pylint
tox==4.14.2
# via -r requirements/ci.txt
-typing-extensions==4.10.0
+typing-extensions==4.11.0
# via
# -r requirements/quality.txt
# asgiref
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 76a579a..9236512 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -14,6 +14,8 @@ attrs==23.2.0
# via -r requirements/test.txt
babel==2.14.0
# via sphinx
+backports-tarfile==1.1.0
+ # via jaraco-context
backports-zoneinfo==0.2.1
# via
# -r requirements/test.txt
@@ -39,7 +41,7 @@ click==8.1.7
# -r requirements/test.txt
# code-annotations
# edx-django-utils
-code-annotations==1.7.0
+code-annotations==1.8.0
# via -r requirements/test.txt
coverage[toml]==7.4.4
# via
@@ -102,13 +104,13 @@ edx-opaque-keys==2.5.1
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-rest-api-client==5.6.1
+edx-rest-api-client==5.7.0
# via -r requirements/test.txt
exceptiongroup==1.2.0
# via
# -r requirements/test.txt
# pytest
-idna==3.6
+idna==3.7
# via
# -r requirements/test.txt
# requests
@@ -129,7 +131,7 @@ iniconfig==2.0.0
# pytest
jaraco-classes==3.4.0
# via keyring
-jaraco-context==4.3.0
+jaraco-context==5.3.0
# via keyring
jaraco-functools==4.0.0
# via keyring
@@ -276,7 +278,7 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
-sqlparse==0.4.4
+sqlparse==0.5.0
# via
# -r requirements/test.txt
# django
@@ -301,7 +303,7 @@ tomli==2.0.1
# pytest
twine==5.0.0
# via -r requirements/doc.in
-typing-extensions==4.10.0
+typing-extensions==4.11.0
# via
# -r requirements/test.txt
# asgiref
diff --git a/requirements/pip.txt b/requirements/pip.txt
index cf44902..e3ffcc7 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -10,5 +10,5 @@ wheel==0.43.0
# The following packages are considered to be unsafe in a requirements file:
pip==24.0
# via -r requirements/pip.in
-setuptools==69.2.0
+setuptools==69.5.1
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 587826e..7f7fc77 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -41,7 +41,7 @@ click==8.1.7
# edx-lint
click-log==0.4.0
# via edx-lint
-code-annotations==1.7.0
+code-annotations==1.8.0
# via
# -r requirements/test.txt
# edx-lint
@@ -101,13 +101,13 @@ edx-opaque-keys==2.5.1
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-rest-api-client==5.6.1
+edx-rest-api-client==5.7.0
# via -r requirements/test.txt
exceptiongroup==1.2.0
# via
# -r requirements/test.txt
# pytest
-idna==3.6
+idna==3.7
# via
# -r requirements/test.txt
# requests
@@ -226,7 +226,7 @@ slumber==0.7.1
# edx-rest-api-client
snowballstemmer==2.2.0
# via pydocstyle
-sqlparse==0.4.4
+sqlparse==0.5.0
# via
# -r requirements/test.txt
# django
@@ -248,7 +248,7 @@ tomli==2.0.1
# pytest
tomlkit==0.12.4
# via pylint
-typing-extensions==4.10.0
+typing-extensions==4.11.0
# via
# -r requirements/test.txt
# asgiref
diff --git a/requirements/test.txt b/requirements/test.txt
index 3f580e7..6d869ee 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -33,7 +33,7 @@ click==8.1.7
# -r requirements/base.txt
# code-annotations
# edx-django-utils
-code-annotations==1.7.0
+code-annotations==1.8.0
# via -r requirements/test.in
coverage[toml]==7.4.4
# via pytest-cov
@@ -84,11 +84,11 @@ edx-opaque-keys==2.5.1
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-rest-api-client==5.6.1
+edx-rest-api-client==5.7.0
# via -r requirements/base.txt
exceptiongroup==1.2.0
# via pytest
-idna==3.6
+idna==3.7
# via
# -r requirements/base.txt
# requests
@@ -167,7 +167,7 @@ slumber==0.7.1
# via
# -r requirements/base.txt
# edx-rest-api-client
-sqlparse==0.4.4
+sqlparse==0.5.0
# via
# -r requirements/base.txt
# django
@@ -183,7 +183,7 @@ tomli==2.0.1
# via
# coverage
# pytest
-typing-extensions==4.10.0
+typing-extensions==4.11.0
# via
# -r requirements/base.txt
# asgiref
From ff0b8fa1dab4de75d96a6e0add49aca46ce14089 Mon Sep 17 00:00:00 2001
From: Zacharis278
Date: Thu, 25 Apr 2024 15:54:40 -0400
Subject: [PATCH 049/104] build: replace codecov
---
.coveragerc | 1 +
.github/workflows/ci.yml | 9 ++++++---
codecov.yml | 15 ---------------
3 files changed, 7 insertions(+), 18 deletions(-)
delete mode 100644 codecov.yml
diff --git a/.coveragerc b/.coveragerc
index a519e2a..c26fb19 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,6 +1,7 @@
[run]
branch = True
data_file = .coverage
+relative_files = True
source=learning_assistant
omit =
test_settings
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e5af682..920d5c5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,7 +38,10 @@ jobs:
- name: Run coverage
if: matrix.python-version == '3.8' && matrix.toxenv == 'django32'
- uses: codecov/codecov-action@v1
+ uses: py-cov-action/python-coverage-comment-action@v3
with:
- flags: unittests
- fail_ci_if_error: true
+ GITHUB_TOKEN: ${{ github.token }}
+ MINIMUM_GREEN: 95
+ MINIMUM_ORANGE: 84
+ ANNOTATE_MISSING_LINES: true
+ ANNOTATION_TYPE: error
diff --git a/codecov.yml b/codecov.yml
deleted file mode 100644
index 681e43e..0000000
--- a/codecov.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-coverage:
- status:
- project:
- default:
- enabled: yes
- target: auto
- patch:
- default:
- enabled: yes
- target: 100%
-ignore:
- - "learning_assistant/platform_imports.py"
-
-
-comment: false
From eec0517d01e35aa3ec11196e132590c13be54e18 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Fri, 26 Apr 2024 14:41:56 -0400
Subject: [PATCH 050/104] chore: Updating Python Requirements
---
requirements/base.txt | 8 +++++---
requirements/ci.txt | 6 +++---
requirements/dev.txt | 32 ++++++++++++++++++++------------
requirements/doc.txt | 24 ++++++++++++++----------
requirements/quality.txt | 20 ++++++++++++--------
requirements/test.txt | 18 +++++++++++-------
6 files changed, 65 insertions(+), 43 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 5473460..f1095d5 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -48,6 +48,8 @@ djangorestframework==3.15.1
# -r requirements/base.in
# drf-jwt
# edx-drf-extensions
+dnspython==2.6.1
+ # via pymongo
drf-jwt==1.19.2
# via edx-drf-extensions
edx-django-utils==5.12.0
@@ -56,7 +58,7 @@ edx-django-utils==5.12.0
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/base.in
-edx-opaque-keys==2.5.1
+edx-opaque-keys==2.9.0
# via
# -r requirements/base.in
# edx-drf-extensions
@@ -68,7 +70,7 @@ jinja2==3.1.3
# via -r requirements/base.in
markupsafe==2.1.5
# via jinja2
-newrelic==9.8.0
+newrelic==9.9.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
@@ -81,7 +83,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==3.13.0
+pymongo==4.4.0
# via edx-opaque-keys
pynacl==1.5.0
# via edx-django-utils
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 641296f..b88bd34 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -20,11 +20,11 @@ packaging==24.0
# via
# pyproject-api
# tox
-platformdirs==4.2.0
+platformdirs==4.2.1
# via
# tox
# virtualenv
-pluggy==1.4.0
+pluggy==1.5.0
# via tox
pyproject-api==1.6.1
# via tox
@@ -34,5 +34,5 @@ tomli==2.0.1
# tox
tox==4.14.2
# via -r requirements/ci.in
-virtualenv==20.25.1
+virtualenv==20.26.0
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index e710757..4bab38f 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -67,7 +67,7 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.4.4
+coverage[toml]==7.5.0
# via
# -r requirements/quality.txt
# pytest-cov
@@ -115,6 +115,10 @@ djangorestframework==3.15.1
# -r requirements/quality.txt
# drf-jwt
# edx-drf-extensions
+dnspython==2.6.1
+ # via
+ # -r requirements/quality.txt
+ # pymongo
drf-jwt==1.19.2
# via
# -r requirements/quality.txt
@@ -126,17 +130,17 @@ edx-django-utils==5.12.0
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/quality.txt
-edx-i18n-tools==1.5.0
+edx-i18n-tools==1.6.0
# via -r requirements/dev.in
edx-lint==5.3.6
# via -r requirements/quality.txt
-edx-opaque-keys==2.5.1
+edx-opaque-keys==2.9.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
edx-rest-api-client==5.7.0
# via -r requirements/quality.txt
-exceptiongroup==1.2.0
+exceptiongroup==1.2.1
# via
# -r requirements/quality.txt
# pytest
@@ -167,8 +171,12 @@ jinja2==3.1.3
# -r requirements/quality.txt
# code-annotations
# diff-cover
-lxml==5.2.1
- # via edx-i18n-tools
+lxml[html-clean,html_clean]==5.2.1
+ # via
+ # edx-i18n-tools
+ # lxml-html-clean
+lxml-html-clean==0.1.1
+ # via lxml
markupsafe==2.1.5
# via
# -r requirements/quality.txt
@@ -177,7 +185,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.8.0
+newrelic==9.9.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -198,14 +206,14 @@ pbr==6.0.0
# stevedore
pip-tools==7.4.1
# via -r requirements/pip-tools.txt
-platformdirs==4.2.0
+platformdirs==4.2.1
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
# pylint
# tox
# virtualenv
-pluggy==1.4.0
+pluggy==1.5.0
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -254,7 +262,7 @@ pylint-plugin-utils==0.8.2
# -r requirements/quality.txt
# pylint-celery
# pylint-django
-pymongo==3.13.0
+pymongo==4.4.0
# via
# -r requirements/quality.txt
# edx-opaque-keys
@@ -271,7 +279,7 @@ pyproject-hooks==1.0.0
# -r requirements/pip-tools.txt
# build
# pip-tools
-pytest==8.1.1
+pytest==8.1.2
# via
# -r requirements/quality.txt
# pytest-cov
@@ -360,7 +368,7 @@ urllib3==2.2.1
# -r requirements/quality.txt
# requests
# responses
-virtualenv==20.25.1
+virtualenv==20.26.0
# via
# -r requirements/ci.txt
# tox
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 9236512..65329da 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -14,7 +14,7 @@ attrs==23.2.0
# via -r requirements/test.txt
babel==2.14.0
# via sphinx
-backports-tarfile==1.1.0
+backports-tarfile==1.1.1
# via jaraco-context
backports-zoneinfo==0.2.1
# via
@@ -43,7 +43,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.txt
-coverage[toml]==7.4.4
+coverage[toml]==7.5.0
# via
# -r requirements/test.txt
# pytest-cov
@@ -81,6 +81,10 @@ djangorestframework==3.15.1
# -r requirements/test.txt
# drf-jwt
# edx-drf-extensions
+dnspython==2.6.1
+ # via
+ # -r requirements/test.txt
+ # pymongo
doc8==1.1.1
# via -r requirements/doc.in
docutils==0.20.1
@@ -100,13 +104,13 @@ edx-django-utils==5.12.0
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/test.txt
-edx-opaque-keys==2.5.1
+edx-opaque-keys==2.9.0
# via
# -r requirements/test.txt
# edx-drf-extensions
edx-rest-api-client==5.7.0
# via -r requirements/test.txt
-exceptiongroup==1.2.0
+exceptiongroup==1.2.1
# via
# -r requirements/test.txt
# pytest
@@ -133,7 +137,7 @@ jaraco-classes==3.4.0
# via keyring
jaraco-context==5.3.0
# via keyring
-jaraco-functools==4.0.0
+jaraco-functools==4.0.1
# via keyring
jeepney==0.8.0
# via
@@ -144,7 +148,7 @@ jinja2==3.1.3
# -r requirements/test.txt
# code-annotations
# sphinx
-keyring==25.1.0
+keyring==25.2.0
# via twine
markdown-it-py==3.0.0
# via rich
@@ -158,7 +162,7 @@ more-itertools==10.2.0
# via
# jaraco-classes
# jaraco-functools
-newrelic==9.8.0
+newrelic==9.9.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -176,7 +180,7 @@ pbr==6.0.0
# stevedore
pkginfo==1.10.0
# via twine
-pluggy==1.4.0
+pluggy==1.5.0
# via
# -r requirements/test.txt
# pytest
@@ -200,7 +204,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==3.13.0
+pymongo==4.4.0
# via
# -r requirements/test.txt
# edx-opaque-keys
@@ -210,7 +214,7 @@ pynacl==1.5.0
# edx-django-utils
pyproject-hooks==1.0.0
# via build
-pytest==8.1.1
+pytest==8.1.2
# via
# -r requirements/test.txt
# pytest-cov
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 7f7fc77..015f5cc 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -45,7 +45,7 @@ code-annotations==1.8.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.4.4
+coverage[toml]==7.5.0
# via
# -r requirements/test.txt
# pytest-cov
@@ -84,6 +84,10 @@ djangorestframework==3.15.1
# -r requirements/test.txt
# drf-jwt
# edx-drf-extensions
+dnspython==2.6.1
+ # via
+ # -r requirements/test.txt
+ # pymongo
drf-jwt==1.19.2
# via
# -r requirements/test.txt
@@ -97,13 +101,13 @@ edx-drf-extensions==10.3.0
# via -r requirements/test.txt
edx-lint==5.3.6
# via -r requirements/quality.in
-edx-opaque-keys==2.5.1
+edx-opaque-keys==2.9.0
# via
# -r requirements/test.txt
# edx-drf-extensions
edx-rest-api-client==5.7.0
# via -r requirements/test.txt
-exceptiongroup==1.2.0
+exceptiongroup==1.2.1
# via
# -r requirements/test.txt
# pytest
@@ -129,7 +133,7 @@ markupsafe==2.1.5
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.8.0
+newrelic==9.9.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -141,9 +145,9 @@ pbr==6.0.0
# via
# -r requirements/test.txt
# stevedore
-platformdirs==4.2.0
+platformdirs==4.2.1
# via pylint
-pluggy==1.4.0
+pluggy==1.5.0
# via
# -r requirements/test.txt
# pytest
@@ -179,7 +183,7 @@ pylint-plugin-utils==0.8.2
# via
# pylint-celery
# pylint-django
-pymongo==3.13.0
+pymongo==4.4.0
# via
# -r requirements/test.txt
# edx-opaque-keys
@@ -187,7 +191,7 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==8.1.1
+pytest==8.1.2
# via
# -r requirements/test.txt
# pytest-cov
diff --git a/requirements/test.txt b/requirements/test.txt
index 6d869ee..63adf92 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -35,7 +35,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.in
-coverage[toml]==7.4.4
+coverage[toml]==7.5.0
# via pytest-cov
cryptography==42.0.5
# via
@@ -69,6 +69,10 @@ djangorestframework==3.15.1
# -r requirements/base.txt
# drf-jwt
# edx-drf-extensions
+dnspython==2.6.1
+ # via
+ # -r requirements/base.txt
+ # pymongo
drf-jwt==1.19.2
# via
# -r requirements/base.txt
@@ -80,13 +84,13 @@ edx-django-utils==5.12.0
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/base.txt
-edx-opaque-keys==2.5.1
+edx-opaque-keys==2.9.0
# via
# -r requirements/base.txt
# edx-drf-extensions
edx-rest-api-client==5.7.0
# via -r requirements/base.txt
-exceptiongroup==1.2.0
+exceptiongroup==1.2.1
# via pytest
idna==3.7
# via
@@ -102,7 +106,7 @@ markupsafe==2.1.5
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.8.0
+newrelic==9.9.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -112,7 +116,7 @@ pbr==6.0.0
# via
# -r requirements/base.txt
# stevedore
-pluggy==1.4.0
+pluggy==1.5.0
# via pytest
psutil==5.9.8
# via
@@ -128,7 +132,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==3.13.0
+pymongo==4.4.0
# via
# -r requirements/base.txt
# edx-opaque-keys
@@ -136,7 +140,7 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==8.1.1
+pytest==8.1.2
# via
# pytest-cov
# pytest-django
From 0fff014212ab83f80cd220344ad3c7d1f887b404 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 30 Apr 2024 11:18:29 -0400
Subject: [PATCH 051/104] chore: Updating Python Requirements
---
requirements/base.txt | 2 +-
requirements/ci.txt | 6 +++---
requirements/dev.txt | 13 ++++++-------
requirements/doc.txt | 7 +++----
requirements/pip-tools.txt | 3 +--
requirements/quality.txt | 4 ++--
requirements/test.txt | 4 ++--
7 files changed, 18 insertions(+), 21 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index f1095d5..036e454 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -52,7 +52,7 @@ dnspython==2.6.1
# via pymongo
drf-jwt==1.19.2
# via edx-drf-extensions
-edx-django-utils==5.12.0
+edx-django-utils==5.13.0
# via
# edx-drf-extensions
# edx-rest-api-client
diff --git a/requirements/ci.txt b/requirements/ci.txt
index b88bd34..2877eac 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -12,7 +12,7 @@ colorama==0.4.6
# via tox
distlib==0.3.8
# via virtualenv
-filelock==3.13.4
+filelock==3.14.0
# via
# tox
# virtualenv
@@ -32,7 +32,7 @@ tomli==2.0.1
# via
# pyproject-api
# tox
-tox==4.14.2
+tox==4.15.0
# via -r requirements/ci.in
-virtualenv==20.26.0
+virtualenv==20.26.1
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 4bab38f..5c5874e 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -123,7 +123,7 @@ drf-jwt==1.19.2
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-django-utils==5.12.0
+edx-django-utils==5.13.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
@@ -144,7 +144,7 @@ exceptiongroup==1.2.1
# via
# -r requirements/quality.txt
# pytest
-filelock==3.13.4
+filelock==3.14.0
# via
# -r requirements/ci.txt
# tox
@@ -274,12 +274,12 @@ pyproject-api==1.6.1
# via
# -r requirements/ci.txt
# tox
-pyproject-hooks==1.0.0
+pyproject-hooks==1.1.0
# via
# -r requirements/pip-tools.txt
# build
# pip-tools
-pytest==8.1.2
+pytest==8.2.0
# via
# -r requirements/quality.txt
# pytest-cov
@@ -347,14 +347,13 @@ tomli==2.0.1
# pip-tools
# pylint
# pyproject-api
- # pyproject-hooks
# pytest
# tox
tomlkit==0.12.4
# via
# -r requirements/quality.txt
# pylint
-tox==4.14.2
+tox==4.15.0
# via -r requirements/ci.txt
typing-extensions==4.11.0
# via
@@ -368,7 +367,7 @@ urllib3==2.2.1
# -r requirements/quality.txt
# requests
# responses
-virtualenv==20.26.0
+virtualenv==20.26.1
# via
# -r requirements/ci.txt
# tox
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 65329da..a4fccb3 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -97,7 +97,7 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.12.0
+edx-django-utils==5.13.0
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -212,9 +212,9 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pyproject-hooks==1.0.0
+pyproject-hooks==1.1.0
# via build
-pytest==8.1.2
+pytest==8.2.0
# via
# -r requirements/test.txt
# pytest-cov
@@ -303,7 +303,6 @@ tomli==2.0.1
# build
# coverage
# doc8
- # pyproject-hooks
# pytest
twine==5.0.0
# via -r requirements/doc.in
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 748bf44..3f05628 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -16,7 +16,7 @@ packaging==24.0
# via build
pip-tools==7.4.1
# via -r requirements/pip-tools.in
-pyproject-hooks==1.0.0
+pyproject-hooks==1.1.0
# via
# build
# pip-tools
@@ -24,7 +24,6 @@ tomli==2.0.1
# via
# build
# pip-tools
- # pyproject-hooks
wheel==0.43.0
# via pip-tools
zipp==3.18.1
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 015f5cc..f0eb3d8 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -92,7 +92,7 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.12.0
+edx-django-utils==5.13.0
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -191,7 +191,7 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==8.1.2
+pytest==8.2.0
# via
# -r requirements/test.txt
# pytest-cov
diff --git a/requirements/test.txt b/requirements/test.txt
index 63adf92..36dee2c 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -77,7 +77,7 @@ drf-jwt==1.19.2
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-django-utils==5.12.0
+edx-django-utils==5.13.0
# via
# -r requirements/base.txt
# edx-drf-extensions
@@ -140,7 +140,7 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==8.1.2
+pytest==8.2.0
# via
# pytest-cov
# pytest-django
From f8d2aad53efcc2cd953de07b17423c6b9e3150a7 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 7 May 2024 11:20:36 -0400
Subject: [PATCH 052/104] chore: Updating Python Requirements
---
requirements/base.txt | 6 +++---
requirements/dev.txt | 10 +++++-----
requirements/doc.txt | 12 ++++++------
requirements/quality.txt | 8 ++++----
requirements/test.txt | 8 ++++----
5 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 036e454..a021d42 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -22,7 +22,7 @@ charset-normalizer==3.3.2
# via requests
click==8.1.7
# via edx-django-utils
-cryptography==42.0.5
+cryptography==42.0.7
# via pyjwt
django==4.2.11
# via
@@ -37,7 +37,7 @@ django==4.2.11
# edx-drf-extensions
django-crum==0.7.9
# via edx-django-utils
-django-model-utils==4.5.0
+django-model-utils==4.5.1
# via -r requirements/base.in
django-waffle==4.1.0
# via
@@ -66,7 +66,7 @@ edx-rest-api-client==5.7.0
# via -r requirements/base.in
idna==3.7
# via requests
-jinja2==3.1.3
+jinja2==3.1.4
# via -r requirements/base.in
markupsafe==2.1.5
# via jinja2
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 5c5874e..f9915c5 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -67,11 +67,11 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.5.0
+coverage[toml]==7.5.1
# via
# -r requirements/quality.txt
# pytest-cov
-cryptography==42.0.5
+cryptography==42.0.7
# via
# -r requirements/quality.txt
# pyjwt
@@ -103,7 +103,7 @@ django-crum==0.7.9
# via
# -r requirements/quality.txt
# edx-django-utils
-django-model-utils==4.5.0
+django-model-utils==4.5.1
# via -r requirements/quality.txt
django-waffle==4.1.0
# via
@@ -166,7 +166,7 @@ isort==5.13.2
# via
# -r requirements/quality.txt
# pylint
-jinja2==3.1.3
+jinja2==3.1.4
# via
# -r requirements/quality.txt
# code-annotations
@@ -234,7 +234,7 @@ pycparser==2.22
# cffi
pydocstyle==6.3.0
# via -r requirements/quality.txt
-pygments==2.17.2
+pygments==2.18.0
# via diff-cover
pyjwt[crypto]==2.8.0
# via
diff --git a/requirements/doc.txt b/requirements/doc.txt
index a4fccb3..02979ad 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -12,7 +12,7 @@ asgiref==3.8.1
# django
attrs==23.2.0
# via -r requirements/test.txt
-babel==2.14.0
+babel==2.15.0
# via sphinx
backports-tarfile==1.1.1
# via jaraco-context
@@ -43,11 +43,11 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.txt
-coverage[toml]==7.5.0
+coverage[toml]==7.5.1
# via
# -r requirements/test.txt
# pytest-cov
-cryptography==42.0.5
+cryptography==42.0.7
# via
# -r requirements/test.txt
# pyjwt
@@ -69,7 +69,7 @@ django-crum==0.7.9
# via
# -r requirements/test.txt
# edx-django-utils
-django-model-utils==4.5.0
+django-model-utils==4.5.1
# via -r requirements/test.txt
django-waffle==4.1.0
# via
@@ -143,7 +143,7 @@ jeepney==0.8.0
# via
# keyring
# secretstorage
-jinja2==3.1.3
+jinja2==3.1.4
# via
# -r requirements/test.txt
# code-annotations
@@ -192,7 +192,7 @@ pycparser==2.22
# via
# -r requirements/test.txt
# cffi
-pygments==2.17.2
+pygments==2.18.0
# via
# doc8
# readme-renderer
diff --git a/requirements/quality.txt b/requirements/quality.txt
index f0eb3d8..2745a0b 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -45,11 +45,11 @@ code-annotations==1.8.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.5.0
+coverage[toml]==7.5.1
# via
# -r requirements/test.txt
# pytest-cov
-cryptography==42.0.5
+cryptography==42.0.7
# via
# -r requirements/test.txt
# pyjwt
@@ -72,7 +72,7 @@ django-crum==0.7.9
# via
# -r requirements/test.txt
# edx-django-utils
-django-model-utils==4.5.0
+django-model-utils==4.5.1
# via -r requirements/test.txt
django-waffle==4.1.0
# via
@@ -123,7 +123,7 @@ isort==5.13.2
# via
# -r requirements/quality.in
# pylint
-jinja2==3.1.3
+jinja2==3.1.4
# via
# -r requirements/test.txt
# code-annotations
diff --git a/requirements/test.txt b/requirements/test.txt
index 36dee2c..102aa73 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -35,9 +35,9 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.in
-coverage[toml]==7.5.0
+coverage[toml]==7.5.1
# via pytest-cov
-cryptography==42.0.5
+cryptography==42.0.7
# via
# -r requirements/base.txt
# pyjwt
@@ -57,7 +57,7 @@ django-crum==0.7.9
# via
# -r requirements/base.txt
# edx-django-utils
-django-model-utils==4.5.0
+django-model-utils==4.5.1
# via -r requirements/base.txt
django-waffle==4.1.0
# via
@@ -98,7 +98,7 @@ idna==3.7
# requests
iniconfig==2.0.0
# via pytest
-jinja2==3.1.3
+jinja2==3.1.4
# via
# -r requirements/base.txt
# code-annotations
From 6703a09605d2b343055c63b26a2ee485dc3b2a17 Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 17 May 2024 16:24:54 -0400
Subject: [PATCH 053/104] chore: Updating Python Requirements (#87)
---
requirements/base.txt | 4 ++--
requirements/ci.txt | 2 +-
requirements/dev.txt | 14 +++++++-------
requirements/doc.txt | 6 +++---
requirements/quality.txt | 10 +++++-----
requirements/test.txt | 2 +-
6 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index a021d42..1c05cdb 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -24,7 +24,7 @@ click==8.1.7
# via edx-django-utils
cryptography==42.0.7
# via pyjwt
-django==4.2.11
+django==4.2.13
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/base.in
@@ -70,7 +70,7 @@ jinja2==3.1.4
# via -r requirements/base.in
markupsafe==2.1.5
# via jinja2
-newrelic==9.9.0
+newrelic==9.9.1
# via edx-django-utils
pbr==6.0.0
# via stevedore
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 2877eac..d3c358a 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -34,5 +34,5 @@ tomli==2.0.1
# tox
tox==4.15.0
# via -r requirements/ci.in
-virtualenv==20.26.1
+virtualenv==20.26.2
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index f9915c5..90189d8 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -8,7 +8,7 @@ asgiref==3.8.1
# via
# -r requirements/quality.txt
# django
-astroid==3.1.0
+astroid==3.2.0
# via
# -r requirements/quality.txt
# pylint
@@ -87,7 +87,7 @@ distlib==0.3.8
# via
# -r requirements/ci.txt
# virtualenv
-django==4.2.11
+django==4.2.13
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/quality.txt
@@ -171,7 +171,7 @@ jinja2==3.1.4
# -r requirements/quality.txt
# code-annotations
# diff-cover
-lxml[html-clean,html_clean]==5.2.1
+lxml[html-clean,html_clean]==5.2.2
# via
# edx-i18n-tools
# lxml-html-clean
@@ -185,7 +185,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.9.0
+newrelic==9.9.1
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -242,7 +242,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.1.0
+pylint==3.2.0
# via
# -r requirements/quality.txt
# edx-lint
@@ -349,7 +349,7 @@ tomli==2.0.1
# pyproject-api
# pytest
# tox
-tomlkit==0.12.4
+tomlkit==0.12.5
# via
# -r requirements/quality.txt
# pylint
@@ -367,7 +367,7 @@ urllib3==2.2.1
# -r requirements/quality.txt
# requests
# responses
-virtualenv==20.26.1
+virtualenv==20.26.2
# via
# -r requirements/ci.txt
# tox
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 02979ad..4ffdfa9 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -54,7 +54,7 @@ cryptography==42.0.7
# secretstorage
ddt==1.7.2
# via -r requirements/test.txt
-django==4.2.11
+django==4.2.13
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -148,7 +148,7 @@ jinja2==3.1.4
# -r requirements/test.txt
# code-annotations
# sphinx
-keyring==25.2.0
+keyring==25.2.1
# via twine
markdown-it-py==3.0.0
# via rich
@@ -162,7 +162,7 @@ more-itertools==10.2.0
# via
# jaraco-classes
# jaraco-functools
-newrelic==9.9.0
+newrelic==9.9.1
# via
# -r requirements/test.txt
# edx-django-utils
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 2745a0b..b259688 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -8,7 +8,7 @@ asgiref==3.8.1
# via
# -r requirements/test.txt
# django
-astroid==3.1.0
+astroid==3.2.0
# via
# pylint
# pylint-celery
@@ -57,7 +57,7 @@ ddt==1.7.2
# via -r requirements/test.txt
dill==0.3.8
# via pylint
-django==4.2.11
+django==4.2.13
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -133,7 +133,7 @@ markupsafe==2.1.5
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.9.0
+newrelic==9.9.1
# via
# -r requirements/test.txt
# edx-django-utils
@@ -169,7 +169,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.1.0
+pylint==3.2.0
# via
# edx-lint
# pylint-celery
@@ -250,7 +250,7 @@ tomli==2.0.1
# coverage
# pylint
# pytest
-tomlkit==0.12.4
+tomlkit==0.12.5
# via pylint
typing-extensions==4.11.0
# via
diff --git a/requirements/test.txt b/requirements/test.txt
index 102aa73..4555ee4 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -106,7 +106,7 @@ markupsafe==2.1.5
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.9.0
+newrelic==9.9.1
# via
# -r requirements/base.txt
# edx-django-utils
From 219aece9ad8fc24ad55f5ddbac049a3f634862ab Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 24 May 2024 06:42:44 -0400
Subject: [PATCH 054/104] chore: Updating Python Requirements (#88)
---
requirements/ci.txt | 2 +-
requirements/dev.txt | 10 +++++-----
requirements/doc.txt | 6 +++---
requirements/pip-tools.txt | 2 +-
requirements/pip.txt | 2 +-
requirements/quality.txt | 8 ++++----
requirements/test.txt | 2 +-
7 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/requirements/ci.txt b/requirements/ci.txt
index d3c358a..0008d40 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -20,7 +20,7 @@ packaging==24.0
# via
# pyproject-api
# tox
-platformdirs==4.2.1
+platformdirs==4.2.2
# via
# tox
# virtualenv
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 90189d8..a5b1370 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -8,7 +8,7 @@ asgiref==3.8.1
# via
# -r requirements/quality.txt
# django
-astroid==3.2.0
+astroid==3.2.2
# via
# -r requirements/quality.txt
# pylint
@@ -206,7 +206,7 @@ pbr==6.0.0
# stevedore
pip-tools==7.4.1
# via -r requirements/pip-tools.txt
-platformdirs==4.2.1
+platformdirs==4.2.2
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -242,7 +242,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.0
+pylint==3.2.2
# via
# -r requirements/quality.txt
# edx-lint
@@ -279,7 +279,7 @@ pyproject-hooks==1.1.0
# -r requirements/pip-tools.txt
# build
# pip-tools
-pytest==8.2.0
+pytest==8.2.1
# via
# -r requirements/quality.txt
# pytest-cov
@@ -375,7 +375,7 @@ wheel==0.43.0
# via
# -r requirements/pip-tools.txt
# pip-tools
-zipp==3.18.1
+zipp==3.18.2
# via
# -r requirements/pip-tools.txt
# importlib-metadata
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 4ffdfa9..ef5a6c8 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -214,7 +214,7 @@ pynacl==1.5.0
# edx-django-utils
pyproject-hooks==1.1.0
# via build
-pytest==8.2.0
+pytest==8.2.1
# via
# -r requirements/test.txt
# pytest-cov
@@ -304,7 +304,7 @@ tomli==2.0.1
# coverage
# doc8
# pytest
-twine==5.0.0
+twine==5.1.0
# via -r requirements/doc.in
typing-extensions==4.11.0
# via
@@ -318,7 +318,7 @@ urllib3==2.2.1
# requests
# responses
# twine
-zipp==3.18.1
+zipp==3.18.2
# via
# importlib-metadata
# importlib-resources
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 3f05628..dbb1ac7 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -26,7 +26,7 @@ tomli==2.0.1
# pip-tools
wheel==0.43.0
# via pip-tools
-zipp==3.18.1
+zipp==3.18.2
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
diff --git a/requirements/pip.txt b/requirements/pip.txt
index e3ffcc7..8a72bb0 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -10,5 +10,5 @@ wheel==0.43.0
# The following packages are considered to be unsafe in a requirements file:
pip==24.0
# via -r requirements/pip.in
-setuptools==69.5.1
+setuptools==70.0.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index b259688..4c03658 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -8,7 +8,7 @@ asgiref==3.8.1
# via
# -r requirements/test.txt
# django
-astroid==3.2.0
+astroid==3.2.2
# via
# pylint
# pylint-celery
@@ -145,7 +145,7 @@ pbr==6.0.0
# via
# -r requirements/test.txt
# stevedore
-platformdirs==4.2.1
+platformdirs==4.2.2
# via pylint
pluggy==1.5.0
# via
@@ -169,7 +169,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.0
+pylint==3.2.2
# via
# edx-lint
# pylint-celery
@@ -191,7 +191,7 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==8.2.0
+pytest==8.2.1
# via
# -r requirements/test.txt
# pytest-cov
diff --git a/requirements/test.txt b/requirements/test.txt
index 4555ee4..5644846 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -140,7 +140,7 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==8.2.0
+pytest==8.2.1
# via
# pytest-cov
# pytest-django
From 4934a50e091be967ff66c87c29e74e5cd617f060 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 4 Jun 2024 11:21:46 -0400
Subject: [PATCH 055/104] chore: Updating Python Requirements
---
requirements/base.txt | 8 ++++----
requirements/dev.txt | 14 +++++++-------
requirements/doc.txt | 18 +++++++++---------
requirements/pip-tools.txt | 2 +-
requirements/quality.txt | 12 ++++++------
requirements/test.txt | 12 ++++++------
6 files changed, 33 insertions(+), 33 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 1c05cdb..e685903 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -12,7 +12,7 @@ backports-zoneinfo==0.2.1
# via
# django
# djangorestframework
-certifi==2024.2.2
+certifi==2024.6.2
# via requests
cffi==1.16.0
# via
@@ -52,7 +52,7 @@ dnspython==2.6.1
# via pymongo
drf-jwt==1.19.2
# via edx-drf-extensions
-edx-django-utils==5.13.0
+edx-django-utils==5.14.2
# via
# edx-drf-extensions
# edx-rest-api-client
@@ -70,7 +70,7 @@ jinja2==3.1.4
# via -r requirements/base.in
markupsafe==2.1.5
# via jinja2
-newrelic==9.9.1
+newrelic==9.10.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
@@ -102,7 +102,7 @@ stevedore==5.2.0
# via
# edx-django-utils
# edx-opaque-keys
-typing-extensions==4.11.0
+typing-extensions==4.12.1
# via
# asgiref
# edx-opaque-keys
diff --git a/requirements/dev.txt b/requirements/dev.txt
index a5b1370..55dca76 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -28,7 +28,7 @@ cachetools==5.3.3
# via
# -r requirements/ci.txt
# tox
-certifi==2024.2.2
+certifi==2024.6.2
# via
# -r requirements/quality.txt
# requests
@@ -67,7 +67,7 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.5.1
+coverage[toml]==7.5.3
# via
# -r requirements/quality.txt
# pytest-cov
@@ -123,7 +123,7 @@ drf-jwt==1.19.2
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-django-utils==5.13.0
+edx-django-utils==5.14.2
# via
# -r requirements/quality.txt
# edx-drf-extensions
@@ -185,7 +185,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.9.1
+newrelic==9.10.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -279,7 +279,7 @@ pyproject-hooks==1.1.0
# -r requirements/pip-tools.txt
# build
# pip-tools
-pytest==8.2.1
+pytest==8.2.2
# via
# -r requirements/quality.txt
# pytest-cov
@@ -355,7 +355,7 @@ tomlkit==0.12.5
# pylint
tox==4.15.0
# via -r requirements/ci.txt
-typing-extensions==4.11.0
+typing-extensions==4.12.1
# via
# -r requirements/quality.txt
# asgiref
@@ -375,7 +375,7 @@ wheel==0.43.0
# via
# -r requirements/pip-tools.txt
# pip-tools
-zipp==3.18.2
+zipp==3.19.1
# via
# -r requirements/pip-tools.txt
# importlib-metadata
diff --git a/requirements/doc.txt b/requirements/doc.txt
index ef5a6c8..f29874c 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -14,7 +14,7 @@ attrs==23.2.0
# via -r requirements/test.txt
babel==2.15.0
# via sphinx
-backports-tarfile==1.1.1
+backports-tarfile==1.2.0
# via jaraco-context
backports-zoneinfo==0.2.1
# via
@@ -23,7 +23,7 @@ backports-zoneinfo==0.2.1
# djangorestframework
build==1.2.1
# via -r requirements/doc.in
-certifi==2024.2.2
+certifi==2024.6.2
# via
# -r requirements/test.txt
# requests
@@ -43,7 +43,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.txt
-coverage[toml]==7.5.1
+coverage[toml]==7.5.3
# via
# -r requirements/test.txt
# pytest-cov
@@ -97,7 +97,7 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.13.0
+edx-django-utils==5.14.2
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -162,7 +162,7 @@ more-itertools==10.2.0
# via
# jaraco-classes
# jaraco-functools
-newrelic==9.9.1
+newrelic==9.10.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -178,7 +178,7 @@ pbr==6.0.0
# via
# -r requirements/test.txt
# stevedore
-pkginfo==1.10.0
+pkginfo==1.11.0
# via twine
pluggy==1.5.0
# via
@@ -214,7 +214,7 @@ pynacl==1.5.0
# edx-django-utils
pyproject-hooks==1.1.0
# via build
-pytest==8.2.1
+pytest==8.2.2
# via
# -r requirements/test.txt
# pytest-cov
@@ -306,7 +306,7 @@ tomli==2.0.1
# pytest
twine==5.1.0
# via -r requirements/doc.in
-typing-extensions==4.11.0
+typing-extensions==4.12.1
# via
# -r requirements/test.txt
# asgiref
@@ -318,7 +318,7 @@ urllib3==2.2.1
# requests
# responses
# twine
-zipp==3.18.2
+zipp==3.19.1
# via
# importlib-metadata
# importlib-resources
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index dbb1ac7..f347c8c 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -26,7 +26,7 @@ tomli==2.0.1
# pip-tools
wheel==0.43.0
# via pip-tools
-zipp==3.18.2
+zipp==3.19.1
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 4c03658..bc74574 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -19,7 +19,7 @@ backports-zoneinfo==0.2.1
# -r requirements/test.txt
# django
# djangorestframework
-certifi==2024.2.2
+certifi==2024.6.2
# via
# -r requirements/test.txt
# requests
@@ -45,7 +45,7 @@ code-annotations==1.8.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.5.1
+coverage[toml]==7.5.3
# via
# -r requirements/test.txt
# pytest-cov
@@ -92,7 +92,7 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.13.0
+edx-django-utils==5.14.2
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -133,7 +133,7 @@ markupsafe==2.1.5
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.9.1
+newrelic==9.10.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -191,7 +191,7 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==8.2.1
+pytest==8.2.2
# via
# -r requirements/test.txt
# pytest-cov
@@ -252,7 +252,7 @@ tomli==2.0.1
# pytest
tomlkit==0.12.5
# via pylint
-typing-extensions==4.11.0
+typing-extensions==4.12.1
# via
# -r requirements/test.txt
# asgiref
diff --git a/requirements/test.txt b/requirements/test.txt
index 5644846..86dbde5 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -15,7 +15,7 @@ backports-zoneinfo==0.2.1
# -r requirements/base.txt
# django
# djangorestframework
-certifi==2024.2.2
+certifi==2024.6.2
# via
# -r requirements/base.txt
# requests
@@ -35,7 +35,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.in
-coverage[toml]==7.5.1
+coverage[toml]==7.5.3
# via pytest-cov
cryptography==42.0.7
# via
@@ -77,7 +77,7 @@ drf-jwt==1.19.2
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-django-utils==5.13.0
+edx-django-utils==5.14.2
# via
# -r requirements/base.txt
# edx-drf-extensions
@@ -106,7 +106,7 @@ markupsafe==2.1.5
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.9.1
+newrelic==9.10.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -140,7 +140,7 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==8.2.1
+pytest==8.2.2
# via
# pytest-cov
# pytest-django
@@ -187,7 +187,7 @@ tomli==2.0.1
# via
# coverage
# pytest
-typing-extensions==4.11.0
+typing-extensions==4.12.1
# via
# -r requirements/base.txt
# asgiref
From 40dfeef1022ccf32ddae7b6f43076e3793b5cff0 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 11 Jun 2024 11:21:37 -0400
Subject: [PATCH 056/104] chore: Upgrade Python requirements
---
requirements/base.txt | 8 ++++----
requirements/ci.txt | 4 ++--
requirements/dev.txt | 18 +++++++++---------
requirements/doc.txt | 18 +++++++++---------
requirements/pip-tools.txt | 4 ++--
requirements/quality.txt | 14 +++++++-------
requirements/test.txt | 12 ++++++------
7 files changed, 39 insertions(+), 39 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index e685903..7869a0f 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -22,7 +22,7 @@ charset-normalizer==3.3.2
# via requests
click==8.1.7
# via edx-django-utils
-cryptography==42.0.7
+cryptography==42.0.8
# via pyjwt
django==4.2.13
# via
@@ -58,7 +58,7 @@ edx-django-utils==5.14.2
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/base.in
-edx-opaque-keys==2.9.0
+edx-opaque-keys==2.10.0
# via
# -r requirements/base.in
# edx-drf-extensions
@@ -83,7 +83,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==4.4.0
+pymongo==4.7.3
# via edx-opaque-keys
pynacl==1.5.0
# via edx-django-utils
@@ -102,7 +102,7 @@ stevedore==5.2.0
# via
# edx-django-utils
# edx-opaque-keys
-typing-extensions==4.12.1
+typing-extensions==4.12.2
# via
# asgiref
# edx-opaque-keys
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 0008d40..9696761 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -16,7 +16,7 @@ filelock==3.14.0
# via
# tox
# virtualenv
-packaging==24.0
+packaging==24.1
# via
# pyproject-api
# tox
@@ -32,7 +32,7 @@ tomli==2.0.1
# via
# pyproject-api
# tox
-tox==4.15.0
+tox==4.15.1
# via -r requirements/ci.in
virtualenv==20.26.2
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 55dca76..3dba046 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -71,7 +71,7 @@ coverage[toml]==7.5.3
# via
# -r requirements/quality.txt
# pytest-cov
-cryptography==42.0.7
+cryptography==42.0.8
# via
# -r requirements/quality.txt
# pyjwt
@@ -134,7 +134,7 @@ edx-i18n-tools==1.6.0
# via -r requirements/dev.in
edx-lint==5.3.6
# via -r requirements/quality.txt
-edx-opaque-keys==2.9.0
+edx-opaque-keys==2.10.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
@@ -189,7 +189,7 @@ newrelic==9.10.0
# via
# -r requirements/quality.txt
# edx-django-utils
-packaging==24.0
+packaging==24.1
# via
# -r requirements/ci.txt
# -r requirements/pip-tools.txt
@@ -242,7 +242,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.2
+pylint==3.2.3
# via
# -r requirements/quality.txt
# edx-lint
@@ -262,7 +262,7 @@ pylint-plugin-utils==0.8.2
# -r requirements/quality.txt
# pylint-celery
# pylint-django
-pymongo==4.4.0
+pymongo==4.7.3
# via
# -r requirements/quality.txt
# edx-opaque-keys
@@ -305,7 +305,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.25.0
+responses==0.25.2
# via -r requirements/quality.txt
semantic-version==2.10.0
# via
@@ -353,9 +353,9 @@ tomlkit==0.12.5
# via
# -r requirements/quality.txt
# pylint
-tox==4.15.0
+tox==4.15.1
# via -r requirements/ci.txt
-typing-extensions==4.12.1
+typing-extensions==4.12.2
# via
# -r requirements/quality.txt
# asgiref
@@ -375,7 +375,7 @@ wheel==0.43.0
# via
# -r requirements/pip-tools.txt
# pip-tools
-zipp==3.19.1
+zipp==3.19.2
# via
# -r requirements/pip-tools.txt
# importlib-metadata
diff --git a/requirements/doc.txt b/requirements/doc.txt
index f29874c..8afaad9 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -47,7 +47,7 @@ coverage[toml]==7.5.3
# via
# -r requirements/test.txt
# pytest-cov
-cryptography==42.0.7
+cryptography==42.0.8
# via
# -r requirements/test.txt
# pyjwt
@@ -104,7 +104,7 @@ edx-django-utils==5.14.2
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/test.txt
-edx-opaque-keys==2.9.0
+edx-opaque-keys==2.10.0
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -158,7 +158,7 @@ markupsafe==2.1.5
# jinja2
mdurl==0.1.2
# via markdown-it-py
-more-itertools==10.2.0
+more-itertools==10.3.0
# via
# jaraco-classes
# jaraco-functools
@@ -168,7 +168,7 @@ newrelic==9.10.0
# edx-django-utils
nh3==0.2.17
# via readme-renderer
-packaging==24.0
+packaging==24.1
# via
# -r requirements/test.txt
# build
@@ -178,7 +178,7 @@ pbr==6.0.0
# via
# -r requirements/test.txt
# stevedore
-pkginfo==1.11.0
+pkginfo==1.11.1
# via twine
pluggy==1.5.0
# via
@@ -204,7 +204,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==4.4.0
+pymongo==4.7.3
# via
# -r requirements/test.txt
# edx-opaque-keys
@@ -248,7 +248,7 @@ requests==2.31.0
# twine
requests-toolbelt==1.0.0
# via twine
-responses==0.25.0
+responses==0.25.2
# via -r requirements/test.txt
restructuredtext-lint==1.4.0
# via doc8
@@ -306,7 +306,7 @@ tomli==2.0.1
# pytest
twine==5.1.0
# via -r requirements/doc.in
-typing-extensions==4.12.1
+typing-extensions==4.12.2
# via
# -r requirements/test.txt
# asgiref
@@ -318,7 +318,7 @@ urllib3==2.2.1
# requests
# responses
# twine
-zipp==3.19.1
+zipp==3.19.2
# via
# importlib-metadata
# importlib-resources
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index f347c8c..3058830 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -12,7 +12,7 @@ importlib-metadata==6.11.0
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# build
-packaging==24.0
+packaging==24.1
# via build
pip-tools==7.4.1
# via -r requirements/pip-tools.in
@@ -26,7 +26,7 @@ tomli==2.0.1
# pip-tools
wheel==0.43.0
# via pip-tools
-zipp==3.19.1
+zipp==3.19.2
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
diff --git a/requirements/quality.txt b/requirements/quality.txt
index bc74574..a7e738a 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -49,7 +49,7 @@ coverage[toml]==7.5.3
# via
# -r requirements/test.txt
# pytest-cov
-cryptography==42.0.7
+cryptography==42.0.8
# via
# -r requirements/test.txt
# pyjwt
@@ -101,7 +101,7 @@ edx-drf-extensions==10.3.0
# via -r requirements/test.txt
edx-lint==5.3.6
# via -r requirements/quality.in
-edx-opaque-keys==2.9.0
+edx-opaque-keys==2.10.0
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -137,7 +137,7 @@ newrelic==9.10.0
# via
# -r requirements/test.txt
# edx-django-utils
-packaging==24.0
+packaging==24.1
# via
# -r requirements/test.txt
# pytest
@@ -169,7 +169,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.2
+pylint==3.2.3
# via
# edx-lint
# pylint-celery
@@ -183,7 +183,7 @@ pylint-plugin-utils==0.8.2
# via
# pylint-celery
# pylint-django
-pymongo==4.4.0
+pymongo==4.7.3
# via
# -r requirements/test.txt
# edx-opaque-keys
@@ -216,7 +216,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.25.0
+responses==0.25.2
# via -r requirements/test.txt
semantic-version==2.10.0
# via
@@ -252,7 +252,7 @@ tomli==2.0.1
# pytest
tomlkit==0.12.5
# via pylint
-typing-extensions==4.12.1
+typing-extensions==4.12.2
# via
# -r requirements/test.txt
# asgiref
diff --git a/requirements/test.txt b/requirements/test.txt
index 86dbde5..aaafa32 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -37,7 +37,7 @@ code-annotations==1.8.0
# via -r requirements/test.in
coverage[toml]==7.5.3
# via pytest-cov
-cryptography==42.0.7
+cryptography==42.0.8
# via
# -r requirements/base.txt
# pyjwt
@@ -84,7 +84,7 @@ edx-django-utils==5.14.2
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/base.txt
-edx-opaque-keys==2.9.0
+edx-opaque-keys==2.10.0
# via
# -r requirements/base.txt
# edx-drf-extensions
@@ -110,7 +110,7 @@ newrelic==9.10.0
# via
# -r requirements/base.txt
# edx-django-utils
-packaging==24.0
+packaging==24.1
# via pytest
pbr==6.0.0
# via
@@ -132,7 +132,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==4.4.0
+pymongo==4.7.3
# via
# -r requirements/base.txt
# edx-opaque-keys
@@ -161,7 +161,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.25.0
+responses==0.25.2
# via -r requirements/test.in
semantic-version==2.10.0
# via
@@ -187,7 +187,7 @@ tomli==2.0.1
# via
# coverage
# pytest
-typing-extensions==4.12.1
+typing-extensions==4.12.2
# via
# -r requirements/base.txt
# asgiref
From 65b89688ed730c95b4e4b3aadaa55ddbb4be34f0 Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 21 Jun 2024 08:36:03 -0400
Subject: [PATCH 057/104] chore: Upgrade Python requirements (#92)
---
requirements/base.txt | 4 ++--
requirements/ci.txt | 2 +-
requirements/dev.txt | 10 +++++-----
requirements/doc.txt | 6 +++---
requirements/quality.txt | 8 ++++----
requirements/test.txt | 6 +++---
6 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 7869a0f..66a9627 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -70,7 +70,7 @@ jinja2==3.1.4
# via -r requirements/base.in
markupsafe==2.1.5
# via jinja2
-newrelic==9.10.0
+newrelic==9.11.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
@@ -106,5 +106,5 @@ typing-extensions==4.12.2
# via
# asgiref
# edx-opaque-keys
-urllib3==2.2.1
+urllib3==2.2.2
# via requests
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 9696761..697608e 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -12,7 +12,7 @@ colorama==0.4.6
# via tox
distlib==0.3.8
# via virtualenv
-filelock==3.14.0
+filelock==3.15.1
# via
# tox
# virtualenv
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 3dba046..86c8c2e 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -144,7 +144,7 @@ exceptiongroup==1.2.1
# via
# -r requirements/quality.txt
# pytest
-filelock==3.14.0
+filelock==3.15.1
# via
# -r requirements/ci.txt
# tox
@@ -185,7 +185,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.10.0
+newrelic==9.11.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -226,7 +226,7 @@ psutil==5.9.8
# via
# -r requirements/quality.txt
# edx-django-utils
-pycodestyle==2.11.1
+pycodestyle==2.12.0
# via -r requirements/quality.txt
pycparser==2.22
# via
@@ -305,7 +305,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.25.2
+responses==0.25.3
# via -r requirements/quality.txt
semantic-version==2.10.0
# via
@@ -362,7 +362,7 @@ typing-extensions==4.12.2
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.2.1
+urllib3==2.2.2
# via
# -r requirements/quality.txt
# requests
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 8afaad9..6d466b0 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -162,7 +162,7 @@ more-itertools==10.3.0
# via
# jaraco-classes
# jaraco-functools
-newrelic==9.10.0
+newrelic==9.11.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -248,7 +248,7 @@ requests==2.31.0
# twine
requests-toolbelt==1.0.0
# via twine
-responses==0.25.2
+responses==0.25.3
# via -r requirements/test.txt
restructuredtext-lint==1.4.0
# via doc8
@@ -312,7 +312,7 @@ typing-extensions==4.12.2
# asgiref
# edx-opaque-keys
# rich
-urllib3==2.2.1
+urllib3==2.2.2
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/quality.txt b/requirements/quality.txt
index a7e738a..0e1dc78 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -133,7 +133,7 @@ markupsafe==2.1.5
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.10.0
+newrelic==9.11.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -155,7 +155,7 @@ psutil==5.9.8
# via
# -r requirements/test.txt
# edx-django-utils
-pycodestyle==2.11.1
+pycodestyle==2.12.0
# via -r requirements/quality.in
pycparser==2.22
# via
@@ -216,7 +216,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.25.2
+responses==0.25.3
# via -r requirements/test.txt
semantic-version==2.10.0
# via
@@ -259,7 +259,7 @@ typing-extensions==4.12.2
# astroid
# edx-opaque-keys
# pylint
-urllib3==2.2.1
+urllib3==2.2.2
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/test.txt b/requirements/test.txt
index aaafa32..0531538 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -106,7 +106,7 @@ markupsafe==2.1.5
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.10.0
+newrelic==9.11.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -161,7 +161,7 @@ requests==2.31.0
# edx-rest-api-client
# responses
# slumber
-responses==0.25.2
+responses==0.25.3
# via -r requirements/test.in
semantic-version==2.10.0
# via
@@ -192,7 +192,7 @@ typing-extensions==4.12.2
# -r requirements/base.txt
# asgiref
# edx-opaque-keys
-urllib3==2.2.1
+urllib3==2.2.2
# via
# -r requirements/base.txt
# requests
From e4987959510b9782c2fda9c5f764a84200792a69 Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Thu, 27 Jun 2024 09:58:01 -0400
Subject: [PATCH 058/104] chore: Upgrade Python requirements (#93)
* chore: Upgrade Python requirements
* test: remove Django 3.2 environment from tox test environment
Extended support for Django 3.2 ended on 04/01/2024. Dependencies of this library are beginning to require Django >= 4.2, which causes version incompatibilities when running tests in CI in the Django 3.2 environment.
This commit removes the Django 3.2 environment from tox.
* test: update Django 4.0 environment to 4.2
This commit updates the Django 4.0 environment in CI to 4.2. Django 4.2 is the current Django version on our platform. Django 4.0 was pulled in via the tox.ini template in the edx-cookiecutters repository.
A corresponding change to the edx-cookiecutters repository to update the template Django version from 4.0 to 4.2 will be requested.
---------
Co-authored-by: michaelroytman
---
.github/workflows/ci.yml | 4 ++--
requirements/base.txt | 8 ++++----
requirements/ci.txt | 6 +++---
requirements/dev.txt | 16 ++++++++--------
requirements/doc.txt | 10 +++++-----
requirements/pip.txt | 4 ++--
requirements/quality.txt | 10 +++++-----
requirements/test.txt | 10 +++++-----
tox.ini | 5 ++---
9 files changed, 36 insertions(+), 37 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 920d5c5..ee80087 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,7 +16,7 @@ jobs:
matrix:
os: [ubuntu-20.04]
python-version: ['3.8']
- toxenv: [quality, pii_check, django32, django40]
+ toxenv: [quality, pii_check, django42]
steps:
- uses: actions/checkout@v2
@@ -37,7 +37,7 @@ jobs:
run: tox
- name: Run coverage
- if: matrix.python-version == '3.8' && matrix.toxenv == 'django32'
+ if: matrix.python-version == '3.8' && matrix.toxenv == 'django40'
uses: py-cov-action/python-coverage-comment-action@v3
with:
GITHUB_TOKEN: ${{ github.token }}
diff --git a/requirements/base.txt b/requirements/base.txt
index 66a9627..e01ebb2 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -43,7 +43,7 @@ django-waffle==4.1.0
# via
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.15.1
+djangorestframework==3.15.2
# via
# -r requirements/base.in
# drf-jwt
@@ -62,7 +62,7 @@ edx-opaque-keys==2.10.0
# via
# -r requirements/base.in
# edx-drf-extensions
-edx-rest-api-client==5.7.0
+edx-rest-api-client==5.7.1
# via -r requirements/base.in
idna==3.7
# via requests
@@ -74,7 +74,7 @@ newrelic==9.11.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
-psutil==5.9.8
+psutil==6.0.0
# via edx-django-utils
pycparser==2.22
# via cffi
@@ -87,7 +87,7 @@ pymongo==4.7.3
# via edx-opaque-keys
pynacl==1.5.0
# via edx-django-utils
-requests==2.31.0
+requests==2.32.3
# via
# edx-drf-extensions
# edx-rest-api-client
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 697608e..154d9d0 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -12,7 +12,7 @@ colorama==0.4.6
# via tox
distlib==0.3.8
# via virtualenv
-filelock==3.15.1
+filelock==3.15.4
# via
# tox
# virtualenv
@@ -26,7 +26,7 @@ platformdirs==4.2.2
# virtualenv
pluggy==1.5.0
# via tox
-pyproject-api==1.6.1
+pyproject-api==1.7.1
# via tox
tomli==2.0.1
# via
@@ -34,5 +34,5 @@ tomli==2.0.1
# tox
tox==4.15.1
# via -r requirements/ci.in
-virtualenv==20.26.2
+virtualenv==20.26.3
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 86c8c2e..92f4139 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -67,7 +67,7 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.5.3
+coverage[toml]==7.5.4
# via
# -r requirements/quality.txt
# pytest-cov
@@ -110,7 +110,7 @@ django-waffle==4.1.0
# -r requirements/quality.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.15.1
+djangorestframework==3.15.2
# via
# -r requirements/quality.txt
# drf-jwt
@@ -138,13 +138,13 @@ edx-opaque-keys==2.10.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-rest-api-client==5.7.0
+edx-rest-api-client==5.7.1
# via -r requirements/quality.txt
exceptiongroup==1.2.1
# via
# -r requirements/quality.txt
# pytest
-filelock==3.15.1
+filelock==3.15.4
# via
# -r requirements/ci.txt
# tox
@@ -222,7 +222,7 @@ pluggy==1.5.0
# tox
polib==1.2.0
# via edx-i18n-tools
-psutil==5.9.8
+psutil==6.0.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -270,7 +270,7 @@ pynacl==1.5.0
# via
# -r requirements/quality.txt
# edx-django-utils
-pyproject-api==1.6.1
+pyproject-api==1.7.1
# via
# -r requirements/ci.txt
# tox
@@ -298,7 +298,7 @@ pyyaml==6.0.1
# code-annotations
# edx-i18n-tools
# responses
-requests==2.31.0
+requests==2.32.3
# via
# -r requirements/quality.txt
# edx-drf-extensions
@@ -367,7 +367,7 @@ urllib3==2.2.2
# -r requirements/quality.txt
# requests
# responses
-virtualenv==20.26.2
+virtualenv==20.26.3
# via
# -r requirements/ci.txt
# tox
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 6d466b0..dddb546 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -43,7 +43,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.txt
-coverage[toml]==7.5.3
+coverage[toml]==7.5.4
# via
# -r requirements/test.txt
# pytest-cov
@@ -76,7 +76,7 @@ django-waffle==4.1.0
# -r requirements/test.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.15.1
+djangorestframework==3.15.2
# via
# -r requirements/test.txt
# drf-jwt
@@ -108,7 +108,7 @@ edx-opaque-keys==2.10.0
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-rest-api-client==5.7.0
+edx-rest-api-client==5.7.1
# via -r requirements/test.txt
exceptiongroup==1.2.1
# via
@@ -184,7 +184,7 @@ pluggy==1.5.0
# via
# -r requirements/test.txt
# pytest
-psutil==5.9.8
+psutil==6.0.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -236,7 +236,7 @@ pyyaml==6.0.1
# responses
readme-renderer==43.0
# via twine
-requests==2.31.0
+requests==2.32.3
# via
# -r requirements/test.txt
# edx-drf-extensions
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 8a72bb0..c7fd078 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -8,7 +8,7 @@ wheel==0.43.0
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
-pip==24.0
+pip==24.1
# via -r requirements/pip.in
-setuptools==70.0.0
+setuptools==70.1.1
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 0e1dc78..ac42484 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -45,7 +45,7 @@ code-annotations==1.8.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.5.3
+coverage[toml]==7.5.4
# via
# -r requirements/test.txt
# pytest-cov
@@ -79,7 +79,7 @@ django-waffle==4.1.0
# -r requirements/test.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.15.1
+djangorestframework==3.15.2
# via
# -r requirements/test.txt
# drf-jwt
@@ -105,7 +105,7 @@ edx-opaque-keys==2.10.0
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-rest-api-client==5.7.0
+edx-rest-api-client==5.7.1
# via -r requirements/test.txt
exceptiongroup==1.2.1
# via
@@ -151,7 +151,7 @@ pluggy==1.5.0
# via
# -r requirements/test.txt
# pytest
-psutil==5.9.8
+psutil==6.0.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -209,7 +209,7 @@ pyyaml==6.0.1
# -r requirements/test.txt
# code-annotations
# responses
-requests==2.31.0
+requests==2.32.3
# via
# -r requirements/test.txt
# edx-drf-extensions
diff --git a/requirements/test.txt b/requirements/test.txt
index 0531538..e6886a5 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -35,7 +35,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.in
-coverage[toml]==7.5.3
+coverage[toml]==7.5.4
# via pytest-cov
cryptography==42.0.8
# via
@@ -64,7 +64,7 @@ django-waffle==4.1.0
# -r requirements/base.txt
# edx-django-utils
# edx-drf-extensions
-djangorestframework==3.15.1
+djangorestframework==3.15.2
# via
# -r requirements/base.txt
# drf-jwt
@@ -88,7 +88,7 @@ edx-opaque-keys==2.10.0
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-rest-api-client==5.7.0
+edx-rest-api-client==5.7.1
# via -r requirements/base.txt
exceptiongroup==1.2.1
# via pytest
@@ -118,7 +118,7 @@ pbr==6.0.0
# stevedore
pluggy==1.5.0
# via pytest
-psutil==5.9.8
+psutil==6.0.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -154,7 +154,7 @@ pyyaml==6.0.1
# via
# code-annotations
# responses
-requests==2.31.0
+requests==2.32.3
# via
# -r requirements/base.txt
# edx-drf-extensions
diff --git a/tox.ini b/tox.ini
index b1237de..b7b2d1a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py38-django{32,40}
+envlist = py38-django{42}
[doc8]
; D001 = Line too long
@@ -36,8 +36,7 @@ norecursedirs = .* docs requirements site-packages
[testenv]
deps =
- django32: Django>=3.2,<4.0
- django40: Django>=4.0,<4.1
+ django42: Django>=4.2,<4.3
-r{toxinidir}/requirements/test.txt
commands =
python manage.py check
From a4a0d02d2c364e548de63271ef4ae68bc052f70a Mon Sep 17 00:00:00 2001
From: Michael Roytman
Date: Thu, 27 Jun 2024 10:03:14 -0400
Subject: [PATCH 059/104] test: fix coverage CI action (#94)
This commit fixes the coverage CI action to use the proper Django version to ensure that it runs successfully in CI.
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ee80087..1522259 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -37,7 +37,7 @@ jobs:
run: tox
- name: Run coverage
- if: matrix.python-version == '3.8' && matrix.toxenv == 'django40'
+ if: matrix.python-version == '3.8' && matrix.toxenv == 'django42'
uses: py-cov-action/python-coverage-comment-action@v3
with:
GITHUB_TOKEN: ${{ github.token }}
From 8ac7e24f533bc88033690420b7d601483da0ebdb Mon Sep 17 00:00:00 2001
From: Zachary Hancock
Date: Mon, 1 Jul 2024 12:24:38 -0400
Subject: [PATCH 060/104] feat: support variation param for model and prompt
(#95)
---
CHANGELOG.rst | 4 ++
learning_assistant/__init__.py | 2 +-
learning_assistant/api.py | 5 +--
learning_assistant/constants.py | 10 +++++
learning_assistant/utils.py | 9 +++--
learning_assistant/views.py | 15 ++++++-
tests/test_api.py | 6 ++-
tests/test_utils.py | 5 ++-
tests/test_views.py | 71 +++++++++++++++++++++++++++++++--
9 files changed, 111 insertions(+), 16 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 411165d..0844df5 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,10 @@ Change Log
Unreleased
**********
+4.3.0 - 2024-07-01
+******************
+* Adds optional parameter to use updated prompt and model for the chat response.
+
4.2.0 - 2024-02-28
******************
* Modify call to Xpert backend to prevent use of course index.
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 590f745..58e4efc 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.2.0'
+__version__ = '4.3.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 38ae1c0..2ae3450 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -103,9 +103,9 @@ def get_block_content(request, user_id, course_id, unit_usage_key):
return cache_data['content_length'], cache_data['content_items']
-def render_prompt_template(request, user_id, course_run_id, unit_usage_key, course_id):
+def render_prompt_template(request, user_id, course_run_id, unit_usage_key, course_id, template_string):
"""
- Return a rendered prompt template, specified by the LEARNING_ASSISTANT_PROMPT_TEMPLATE setting.
+ Return a rendered prompt template.
"""
unit_content = ''
@@ -117,7 +117,6 @@ def render_prompt_template(request, user_id, course_run_id, unit_usage_key, cour
skill_names = course_data['skill_names']
title = course_data['title']
- template_string = getattr(settings, 'LEARNING_ASSISTANT_PROMPT_TEMPLATE', '')
template = Environment(loader=BaseLoader).from_string(template_string)
data = template.render(unit_content=unit_content, skill_names=skill_names, title=title)
return data
diff --git a/learning_assistant/constants.py b/learning_assistant/constants.py
index 7027a28..103917b 100644
--- a/learning_assistant/constants.py
+++ b/learning_assistant/constants.py
@@ -14,3 +14,13 @@
"html": "TEXT",
"video": "VIDEO",
}
+
+
+class GptModels:
+ GPT_3_5_TURBO = 'gpt-3.5-turbo'
+ GPT_3_5_TURBO_0125 = 'gpt-3.5-turbo-0125'
+ GPT_4o = 'gpt-4o'
+
+
+class ResponseVariations:
+ GPT4_UPDATED_PROMPT = 'updated_prompt'
diff --git a/learning_assistant/utils.py b/learning_assistant/utils.py
index fc72daf..8d9e79a 100644
--- a/learning_assistant/utils.py
+++ b/learning_assistant/utils.py
@@ -52,18 +52,19 @@ def get_reduced_message_list(prompt_template, message_list):
return [system_message] + new_message_list
-def create_request_body(prompt_template, message_list):
+def create_request_body(prompt_template, message_list, gpt_model):
"""
Form request body to be passed to the chat endpoint.
"""
response_body = {
- 'message_list': get_reduced_message_list(prompt_template, message_list)
+ 'message_list': get_reduced_message_list(prompt_template, message_list),
+ 'model': gpt_model,
}
return response_body
-def get_chat_response(prompt_template, message_list):
+def get_chat_response(prompt_template, message_list, gpt_model):
"""
Pass message list to chat endpoint, as defined by the CHAT_COMPLETION_API setting.
"""
@@ -74,7 +75,7 @@ def get_chat_response(prompt_template, message_list):
connect_timeout = getattr(settings, 'CHAT_COMPLETION_API_CONNECT_TIMEOUT', 1)
read_timeout = getattr(settings, 'CHAT_COMPLETION_API_READ_TIMEOUT', 15)
- body = create_request_body(prompt_template, message_list)
+ body = create_request_body(prompt_template, message_list, gpt_model)
try:
response = requests.post(
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 52ad248..7deaacd 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -3,6 +3,7 @@
"""
import logging
+from django.conf import settings
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
@@ -20,6 +21,7 @@
pass
from learning_assistant.api import get_course_id, learning_assistant_enabled, render_prompt_template
+from learning_assistant.constants import GptModels, ResponseVariations
from learning_assistant.serializers import MessageSerializer
from learning_assistant.utils import get_chat_response, user_role_is_staff
@@ -73,6 +75,7 @@ def post(self, request, course_run_id):
)
unit_id = request.query_params.get('unit_id')
+ response_variation = request.query_params.get('response_variation')
message_list = request.data
serializer = MessageSerializer(data=message_list, many=True)
@@ -95,9 +98,17 @@ def post(self, request, course_run_id):
course_id = get_course_id(course_run_id)
- prompt_template = render_prompt_template(request, request.user.id, course_run_id, unit_id, course_id)
+ if response_variation == ResponseVariations.GPT4_UPDATED_PROMPT:
+ gpt_model = GptModels.GPT_4o
+ template_string = getattr(settings, 'LEARNING_ASSISTANT_EXPERIMENTAL_PROMPT_TEMPLATE', '')
+ else:
+ gpt_model = GptModels.GPT_3_5_TURBO_0125
+ template_string = getattr(settings, 'LEARNING_ASSISTANT_PROMPT_TEMPLATE', '')
- status_code, message = get_chat_response(prompt_template, message_list)
+ prompt_template = render_prompt_template(
+ request, request.user.id, course_run_id, unit_id, course_id, template_string
+ )
+ status_code, message = get_chat_response(prompt_template, message_list, gpt_model)
return Response(status=status_code, data=message)
diff --git a/tests/test_api.py b/tests/test_api.py
index 1f18d5f..d2d4861 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -5,6 +5,7 @@
from unittest.mock import MagicMock, patch
import ddt
+from django.conf import settings
from django.core.cache import cache
from django.test import TestCase, override_settings
from opaque_keys.edx.keys import CourseKey, UsageKey
@@ -202,8 +203,11 @@ def test_render_prompt_template(
course_run_id = self.course_run_id
unit_usage_key = 'block-v1:edX+A+B+type@vertical+block@verticalD'
course_id = 'edx+test'
+ template_string = getattr(settings, 'LEARNING_ASSISTANT_PROMPT_TEMPLATE', '')
- prompt_text = render_prompt_template(request, user_id, course_run_id, unit_usage_key, course_id)
+ prompt_text = render_prompt_template(
+ request, user_id, course_run_id, unit_usage_key, course_id, template_string
+ )
if unit_content and flag_enabled:
self.assertIn(unit_content, prompt_text)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 21b23c4..93ba1bd 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -27,7 +27,7 @@ def setUp(self):
self.course_id = 'edx+test'
def get_response(self):
- return get_chat_response(self.prompt_template, self.message_list)
+ return get_chat_response(self.prompt_template, self.message_list, 'gpt-version-test')
@override_settings(CHAT_COMPLETION_API=None)
def test_no_endpoint_setting(self):
@@ -89,7 +89,8 @@ def test_post_request_structure(self, mock_requests):
headers = {'Content-Type': 'application/json', 'x-api-key': settings.CHAT_COMPLETION_API_KEY}
response_body = {
- 'message_list': [{'role': 'system', 'content': self.prompt_template}] + self.message_list
+ 'message_list': [{'role': 'system', 'content': self.prompt_template}] + self.message_list,
+ 'model': 'gpt-version-test',
}
self.get_response()
diff --git a/tests/test_views.py b/tests/test_views.py
index fbbe8b5..06eec6b 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -10,10 +10,12 @@
from django.conf import settings
from django.contrib.auth import get_user_model, login
from django.http import HttpRequest
-from django.test import TestCase
+from django.test import TestCase, override_settings
from django.test.client import Client
from django.urls import reverse
+from learning_assistant.constants import GptModels, ResponseVariations
+
User = get_user_model()
@@ -69,6 +71,7 @@ def setUp(self):
self.client.login_user(self.user)
+@ddt.ddt
class CourseChatViewTests(LoggedInTestCase):
"""
Test for the CourseChatView
@@ -155,13 +158,16 @@ def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
@patch('learning_assistant.views.get_user_role')
@patch('learning_assistant.views.CourseEnrollment.get_enrollment')
@patch('learning_assistant.views.CourseMode')
- def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle, mock_chat_response, mock_render):
+ @override_settings(LEARNING_ASSISTANT_PROMPT_TEMPLATE='This is the default template')
+ def test_chat_response_default(
+ self, mock_mode, mock_enrollment, mock_role, mock_waffle, mock_chat_response, mock_render
+ ):
mock_waffle.return_value = True
mock_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
mock_enrollment.return_value = MagicMock(mode='verified')
mock_chat_response.return_value = (200, {'role': 'assistant', 'content': 'Something else'})
- mock_render.return_value = 'This is a template'
+ mock_render.return_value = 'Rendered template mock'
test_unit_id = 'test-unit-id'
test_data = [
@@ -178,6 +184,65 @@ def test_chat_response(self, mock_mode, mock_enrollment, mock_role, mock_waffle,
render_args = mock_render.call_args.args
self.assertIn(test_unit_id, render_args)
+ self.assertIn('This is the default template', render_args)
+
+ mock_chat_response.assert_called_with(
+ 'Rendered template mock',
+ test_data,
+ GptModels.GPT_3_5_TURBO_0125
+ )
+
+ @ddt.data(ResponseVariations.GPT4_UPDATED_PROMPT, 'invalid-variation')
+ @patch('learning_assistant.views.render_prompt_template')
+ @patch('learning_assistant.views.get_chat_response')
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ @patch('learning_assistant.views.get_user_role')
+ @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseMode')
+ @override_settings(LEARNING_ASSISTANT_EXPERIMENTAL_PROMPT_TEMPLATE='This is a template for GPT-4o variation')
+ @override_settings(LEARNING_ASSISTANT_PROMPT_TEMPLATE='This is the default template')
+ def test_chat_response_variation(
+ self, variation, mock_mode, mock_enrollment, mock_role, mock_waffle, mock_chat_response, mock_render
+ ):
+ mock_waffle.return_value = True
+ mock_role.return_value = 'student'
+ mock_mode.VERIFIED_MODES = ['verified']
+ mock_enrollment.return_value = MagicMock(mode='verified')
+ mock_chat_response.return_value = (200, {'role': 'assistant', 'content': 'Something else'})
+ mock_render.return_value = 'Rendered template mock'
+ test_unit_id = 'test-unit-id'
+
+ test_data = [
+ {'role': 'user', 'content': 'What is 2+2?'},
+ {'role': 'assistant', 'content': 'It is 4'}
+ ]
+
+ response = self.client.post(
+ reverse(
+ 'chat',
+ kwargs={'course_run_id': self.course_id}
+ )+f'?unit_id={test_unit_id}&response_variation={variation}',
+ data=json.dumps(test_data),
+ content_type='application/json',
+ )
+ self.assertEqual(response.status_code, 200)
+
+ if variation == ResponseVariations.GPT4_UPDATED_PROMPT:
+ expected_template = 'This is a template for GPT-4o variation'
+ expected_model = GptModels.GPT_4o
+ else:
+ expected_template = 'This is the default template'
+ expected_model = GptModels.GPT_3_5_TURBO_0125
+
+ render_args = mock_render.call_args.args
+ self.assertIn(test_unit_id, render_args)
+ self.assertIn(expected_template, render_args)
+
+ mock_chat_response.assert_called_with(
+ 'Rendered template mock',
+ test_data,
+ expected_model,
+ )
@ddt.ddt
From 7387b5ba471d8df08e4e49db89e67cdd0956ff4c Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 5 Jul 2024 09:11:13 -0400
Subject: [PATCH 061/104] chore: Upgrade Python requirements (#96)
---
requirements/base.txt | 2 +-
requirements/dev.txt | 6 +++---
requirements/doc.txt | 6 +++---
requirements/pip.txt | 4 ++--
requirements/quality.txt | 4 ++--
requirements/test.txt | 2 +-
6 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index e01ebb2..3fb021e 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -83,7 +83,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==4.7.3
+pymongo==4.8.0
# via edx-opaque-keys
pynacl==1.5.0
# via edx-django-utils
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 92f4139..9ec7df0 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -77,7 +77,7 @@ cryptography==42.0.8
# pyjwt
ddt==1.7.2
# via -r requirements/quality.txt
-diff-cover==9.0.0
+diff-cover==9.1.0
# via -r requirements/dev.in
dill==0.3.8
# via
@@ -242,7 +242,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.3
+pylint==3.2.5
# via
# -r requirements/quality.txt
# edx-lint
@@ -262,7 +262,7 @@ pylint-plugin-utils==0.8.2
# -r requirements/quality.txt
# pylint-celery
# pylint-django
-pymongo==4.7.3
+pymongo==4.8.0
# via
# -r requirements/quality.txt
# edx-opaque-keys
diff --git a/requirements/doc.txt b/requirements/doc.txt
index dddb546..a6a986e 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -178,7 +178,7 @@ pbr==6.0.0
# via
# -r requirements/test.txt
# stevedore
-pkginfo==1.11.1
+pkginfo==1.10.0
# via twine
pluggy==1.5.0
# via
@@ -204,7 +204,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==4.7.3
+pymongo==4.8.0
# via
# -r requirements/test.txt
# edx-opaque-keys
@@ -304,7 +304,7 @@ tomli==2.0.1
# coverage
# doc8
# pytest
-twine==5.1.0
+twine==5.1.1
# via -r requirements/doc.in
typing-extensions==4.12.2
# via
diff --git a/requirements/pip.txt b/requirements/pip.txt
index c7fd078..bc64c4e 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -8,7 +8,7 @@ wheel==0.43.0
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
-pip==24.1
+pip==24.1.1
# via -r requirements/pip.in
-setuptools==70.1.1
+setuptools==70.2.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index ac42484..f95bf1a 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -169,7 +169,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.3
+pylint==3.2.5
# via
# edx-lint
# pylint-celery
@@ -183,7 +183,7 @@ pylint-plugin-utils==0.8.2
# via
# pylint-celery
# pylint-django
-pymongo==4.7.3
+pymongo==4.8.0
# via
# -r requirements/test.txt
# edx-opaque-keys
diff --git a/requirements/test.txt b/requirements/test.txt
index e6886a5..634431c 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -132,7 +132,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==4.7.3
+pymongo==4.8.0
# via
# -r requirements/base.txt
# edx-opaque-keys
From ee84d4cb4b232bc7276582240b8d249502d13c8e Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 9 Jul 2024 11:21:23 -0400
Subject: [PATCH 062/104] chore: Upgrade Python requirements
---
requirements/base.txt | 4 ++--
requirements/ci.txt | 2 +-
requirements/dev.txt | 6 +++---
requirements/doc.txt | 6 +++---
requirements/pip.txt | 2 +-
requirements/quality.txt | 4 ++--
requirements/test.txt | 2 +-
7 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 3fb021e..ac05f83 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -12,7 +12,7 @@ backports-zoneinfo==0.2.1
# via
# django
# djangorestframework
-certifi==2024.6.2
+certifi==2024.7.4
# via requests
cffi==1.16.0
# via
@@ -24,7 +24,7 @@ click==8.1.7
# via edx-django-utils
cryptography==42.0.8
# via pyjwt
-django==4.2.13
+django==4.2.14
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/base.in
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 154d9d0..2129e93 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -32,7 +32,7 @@ tomli==2.0.1
# via
# pyproject-api
# tox
-tox==4.15.1
+tox==4.16.0
# via -r requirements/ci.in
virtualenv==20.26.3
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 9ec7df0..a1a84b9 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -28,7 +28,7 @@ cachetools==5.3.3
# via
# -r requirements/ci.txt
# tox
-certifi==2024.6.2
+certifi==2024.7.4
# via
# -r requirements/quality.txt
# requests
@@ -87,7 +87,7 @@ distlib==0.3.8
# via
# -r requirements/ci.txt
# virtualenv
-django==4.2.13
+django==4.2.14
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/quality.txt
@@ -353,7 +353,7 @@ tomlkit==0.12.5
# via
# -r requirements/quality.txt
# pylint
-tox==4.15.1
+tox==4.16.0
# via -r requirements/ci.txt
typing-extensions==4.12.2
# via
diff --git a/requirements/doc.txt b/requirements/doc.txt
index a6a986e..7ccd900 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -23,7 +23,7 @@ backports-zoneinfo==0.2.1
# djangorestframework
build==1.2.1
# via -r requirements/doc.in
-certifi==2024.6.2
+certifi==2024.7.4
# via
# -r requirements/test.txt
# requests
@@ -54,7 +54,7 @@ cryptography==42.0.8
# secretstorage
ddt==1.7.2
# via -r requirements/test.txt
-django==4.2.13
+django==4.2.14
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -166,7 +166,7 @@ newrelic==9.11.0
# via
# -r requirements/test.txt
# edx-django-utils
-nh3==0.2.17
+nh3==0.2.18
# via readme-renderer
packaging==24.1
# via
diff --git a/requirements/pip.txt b/requirements/pip.txt
index bc64c4e..a012442 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -8,7 +8,7 @@ wheel==0.43.0
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
-pip==24.1.1
+pip==24.1.2
# via -r requirements/pip.in
setuptools==70.2.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index f95bf1a..5d8c971 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -19,7 +19,7 @@ backports-zoneinfo==0.2.1
# -r requirements/test.txt
# django
# djangorestframework
-certifi==2024.6.2
+certifi==2024.7.4
# via
# -r requirements/test.txt
# requests
@@ -57,7 +57,7 @@ ddt==1.7.2
# via -r requirements/test.txt
dill==0.3.8
# via pylint
-django==4.2.13
+django==4.2.14
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
diff --git a/requirements/test.txt b/requirements/test.txt
index 634431c..d3cc199 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -15,7 +15,7 @@ backports-zoneinfo==0.2.1
# -r requirements/base.txt
# django
# djangorestframework
-certifi==2024.6.2
+certifi==2024.7.4
# via
# -r requirements/base.txt
# requests
From 4b45a8c7afa307550df3d424057e98125921c4b5 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 16 Jul 2024 11:20:03 -0400
Subject: [PATCH 063/104] chore: Upgrade Python requirements
---
requirements/base.txt | 14 +++--------
requirements/ci.txt | 8 ++----
requirements/dev.txt | 49 ++++++-------------------------------
requirements/doc.txt | 50 +++++++++-----------------------------
requirements/pip-tools.txt | 12 +--------
requirements/pip.txt | 4 +--
requirements/quality.txt | 32 ++++++------------------
requirements/test.txt | 20 +++------------
8 files changed, 40 insertions(+), 149 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index ac05f83..9bb7b59 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.8
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -8,10 +8,6 @@ asgiref==3.8.1
# via django
attrs==23.2.0
# via -r requirements/base.in
-backports-zoneinfo==0.2.1
- # via
- # django
- # djangorestframework
certifi==2024.7.4
# via requests
cffi==1.16.0
@@ -70,7 +66,7 @@ jinja2==3.1.4
# via -r requirements/base.in
markupsafe==2.1.5
# via jinja2
-newrelic==9.11.0
+newrelic==9.12.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
@@ -96,15 +92,13 @@ semantic-version==2.10.0
# via edx-drf-extensions
slumber==0.7.1
# via edx-rest-api-client
-sqlparse==0.5.0
+sqlparse==0.5.1
# via django
stevedore==5.2.0
# via
# edx-django-utils
# edx-opaque-keys
typing-extensions==4.12.2
- # via
- # asgiref
- # edx-opaque-keys
+ # via edx-opaque-keys
urllib3==2.2.2
# via requests
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 2129e93..564c0f5 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -1,10 +1,10 @@
#
-# This file is autogenerated by pip-compile with Python 3.8
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
#
-cachetools==5.3.3
+cachetools==5.4.0
# via tox
chardet==5.2.0
# via tox
@@ -28,10 +28,6 @@ pluggy==1.5.0
# via tox
pyproject-api==1.7.1
# via tox
-tomli==2.0.1
- # via
- # pyproject-api
- # tox
tox==4.16.0
# via -r requirements/ci.in
virtualenv==20.26.3
diff --git a/requirements/dev.txt b/requirements/dev.txt
index a1a84b9..1bbe358 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.8
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -8,23 +8,18 @@ asgiref==3.8.1
# via
# -r requirements/quality.txt
# django
-astroid==3.2.2
+astroid==3.2.3
# via
# -r requirements/quality.txt
# pylint
# pylint-celery
attrs==23.2.0
# via -r requirements/quality.txt
-backports-zoneinfo==0.2.1
- # via
- # -r requirements/quality.txt
- # django
- # djangorestframework
build==1.2.1
# via
# -r requirements/pip-tools.txt
# pip-tools
-cachetools==5.3.3
+cachetools==5.4.0
# via
# -r requirements/ci.txt
# tox
@@ -67,7 +62,7 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.5.4
+coverage[toml]==7.6.0
# via
# -r requirements/quality.txt
# pytest-cov
@@ -132,7 +127,7 @@ edx-drf-extensions==10.3.0
# via -r requirements/quality.txt
edx-i18n-tools==1.6.0
# via -r requirements/dev.in
-edx-lint==5.3.6
+edx-lint==5.3.7
# via -r requirements/quality.txt
edx-opaque-keys==2.10.0
# via
@@ -140,10 +135,6 @@ edx-opaque-keys==2.10.0
# edx-drf-extensions
edx-rest-api-client==5.7.1
# via -r requirements/quality.txt
-exceptiongroup==1.2.1
- # via
- # -r requirements/quality.txt
- # pytest
filelock==3.15.4
# via
# -r requirements/ci.txt
@@ -153,11 +144,6 @@ idna==3.7
# via
# -r requirements/quality.txt
# requests
-importlib-metadata==6.11.0
- # via
- # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
- # -r requirements/pip-tools.txt
- # build
iniconfig==2.0.0
# via
# -r requirements/quality.txt
@@ -185,7 +171,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.11.0
+newrelic==9.12.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -323,7 +309,7 @@ snowballstemmer==2.2.0
# via
# -r requirements/quality.txt
# pydocstyle
-sqlparse==0.5.0
+sqlparse==0.5.1
# via
# -r requirements/quality.txt
# django
@@ -337,19 +323,7 @@ text-unidecode==1.3
# via
# -r requirements/quality.txt
# python-slugify
-tomli==2.0.1
- # via
- # -r requirements/ci.txt
- # -r requirements/pip-tools.txt
- # -r requirements/quality.txt
- # build
- # coverage
- # pip-tools
- # pylint
- # pyproject-api
- # pytest
- # tox
-tomlkit==0.12.5
+tomlkit==0.13.0
# via
# -r requirements/quality.txt
# pylint
@@ -358,10 +332,7 @@ tox==4.16.0
typing-extensions==4.12.2
# via
# -r requirements/quality.txt
- # asgiref
- # astroid
# edx-opaque-keys
- # pylint
urllib3==2.2.2
# via
# -r requirements/quality.txt
@@ -375,10 +346,6 @@ wheel==0.43.0
# via
# -r requirements/pip-tools.txt
# pip-tools
-zipp==3.19.2
- # via
- # -r requirements/pip-tools.txt
- # importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# pip
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 7ccd900..4129f19 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -1,10 +1,10 @@
#
-# This file is autogenerated by pip-compile with Python 3.8
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
#
-alabaster==0.7.13
+alabaster==0.7.16
# via sphinx
asgiref==3.8.1
# via
@@ -16,11 +16,6 @@ babel==2.15.0
# via sphinx
backports-tarfile==1.2.0
# via jaraco-context
-backports-zoneinfo==0.2.1
- # via
- # -r requirements/test.txt
- # django
- # djangorestframework
build==1.2.1
# via -r requirements/doc.in
certifi==2024.7.4
@@ -43,7 +38,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.txt
-coverage[toml]==7.5.4
+coverage[toml]==7.6.0
# via
# -r requirements/test.txt
# pytest-cov
@@ -110,10 +105,6 @@ edx-opaque-keys==2.10.0
# edx-drf-extensions
edx-rest-api-client==5.7.1
# via -r requirements/test.txt
-exceptiongroup==1.2.1
- # via
- # -r requirements/test.txt
- # pytest
idna==3.7
# via
# -r requirements/test.txt
@@ -123,12 +114,8 @@ imagesize==1.4.1
importlib-metadata==6.11.0
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
- # build
# keyring
- # sphinx
# twine
-importlib-resources==6.4.0
- # via keyring
iniconfig==2.0.0
# via
# -r requirements/test.txt
@@ -162,7 +149,7 @@ more-itertools==10.3.0
# via
# jaraco-classes
# jaraco-functools
-newrelic==9.11.0
+newrelic==9.12.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -227,8 +214,6 @@ python-slugify==8.0.4
# via
# -r requirements/test.txt
# code-annotations
-pytz==2024.1
- # via babel
pyyaml==6.0.1
# via
# -r requirements/test.txt
@@ -268,21 +253,21 @@ slumber==0.7.1
# edx-rest-api-client
snowballstemmer==2.2.0
# via sphinx
-sphinx==7.1.2
+sphinx==7.4.4
# via -r requirements/doc.in
-sphinxcontrib-applehelp==1.0.4
+sphinxcontrib-applehelp==1.0.8
# via sphinx
-sphinxcontrib-devhelp==1.0.2
+sphinxcontrib-devhelp==1.0.6
# via sphinx
-sphinxcontrib-htmlhelp==2.0.1
+sphinxcontrib-htmlhelp==2.0.5
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
-sphinxcontrib-qthelp==1.0.3
+sphinxcontrib-qthelp==1.0.7
# via sphinx
-sphinxcontrib-serializinghtml==1.1.5
+sphinxcontrib-serializinghtml==1.1.10
# via sphinx
-sqlparse==0.5.0
+sqlparse==0.5.1
# via
# -r requirements/test.txt
# django
@@ -297,21 +282,12 @@ text-unidecode==1.3
# via
# -r requirements/test.txt
# python-slugify
-tomli==2.0.1
- # via
- # -r requirements/test.txt
- # build
- # coverage
- # doc8
- # pytest
twine==5.1.1
# via -r requirements/doc.in
typing-extensions==4.12.2
# via
# -r requirements/test.txt
- # asgiref
# edx-opaque-keys
- # rich
urllib3==2.2.2
# via
# -r requirements/test.txt
@@ -319,6 +295,4 @@ urllib3==2.2.2
# responses
# twine
zipp==3.19.2
- # via
- # importlib-metadata
- # importlib-resources
+ # via importlib-metadata
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index 3058830..b544e9f 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.8
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -8,10 +8,6 @@ build==1.2.1
# via pip-tools
click==8.1.7
# via pip-tools
-importlib-metadata==6.11.0
- # via
- # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
- # build
packaging==24.1
# via build
pip-tools==7.4.1
@@ -20,14 +16,8 @@ pyproject-hooks==1.1.0
# via
# build
# pip-tools
-tomli==2.0.1
- # via
- # build
- # pip-tools
wheel==0.43.0
# via pip-tools
-zipp==3.19.2
- # via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# pip
diff --git a/requirements/pip.txt b/requirements/pip.txt
index a012442..df29e61 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.8
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -10,5 +10,5 @@ wheel==0.43.0
# The following packages are considered to be unsafe in a requirements file:
pip==24.1.2
# via -r requirements/pip.in
-setuptools==70.2.0
+setuptools==70.3.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 5d8c971..5ba737a 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.8
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -8,17 +8,12 @@ asgiref==3.8.1
# via
# -r requirements/test.txt
# django
-astroid==3.2.2
+astroid==3.2.3
# via
# pylint
# pylint-celery
attrs==23.2.0
# via -r requirements/test.txt
-backports-zoneinfo==0.2.1
- # via
- # -r requirements/test.txt
- # django
- # djangorestframework
certifi==2024.7.4
# via
# -r requirements/test.txt
@@ -45,7 +40,7 @@ code-annotations==1.8.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.5.4
+coverage[toml]==7.6.0
# via
# -r requirements/test.txt
# pytest-cov
@@ -99,7 +94,7 @@ edx-django-utils==5.14.2
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/test.txt
-edx-lint==5.3.6
+edx-lint==5.3.7
# via -r requirements/quality.in
edx-opaque-keys==2.10.0
# via
@@ -107,10 +102,6 @@ edx-opaque-keys==2.10.0
# edx-drf-extensions
edx-rest-api-client==5.7.1
# via -r requirements/test.txt
-exceptiongroup==1.2.1
- # via
- # -r requirements/test.txt
- # pytest
idna==3.7
# via
# -r requirements/test.txt
@@ -133,7 +124,7 @@ markupsafe==2.1.5
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.11.0
+newrelic==9.12.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -230,7 +221,7 @@ slumber==0.7.1
# edx-rest-api-client
snowballstemmer==2.2.0
# via pydocstyle
-sqlparse==0.5.0
+sqlparse==0.5.1
# via
# -r requirements/test.txt
# django
@@ -244,21 +235,12 @@ text-unidecode==1.3
# via
# -r requirements/test.txt
# python-slugify
-tomli==2.0.1
- # via
- # -r requirements/test.txt
- # coverage
- # pylint
- # pytest
-tomlkit==0.12.5
+tomlkit==0.13.0
# via pylint
typing-extensions==4.12.2
# via
# -r requirements/test.txt
- # asgiref
- # astroid
# edx-opaque-keys
- # pylint
urllib3==2.2.2
# via
# -r requirements/test.txt
diff --git a/requirements/test.txt b/requirements/test.txt
index d3cc199..d1f8b45 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.8
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -10,11 +10,6 @@ asgiref==3.8.1
# django
attrs==23.2.0
# via -r requirements/base.txt
-backports-zoneinfo==0.2.1
- # via
- # -r requirements/base.txt
- # django
- # djangorestframework
certifi==2024.7.4
# via
# -r requirements/base.txt
@@ -35,7 +30,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.in
-coverage[toml]==7.5.4
+coverage[toml]==7.6.0
# via pytest-cov
cryptography==42.0.8
# via
@@ -90,8 +85,6 @@ edx-opaque-keys==2.10.0
# edx-drf-extensions
edx-rest-api-client==5.7.1
# via -r requirements/base.txt
-exceptiongroup==1.2.1
- # via pytest
idna==3.7
# via
# -r requirements/base.txt
@@ -106,7 +99,7 @@ markupsafe==2.1.5
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.11.0
+newrelic==9.12.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -171,7 +164,7 @@ slumber==0.7.1
# via
# -r requirements/base.txt
# edx-rest-api-client
-sqlparse==0.5.0
+sqlparse==0.5.1
# via
# -r requirements/base.txt
# django
@@ -183,14 +176,9 @@ stevedore==5.2.0
# edx-opaque-keys
text-unidecode==1.3
# via python-slugify
-tomli==2.0.1
- # via
- # coverage
- # pytest
typing-extensions==4.12.2
# via
# -r requirements/base.txt
- # asgiref
# edx-opaque-keys
urllib3==2.2.2
# via
From a50be692e96f605ceffb16234595e7998ada1c00 Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 26 Jul 2024 11:20:33 -0400
Subject: [PATCH 064/104] chore: Upgrade Python requirements (#99)
---
requirements/base.txt | 2 +-
requirements/dev.txt | 10 +++++-----
requirements/doc.txt | 10 +++++-----
requirements/pip.txt | 2 +-
requirements/quality.txt | 8 ++++----
requirements/test.txt | 4 ++--
6 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 9bb7b59..f1c464b 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -18,7 +18,7 @@ charset-normalizer==3.3.2
# via requests
click==8.1.7
# via edx-django-utils
-cryptography==42.0.8
+cryptography==43.0.0
# via pyjwt
django==4.2.14
# via
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 1bbe358..5705d9a 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -8,7 +8,7 @@ asgiref==3.8.1
# via
# -r requirements/quality.txt
# django
-astroid==3.2.3
+astroid==3.2.4
# via
# -r requirements/quality.txt
# pylint
@@ -66,13 +66,13 @@ coverage[toml]==7.6.0
# via
# -r requirements/quality.txt
# pytest-cov
-cryptography==42.0.8
+cryptography==43.0.0
# via
# -r requirements/quality.txt
# pyjwt
ddt==1.7.2
# via -r requirements/quality.txt
-diff-cover==9.1.0
+diff-cover==9.1.1
# via -r requirements/dev.in
dill==0.3.8
# via
@@ -228,7 +228,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.5
+pylint==3.2.6
# via
# -r requirements/quality.txt
# edx-lint
@@ -265,7 +265,7 @@ pyproject-hooks==1.1.0
# -r requirements/pip-tools.txt
# build
# pip-tools
-pytest==8.2.2
+pytest==8.3.1
# via
# -r requirements/quality.txt
# pytest-cov
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 4129f19..428d271 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -42,7 +42,7 @@ coverage[toml]==7.6.0
# via
# -r requirements/test.txt
# pytest-cov
-cryptography==42.0.8
+cryptography==43.0.0
# via
# -r requirements/test.txt
# pyjwt
@@ -201,7 +201,7 @@ pynacl==1.5.0
# edx-django-utils
pyproject-hooks==1.1.0
# via build
-pytest==8.2.2
+pytest==8.3.1
# via
# -r requirements/test.txt
# pytest-cov
@@ -253,17 +253,17 @@ slumber==0.7.1
# edx-rest-api-client
snowballstemmer==2.2.0
# via sphinx
-sphinx==7.4.4
+sphinx==7.4.7
# via -r requirements/doc.in
sphinxcontrib-applehelp==1.0.8
# via sphinx
sphinxcontrib-devhelp==1.0.6
# via sphinx
-sphinxcontrib-htmlhelp==2.0.5
+sphinxcontrib-htmlhelp==2.0.6
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
-sphinxcontrib-qthelp==1.0.7
+sphinxcontrib-qthelp==1.0.8
# via sphinx
sphinxcontrib-serializinghtml==1.1.10
# via sphinx
diff --git a/requirements/pip.txt b/requirements/pip.txt
index df29e61..854334d 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -10,5 +10,5 @@ wheel==0.43.0
# The following packages are considered to be unsafe in a requirements file:
pip==24.1.2
# via -r requirements/pip.in
-setuptools==70.3.0
+setuptools==71.1.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 5ba737a..92994ef 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -8,7 +8,7 @@ asgiref==3.8.1
# via
# -r requirements/test.txt
# django
-astroid==3.2.3
+astroid==3.2.4
# via
# pylint
# pylint-celery
@@ -44,7 +44,7 @@ coverage[toml]==7.6.0
# via
# -r requirements/test.txt
# pytest-cov
-cryptography==42.0.8
+cryptography==43.0.0
# via
# -r requirements/test.txt
# pyjwt
@@ -160,7 +160,7 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.5
+pylint==3.2.6
# via
# edx-lint
# pylint-celery
@@ -182,7 +182,7 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==8.2.2
+pytest==8.3.1
# via
# -r requirements/test.txt
# pytest-cov
diff --git a/requirements/test.txt b/requirements/test.txt
index d1f8b45..647df9f 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -32,7 +32,7 @@ code-annotations==1.8.0
# via -r requirements/test.in
coverage[toml]==7.6.0
# via pytest-cov
-cryptography==42.0.8
+cryptography==43.0.0
# via
# -r requirements/base.txt
# pyjwt
@@ -133,7 +133,7 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==8.2.2
+pytest==8.3.1
# via
# pytest-cov
# pytest-django
From 7a55125b0883b033aa73f90b249771dfbcbd7242 Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 2 Aug 2024 09:47:09 -0400
Subject: [PATCH 065/104] chore: Upgrade Python requirements (#100)
---
requirements/dev.txt | 8 ++++----
requirements/doc.txt | 16 ++++++++--------
requirements/pip.txt | 4 ++--
requirements/quality.txt | 2 +-
requirements/test.txt | 2 +-
5 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 5705d9a..5d598ca 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -125,7 +125,7 @@ edx-django-utils==5.14.2
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/quality.txt
-edx-i18n-tools==1.6.0
+edx-i18n-tools==1.6.1
# via -r requirements/dev.in
edx-lint==5.3.7
# via -r requirements/quality.txt
@@ -161,7 +161,7 @@ lxml[html-clean,html_clean]==5.2.2
# via
# edx-i18n-tools
# lxml-html-clean
-lxml-html-clean==0.1.1
+lxml-html-clean==0.2.0
# via lxml
markupsafe==2.1.5
# via
@@ -184,7 +184,7 @@ packaging==24.1
# pyproject-api
# pytest
# tox
-path==16.14.0
+path==17.0.0
# via edx-i18n-tools
pbr==6.0.0
# via
@@ -265,7 +265,7 @@ pyproject-hooks==1.1.0
# -r requirements/pip-tools.txt
# build
# pip-tools
-pytest==8.3.1
+pytest==8.3.2
# via
# -r requirements/quality.txt
# pytest-cov
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 428d271..bf9e17a 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -4,7 +4,7 @@
#
# make upgrade
#
-alabaster==0.7.16
+alabaster==1.0.0
# via sphinx
asgiref==3.8.1
# via
@@ -201,7 +201,7 @@ pynacl==1.5.0
# edx-django-utils
pyproject-hooks==1.1.0
# via build
-pytest==8.3.1
+pytest==8.3.2
# via
# -r requirements/test.txt
# pytest-cov
@@ -253,19 +253,19 @@ slumber==0.7.1
# edx-rest-api-client
snowballstemmer==2.2.0
# via sphinx
-sphinx==7.4.7
+sphinx==8.0.2
# via -r requirements/doc.in
-sphinxcontrib-applehelp==1.0.8
+sphinxcontrib-applehelp==2.0.0
# via sphinx
-sphinxcontrib-devhelp==1.0.6
+sphinxcontrib-devhelp==2.0.0
# via sphinx
-sphinxcontrib-htmlhelp==2.0.6
+sphinxcontrib-htmlhelp==2.1.0
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
-sphinxcontrib-qthelp==1.0.8
+sphinxcontrib-qthelp==2.0.0
# via sphinx
-sphinxcontrib-serializinghtml==1.1.10
+sphinxcontrib-serializinghtml==2.0.0
# via sphinx
sqlparse==0.5.1
# via
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 854334d..54b0571 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -8,7 +8,7 @@ wheel==0.43.0
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
-pip==24.1.2
+pip==24.2
# via -r requirements/pip.in
-setuptools==71.1.0
+setuptools==72.1.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 92994ef..fba0e62 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -182,7 +182,7 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==8.3.1
+pytest==8.3.2
# via
# -r requirements/test.txt
# pytest-cov
diff --git a/requirements/test.txt b/requirements/test.txt
index 647df9f..ddb131e 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -133,7 +133,7 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==8.3.1
+pytest==8.3.2
# via
# pytest-cov
# pytest-django
From 0ecdd23c7362060b9beb65bb6e7d6530a21993af Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Mon, 12 Aug 2024 10:20:19 -0400
Subject: [PATCH 066/104] chore: Upgrade Python requirements (#101)
---
requirements/base.txt | 8 ++++----
requirements/ci.txt | 2 +-
requirements/dev.txt | 28 ++++++++++++----------------
requirements/doc.txt | 14 +++++++-------
requirements/pip-tools.txt | 2 +-
requirements/pip.txt | 2 +-
requirements/quality.txt | 12 ++++++------
requirements/test.txt | 8 ++++----
8 files changed, 36 insertions(+), 40 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index f1c464b..53b82e4 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -6,7 +6,7 @@
#
asgiref==3.8.1
# via django
-attrs==23.2.0
+attrs==24.2.0
# via -r requirements/base.in
certifi==2024.7.4
# via requests
@@ -20,7 +20,7 @@ click==8.1.7
# via edx-django-utils
cryptography==43.0.0
# via pyjwt
-django==4.2.14
+django==4.2.15
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/base.in
@@ -48,7 +48,7 @@ dnspython==2.6.1
# via pymongo
drf-jwt==1.19.2
# via edx-drf-extensions
-edx-django-utils==5.14.2
+edx-django-utils==5.15.0
# via
# edx-drf-extensions
# edx-rest-api-client
@@ -74,7 +74,7 @@ psutil==6.0.0
# via edx-django-utils
pycparser==2.22
# via cffi
-pyjwt[crypto]==2.8.0
+pyjwt[crypto]==2.9.0
# via
# drf-jwt
# edx-drf-extensions
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 564c0f5..aa86395 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -28,7 +28,7 @@ pluggy==1.5.0
# via tox
pyproject-api==1.7.1
# via tox
-tox==4.16.0
+tox==4.17.0
# via -r requirements/ci.in
virtualenv==20.26.3
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 5d598ca..5217a06 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -13,7 +13,7 @@ astroid==3.2.4
# -r requirements/quality.txt
# pylint
# pylint-celery
-attrs==23.2.0
+attrs==24.2.0
# via -r requirements/quality.txt
build==1.2.1
# via
@@ -62,7 +62,7 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.6.0
+coverage[toml]==7.6.1
# via
# -r requirements/quality.txt
# pytest-cov
@@ -82,7 +82,7 @@ distlib==0.3.8
# via
# -r requirements/ci.txt
# virtualenv
-django==4.2.14
+django==4.2.15
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/quality.txt
@@ -118,14 +118,14 @@ drf-jwt==1.19.2
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-django-utils==5.14.2
+edx-django-utils==5.15.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/quality.txt
-edx-i18n-tools==1.6.1
+edx-i18n-tools==1.6.2
# via -r requirements/dev.in
edx-lint==5.3.7
# via -r requirements/quality.txt
@@ -157,12 +157,8 @@ jinja2==3.1.4
# -r requirements/quality.txt
# code-annotations
# diff-cover
-lxml[html-clean,html_clean]==5.2.2
- # via
- # edx-i18n-tools
- # lxml-html-clean
-lxml-html-clean==0.2.0
- # via lxml
+lxml==5.2.2
+ # via edx-i18n-tools
markupsafe==2.1.5
# via
# -r requirements/quality.txt
@@ -184,7 +180,7 @@ packaging==24.1
# pyproject-api
# pytest
# tox
-path==17.0.0
+path==16.16.0
# via edx-i18n-tools
pbr==6.0.0
# via
@@ -212,7 +208,7 @@ psutil==6.0.0
# via
# -r requirements/quality.txt
# edx-django-utils
-pycodestyle==2.12.0
+pycodestyle==2.12.1
# via -r requirements/quality.txt
pycparser==2.22
# via
@@ -222,7 +218,7 @@ pydocstyle==6.3.0
# via -r requirements/quality.txt
pygments==2.18.0
# via diff-cover
-pyjwt[crypto]==2.8.0
+pyjwt[crypto]==2.9.0
# via
# -r requirements/quality.txt
# drf-jwt
@@ -327,7 +323,7 @@ tomlkit==0.13.0
# via
# -r requirements/quality.txt
# pylint
-tox==4.16.0
+tox==4.17.0
# via -r requirements/ci.txt
typing-extensions==4.12.2
# via
@@ -342,7 +338,7 @@ virtualenv==20.26.3
# via
# -r requirements/ci.txt
# tox
-wheel==0.43.0
+wheel==0.44.0
# via
# -r requirements/pip-tools.txt
# pip-tools
diff --git a/requirements/doc.txt b/requirements/doc.txt
index bf9e17a..159017d 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -10,7 +10,7 @@ asgiref==3.8.1
# via
# -r requirements/test.txt
# django
-attrs==23.2.0
+attrs==24.2.0
# via -r requirements/test.txt
babel==2.15.0
# via sphinx
@@ -38,7 +38,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.txt
-coverage[toml]==7.6.0
+coverage[toml]==7.6.1
# via
# -r requirements/test.txt
# pytest-cov
@@ -49,7 +49,7 @@ cryptography==43.0.0
# secretstorage
ddt==1.7.2
# via -r requirements/test.txt
-django==4.2.14
+django==4.2.15
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -92,7 +92,7 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.14.2
+edx-django-utils==5.15.0
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -124,7 +124,7 @@ jaraco-classes==3.4.0
# via keyring
jaraco-context==5.3.0
# via keyring
-jaraco-functools==4.0.1
+jaraco-functools==4.0.2
# via keyring
jeepney==0.8.0
# via
@@ -135,7 +135,7 @@ jinja2==3.1.4
# -r requirements/test.txt
# code-annotations
# sphinx
-keyring==25.2.1
+keyring==25.3.0
# via twine
markdown-it-py==3.0.0
# via rich
@@ -185,7 +185,7 @@ pygments==2.18.0
# readme-renderer
# rich
# sphinx
-pyjwt[crypto]==2.8.0
+pyjwt[crypto]==2.9.0
# via
# -r requirements/test.txt
# drf-jwt
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index b544e9f..fedf88d 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -16,7 +16,7 @@ pyproject-hooks==1.1.0
# via
# build
# pip-tools
-wheel==0.43.0
+wheel==0.44.0
# via pip-tools
# The following packages are considered to be unsafe in a requirements file:
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 54b0571..7a6ada8 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -4,7 +4,7 @@
#
# make upgrade
#
-wheel==0.43.0
+wheel==0.44.0
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
diff --git a/requirements/quality.txt b/requirements/quality.txt
index fba0e62..37ea8a6 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -12,7 +12,7 @@ astroid==3.2.4
# via
# pylint
# pylint-celery
-attrs==23.2.0
+attrs==24.2.0
# via -r requirements/test.txt
certifi==2024.7.4
# via
@@ -40,7 +40,7 @@ code-annotations==1.8.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.6.0
+coverage[toml]==7.6.1
# via
# -r requirements/test.txt
# pytest-cov
@@ -52,7 +52,7 @@ ddt==1.7.2
# via -r requirements/test.txt
dill==0.3.8
# via pylint
-django==4.2.14
+django==4.2.15
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -87,7 +87,7 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.14.2
+edx-django-utils==5.15.0
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -146,7 +146,7 @@ psutil==6.0.0
# via
# -r requirements/test.txt
# edx-django-utils
-pycodestyle==2.12.0
+pycodestyle==2.12.1
# via -r requirements/quality.in
pycparser==2.22
# via
@@ -154,7 +154,7 @@ pycparser==2.22
# cffi
pydocstyle==6.3.0
# via -r requirements/quality.in
-pyjwt[crypto]==2.8.0
+pyjwt[crypto]==2.9.0
# via
# -r requirements/test.txt
# drf-jwt
diff --git a/requirements/test.txt b/requirements/test.txt
index ddb131e..bc5d60c 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -8,7 +8,7 @@ asgiref==3.8.1
# via
# -r requirements/base.txt
# django
-attrs==23.2.0
+attrs==24.2.0
# via -r requirements/base.txt
certifi==2024.7.4
# via
@@ -30,7 +30,7 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.in
-coverage[toml]==7.6.0
+coverage[toml]==7.6.1
# via pytest-cov
cryptography==43.0.0
# via
@@ -72,7 +72,7 @@ drf-jwt==1.19.2
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-django-utils==5.14.2
+edx-django-utils==5.15.0
# via
# -r requirements/base.txt
# edx-drf-extensions
@@ -119,7 +119,7 @@ pycparser==2.22
# via
# -r requirements/base.txt
# cffi
-pyjwt[crypto]==2.8.0
+pyjwt[crypto]==2.9.0
# via
# -r requirements/base.txt
# drf-jwt
From 0a0311a95be99a45dd540c141d1ea6eba66ee5fb Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 13 Aug 2024 11:22:21 -0400
Subject: [PATCH 067/104] chore: Upgrade Python requirements
---
requirements/base.txt | 4 ++--
requirements/ci.txt | 2 +-
requirements/dev.txt | 10 +++++-----
requirements/doc.txt | 15 +++++++--------
requirements/pip.txt | 2 +-
requirements/quality.txt | 6 +++---
requirements/test.txt | 6 +++---
7 files changed, 22 insertions(+), 23 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 53b82e4..2da1dd3 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -10,7 +10,7 @@ attrs==24.2.0
# via -r requirements/base.in
certifi==2024.7.4
# via requests
-cffi==1.16.0
+cffi==1.17.0
# via
# cryptography
# pynacl
@@ -66,7 +66,7 @@ jinja2==3.1.4
# via -r requirements/base.in
markupsafe==2.1.5
# via jinja2
-newrelic==9.12.0
+newrelic==9.13.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
diff --git a/requirements/ci.txt b/requirements/ci.txt
index aa86395..f5468ae 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -28,7 +28,7 @@ pluggy==1.5.0
# via tox
pyproject-api==1.7.1
# via tox
-tox==4.17.0
+tox==4.17.1
# via -r requirements/ci.in
virtualenv==20.26.3
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 5217a06..c0f6e31 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -27,7 +27,7 @@ certifi==2024.7.4
# via
# -r requirements/quality.txt
# requests
-cffi==1.16.0
+cffi==1.17.0
# via
# -r requirements/quality.txt
# cryptography
@@ -157,7 +157,7 @@ jinja2==3.1.4
# -r requirements/quality.txt
# code-annotations
# diff-cover
-lxml==5.2.2
+lxml==5.3.0
# via edx-i18n-tools
markupsafe==2.1.5
# via
@@ -167,7 +167,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.12.0
+newrelic==9.13.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -274,7 +274,7 @@ python-slugify==8.0.4
# via
# -r requirements/quality.txt
# code-annotations
-pyyaml==6.0.1
+pyyaml==6.0.2
# via
# -r requirements/quality.txt
# code-annotations
@@ -323,7 +323,7 @@ tomlkit==0.13.0
# via
# -r requirements/quality.txt
# pylint
-tox==4.17.0
+tox==4.17.1
# via -r requirements/ci.txt
typing-extensions==4.12.2
# via
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 159017d..41093f0 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -12,7 +12,7 @@ asgiref==3.8.1
# django
attrs==24.2.0
# via -r requirements/test.txt
-babel==2.15.0
+babel==2.16.0
# via sphinx
backports-tarfile==1.2.0
# via jaraco-context
@@ -22,7 +22,7 @@ certifi==2024.7.4
# via
# -r requirements/test.txt
# requests
-cffi==1.16.0
+cffi==1.17.0
# via
# -r requirements/test.txt
# cryptography
@@ -111,9 +111,8 @@ idna==3.7
# requests
imagesize==1.4.1
# via sphinx
-importlib-metadata==6.11.0
+importlib-metadata==8.2.0
# via
- # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# keyring
# twine
iniconfig==2.0.0
@@ -145,11 +144,11 @@ markupsafe==2.1.5
# jinja2
mdurl==0.1.2
# via markdown-it-py
-more-itertools==10.3.0
+more-itertools==10.4.0
# via
# jaraco-classes
# jaraco-functools
-newrelic==9.12.0
+newrelic==9.13.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -214,7 +213,7 @@ python-slugify==8.0.4
# via
# -r requirements/test.txt
# code-annotations
-pyyaml==6.0.1
+pyyaml==6.0.2
# via
# -r requirements/test.txt
# code-annotations
@@ -294,5 +293,5 @@ urllib3==2.2.2
# requests
# responses
# twine
-zipp==3.19.2
+zipp==3.20.0
# via importlib-metadata
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 7a6ada8..5f8b9c0 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -10,5 +10,5 @@ wheel==0.44.0
# The following packages are considered to be unsafe in a requirements file:
pip==24.2
# via -r requirements/pip.in
-setuptools==72.1.0
+setuptools==72.2.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 37ea8a6..f8ad7e0 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -18,7 +18,7 @@ certifi==2024.7.4
# via
# -r requirements/test.txt
# requests
-cffi==1.16.0
+cffi==1.17.0
# via
# -r requirements/test.txt
# cryptography
@@ -124,7 +124,7 @@ markupsafe==2.1.5
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.12.0
+newrelic==9.13.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -195,7 +195,7 @@ python-slugify==8.0.4
# via
# -r requirements/test.txt
# code-annotations
-pyyaml==6.0.1
+pyyaml==6.0.2
# via
# -r requirements/test.txt
# code-annotations
diff --git a/requirements/test.txt b/requirements/test.txt
index bc5d60c..8d7f74b 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -14,7 +14,7 @@ certifi==2024.7.4
# via
# -r requirements/base.txt
# requests
-cffi==1.16.0
+cffi==1.17.0
# via
# -r requirements/base.txt
# cryptography
@@ -99,7 +99,7 @@ markupsafe==2.1.5
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.12.0
+newrelic==9.13.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -143,7 +143,7 @@ pytest-django==4.8.0
# via -r requirements/test.in
python-slugify==8.0.4
# via code-annotations
-pyyaml==6.0.1
+pyyaml==6.0.2
# via
# code-annotations
# responses
From e205f9afb98a1571723b62a0ac2c5fc710e8add8 Mon Sep 17 00:00:00 2001
From: edX requirements bot
Date: Tue, 20 Aug 2024 11:21:30 -0400
Subject: [PATCH 068/104] chore: Upgrade Python requirements
---
requirements/ci.txt | 4 ++--
requirements/dev.txt | 6 +++---
requirements/doc.txt | 4 ++--
requirements/pip.txt | 2 +-
requirements/quality.txt | 2 +-
5 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/requirements/ci.txt b/requirements/ci.txt
index f5468ae..e39ed77 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -4,7 +4,7 @@
#
# make upgrade
#
-cachetools==5.4.0
+cachetools==5.5.0
# via tox
chardet==5.2.0
# via tox
@@ -28,7 +28,7 @@ pluggy==1.5.0
# via tox
pyproject-api==1.7.1
# via tox
-tox==4.17.1
+tox==4.18.0
# via -r requirements/ci.in
virtualenv==20.26.3
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index c0f6e31..e0913fd 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -19,7 +19,7 @@ build==1.2.1
# via
# -r requirements/pip-tools.txt
# pip-tools
-cachetools==5.4.0
+cachetools==5.5.0
# via
# -r requirements/ci.txt
# tox
@@ -319,11 +319,11 @@ text-unidecode==1.3
# via
# -r requirements/quality.txt
# python-slugify
-tomlkit==0.13.0
+tomlkit==0.13.2
# via
# -r requirements/quality.txt
# pylint
-tox==4.17.1
+tox==4.18.0
# via -r requirements/ci.txt
typing-extensions==4.12.2
# via
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 41093f0..5424e38 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -111,7 +111,7 @@ idna==3.7
# requests
imagesize==1.4.1
# via sphinx
-importlib-metadata==8.2.0
+importlib-metadata==8.3.0
# via
# keyring
# twine
@@ -121,7 +121,7 @@ iniconfig==2.0.0
# pytest
jaraco-classes==3.4.0
# via keyring
-jaraco-context==5.3.0
+jaraco-context==6.0.1
# via keyring
jaraco-functools==4.0.2
# via keyring
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 5f8b9c0..f0cf3d1 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -10,5 +10,5 @@ wheel==0.44.0
# The following packages are considered to be unsafe in a requirements file:
pip==24.2
# via -r requirements/pip.in
-setuptools==72.2.0
+setuptools==73.0.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index f8ad7e0..b5f115a 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -235,7 +235,7 @@ text-unidecode==1.3
# via
# -r requirements/test.txt
# python-slugify
-tomlkit==0.13.0
+tomlkit==0.13.2
# via pylint
typing-extensions==4.12.2
# via
From ee73e3ced0aee3e7c81ac78b9a533e8d33ed73b3 Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 30 Aug 2024 11:53:21 -0400
Subject: [PATCH 069/104] chore: Upgrade Python requirements (#105)
* chore: Upgrade Python requirements
* fix: upgrade python version
---------
Co-authored-by: Alie Langston
---
.github/workflows/ci.yml | 4 ++--
requirements/base.txt | 8 ++++----
requirements/dev.txt | 10 +++++-----
requirements/doc.txt | 14 +++++++-------
requirements/pip.txt | 2 +-
requirements/quality.txt | 10 +++++-----
requirements/test.txt | 8 ++++----
tox.ini | 2 +-
8 files changed, 29 insertions(+), 29 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1522259..4b4ceb1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-20.04]
- python-version: ['3.8']
+ python-version: ['3.11']
toxenv: [quality, pii_check, django42]
steps:
@@ -37,7 +37,7 @@ jobs:
run: tox
- name: Run coverage
- if: matrix.python-version == '3.8' && matrix.toxenv == 'django42'
+ if: matrix.python-version == '3.11' && matrix.toxenv == 'django42'
uses: py-cov-action/python-coverage-comment-action@v3
with:
GITHUB_TOKEN: ${{ github.token }}
diff --git a/requirements/base.txt b/requirements/base.txt
index 2da1dd3..383ba71 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -8,7 +8,7 @@ asgiref==3.8.1
# via django
attrs==24.2.0
# via -r requirements/base.in
-certifi==2024.7.4
+certifi==2024.8.30
# via requests
cffi==1.17.0
# via
@@ -60,7 +60,7 @@ edx-opaque-keys==2.10.0
# edx-drf-extensions
edx-rest-api-client==5.7.1
# via -r requirements/base.in
-idna==3.7
+idna==3.8
# via requests
jinja2==3.1.4
# via -r requirements/base.in
@@ -68,7 +68,7 @@ markupsafe==2.1.5
# via jinja2
newrelic==9.13.0
# via edx-django-utils
-pbr==6.0.0
+pbr==6.1.0
# via stevedore
psutil==6.0.0
# via edx-django-utils
@@ -94,7 +94,7 @@ slumber==0.7.1
# via edx-rest-api-client
sqlparse==0.5.1
# via django
-stevedore==5.2.0
+stevedore==5.3.0
# via
# edx-django-utils
# edx-opaque-keys
diff --git a/requirements/dev.txt b/requirements/dev.txt
index e0913fd..a729eb7 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -23,7 +23,7 @@ cachetools==5.5.0
# via
# -r requirements/ci.txt
# tox
-certifi==2024.7.4
+certifi==2024.8.30
# via
# -r requirements/quality.txt
# requests
@@ -127,7 +127,7 @@ edx-drf-extensions==10.3.0
# via -r requirements/quality.txt
edx-i18n-tools==1.6.2
# via -r requirements/dev.in
-edx-lint==5.3.7
+edx-lint==5.4.0
# via -r requirements/quality.txt
edx-opaque-keys==2.10.0
# via
@@ -140,7 +140,7 @@ filelock==3.15.4
# -r requirements/ci.txt
# tox
# virtualenv
-idna==3.7
+idna==3.8
# via
# -r requirements/quality.txt
# requests
@@ -182,7 +182,7 @@ packaging==24.1
# tox
path==16.16.0
# via edx-i18n-tools
-pbr==6.0.0
+pbr==6.1.0
# via
# -r requirements/quality.txt
# stevedore
@@ -309,7 +309,7 @@ sqlparse==0.5.1
# via
# -r requirements/quality.txt
# django
-stevedore==5.2.0
+stevedore==5.3.0
# via
# -r requirements/quality.txt
# code-annotations
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 5424e38..3a894cf 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -18,7 +18,7 @@ backports-tarfile==1.2.0
# via jaraco-context
build==1.2.1
# via -r requirements/doc.in
-certifi==2024.7.4
+certifi==2024.8.30
# via
# -r requirements/test.txt
# requests
@@ -105,13 +105,13 @@ edx-opaque-keys==2.10.0
# edx-drf-extensions
edx-rest-api-client==5.7.1
# via -r requirements/test.txt
-idna==3.7
+idna==3.8
# via
# -r requirements/test.txt
# requests
imagesize==1.4.1
# via sphinx
-importlib-metadata==8.3.0
+importlib-metadata==8.4.0
# via
# keyring
# twine
@@ -160,7 +160,7 @@ packaging==24.1
# build
# pytest
# sphinx
-pbr==6.0.0
+pbr==6.1.0
# via
# -r requirements/test.txt
# stevedore
@@ -238,7 +238,7 @@ restructuredtext-lint==1.4.0
# via doc8
rfc3986==2.0.0
# via twine
-rich==13.7.1
+rich==13.8.0
# via twine
secretstorage==3.3.3
# via keyring
@@ -270,7 +270,7 @@ sqlparse==0.5.1
# via
# -r requirements/test.txt
# django
-stevedore==5.2.0
+stevedore==5.3.0
# via
# -r requirements/test.txt
# code-annotations
@@ -293,5 +293,5 @@ urllib3==2.2.2
# requests
# responses
# twine
-zipp==3.20.0
+zipp==3.20.1
# via importlib-metadata
diff --git a/requirements/pip.txt b/requirements/pip.txt
index f0cf3d1..92563d4 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -10,5 +10,5 @@ wheel==0.44.0
# The following packages are considered to be unsafe in a requirements file:
pip==24.2
# via -r requirements/pip.in
-setuptools==73.0.0
+setuptools==74.0.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index b5f115a..6c5fbfa 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -14,7 +14,7 @@ astroid==3.2.4
# pylint-celery
attrs==24.2.0
# via -r requirements/test.txt
-certifi==2024.7.4
+certifi==2024.8.30
# via
# -r requirements/test.txt
# requests
@@ -94,7 +94,7 @@ edx-django-utils==5.15.0
# edx-rest-api-client
edx-drf-extensions==10.3.0
# via -r requirements/test.txt
-edx-lint==5.3.7
+edx-lint==5.4.0
# via -r requirements/quality.in
edx-opaque-keys==2.10.0
# via
@@ -102,7 +102,7 @@ edx-opaque-keys==2.10.0
# edx-drf-extensions
edx-rest-api-client==5.7.1
# via -r requirements/test.txt
-idna==3.7
+idna==3.8
# via
# -r requirements/test.txt
# requests
@@ -132,7 +132,7 @@ packaging==24.1
# via
# -r requirements/test.txt
# pytest
-pbr==6.0.0
+pbr==6.1.0
# via
# -r requirements/test.txt
# stevedore
@@ -225,7 +225,7 @@ sqlparse==0.5.1
# via
# -r requirements/test.txt
# django
-stevedore==5.2.0
+stevedore==5.3.0
# via
# -r requirements/test.txt
# code-annotations
diff --git a/requirements/test.txt b/requirements/test.txt
index 8d7f74b..30a908b 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -10,7 +10,7 @@ asgiref==3.8.1
# django
attrs==24.2.0
# via -r requirements/base.txt
-certifi==2024.7.4
+certifi==2024.8.30
# via
# -r requirements/base.txt
# requests
@@ -85,7 +85,7 @@ edx-opaque-keys==2.10.0
# edx-drf-extensions
edx-rest-api-client==5.7.1
# via -r requirements/base.txt
-idna==3.7
+idna==3.8
# via
# -r requirements/base.txt
# requests
@@ -105,7 +105,7 @@ newrelic==9.13.0
# edx-django-utils
packaging==24.1
# via pytest
-pbr==6.0.0
+pbr==6.1.0
# via
# -r requirements/base.txt
# stevedore
@@ -168,7 +168,7 @@ sqlparse==0.5.1
# via
# -r requirements/base.txt
# django
-stevedore==5.2.0
+stevedore==5.3.0
# via
# -r requirements/base.txt
# code-annotations
diff --git a/tox.ini b/tox.ini
index b7b2d1a..17596e6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py38-django{42}
+envlist = py311-django{42}
[doc8]
; D001 = Line too long
From bb7c1cb76a73c597ab199fbba4f2d9f40d495360 Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Thu, 5 Sep 2024 11:23:10 -0400
Subject: [PATCH 070/104] chore: Upgrade Python requirements (#106)
---
requirements/base.txt | 4 ++--
requirements/dev.txt | 8 ++++----
requirements/doc.txt | 12 ++++++------
requirements/pip.txt | 2 +-
requirements/quality.txt | 8 ++++----
requirements/test.txt | 4 ++--
6 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index 383ba71..fa877e3 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -20,7 +20,7 @@ click==8.1.7
# via edx-django-utils
cryptography==43.0.0
# via pyjwt
-django==4.2.15
+django==4.2.16
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/base.in
@@ -52,7 +52,7 @@ edx-django-utils==5.15.0
# via
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.3.0
+edx-drf-extensions==10.4.0
# via -r requirements/base.in
edx-opaque-keys==2.10.0
# via
diff --git a/requirements/dev.txt b/requirements/dev.txt
index a729eb7..3b85702 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -82,7 +82,7 @@ distlib==0.3.8
# via
# -r requirements/ci.txt
# virtualenv
-django==4.2.15
+django==4.2.16
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/quality.txt
@@ -123,7 +123,7 @@ edx-django-utils==5.15.0
# -r requirements/quality.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.3.0
+edx-drf-extensions==10.4.0
# via -r requirements/quality.txt
edx-i18n-tools==1.6.2
# via -r requirements/dev.in
@@ -224,7 +224,7 @@ pyjwt[crypto]==2.9.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.6
+pylint==3.2.7
# via
# -r requirements/quality.txt
# edx-lint
@@ -268,7 +268,7 @@ pytest==8.3.2
# pytest-django
pytest-cov==5.0.0
# via -r requirements/quality.txt
-pytest-django==4.8.0
+pytest-django==4.9.0
# via -r requirements/quality.txt
python-slugify==8.0.4
# via
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 3a894cf..5ef1c7d 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -49,7 +49,7 @@ cryptography==43.0.0
# secretstorage
ddt==1.7.2
# via -r requirements/test.txt
-django==4.2.15
+django==4.2.16
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -80,9 +80,9 @@ dnspython==2.6.1
# via
# -r requirements/test.txt
# pymongo
-doc8==1.1.1
+doc8==1.1.2
# via -r requirements/doc.in
-docutils==0.20.1
+docutils==0.21.2
# via
# doc8
# readme-renderer
@@ -97,7 +97,7 @@ edx-django-utils==5.15.0
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.3.0
+edx-drf-extensions==10.4.0
# via -r requirements/test.txt
edx-opaque-keys==2.10.0
# via
@@ -207,7 +207,7 @@ pytest==8.3.2
# pytest-django
pytest-cov==5.0.0
# via -r requirements/test.txt
-pytest-django==4.8.0
+pytest-django==4.9.0
# via -r requirements/test.txt
python-slugify==8.0.4
# via
@@ -218,7 +218,7 @@ pyyaml==6.0.2
# -r requirements/test.txt
# code-annotations
# responses
-readme-renderer==43.0
+readme-renderer==44.0
# via twine
requests==2.32.3
# via
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 92563d4..e7f75c5 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -10,5 +10,5 @@ wheel==0.44.0
# The following packages are considered to be unsafe in a requirements file:
pip==24.2
# via -r requirements/pip.in
-setuptools==74.0.0
+setuptools==74.1.1
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 6c5fbfa..67ff2a9 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -52,7 +52,7 @@ ddt==1.7.2
# via -r requirements/test.txt
dill==0.3.8
# via pylint
-django==4.2.15
+django==4.2.16
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/test.txt
@@ -92,7 +92,7 @@ edx-django-utils==5.15.0
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.3.0
+edx-drf-extensions==10.4.0
# via -r requirements/test.txt
edx-lint==5.4.0
# via -r requirements/quality.in
@@ -160,7 +160,7 @@ pyjwt[crypto]==2.9.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.6
+pylint==3.2.7
# via
# edx-lint
# pylint-celery
@@ -189,7 +189,7 @@ pytest==8.3.2
# pytest-django
pytest-cov==5.0.0
# via -r requirements/test.txt
-pytest-django==4.8.0
+pytest-django==4.9.0
# via -r requirements/test.txt
python-slugify==8.0.4
# via
diff --git a/requirements/test.txt b/requirements/test.txt
index 30a908b..fd7013b 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -77,7 +77,7 @@ edx-django-utils==5.15.0
# -r requirements/base.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.3.0
+edx-drf-extensions==10.4.0
# via -r requirements/base.txt
edx-opaque-keys==2.10.0
# via
@@ -139,7 +139,7 @@ pytest==8.3.2
# pytest-django
pytest-cov==5.0.0
# via -r requirements/test.in
-pytest-django==4.8.0
+pytest-django==4.9.0
# via -r requirements/test.in
python-slugify==8.0.4
# via code-annotations
From 3c6065998c52e8cba4867f199cf766dfa3add175 Mon Sep 17 00:00:00 2001
From: Alison Langston <46360176+alangsto@users.noreply.github.com>
Date: Tue, 10 Sep 2024 08:57:24 -0400
Subject: [PATCH 071/104] feat: remove GPT model field in post request (#107)
---
CHANGELOG.rst | 4 +++
learning_assistant/__init__.py | 2 +-
learning_assistant/constants.py | 10 ------
learning_assistant/utils.py | 7 ++--
learning_assistant/views.py | 11 ++-----
tests/test_utils.py | 3 +-
tests/test_views.py | 57 +--------------------------------
7 files changed, 12 insertions(+), 82 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0844df5..14cb096 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,10 @@ Change Log
Unreleased
**********
+4.3.1 - 2024-09-10
+******************
+* Remove GPT model field as part of POST request to Xpert backend
+
4.3.0 - 2024-07-01
******************
* Adds optional parameter to use updated prompt and model for the chat response.
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 58e4efc..cc9f1cd 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.3.0'
+__version__ = '4.3.1'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/constants.py b/learning_assistant/constants.py
index 103917b..7027a28 100644
--- a/learning_assistant/constants.py
+++ b/learning_assistant/constants.py
@@ -14,13 +14,3 @@
"html": "TEXT",
"video": "VIDEO",
}
-
-
-class GptModels:
- GPT_3_5_TURBO = 'gpt-3.5-turbo'
- GPT_3_5_TURBO_0125 = 'gpt-3.5-turbo-0125'
- GPT_4o = 'gpt-4o'
-
-
-class ResponseVariations:
- GPT4_UPDATED_PROMPT = 'updated_prompt'
diff --git a/learning_assistant/utils.py b/learning_assistant/utils.py
index 8d9e79a..79a9884 100644
--- a/learning_assistant/utils.py
+++ b/learning_assistant/utils.py
@@ -52,19 +52,18 @@ def get_reduced_message_list(prompt_template, message_list):
return [system_message] + new_message_list
-def create_request_body(prompt_template, message_list, gpt_model):
+def create_request_body(prompt_template, message_list):
"""
Form request body to be passed to the chat endpoint.
"""
response_body = {
'message_list': get_reduced_message_list(prompt_template, message_list),
- 'model': gpt_model,
}
return response_body
-def get_chat_response(prompt_template, message_list, gpt_model):
+def get_chat_response(prompt_template, message_list):
"""
Pass message list to chat endpoint, as defined by the CHAT_COMPLETION_API setting.
"""
@@ -75,7 +74,7 @@ def get_chat_response(prompt_template, message_list, gpt_model):
connect_timeout = getattr(settings, 'CHAT_COMPLETION_API_CONNECT_TIMEOUT', 1)
read_timeout = getattr(settings, 'CHAT_COMPLETION_API_READ_TIMEOUT', 15)
- body = create_request_body(prompt_template, message_list, gpt_model)
+ body = create_request_body(prompt_template, message_list)
try:
response = requests.post(
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 7deaacd..ea552fd 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -21,7 +21,6 @@
pass
from learning_assistant.api import get_course_id, learning_assistant_enabled, render_prompt_template
-from learning_assistant.constants import GptModels, ResponseVariations
from learning_assistant.serializers import MessageSerializer
from learning_assistant.utils import get_chat_response, user_role_is_staff
@@ -75,7 +74,6 @@ def post(self, request, course_run_id):
)
unit_id = request.query_params.get('unit_id')
- response_variation = request.query_params.get('response_variation')
message_list = request.data
serializer = MessageSerializer(data=message_list, many=True)
@@ -98,17 +96,12 @@ def post(self, request, course_run_id):
course_id = get_course_id(course_run_id)
- if response_variation == ResponseVariations.GPT4_UPDATED_PROMPT:
- gpt_model = GptModels.GPT_4o
- template_string = getattr(settings, 'LEARNING_ASSISTANT_EXPERIMENTAL_PROMPT_TEMPLATE', '')
- else:
- gpt_model = GptModels.GPT_3_5_TURBO_0125
- template_string = getattr(settings, 'LEARNING_ASSISTANT_PROMPT_TEMPLATE', '')
+ template_string = getattr(settings, 'LEARNING_ASSISTANT_EXPERIMENTAL_PROMPT_TEMPLATE', '')
prompt_template = render_prompt_template(
request, request.user.id, course_run_id, unit_id, course_id, template_string
)
- status_code, message = get_chat_response(prompt_template, message_list, gpt_model)
+ status_code, message = get_chat_response(prompt_template, message_list)
return Response(status=status_code, data=message)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 93ba1bd..b81830e 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -27,7 +27,7 @@ def setUp(self):
self.course_id = 'edx+test'
def get_response(self):
- return get_chat_response(self.prompt_template, self.message_list, 'gpt-version-test')
+ return get_chat_response(self.prompt_template, self.message_list)
@override_settings(CHAT_COMPLETION_API=None)
def test_no_endpoint_setting(self):
@@ -90,7 +90,6 @@ def test_post_request_structure(self, mock_requests):
response_body = {
'message_list': [{'role': 'system', 'content': self.prompt_template}] + self.message_list,
- 'model': 'gpt-version-test',
}
self.get_response()
diff --git a/tests/test_views.py b/tests/test_views.py
index 06eec6b..355a82a 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -14,8 +14,6 @@
from django.test.client import Client
from django.urls import reverse
-from learning_assistant.constants import GptModels, ResponseVariations
-
User = get_user_model()
@@ -158,7 +156,7 @@ def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
@patch('learning_assistant.views.get_user_role')
@patch('learning_assistant.views.CourseEnrollment.get_enrollment')
@patch('learning_assistant.views.CourseMode')
- @override_settings(LEARNING_ASSISTANT_PROMPT_TEMPLATE='This is the default template')
+ @override_settings(LEARNING_ASSISTANT_EXPERIMENTAL_PROMPT_TEMPLATE='This is the default template')
def test_chat_response_default(
self, mock_mode, mock_enrollment, mock_role, mock_waffle, mock_chat_response, mock_render
):
@@ -189,59 +187,6 @@ def test_chat_response_default(
mock_chat_response.assert_called_with(
'Rendered template mock',
test_data,
- GptModels.GPT_3_5_TURBO_0125
- )
-
- @ddt.data(ResponseVariations.GPT4_UPDATED_PROMPT, 'invalid-variation')
- @patch('learning_assistant.views.render_prompt_template')
- @patch('learning_assistant.views.get_chat_response')
- @patch('learning_assistant.views.learning_assistant_enabled')
- @patch('learning_assistant.views.get_user_role')
- @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
- @patch('learning_assistant.views.CourseMode')
- @override_settings(LEARNING_ASSISTANT_EXPERIMENTAL_PROMPT_TEMPLATE='This is a template for GPT-4o variation')
- @override_settings(LEARNING_ASSISTANT_PROMPT_TEMPLATE='This is the default template')
- def test_chat_response_variation(
- self, variation, mock_mode, mock_enrollment, mock_role, mock_waffle, mock_chat_response, mock_render
- ):
- mock_waffle.return_value = True
- mock_role.return_value = 'student'
- mock_mode.VERIFIED_MODES = ['verified']
- mock_enrollment.return_value = MagicMock(mode='verified')
- mock_chat_response.return_value = (200, {'role': 'assistant', 'content': 'Something else'})
- mock_render.return_value = 'Rendered template mock'
- test_unit_id = 'test-unit-id'
-
- test_data = [
- {'role': 'user', 'content': 'What is 2+2?'},
- {'role': 'assistant', 'content': 'It is 4'}
- ]
-
- response = self.client.post(
- reverse(
- 'chat',
- kwargs={'course_run_id': self.course_id}
- )+f'?unit_id={test_unit_id}&response_variation={variation}',
- data=json.dumps(test_data),
- content_type='application/json',
- )
- self.assertEqual(response.status_code, 200)
-
- if variation == ResponseVariations.GPT4_UPDATED_PROMPT:
- expected_template = 'This is a template for GPT-4o variation'
- expected_model = GptModels.GPT_4o
- else:
- expected_template = 'This is the default template'
- expected_model = GptModels.GPT_3_5_TURBO_0125
-
- render_args = mock_render.call_args.args
- self.assertIn(test_unit_id, render_args)
- self.assertIn(expected_template, render_args)
-
- mock_chat_response.assert_called_with(
- 'Rendered template mock',
- test_data,
- expected_model,
)
From f4cf44a55a1ce8995c5f5c5fcc8b08246818422c Mon Sep 17 00:00:00 2001
From: Alison Langston <46360176+alangsto@users.noreply.github.com>
Date: Fri, 20 Sep 2024 09:20:12 -0400
Subject: [PATCH 072/104] fix: handle invalid unit ids (#110)
* fix: handle invalid unit ids
* fix: update log level
---
CHANGELOG.rst | 4 ++++
learning_assistant/__init__.py | 2 +-
learning_assistant/api.py | 15 +++++++++-----
tests/test_api.py | 36 +++++++++++++++++++++++-----------
4 files changed, 40 insertions(+), 17 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 14cb096..11899c4 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,10 @@ Change Log
Unreleased
**********
+4.3.2 - 2024-09-19
+******************
+* Add error handling for invalid unit usage keys
+
4.3.1 - 2024-09-10
******************
* Remove GPT model field as part of POST request to Xpert backend
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index cc9f1cd..a4f70ba 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.3.1'
+__version__ = '4.3.2'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 2ae3450..3e0b5e0 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -7,7 +7,7 @@
from django.core.cache import cache
from edx_django_utils.cache import get_cache_key
from jinja2 import BaseLoader, Environment
-from opaque_keys.edx.keys import CourseKey
+from opaque_keys import InvalidKeyError
from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, CATEGORY_TYPE_MAP
from learning_assistant.data import LearningAssistantCourseEnabledData
@@ -22,7 +22,6 @@
traverse_block_pre_order,
)
from learning_assistant.text_utils import html_to_text
-from learning_assistant.toggles import course_content_enabled
log = logging.getLogger(__name__)
@@ -109,9 +108,15 @@ def render_prompt_template(request, user_id, course_run_id, unit_usage_key, cour
"""
unit_content = ''
- course_run_key = CourseKey.from_string(course_run_id)
- if unit_usage_key and course_content_enabled(course_run_key):
- _, unit_content = get_block_content(request, user_id, course_run_id, unit_usage_key)
+ if unit_usage_key:
+ try:
+ _, unit_content = get_block_content(request, user_id, course_run_id, unit_usage_key)
+ except InvalidKeyError:
+ log.warning(
+ 'Failed to retrieve course content for course_id=%(course_run_id)s because of '
+ 'invalid unit_id=%(unit_usage_key)s',
+ {'course_run_id': course_run_id, 'unit_usage_key': unit_usage_key}
+ )
course_data = get_cache_course_data(course_id, ['skill_names', 'title'])
skill_names = course_data['skill_names']
diff --git a/tests/test_api.py b/tests/test_api.py
index d2d4861..5cb2a43 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -8,6 +8,7 @@
from django.conf import settings
from django.core.cache import cache
from django.test import TestCase, override_settings
+from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey
from learning_assistant.api import (
@@ -178,20 +179,13 @@ def test_get_block_content(self, mock_get_children_contents, mock_get_single_blo
self.assertEqual(items, content_items)
@ddt.data(
- ('This is content.', True),
- ('', True),
- ('This is content.', False),
- ('', False),
+ 'This is content.',
+ ''
)
- @ddt.unpack
@patch('learning_assistant.api.get_cache_course_data')
- @patch('learning_assistant.toggles._is_learning_assistant_waffle_flag_enabled')
@patch('learning_assistant.api.get_block_content')
- def test_render_prompt_template(
- self, unit_content, flag_enabled, mock_get_content, mock_is_flag_enabled, mock_cache
- ):
+ def test_render_prompt_template(self, unit_content, mock_get_content, mock_cache):
mock_get_content.return_value = (len(unit_content), unit_content)
- mock_is_flag_enabled.return_value = flag_enabled
skills_content = ['skills']
title = 'title'
mock_cache.return_value = {'skill_names': skills_content, 'title': title}
@@ -209,13 +203,33 @@ def test_render_prompt_template(
request, user_id, course_run_id, unit_usage_key, course_id, template_string
)
- if unit_content and flag_enabled:
+ if unit_content:
self.assertIn(unit_content, prompt_text)
else:
self.assertNotIn('The following text is useful.', prompt_text)
self.assertIn(str(skills_content), prompt_text)
self.assertIn(title, prompt_text)
+ @patch('learning_assistant.api.get_cache_course_data', MagicMock())
+ @patch('learning_assistant.api.get_block_content')
+ def test_render_prompt_template_invalid_unit_key(self, mock_get_content):
+ mock_get_content.side_effect = InvalidKeyError('foo', 'bar')
+
+ # mock arguments that are passed through to `get_block_content` function. the value of these
+ # args does not matter for this test right now, as the `get_block_content` function is entirely mocked.
+ request = MagicMock()
+ user_id = 1
+ course_run_id = self.course_run_id
+ unit_usage_key = 'block-v1:edX+A+B+type@vertical+block@verticalD'
+ course_id = 'edx+test'
+ template_string = getattr(settings, 'LEARNING_ASSISTANT_PROMPT_TEMPLATE', '')
+
+ prompt_text = render_prompt_template(
+ request, user_id, course_run_id, unit_usage_key, course_id, template_string
+ )
+
+ self.assertNotIn('The following text is useful.', prompt_text)
+
@ddt.ddt
class LearningAssistantCourseEnabledApiTests(TestCase):
From d89f5ad2fb90548e8fbcc245e469d3330cd553c8 Mon Sep 17 00:00:00 2001
From: Alison Langston <46360176+alangsto@users.noreply.github.com>
Date: Tue, 15 Oct 2024 09:32:28 -0400
Subject: [PATCH 073/104] feat: use default prompt template (#114)
---
CHANGELOG.rst | 4 ++++
learning_assistant/__init__.py | 2 +-
learning_assistant/views.py | 2 +-
tests/test_views.py | 2 +-
4 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 11899c4..52d87ef 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,10 @@ Change Log
Unreleased
**********
+4.3.3 - 2024-10-15
+******************
+* Use `LEARNING_ASSISTANT_PROMPT_TEMPLATE` for prompt
+
4.3.2 - 2024-09-19
******************
* Add error handling for invalid unit usage keys
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index a4f70ba..f654ddd 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.3.2'
+__version__ = '4.3.3'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index ea552fd..7739240 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -96,7 +96,7 @@ def post(self, request, course_run_id):
course_id = get_course_id(course_run_id)
- template_string = getattr(settings, 'LEARNING_ASSISTANT_EXPERIMENTAL_PROMPT_TEMPLATE', '')
+ template_string = getattr(settings, 'LEARNING_ASSISTANT_PROMPT_TEMPLATE', '')
prompt_template = render_prompt_template(
request, request.user.id, course_run_id, unit_id, course_id, template_string
diff --git a/tests/test_views.py b/tests/test_views.py
index 355a82a..7ff440e 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -156,7 +156,7 @@ def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
@patch('learning_assistant.views.get_user_role')
@patch('learning_assistant.views.CourseEnrollment.get_enrollment')
@patch('learning_assistant.views.CourseMode')
- @override_settings(LEARNING_ASSISTANT_EXPERIMENTAL_PROMPT_TEMPLATE='This is the default template')
+ @override_settings(LEARNING_ASSISTANT_PROMPT_TEMPLATE='This is the default template')
def test_chat_response_default(
self, mock_mode, mock_enrollment, mock_role, mock_waffle, mock_chat_response, mock_render
):
From 5f5c618b06a8b171d3abc64e41f86275607d9699 Mon Sep 17 00:00:00 2001
From: Alison Langston <46360176+alangsto@users.noreply.github.com>
Date: Tue, 22 Oct 2024 13:43:27 -0400
Subject: [PATCH 074/104] feat: add model for messages (#117)
---
CHANGELOG.rst | 1 +
.../0007_learningassistantmessage.py | 34 +++++++++++++++++++
learning_assistant/models.py | 18 ++++++++++
3 files changed, 53 insertions(+)
create mode 100644 learning_assistant/migrations/0007_learningassistantmessage.py
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 52d87ef..fb53e08 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -13,6 +13,7 @@ Change Log
Unreleased
**********
+* Add LearningAssistantMessage model
4.3.3 - 2024-10-15
******************
diff --git a/learning_assistant/migrations/0007_learningassistantmessage.py b/learning_assistant/migrations/0007_learningassistantmessage.py
new file mode 100644
index 0000000..88a1606
--- /dev/null
+++ b/learning_assistant/migrations/0007_learningassistantmessage.py
@@ -0,0 +1,34 @@
+# Generated by Django 4.2.15 on 2024-10-22 12:17
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+import model_utils.fields
+import opaque_keys.edx.django.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('learning_assistant', '0006_delete_courseprompt'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='LearningAssistantMessage',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
+ ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
+ ('course_id', opaque_keys.edx.django.models.CourseKeyField(db_index=True, max_length=255)),
+ ('role', models.CharField(max_length=64)),
+ ('content', models.TextField()),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ ]
diff --git a/learning_assistant/models.py b/learning_assistant/models.py
index 55b3335..c890087 100644
--- a/learning_assistant/models.py
+++ b/learning_assistant/models.py
@@ -1,10 +1,13 @@
"""
Database models for learning_assistant.
"""
+from django.contrib.auth import get_user_model
from django.db import models
from model_utils.models import TimeStampedModel
from opaque_keys.edx.django.models import CourseKeyField
+USER_MODEL = get_user_model()
+
class LearningAssistantCourseEnabled(TimeStampedModel):
"""
@@ -21,3 +24,18 @@ class LearningAssistantCourseEnabled(TimeStampedModel):
course_id = CourseKeyField(max_length=255, db_index=True, unique=True)
enabled = models.BooleanField()
+
+
+class LearningAssistantMessage(TimeStampedModel):
+ """
+ This model stores messages sent to and received from the learning assistant.
+
+ .. pii: content
+ .. pii_types: other
+ .. pii_retirement: third_party
+ """
+
+ course_id = CourseKeyField(max_length=255, db_index=True)
+ user = models.ForeignKey(USER_MODEL, db_index=True, on_delete=models.CASCADE)
+ role = models.CharField(max_length=64)
+ content = models.TextField()
From ecfb6e850d70a279618631f29117984556ba0cab Mon Sep 17 00:00:00 2001
From: Isaac Lee <124631592+ilee2u@users.noreply.github.com>
Date: Thu, 31 Oct 2024 11:18:30 -0400
Subject: [PATCH 075/104] feat: GET endpoint to retrieve message history (#119)
* feat: GET endpoint to retrieve message history
* fix: added message count as query parameter
* chore: rename to MESSAGE_COUNT
* chore: lint
* chore: isort
* fix: corrected query parameter
- some docstring nits
- logic to ensure no index error w/ message_count in api
- serialize data before returning
* test: add tests
* test: fix view tests
* chore: lint
* chore: lint
* chore: LINT
* fix: actually correct query param
* chore: function param lint
* chore: nits
---
CHANGELOG.rst | 4 +
learning_assistant/__init__.py | 2 +-
learning_assistant/api.py | 13 ++-
learning_assistant/text_utils.py | 2 +-
learning_assistant/urls.py | 7 +-
learning_assistant/views.py | 73 +++++++++++++++-
tests/test_api.py | 145 ++++++++++++++++++++++++++++++-
tests/test_plugins_api.py | 1 +
tests/test_views.py | 123 +++++++++++++++++++++++++-
9 files changed, 361 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index fb53e08..5fffeaa 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -15,6 +15,10 @@ Unreleased
**********
* Add LearningAssistantMessage model
+4.4.0 - 2024-10-30
+******************
+* Add new GET endpoint to retrieve a user's message history in a given course.
+
4.3.3 - 2024-10-15
******************
* Use `LEARNING_ASSISTANT_PROMPT_TEMPLATE` for prompt
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index f654ddd..f8f5d1c 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.3.3'
+__version__ = '4.4.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 3e0b5e0..0790059 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -11,7 +11,7 @@
from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, CATEGORY_TYPE_MAP
from learning_assistant.data import LearningAssistantCourseEnabledData
-from learning_assistant.models import LearningAssistantCourseEnabled
+from learning_assistant.models import LearningAssistantCourseEnabled, LearningAssistantMessage
from learning_assistant.platform_imports import (
block_get_children,
block_leaf_filter,
@@ -187,3 +187,14 @@ def get_course_id(course_run_id):
course_data = get_cache_course_run_data(course_run_id, ['course'])
course_key = course_data['course']
return course_key
+
+
+def get_message_history(course_id, user, message_count):
+ """
+ Given a course run id (str), user (User), and message count (int), return the associated message history.
+
+ Returns a number of messages equal to the message_count value.
+ """
+ message_history = LearningAssistantMessage.objects.filter(
+ course_id=course_id, user=user).order_by('-created')[:message_count]
+ return message_history
diff --git a/learning_assistant/text_utils.py b/learning_assistant/text_utils.py
index b97872c..a1795e9 100644
--- a/learning_assistant/text_utils.py
+++ b/learning_assistant/text_utils.py
@@ -20,7 +20,7 @@ def cleanup_text(text):
return stripped
-class _HTMLToTextHelper(HTMLParser): # lint-amnesty, pylint: disable=abstract-method
+class _HTMLToTextHelper(HTMLParser): # lint-amnesty
"""
Helper function for html_to_text below.
"""
diff --git a/learning_assistant/urls.py b/learning_assistant/urls.py
index a5d3bbe..b0dfb48 100644
--- a/learning_assistant/urls.py
+++ b/learning_assistant/urls.py
@@ -4,7 +4,7 @@
from django.urls import re_path
from learning_assistant.constants import COURSE_ID_PATTERN
-from learning_assistant.views import CourseChatView, LearningAssistantEnabledView
+from learning_assistant.views import CourseChatView, LearningAssistantEnabledView, LearningAssistantMessageHistoryView
app_name = 'learning_assistant'
@@ -19,4 +19,9 @@
LearningAssistantEnabledView.as_view(),
name='enabled',
),
+ re_path(
+ fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}/history',
+ LearningAssistantMessageHistoryView.as_view(),
+ name='message-history',
+ ),
]
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 7739240..68b1c0a 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -20,7 +20,12 @@
except ImportError:
pass
-from learning_assistant.api import get_course_id, learning_assistant_enabled, render_prompt_template
+from learning_assistant.api import (
+ get_course_id,
+ get_message_history,
+ learning_assistant_enabled,
+ render_prompt_template,
+)
from learning_assistant.serializers import MessageSerializer
from learning_assistant.utils import get_chat_response, user_role_is_staff
@@ -149,3 +154,69 @@ def get(self, request, course_run_id):
}
return Response(status=http_status.HTTP_200_OK, data=data)
+
+
+class LearningAssistantMessageHistoryView(APIView):
+ """
+ View to retrieve the message history for user in a course.
+
+ This endpoint returns the message history stored in the LearningAssistantMessage table in a course
+ represented by the course_key, which is provided in the URL.
+
+ Accepts: [GET]
+
+ Path: /learning_assistant/v1/course_id/{course_key}/history
+
+ Parameters:
+ * course_key: the ID of the course
+
+ Responses:
+ * 200: OK
+ * 400: Malformed Request - Course ID is not a valid course ID.
+ """
+
+ authentication_classes = (SessionAuthentication, JwtAuthentication,)
+ permission_classes = (IsAuthenticated,)
+
+ def get(self, request, course_run_id):
+ """
+ Given a course run ID, retrieve the message history for the corresponding user.
+
+ The response will be in the following format.
+
+ [{'role': 'assistant', 'content': 'something'}]
+ """
+ try:
+ courserun_key = CourseKey.from_string(course_run_id)
+ except InvalidKeyError:
+ return Response(
+ status=http_status.HTTP_400_BAD_REQUEST,
+ data={'detail': 'Course ID is not a valid course ID.'}
+ )
+
+ if not learning_assistant_enabled(courserun_key):
+ return Response(
+ status=http_status.HTTP_403_FORBIDDEN,
+ data={'detail': 'Learning assistant not enabled for course.'}
+ )
+
+ # If user does not have an enrollment record, or is not staff, they should not have access
+ user_role = get_user_role(request.user, courserun_key)
+ enrollment_object = CourseEnrollment.get_enrollment(request.user, courserun_key)
+ enrollment_mode = enrollment_object.mode if enrollment_object else None
+ if (
+ (enrollment_mode not in CourseMode.VERIFIED_MODES)
+ and not user_role_is_staff(user_role)
+ ):
+ return Response(
+ status=http_status.HTTP_403_FORBIDDEN,
+ data={'detail': 'Must be staff or have valid enrollment.'}
+ )
+
+ course_id = get_course_id(course_run_id)
+ user = request.user
+
+ message_count = int(request.GET.get('message_count', 50))
+ message_history = get_message_history(course_id, user, message_count)
+ data = MessageSerializer(message_history, many=True).data
+ return Response(status=http_status.HTTP_200_OK, data=data)
diff --git a/tests/test_api.py b/tests/test_api.py
index 5cb2a43..3ca365d 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -6,6 +6,7 @@
import ddt
from django.conf import settings
+from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.test import TestCase, override_settings
from opaque_keys import InvalidKeyError
@@ -16,16 +17,19 @@
_get_children_contents,
_leaf_filter,
get_block_content,
+ get_message_history,
learning_assistant_available,
learning_assistant_enabled,
render_prompt_template,
set_learning_assistant_enabled,
)
from learning_assistant.data import LearningAssistantCourseEnabledData
-from learning_assistant.models import LearningAssistantCourseEnabled
+from learning_assistant.models import LearningAssistantCourseEnabled, LearningAssistantMessage
fake_transcript = 'This is the text version from the transcript'
+User = get_user_model()
+
class FakeChild:
"""Fake child block for testing"""
@@ -49,6 +53,7 @@ def get_html(self):
class FakeBlock:
"Fake block for testing, returns given children"
+
def __init__(self, children):
self.children = children
self.scope_ids = lambda: None
@@ -236,6 +241,7 @@ class LearningAssistantCourseEnabledApiTests(TestCase):
"""
Test suite for learning_assistant_available, learning_assistant_enabled, and set_learning_assistant_enabled.
"""
+
def setUp(self):
super().setUp()
self.course_key = CourseKey.from_string('course-v1:edx+fake+1')
@@ -305,3 +311,140 @@ def test_learning_assistant_available(self, learning_assistant_available_setting
expected_value = learning_assistant_available_setting_value
self.assertEqual(return_value, expected_value)
+
+
+@ddt.ddt
+class GetMessageHistoryTests(TestCase):
+ """
+ Test suite for get_message_history.
+ """
+
+ def setUp(self):
+ super().setUp()
+ self.course_id = 'course-v1:edx+fake+1'
+ self.course_key = CourseKey.from_string(self.course_id)
+ self.user = User(username='tester', email='tester@test.com')
+ self.user.save()
+
+ self.role = 'verified'
+
+ def test_get_message_history(self):
+ message_count = 5
+ for i in range(1, message_count + 1):
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role=self.role,
+ content=f'Content of message {i}',
+ )
+
+ return_value = get_message_history(self.course_id, self.user, message_count)
+
+ expected_value = LearningAssistantMessage.objects.filter(
+ course_id=self.course_id, user=self.user).order_by('-created')[:message_count]
+
+ # Ensure same number of entries
+ self.assertEqual(len(return_value), len(expected_value))
+
+ # Ensure values are as expected for all LearningAssistantMessage instances
+ for i, return_value in enumerate(return_value):
+ self.assertEqual(return_value.course_id, expected_value[i].course_id)
+ self.assertEqual(return_value.user, expected_value[i].user)
+ self.assertEqual(return_value.role, expected_value[i].role)
+ self.assertEqual(return_value.content, expected_value[i].content)
+
+ @ddt.data(
+ 0, 1, 5, 10, 50
+ )
+ def test_get_message_history_message_count(self, actual_message_count):
+ for i in range(1, actual_message_count + 1):
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role=self.role,
+ content=f'Content of message {i}',
+ )
+
+ message_count_parameter = 5
+ return_value = get_message_history(self.course_id, self.user, message_count_parameter)
+
+ expected_value = LearningAssistantMessage.objects.filter(
+ course_id=self.course_id, user=self.user).order_by('-created')[:message_count_parameter]
+
+ # Ensure same number of entries
+ self.assertEqual(len(return_value), len(expected_value))
+
+ def test_get_message_history_user_difference(self):
+ # Default Message
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role=self.role,
+ content='Expected content of message',
+ )
+
+ # New message w/ new user
+ new_user = User(username='not_tester', email='not_tester@test.com')
+ new_user.save()
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=new_user,
+ role=self.role,
+ content='Expected content of message',
+ )
+
+ message_count = 2
+ return_value = get_message_history(self.course_id, self.user, message_count)
+
+ expected_value = LearningAssistantMessage.objects.filter(
+ course_id=self.course_id, user=self.user).order_by('-created')[:message_count]
+
+ # Ensure we filtered one of the two present messages
+ self.assertNotEqual(len(return_value), LearningAssistantMessage.objects.count())
+
+ # Ensure same number of entries
+ self.assertEqual(len(return_value), len(expected_value))
+
+ # Ensure values are as expected for all LearningAssistantMessage instances
+ for i, return_value in enumerate(return_value):
+ self.assertEqual(return_value.course_id, expected_value[i].course_id)
+ self.assertEqual(return_value.user, expected_value[i].user)
+ self.assertEqual(return_value.role, expected_value[i].role)
+ self.assertEqual(return_value.content, expected_value[i].content)
+
+ def test_get_message_course_id_differences(self):
+ # Default Message
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role=self.role,
+ content='Expected content of message',
+ )
+
+ # New message
+ wrong_course_id = 'course-v1:wrong+id+1'
+ LearningAssistantMessage.objects.create(
+ course_id=wrong_course_id,
+ user=self.user,
+ role=self.role,
+ content='Expected content of message',
+ )
+
+ message_count = 2
+ return_value = get_message_history(self.course_id, self.user, message_count)
+
+ expected_value = LearningAssistantMessage.objects.filter(
+ course_id=self.course_id, user=self.user).order_by('-created')[:message_count]
+
+ # Ensure we filtered one of the two present messages
+ self.assertNotEqual(len(return_value), LearningAssistantMessage.objects.count())
+
+ # Ensure same number of entries
+ self.assertEqual(len(return_value), len(expected_value))
+
+ # Ensure values are as expected for all LearningAssistantMessage instances
+ for i, return_value in enumerate(return_value):
+ self.assertEqual(return_value.course_id, expected_value[i].course_id)
+ self.assertEqual(return_value.user, expected_value[i].user)
+ self.assertEqual(return_value.role, expected_value[i].role)
+ self.assertEqual(return_value.content, expected_value[i].content)
diff --git a/tests/test_plugins_api.py b/tests/test_plugins_api.py
index 076335d..4e30539 100644
--- a/tests/test_plugins_api.py
+++ b/tests/test_plugins_api.py
@@ -19,6 +19,7 @@ class PluginApiTests(TestCase):
"""
Test suite for the plugins_api module.
"""
+
def setUp(self):
super().setUp()
self.course_key = CourseKey.from_string('course-v1:edx+fake+1')
diff --git a/tests/test_views.py b/tests/test_views.py
index 7ff440e..492a10d 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -14,6 +14,8 @@
from django.test.client import Client
from django.urls import reverse
+from learning_assistant.models import LearningAssistantMessage
+
User = get_user_model()
@@ -21,6 +23,7 @@ class TestClient(Client):
"""
Allows for 'fake logins' of a user so we don't need to expose a 'login' HTTP endpoint.
"""
+
def login_user(self, user):
"""
Login as specified user.
@@ -64,7 +67,7 @@ def setUp(self):
"""
super().setUp()
self.client = TestClient()
- self.user = User(username='tester', email='tester@test.com')
+ self.user = User(username='tester', email='tester@test.com', is_staff=True)
self.user.save()
self.client.login_user(self.user)
@@ -210,12 +213,12 @@ def setUp(self):
)
@ddt.unpack
@patch('learning_assistant.views.learning_assistant_enabled')
- def test_learning_assistant_enabled(self, mock_value, expected_value, mock_learning_assistant_enabled):
+ def test_learning_assistant_enabled(self, mock_value, message, mock_learning_assistant_enabled):
mock_learning_assistant_enabled.return_value = mock_value
response = self.client.get(reverse('enabled', kwargs={'course_run_id': self.course_id}))
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.data, {'enabled': expected_value})
+ self.assertEqual(response.data, {'enabled': message})
@patch('learning_assistant.views.learning_assistant_enabled')
def test_invalid_course_id(self, mock_learning_assistant_enabled):
@@ -223,3 +226,117 @@ def test_invalid_course_id(self, mock_learning_assistant_enabled):
response = self.client.get(reverse('enabled', kwargs={'course_run_id': self.course_id+'+invalid'}))
self.assertEqual(response.status_code, 400)
+
+
+@ddt.ddt
+class LearningAssistantMessageHistoryViewTests(LoggedInTestCase):
+ """
+ Tests for the LearningAssistantMessageHistoryView
+ """
+
+ def setUp(self):
+ super().setUp()
+ self.course_id = 'course-v1:edx+test+23'
+
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ def test_invalid_course_id(self, mock_learning_assistant_enabled):
+ mock_learning_assistant_enabled.return_value = True
+ response = self.client.get(reverse('enabled', kwargs={'course_run_id': self.course_id+'+invalid'}))
+
+ self.assertEqual(response.status_code, 400)
+
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ def test_course_waffle_inactive(self, mock_waffle):
+ mock_waffle.return_value = False
+ message_count = 5
+ response = self.client.get(
+ reverse('message-history', kwargs={'course_run_id': self.course_id})+f'?message_count={message_count}',
+ content_type='application/json'
+ )
+ self.assertEqual(response.status_code, 403)
+
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ def test_learning_assistant_not_enabled(self, mock_learning_assistant_enabled):
+ mock_learning_assistant_enabled.return_value = False
+ message_count = 5
+ response = self.client.get(
+ reverse('message-history', kwargs={'course_run_id': self.course_id})+f'?message_count={message_count}',
+ content_type='application/json'
+ )
+
+ self.assertEqual(response.status_code, 403)
+
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ @patch('learning_assistant.views.get_user_role')
+ @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseMode')
+ def test_user_no_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_role, mock_waffle):
+ mock_waffle.return_value = True
+ mock_role.return_value = 'student'
+ mock_mode.VERIFIED_MODES = ['verified']
+ mock_enrollment.return_value = None
+
+ message_count = 5
+ response = self.client.get(
+ reverse('message-history', kwargs={'course_run_id': self.course_id})+f'?message_count={message_count}',
+ content_type='application/json'
+ )
+ self.assertEqual(response.status_code, 403)
+
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ @patch('learning_assistant.views.get_user_role')
+ @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseMode')
+ def test_user_audit_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_role, mock_waffle):
+ mock_waffle.return_value = True
+ mock_role.return_value = 'student'
+ mock_mode.VERIFIED_MODES = ['verified']
+ mock_enrollment.return_value = MagicMock(mode='audit')
+
+ message_count = 5
+ response = self.client.get(
+ reverse('message-history', kwargs={'course_run_id': self.course_id})+f'?message_count={message_count}',
+ content_type='application/json'
+ )
+ self.assertEqual(response.status_code, 403)
+
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ @patch('learning_assistant.views.get_user_role')
+ @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseMode')
+ @patch('learning_assistant.views.get_course_id')
+ def test_learning_message_history_view_get(
+ self,
+ mock_get_course_id,
+ mock_mode,
+ mock_enrollment,
+ mock_role,
+ mock_waffle
+ ):
+ mock_waffle.return_value = True
+ mock_role.return_value = 'student'
+ mock_mode.VERIFIED_MODES = ['verified']
+ mock_enrollment.return_value = MagicMock(mode='verified')
+
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role='staff',
+ content='Expected content of message',
+ )
+ message_count = 1
+ mock_get_course_id.return_value = self.course_id
+ response = self.client.get(
+ reverse('message-history', kwargs={'course_run_id': self.course_id})+f'?message_count={message_count}',
+ content_type='application/json'
+ )
+ data = response.data
+
+ # Ensure same number of entries
+ actual_message_count = LearningAssistantMessage.objects.count()
+ self.assertEqual(len(data), actual_message_count)
+
+ # Ensure values are as expected
+ actual_message = LearningAssistantMessage.objects.get(course_id=self.course_id)
+ self.assertEqual(data[0]['role'], actual_message.role)
+ self.assertEqual(data[0]['content'], actual_message.content)
From 9a4f62d194b7f259628efdd6ae62eab2d06ba00b Mon Sep 17 00:00:00 2001
From: Alison Langston <46360176+alangsto@users.noreply.github.com>
Date: Thu, 31 Oct 2024 13:21:31 -0400
Subject: [PATCH 076/104] feat: add management command to remove expired
messages (#122)
* feat: add management command to remove expired messages
* fix: update for lint
---
CHANGELOG.rst | 1 +
.../commands/retire_user_messages.py | 68 +++++++++++++++++++
.../tests/test_retire_user_messages.py | 68 +++++++++++++++++++
tests/__init__.py | 0
4 files changed, 137 insertions(+)
create mode 100644 learning_assistant/management/commands/retire_user_messages.py
create mode 100644 learning_assistant/management/commands/tests/test_retire_user_messages.py
create mode 100644 tests/__init__.py
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 5fffeaa..a3882fd 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,7 @@ Change Log
Unreleased
**********
* Add LearningAssistantMessage model
+* Add management command to remove expired messages
4.4.0 - 2024-10-30
******************
diff --git a/learning_assistant/management/commands/retire_user_messages.py b/learning_assistant/management/commands/retire_user_messages.py
new file mode 100644
index 0000000..8b90082
--- /dev/null
+++ b/learning_assistant/management/commands/retire_user_messages.py
@@ -0,0 +1,68 @@
+""""
+Django management command to remove LearningAssistantMessage objects
+if they have reached their expiration date.
+"""
+import logging
+import time
+from datetime import datetime, timedelta
+
+from django.conf import settings
+from django.core.management.base import BaseCommand
+
+from learning_assistant.models import LearningAssistantMessage
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """
+ Django Management command to remove expired messages.
+ """
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '--batch_size',
+ action='store',
+ dest='batch_size',
+ type=int,
+ default=300,
+ help='Maximum number of messages to remove. '
+ 'This helps avoid overloading the database while updating large amount of data.'
+ )
+ parser.add_argument(
+ '--sleep_time',
+ action='store',
+ dest='sleep_time',
+ type=int,
+ default=10,
+ help='Sleep time in seconds between update of batches'
+ )
+
+ def handle(self, *args, **options):
+ """
+ Management command entry point.
+ """
+ batch_size = options['batch_size']
+ sleep_time = options['sleep_time']
+
+ expiry_date = datetime.now() - timedelta(days=getattr(settings, 'LEARNING_ASSISTANT_MESSAGES_EXPIRY', 30))
+
+ total_deleted = 0
+ deleted_count = None
+
+ while deleted_count != 0:
+ ids_to_delete = LearningAssistantMessage.objects.filter(
+ created__lte=expiry_date
+ ).values_list('id', flat=True)[:batch_size]
+
+ ids_to_delete = list(ids_to_delete)
+ delete_queryset = LearningAssistantMessage.objects.filter(
+ id__in=ids_to_delete
+ )
+ deleted_count, _ = delete_queryset.delete()
+
+ total_deleted += deleted_count
+ log.info(f'{deleted_count} messages deleted.')
+ time.sleep(sleep_time)
+
+ log.info(f'Job completed. {total_deleted} messages deleted.')
diff --git a/learning_assistant/management/commands/tests/test_retire_user_messages.py b/learning_assistant/management/commands/tests/test_retire_user_messages.py
new file mode 100644
index 0000000..afd720f
--- /dev/null
+++ b/learning_assistant/management/commands/tests/test_retire_user_messages.py
@@ -0,0 +1,68 @@
+"""
+Tests for the retire_user_messages management command
+"""
+from datetime import datetime, timedelta
+
+from django.contrib.auth import get_user_model
+from django.core.management import call_command
+from django.test import TestCase
+
+from learning_assistant.models import LearningAssistantMessage
+
+User = get_user_model()
+
+
+class RetireUserMessagesTests(TestCase):
+ """
+ Tests for the retire_user_messages command.
+ """
+
+ def setUp(self):
+ """
+ Build up test data
+ """
+ super().setUp()
+ self.user = User(username='tester', email='tester@test.com')
+ self.user.save()
+
+ self.course_id = 'course-v1:edx+test+23'
+
+ LearningAssistantMessage.objects.create(
+ user=self.user,
+ course_id=self.course_id,
+ role='user',
+ content='Hello',
+ created=datetime.now() - timedelta(days=60)
+ )
+
+ LearningAssistantMessage.objects.create(
+ user=self.user,
+ course_id=self.course_id,
+ role='user',
+ content='Hello',
+ created=datetime.now() - timedelta(days=2)
+ )
+
+ LearningAssistantMessage.objects.create(
+ user=self.user,
+ course_id=self.course_id,
+ role='user',
+ content='Hello',
+ created=datetime.now() - timedelta(days=4)
+ )
+
+ def test_run_command(self):
+ """
+ Run the management command
+ """
+ current_messages = LearningAssistantMessage.objects.filter()
+ self.assertEqual(len(current_messages), 3)
+
+ call_command(
+ 'retire_user_messages',
+ batch_size=2,
+ sleep_time=0,
+ )
+
+ current_messages = LearningAssistantMessage.objects.filter()
+ self.assertEqual(len(current_messages), 2)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
From dbcaa394e40f18d7c125652ec01819c26f3a427b Mon Sep 17 00:00:00 2001
From: Alison Langston <46360176+alangsto@users.noreply.github.com>
Date: Thu, 31 Oct 2024 13:38:04 -0400
Subject: [PATCH 077/104] feat: update release version (#123)
---
CHANGELOG.rst | 5 ++++-
learning_assistant/__init__.py | 2 +-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a3882fd..baed526 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -13,11 +13,14 @@ Change Log
Unreleased
**********
-* Add LearningAssistantMessage model
+
+4.4.1 - 2024-10-31
+******************
* Add management command to remove expired messages
4.4.0 - 2024-10-30
******************
+* Add LearningAssistantMessage model
* Add new GET endpoint to retrieve a user's message history in a given course.
4.3.3 - 2024-10-15
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index f8f5d1c..fb90252 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.4.0'
+__version__ = '4.4.1'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
From 99ccaa22046beeb7d7c925d6c41b7b7966c2ecf0 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Wed, 30 Oct 2024 09:40:54 -0300
Subject: [PATCH 078/104] feat: Adding chat messages to the DB
---
learning_assistant/models.py | 10 +++++++++-
learning_assistant/serializers.py | 4 +++-
learning_assistant/views.py | 28 ++++++++++++++++++++++++++++
tests/test_views.py | 15 ++++++++++++++-
4 files changed, 54 insertions(+), 3 deletions(-)
diff --git a/learning_assistant/models.py b/learning_assistant/models.py
index c890087..482bfe2 100644
--- a/learning_assistant/models.py
+++ b/learning_assistant/models.py
@@ -35,7 +35,15 @@ class LearningAssistantMessage(TimeStampedModel):
.. pii_retirement: third_party
"""
+ USER_ROLE = 'user'
+ ASSISTANT_ROLE = 'assistant'
+
+ Roles = (
+ (USER_ROLE, USER_ROLE),
+ (ASSISTANT_ROLE, ASSISTANT_ROLE),
+ )
+
course_id = CourseKeyField(max_length=255, db_index=True)
user = models.ForeignKey(USER_MODEL, db_index=True, on_delete=models.CASCADE)
- role = models.CharField(max_length=64)
+ role = models.CharField(choices=Roles, max_length=64)
content = models.TextField()
diff --git a/learning_assistant/serializers.py b/learning_assistant/serializers.py
index a212654..62141ef 100644
--- a/learning_assistant/serializers.py
+++ b/learning_assistant/serializers.py
@@ -3,6 +3,8 @@
"""
from rest_framework import serializers
+from learning_assistant.models import LearningAssistantMessage
+
class MessageSerializer(serializers.Serializer): # pylint: disable=abstract-method
"""
@@ -16,7 +18,7 @@ def validate_role(self, value):
"""
Validate that role is one of two acceptable values.
"""
- valid_roles = ['user', 'assistant']
+ valid_roles = [LearningAssistantMessage.USER_ROLE, LearningAssistantMessage.ASSISTANT_ROLE]
if value not in valid_roles:
raise serializers.ValidationError('Must be valid role.')
return value
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 68b1c0a..aa9dc26 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -4,6 +4,7 @@
import logging
from django.conf import settings
+from django.contrib.auth import get_user_model
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
@@ -26,10 +27,12 @@
learning_assistant_enabled,
render_prompt_template,
)
+from learning_assistant.models import LearningAssistantMessage
from learning_assistant.serializers import MessageSerializer
from learning_assistant.utils import get_chat_response, user_role_is_staff
log = logging.getLogger(__name__)
+User = get_user_model()
class CourseChatView(APIView):
@@ -81,6 +84,15 @@ def post(self, request, course_run_id):
unit_id = request.query_params.get('unit_id')
message_list = request.data
+
+ # Check that the last message in the list corresponds to a user
+ new_user_message = message_list[-1]
+ if new_user_message['role'] != LearningAssistantMessage.USER_ROLE:
+ return Response(
+ status=http_status.HTTP_400_BAD_REQUEST,
+ data={'detail': "Expects user role on last message."}
+ )
+
serializer = MessageSerializer(data=message_list, many=True)
# serializer will not be valid in the case that the message list contains any roles other than
@@ -108,6 +120,22 @@ def post(self, request, course_run_id):
)
status_code, message = get_chat_response(prompt_template, message_list)
+ user = User.objects.get(id=request.user.id) # Based on the previous code, user exists.
+
+ # Save the user message to the database.
+ LearningAssistantMessage.objects.create(
+ user=user,
+ role=LearningAssistantMessage.USER_ROLE,
+ content=new_user_message['content'],
+ )
+
+ # Save the assistant response to the database.
+ LearningAssistantMessage.objects.create(
+ user=user,
+ role=LearningAssistantMessage.ASSISTANT_ROLE,
+ content=message['content'],
+ )
+
return Response(status=status_code, data=message)
diff --git a/tests/test_views.py b/tests/test_views.py
index 492a10d..35e90d3 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -173,7 +173,8 @@ def test_chat_response_default(
test_data = [
{'role': 'user', 'content': 'What is 2+2?'},
- {'role': 'assistant', 'content': 'It is 4'}
+ {'role': 'assistant', 'content': 'It is 4'},
+ {'role': 'user', 'content': 'And what else?'},
]
response = self.client.post(
@@ -181,8 +182,20 @@ def test_chat_response_default(
data=json.dumps(test_data),
content_type='application/json'
)
+
self.assertEqual(response.status_code, 200)
+ last_rows = LearningAssistantMessage.objects.all().order_by('-created').values()[:2][::-1]
+
+ user_msg = last_rows[0]
+ assistant_msg = last_rows[1]
+
+ self.assertEqual(user_msg['role'], LearningAssistantMessage.USER_ROLE)
+ self.assertEqual(user_msg['content'], test_data[2]['content'])
+
+ self.assertEqual(assistant_msg['role'], LearningAssistantMessage.ASSISTANT_ROLE)
+ self.assertEqual(assistant_msg['content'], 'Something else')
+
render_args = mock_render.call_args.args
self.assertIn(test_unit_id, render_args)
self.assertIn('This is the default template', render_args)
From c419fdefefd96045d68f610956e6cf91462adcce Mon Sep 17 00:00:00 2001
From: Marcos
Date: Wed, 30 Oct 2024 12:23:49 -0300
Subject: [PATCH 079/104] chore: Moved code to save the message to its own
method
---
learning_assistant/views.py | 39 ++++++++++++++++++++++++-------------
1 file changed, 25 insertions(+), 14 deletions(-)
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index aa9dc26..792aee3 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -43,6 +43,27 @@ class CourseChatView(APIView):
authentication_classes = (SessionAuthentication, JwtAuthentication,)
permission_classes = (IsAuthenticated,)
+ def __save_user_interaction(self, user_id, user_message, assistant_message):
+ """
+ Saves the last question/response to the database.
+ """
+ user = User.objects.get(id=user_id)
+
+ # Save the user message to the database.
+ LearningAssistantMessage.objects.create(
+ user=user,
+ role=LearningAssistantMessage.USER_ROLE,
+ content=user_message,
+ )
+
+ # Save the assistant response to the database.
+ LearningAssistantMessage.objects.create(
+ user=user,
+ role=LearningAssistantMessage.ASSISTANT_ROLE,
+ content=assistant_message,
+ )
+
+
def post(self, request, course_run_id):
"""
Given a course run ID, retrieve a chat response for that course.
@@ -120,20 +141,10 @@ def post(self, request, course_run_id):
)
status_code, message = get_chat_response(prompt_template, message_list)
- user = User.objects.get(id=request.user.id) # Based on the previous code, user exists.
-
- # Save the user message to the database.
- LearningAssistantMessage.objects.create(
- user=user,
- role=LearningAssistantMessage.USER_ROLE,
- content=new_user_message['content'],
- )
-
- # Save the assistant response to the database.
- LearningAssistantMessage.objects.create(
- user=user,
- role=LearningAssistantMessage.ASSISTANT_ROLE,
- content=message['content'],
+ self.__save_user_interaction(
+ user_id=request.user.id,
+ user_message=new_user_message['content'],
+ assistant_message=message['content']
)
return Response(status=status_code, data=message)
From 79917a5863ee026ff794f2c4cbf0cc744fe3c82f Mon Sep 17 00:00:00 2001
From: Marcos
Date: Wed, 30 Oct 2024 15:35:04 -0300
Subject: [PATCH 080/104] feat: Added learning_assistant.enable_chat_history
waffle flag
---
learning_assistant/toggles.py | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/learning_assistant/toggles.py b/learning_assistant/toggles.py
index 88d5ae6..61d515e 100644
--- a/learning_assistant/toggles.py
+++ b/learning_assistant/toggles.py
@@ -14,6 +14,16 @@
# .. toggle_tickets: COSMO-80
ENABLE_COURSE_CONTENT = 'enable_course_content'
+# .. toggle_name: learning_assistant.enable_chat_history
+# .. toggle_implementation: CourseWaffleFlag
+# .. toggle_default: False
+# .. toggle_description: Waffle flag to enable the chat history with the learning assistant
+# .. toggle_use_cases: temporary
+# .. toggle_creation_date: 2024-10-30
+# .. toggle_target_removal_date: 2024-12-31
+# .. toggle_tickets: COSMO-436
+ENABLE_CHAT_HISTORY = 'enable_chat_history'
+
def _is_learning_assistant_waffle_flag_enabled(flag_name, course_key):
"""
@@ -32,3 +42,10 @@ def course_content_enabled(course_key):
Return whether the learning_assistant.enable_course_content WaffleFlag is on.
"""
return _is_learning_assistant_waffle_flag_enabled(ENABLE_COURSE_CONTENT, course_key)
+
+
+def chat_history_enabled(course_key):
+ """
+ Return whether the learning_assistant.enable_chat_history WaffleFlag is on.
+ """
+ return _is_learning_assistant_waffle_flag_enabled(ENABLE_CHAT_HISTORY, course_key)
From dae5df8a781347a5df4080823fa7fb695c02df08 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Wed, 30 Oct 2024 15:36:05 -0300
Subject: [PATCH 081/104] feat: Added save_chat_message() to API
---
learning_assistant/api.py | 20 ++++++++++++++++++++
tests/test_api.py | 26 +++++++++++++++++++++++++-
2 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 0790059..7ddaa4b 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -4,6 +4,7 @@
import logging
from django.conf import settings
+from django.contrib.auth import get_user_model
from django.core.cache import cache
from edx_django_utils.cache import get_cache_key
from jinja2 import BaseLoader, Environment
@@ -24,6 +25,7 @@
from learning_assistant.text_utils import html_to_text
log = logging.getLogger(__name__)
+User = get_user_model()
def _extract_block_contents(child, category):
@@ -188,6 +190,24 @@ def get_course_id(course_run_id):
course_key = course_data['course']
return course_key
+def save_chat_message(user_id, chat_role, message):
+ """
+ Saves the chat message to the database.
+ """
+
+ user = None
+ try:
+ user = User.objects.get(id=user_id)
+ except User.DoesNotExist:
+ raise Exception("User does not exists.")
+
+ # Save the user message to the database.
+ LearningAssistantMessage.objects.create(
+ user=user,
+ role=chat_role,
+ content=message,
+ )
+
def get_message_history(course_id, user, message_count):
"""
diff --git a/tests/test_api.py b/tests/test_api.py
index 3ca365d..a2f51c3 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -21,13 +21,13 @@
learning_assistant_available,
learning_assistant_enabled,
render_prompt_template,
+ save_chat_message,
set_learning_assistant_enabled,
)
from learning_assistant.data import LearningAssistantCourseEnabledData
from learning_assistant.models import LearningAssistantCourseEnabled, LearningAssistantMessage
fake_transcript = 'This is the text version from the transcript'
-
User = get_user_model()
@@ -235,6 +235,30 @@ def test_render_prompt_template_invalid_unit_key(self, mock_get_content):
self.assertNotIn('The following text is useful.', prompt_text)
+@ddt.ddt
+class TestLearningAssistantCourseEnabledApi(TestCase):
+ """
+ Test suite for save_chat_message.
+ """
+ def setUp(self):
+ super().setUp()
+
+ self.test_user = User.objects.create(username='username', password='password')
+
+ @ddt.data(
+ (LearningAssistantMessage.USER_ROLE, 'What is the meaning of life, the universe and everything?'),
+ (LearningAssistantMessage.ASSISTANT_ROLE, '42'),
+ )
+ @ddt.unpack
+ def test_save_chat_message(self, chat_role, message):
+ save_chat_message(self.test_user.id, chat_role, message)
+
+ row = LearningAssistantMessage.objects.all().last()
+
+ self.assertEqual(row.role, chat_role)
+ self.assertEqual(row.content, message)
+
+
@ddt.ddt
class LearningAssistantCourseEnabledApiTests(TestCase):
From 23fd0131484c975cb8fab81b1276010820213534 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Wed, 30 Oct 2024 17:53:09 -0300
Subject: [PATCH 082/104] chore: Fixed coverage for CourseChatView
---
learning_assistant/api.py | 8 +++---
learning_assistant/views.py | 40 ++++++++---------------------
tests/test_api.py | 2 +-
tests/test_views.py | 50 ++++++++++++++++++++++++-------------
4 files changed, 48 insertions(+), 52 deletions(-)
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 7ddaa4b..e6fa870 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -190,16 +190,16 @@ def get_course_id(course_run_id):
course_key = course_data['course']
return course_key
+
def save_chat_message(user_id, chat_role, message):
"""
- Saves the chat message to the database.
+ Save the chat message to the database.
"""
-
user = None
try:
user = User.objects.get(id=user_id)
- except User.DoesNotExist:
- raise Exception("User does not exists.")
+ except User.DoesNotExist as exc:
+ raise Exception("User does not exists.") from exc
# Save the user message to the database.
LearningAssistantMessage.objects.create(
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 792aee3..44eb8d0 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -4,7 +4,6 @@
import logging
from django.conf import settings
-from django.contrib.auth import get_user_model
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
@@ -26,13 +25,14 @@
get_message_history,
learning_assistant_enabled,
render_prompt_template,
+ save_chat_message,
)
from learning_assistant.models import LearningAssistantMessage
from learning_assistant.serializers import MessageSerializer
+from learning_assistant.toggles import chat_history_enabled
from learning_assistant.utils import get_chat_response, user_role_is_staff
log = logging.getLogger(__name__)
-User = get_user_model()
class CourseChatView(APIView):
@@ -43,27 +43,6 @@ class CourseChatView(APIView):
authentication_classes = (SessionAuthentication, JwtAuthentication,)
permission_classes = (IsAuthenticated,)
- def __save_user_interaction(self, user_id, user_message, assistant_message):
- """
- Saves the last question/response to the database.
- """
- user = User.objects.get(id=user_id)
-
- # Save the user message to the database.
- LearningAssistantMessage.objects.create(
- user=user,
- role=LearningAssistantMessage.USER_ROLE,
- content=user_message,
- )
-
- # Save the assistant response to the database.
- LearningAssistantMessage.objects.create(
- user=user,
- role=LearningAssistantMessage.ASSISTANT_ROLE,
- content=assistant_message,
- )
-
-
def post(self, request, course_run_id):
"""
Given a course run ID, retrieve a chat response for that course.
@@ -114,6 +93,12 @@ def post(self, request, course_run_id):
data={'detail': "Expects user role on last message."}
)
+ course_id = get_course_id(course_run_id)
+ user_id = request.user.id
+
+ if chat_history_enabled(course_id):
+ save_chat_message(user_id, LearningAssistantMessage.USER_ROLE, new_user_message['content'])
+
serializer = MessageSerializer(data=message_list, many=True)
# serializer will not be valid in the case that the message list contains any roles other than
@@ -132,8 +117,6 @@ def post(self, request, course_run_id):
}
)
- course_id = get_course_id(course_run_id)
-
template_string = getattr(settings, 'LEARNING_ASSISTANT_PROMPT_TEMPLATE', '')
prompt_template = render_prompt_template(
@@ -141,11 +124,8 @@ def post(self, request, course_run_id):
)
status_code, message = get_chat_response(prompt_template, message_list)
- self.__save_user_interaction(
- user_id=request.user.id,
- user_message=new_user_message['content'],
- assistant_message=message['content']
- )
+ if chat_history_enabled(course_id):
+ save_chat_message(user_id, LearningAssistantMessage.ASSISTANT_ROLE, message['content'])
return Response(status=status_code, data=message)
diff --git a/tests/test_api.py b/tests/test_api.py
index a2f51c3..6969af8 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -235,6 +235,7 @@ def test_render_prompt_template_invalid_unit_key(self, mock_get_content):
self.assertNotIn('The following text is useful.', prompt_text)
+
@ddt.ddt
class TestLearningAssistantCourseEnabledApi(TestCase):
"""
@@ -259,7 +260,6 @@ def test_save_chat_message(self, chat_role, message):
self.assertEqual(row.content, message)
-
@ddt.ddt
class LearningAssistantCourseEnabledApiTests(TestCase):
"""
diff --git a/tests/test_views.py b/tests/test_views.py
index 35e90d3..c521a30 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -4,7 +4,7 @@
import json
import sys
from importlib import import_module
-from unittest.mock import MagicMock, patch
+from unittest.mock import MagicMock, call, patch
import ddt
from django.conf import settings
@@ -19,7 +19,7 @@
User = get_user_model()
-class TestClient(Client):
+class FakeClient(Client):
"""
Allows for 'fake logins' of a user so we don't need to expose a 'login' HTTP endpoint.
"""
@@ -66,14 +66,14 @@ def setUp(self):
Setup for tests.
"""
super().setUp()
- self.client = TestClient()
+ self.client = FakeClient()
self.user = User(username='tester', email='tester@test.com', is_staff=True)
self.user.save()
self.client.login_user(self.user)
@ddt.ddt
-class CourseChatViewTests(LoggedInTestCase):
+class TestCourseChatView(LoggedInTestCase):
"""
Test for the CourseChatView
"""
@@ -153,15 +153,27 @@ def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
)
self.assertEqual(response.status_code, 400)
+ @ddt.data(True, False) # TODO: Fix this - See below.
@patch('learning_assistant.views.render_prompt_template')
@patch('learning_assistant.views.get_chat_response')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
@patch('learning_assistant.views.CourseEnrollment.get_enrollment')
@patch('learning_assistant.views.CourseMode')
+ @patch('learning_assistant.api.save_chat_message')
+ @patch('learning_assistant.toggles.chat_history_enabled')
@override_settings(LEARNING_ASSISTANT_PROMPT_TEMPLATE='This is the default template')
def test_chat_response_default(
- self, mock_mode, mock_enrollment, mock_role, mock_waffle, mock_chat_response, mock_render
+ self,
+ enabled_flag,
+ mock_chat_history_enabled,
+ mock_save_chat_message,
+ mock_mode,
+ mock_enrollment,
+ mock_role,
+ mock_waffle,
+ mock_chat_response,
+ mock_render,
):
mock_waffle.return_value = True
mock_role.return_value = 'student'
@@ -171,6 +183,14 @@ def test_chat_response_default(
mock_render.return_value = 'Rendered template mock'
test_unit_id = 'test-unit-id'
+ # TODO: Fix this...
+ # For some reason this only works the first time. The 2nd time (enabled_flag = False)
+ # Doesn't actually work since the mocked chat_history_enabled() will return False no matter what.
+ # Swap the order of the @ddt.data() above by: @ddt.data(False, True) and watch it fail.
+ # The value for enabled_flag is corrct on this scope, but the mocked method doesn't update.
+ # It even happens if we split the test cases into two different methods.
+ mock_chat_history_enabled.return_value = enabled_flag
+
test_data = [
{'role': 'user', 'content': 'What is 2+2?'},
{'role': 'assistant', 'content': 'It is 4'},
@@ -182,20 +202,8 @@ def test_chat_response_default(
data=json.dumps(test_data),
content_type='application/json'
)
-
self.assertEqual(response.status_code, 200)
- last_rows = LearningAssistantMessage.objects.all().order_by('-created').values()[:2][::-1]
-
- user_msg = last_rows[0]
- assistant_msg = last_rows[1]
-
- self.assertEqual(user_msg['role'], LearningAssistantMessage.USER_ROLE)
- self.assertEqual(user_msg['content'], test_data[2]['content'])
-
- self.assertEqual(assistant_msg['role'], LearningAssistantMessage.ASSISTANT_ROLE)
- self.assertEqual(assistant_msg['content'], 'Something else')
-
render_args = mock_render.call_args.args
self.assertIn(test_unit_id, render_args)
self.assertIn('This is the default template', render_args)
@@ -205,6 +213,14 @@ def test_chat_response_default(
test_data,
)
+ if enabled_flag:
+ mock_save_chat_message.assert_has_calls([
+ call(self.user.id, LearningAssistantMessage.USER_ROLE, test_data[-1]['content']),
+ call(self.user.id, LearningAssistantMessage.ASSISTANT_ROLE, 'Something else')
+ ])
+ else:
+ mock_save_chat_message.assert_not_called()
+
@ddt.ddt
class LearningAssistantEnabledViewTests(LoggedInTestCase):
From 352340ea71c5ec7fae32cee3346827755e25a828 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Thu, 31 Oct 2024 13:25:21 -0300
Subject: [PATCH 083/104] fix: Swapped course id for course key
---
learning_assistant/views.py | 7 ++++---
tests/test_views.py | 12 +++++++-----
2 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 44eb8d0..3e136fe 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -93,10 +93,9 @@ def post(self, request, course_run_id):
data={'detail': "Expects user role on last message."}
)
- course_id = get_course_id(course_run_id)
user_id = request.user.id
- if chat_history_enabled(course_id):
+ if chat_history_enabled(courserun_key):
save_chat_message(user_id, LearningAssistantMessage.USER_ROLE, new_user_message['content'])
serializer = MessageSerializer(data=message_list, many=True)
@@ -117,6 +116,8 @@ def post(self, request, course_run_id):
}
)
+ course_id = get_course_id(course_run_id)
+
template_string = getattr(settings, 'LEARNING_ASSISTANT_PROMPT_TEMPLATE', '')
prompt_template = render_prompt_template(
@@ -124,7 +125,7 @@ def post(self, request, course_run_id):
)
status_code, message = get_chat_response(prompt_template, message_list)
- if chat_history_enabled(course_id):
+ if chat_history_enabled(courserun_key):
save_chat_message(user_id, LearningAssistantMessage.ASSISTANT_ROLE, message['content'])
return Response(status=status_code, data=message)
diff --git a/tests/test_views.py b/tests/test_views.py
index c521a30..4e791b7 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -184,11 +184,13 @@ def test_chat_response_default(
test_unit_id = 'test-unit-id'
# TODO: Fix this...
- # For some reason this only works the first time. The 2nd time (enabled_flag = False)
- # Doesn't actually work since the mocked chat_history_enabled() will return False no matter what.
- # Swap the order of the @ddt.data() above by: @ddt.data(False, True) and watch it fail.
- # The value for enabled_flag is corrct on this scope, but the mocked method doesn't update.
- # It even happens if we split the test cases into two different methods.
+ # For some reason this assignment only works the first iteration. The 2nd time onwards the return value is
+ # always falsy. Swap the order of the @ddt.data() above by: @ddt.data(False, True) to see it fail.
+ # I'm leaving it like this because we are testing the False return in the second iteration, but it's important
+ # to consider whenever this test needs to be updated.
+ # It even happens if we split the test cases into two different methods (instead of @ddt.data()), so there's
+ # probably some scoping issues in how the test is set up.
+ # Note: There's a similar test for LearningAssistantEnabledView in this file that works just fine.
mock_chat_history_enabled.return_value = enabled_flag
test_data = [
From 7cdc76331a83a515f91ad6b1b2a88810775fa570 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Mon, 4 Nov 2024 11:16:58 -0300
Subject: [PATCH 084/104] chore: Fixed patching on the CourseChatView unit
tests
---
tests/test_views.py | 14 +++-----------
1 file changed, 3 insertions(+), 11 deletions(-)
diff --git a/tests/test_views.py b/tests/test_views.py
index 4e791b7..9e07e55 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -153,15 +153,15 @@ def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
)
self.assertEqual(response.status_code, 400)
- @ddt.data(True, False) # TODO: Fix this - See below.
+ @ddt.data(False, True)
@patch('learning_assistant.views.render_prompt_template')
@patch('learning_assistant.views.get_chat_response')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
@patch('learning_assistant.views.CourseEnrollment.get_enrollment')
@patch('learning_assistant.views.CourseMode')
- @patch('learning_assistant.api.save_chat_message')
- @patch('learning_assistant.toggles.chat_history_enabled')
+ @patch('learning_assistant.views.save_chat_message')
+ @patch('learning_assistant.views.chat_history_enabled')
@override_settings(LEARNING_ASSISTANT_PROMPT_TEMPLATE='This is the default template')
def test_chat_response_default(
self,
@@ -183,14 +183,6 @@ def test_chat_response_default(
mock_render.return_value = 'Rendered template mock'
test_unit_id = 'test-unit-id'
- # TODO: Fix this...
- # For some reason this assignment only works the first iteration. The 2nd time onwards the return value is
- # always falsy. Swap the order of the @ddt.data() above by: @ddt.data(False, True) to see it fail.
- # I'm leaving it like this because we are testing the False return in the second iteration, but it's important
- # to consider whenever this test needs to be updated.
- # It even happens if we split the test cases into two different methods (instead of @ddt.data()), so there's
- # probably some scoping issues in how the test is set up.
- # Note: There's a similar test for LearningAssistantEnabledView in this file that works just fine.
mock_chat_history_enabled.return_value = enabled_flag
test_data = [
From f0b0cf8d3ec4393d4b1aeae7bdd9519c94a65864 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Mon, 4 Nov 2024 11:57:09 -0300
Subject: [PATCH 085/104] fix: Added migration after updating role in model
---
...0008_alter_learningassistantmessage_role.py | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
create mode 100644 learning_assistant/migrations/0008_alter_learningassistantmessage_role.py
diff --git a/learning_assistant/migrations/0008_alter_learningassistantmessage_role.py b/learning_assistant/migrations/0008_alter_learningassistantmessage_role.py
new file mode 100644
index 0000000..bd699b3
--- /dev/null
+++ b/learning_assistant/migrations/0008_alter_learningassistantmessage_role.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.14 on 2024-11-04 08:52
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('learning_assistant', '0007_learningassistantmessage'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='learningassistantmessage',
+ name='role',
+ field=models.CharField(choices=[('user', 'user'), ('assistant', 'assistant')], max_length=64),
+ ),
+ ]
From 4fa9bf5148bc68dc6962bb935ea39f33ecb0b181 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Mon, 4 Nov 2024 15:23:31 -0300
Subject: [PATCH 086/104] fix: Added course run key to save_chat_message()
---
learning_assistant/api.py | 4 +++-
learning_assistant/views.py | 4 ++--
tests/test_api.py | 4 +++-
tests/test_views.py | 6 ++++--
4 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index e6fa870..eaf4b10 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -191,7 +191,7 @@ def get_course_id(course_run_id):
return course_key
-def save_chat_message(user_id, chat_role, message):
+def save_chat_message(courserun_key, user_id, chat_role, message):
"""
Save the chat message to the database.
"""
@@ -203,9 +203,11 @@ def save_chat_message(user_id, chat_role, message):
# Save the user message to the database.
LearningAssistantMessage.objects.create(
+ course_id=courserun_key,
user=user,
role=chat_role,
content=message,
+
)
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 3e136fe..583aed1 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -96,7 +96,7 @@ def post(self, request, course_run_id):
user_id = request.user.id
if chat_history_enabled(courserun_key):
- save_chat_message(user_id, LearningAssistantMessage.USER_ROLE, new_user_message['content'])
+ save_chat_message(courserun_key, user_id, LearningAssistantMessage.USER_ROLE, new_user_message['content'])
serializer = MessageSerializer(data=message_list, many=True)
@@ -126,7 +126,7 @@ def post(self, request, course_run_id):
status_code, message = get_chat_response(prompt_template, message_list)
if chat_history_enabled(courserun_key):
- save_chat_message(user_id, LearningAssistantMessage.ASSISTANT_ROLE, message['content'])
+ save_chat_message(courserun_key, user_id, LearningAssistantMessage.ASSISTANT_ROLE, message['content'])
return Response(status=status_code, data=message)
diff --git a/tests/test_api.py b/tests/test_api.py
index 6969af8..0470e32 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -245,6 +245,7 @@ def setUp(self):
super().setUp()
self.test_user = User.objects.create(username='username', password='password')
+ self.course_run_key = CourseKey.from_string('course-v1:edx+test+23')
@ddt.data(
(LearningAssistantMessage.USER_ROLE, 'What is the meaning of life, the universe and everything?'),
@@ -252,10 +253,11 @@ def setUp(self):
)
@ddt.unpack
def test_save_chat_message(self, chat_role, message):
- save_chat_message(self.test_user.id, chat_role, message)
+ save_chat_message(self.course_run_key, self.test_user.id, chat_role, message)
row = LearningAssistantMessage.objects.all().last()
+ self.assertEqual(row.course_id, self.course_run_key)
self.assertEqual(row.role, chat_role)
self.assertEqual(row.content, message)
diff --git a/tests/test_views.py b/tests/test_views.py
index 9e07e55..5125d27 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -13,6 +13,7 @@
from django.test import TestCase, override_settings
from django.test.client import Client
from django.urls import reverse
+from opaque_keys.edx.keys import CourseKey
from learning_assistant.models import LearningAssistantMessage
@@ -85,6 +86,7 @@ class TestCourseChatView(LoggedInTestCase):
def setUp(self):
super().setUp()
self.course_id = 'course-v1:edx+test+23'
+ self.course_run_key = CourseKey.from_string(self.course_id)
self.patcher = patch(
'learning_assistant.api.get_cache_course_run_data',
@@ -209,8 +211,8 @@ def test_chat_response_default(
if enabled_flag:
mock_save_chat_message.assert_has_calls([
- call(self.user.id, LearningAssistantMessage.USER_ROLE, test_data[-1]['content']),
- call(self.user.id, LearningAssistantMessage.ASSISTANT_ROLE, 'Something else')
+ call(self.course_run_key, self.user.id, LearningAssistantMessage.USER_ROLE, test_data[-1]['content']),
+ call(self.course_run_key, self.user.id, LearningAssistantMessage.ASSISTANT_ROLE, 'Something else')
])
else:
mock_save_chat_message.assert_not_called()
From 7b8425d2a9ee033da96279681730285e85ed94a4 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Wed, 6 Nov 2024 13:03:10 -0300
Subject: [PATCH 087/104] fix: Fixed package version
---
learning_assistant/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index fb90252..ea4266b 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.4.1'
+__version__ = '4.4.3'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
From d2d5808c61b892570f26b7c7b81dae7477a64a3c Mon Sep 17 00:00:00 2001
From: Marcos
Date: Wed, 6 Nov 2024 16:37:48 -0300
Subject: [PATCH 088/104] fix: Fixed Couseware History fetching
---
learning_assistant/api.py | 6 +++---
learning_assistant/views.py | 3 +--
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index eaf4b10..272e201 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -211,12 +211,12 @@ def save_chat_message(courserun_key, user_id, chat_role, message):
)
-def get_message_history(course_id, user, message_count):
+def get_message_history(courserun_key, user, message_count):
"""
- Given a course run id (str), user (User), and message count (int), return the associated message history.
+ Given a courserun key (CourseKey), user (User), and message count (int), return the associated message history.
Returns a number of messages equal to the message_count value.
"""
message_history = LearningAssistantMessage.objects.filter(
- course_id=course_id, user=user).order_by('-created')[:message_count]
+ course_id=courserun_key, user=user).order_by('-created')[:message_count]
return message_history
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 583aed1..b1b5c03 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -233,10 +233,9 @@ def get(self, request, course_run_id):
data={'detail': 'Must be staff or have valid enrollment.'}
)
- course_id = get_course_id(course_run_id)
user = request.user
message_count = int(request.GET.get('message_count', 50))
- message_history = get_message_history(course_id, user, message_count)
+ message_history = get_message_history(courserun_key, user, message_count)
data = MessageSerializer(message_history, many=True).data
return Response(status=http_status.HTTP_200_OK, data=data)
From b56f1461c721e571ffb7294b41a626f81924a32c Mon Sep 17 00:00:00 2001
From: Marcos
Date: Wed, 6 Nov 2024 16:51:05 -0300
Subject: [PATCH 089/104] chore: Updated tests
---
tests/test_api.py | 29 ++++++++++++++---------------
1 file changed, 14 insertions(+), 15 deletions(-)
diff --git a/tests/test_api.py b/tests/test_api.py
index 0470e32..8b66845 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -347,8 +347,7 @@ class GetMessageHistoryTests(TestCase):
def setUp(self):
super().setUp()
- self.course_id = 'course-v1:edx+fake+1'
- self.course_key = CourseKey.from_string(self.course_id)
+ self.course_key = CourseKey.from_string('course-v1:edx+fake+1')
self.user = User(username='tester', email='tester@test.com')
self.user.save()
@@ -358,16 +357,16 @@ def test_get_message_history(self):
message_count = 5
for i in range(1, message_count + 1):
LearningAssistantMessage.objects.create(
- course_id=self.course_id,
+ course_id=self.course_key,
user=self.user,
role=self.role,
content=f'Content of message {i}',
)
- return_value = get_message_history(self.course_id, self.user, message_count)
+ return_value = get_message_history(self.course_key, self.user, message_count)
expected_value = LearningAssistantMessage.objects.filter(
- course_id=self.course_id, user=self.user).order_by('-created')[:message_count]
+ course_id=self.course_key, user=self.user).order_by('-created')[:message_count]
# Ensure same number of entries
self.assertEqual(len(return_value), len(expected_value))
@@ -385,17 +384,17 @@ def test_get_message_history(self):
def test_get_message_history_message_count(self, actual_message_count):
for i in range(1, actual_message_count + 1):
LearningAssistantMessage.objects.create(
- course_id=self.course_id,
+ course_id=self.course_key,
user=self.user,
role=self.role,
content=f'Content of message {i}',
)
message_count_parameter = 5
- return_value = get_message_history(self.course_id, self.user, message_count_parameter)
+ return_value = get_message_history(self.course_key, self.user, message_count_parameter)
expected_value = LearningAssistantMessage.objects.filter(
- course_id=self.course_id, user=self.user).order_by('-created')[:message_count_parameter]
+ course_id=self.course_key, user=self.user).order_by('-created')[:message_count_parameter]
# Ensure same number of entries
self.assertEqual(len(return_value), len(expected_value))
@@ -403,7 +402,7 @@ def test_get_message_history_message_count(self, actual_message_count):
def test_get_message_history_user_difference(self):
# Default Message
LearningAssistantMessage.objects.create(
- course_id=self.course_id,
+ course_id=self.course_key,
user=self.user,
role=self.role,
content='Expected content of message',
@@ -413,17 +412,17 @@ def test_get_message_history_user_difference(self):
new_user = User(username='not_tester', email='not_tester@test.com')
new_user.save()
LearningAssistantMessage.objects.create(
- course_id=self.course_id,
+ course_id=self.course_key,
user=new_user,
role=self.role,
content='Expected content of message',
)
message_count = 2
- return_value = get_message_history(self.course_id, self.user, message_count)
+ return_value = get_message_history(self.course_key, self.user, message_count)
expected_value = LearningAssistantMessage.objects.filter(
- course_id=self.course_id, user=self.user).order_by('-created')[:message_count]
+ course_id=self.course_key, user=self.user).order_by('-created')[:message_count]
# Ensure we filtered one of the two present messages
self.assertNotEqual(len(return_value), LearningAssistantMessage.objects.count())
@@ -441,7 +440,7 @@ def test_get_message_history_user_difference(self):
def test_get_message_course_id_differences(self):
# Default Message
LearningAssistantMessage.objects.create(
- course_id=self.course_id,
+ course_id=self.course_key,
user=self.user,
role=self.role,
content='Expected content of message',
@@ -457,10 +456,10 @@ def test_get_message_course_id_differences(self):
)
message_count = 2
- return_value = get_message_history(self.course_id, self.user, message_count)
+ return_value = get_message_history(self.course_key, self.user, message_count)
expected_value = LearningAssistantMessage.objects.filter(
- course_id=self.course_id, user=self.user).order_by('-created')[:message_count]
+ course_id=self.course_key, user=self.user).order_by('-created')[:message_count]
# Ensure we filtered one of the two present messages
self.assertNotEqual(len(return_value), LearningAssistantMessage.objects.count())
From 510303bfea08a675b57ed1ff608b74ec363729b7 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Wed, 6 Nov 2024 17:50:01 -0300
Subject: [PATCH 090/104] feat: Added timestamp to Learning Assistant History
response
---
learning_assistant/serializers.py | 13 +++++++++++++
tests/test_views.py | 1 +
2 files changed, 14 insertions(+)
diff --git a/learning_assistant/serializers.py b/learning_assistant/serializers.py
index 62141ef..1896182 100644
--- a/learning_assistant/serializers.py
+++ b/learning_assistant/serializers.py
@@ -13,6 +13,19 @@ class MessageSerializer(serializers.Serializer): # pylint: disable=abstract-met
role = serializers.CharField(required=True)
content = serializers.CharField(required=True)
+ timestamp = serializers.DateTimeField(required=False, source='created')
+
+ class Meta:
+ """
+ Serializer metadata.
+ """
+
+ model = LearningAssistantMessage
+ fields = (
+ 'role',
+ 'content',
+ 'timestamp',
+ )
def validate_role(self, value):
"""
diff --git a/tests/test_views.py b/tests/test_views.py
index 5125d27..22b1cf0 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -365,3 +365,4 @@ def test_learning_message_history_view_get(
actual_message = LearningAssistantMessage.objects.get(course_id=self.course_id)
self.assertEqual(data[0]['role'], actual_message.role)
self.assertEqual(data[0]['content'], actual_message.content)
+ self.assertEqual(data[0]['timestamp'], actual_message.created.isoformat())
From 27e4f8b8e58c397abebbe7e8ad278c7b1790b6ad Mon Sep 17 00:00:00 2001
From: Marcos
Date: Thu, 7 Nov 2024 12:24:27 -0300
Subject: [PATCH 091/104] chore: Release bump to v4.4.4
---
CHANGELOG.rst | 13 +++++++++++++
learning_assistant/__init__.py | 2 +-
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index baed526..ad32abb 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,19 @@ Change Log
Unreleased
**********
+4.4.4 - 2024-11-06
+******************
+* Fixed Learning Assistant History endpoint
+* Added timestamp to the Learning Assistant History payload
+
+4.4.3 - 2024-11-06
+******************
+* Fixed package version
+
+4.4.2 - 2024-11-04
+******************
+* Added chat messages to the DB
+
4.4.1 - 2024-10-31
******************
* Add management command to remove expired messages
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index ea4266b..27b6a0f 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.4.3'
+__version__ = '4.4.4'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
From cc51613ac08fd328e1811e78322d80fa540a6e8a Mon Sep 17 00:00:00 2001
From: Marcos
Date: Fri, 8 Nov 2024 12:57:23 -0300
Subject: [PATCH 092/104] fix: Updated Learning Assistant History payload to
return in asc order
---
CHANGELOG.rst | 1 +
learning_assistant/views.py | 2 +-
tests/test_views.py | 30 +++++++++++++++++++++---------
3 files changed, 23 insertions(+), 10 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index ad32abb..910051e 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -13,6 +13,7 @@ Change Log
Unreleased
**********
+* Updated Learning Assistant History payload to return in ascending order
4.4.4 - 2024-11-06
******************
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index b1b5c03..02c3fca 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -236,6 +236,6 @@ def get(self, request, course_run_id):
user = request.user
message_count = int(request.GET.get('message_count', 50))
- message_history = get_message_history(courserun_key, user, message_count)
+ message_history = reversed(list(get_message_history(courserun_key, user, message_count))) # Reversing order
data = MessageSerializer(message_history, many=True).data
return Response(status=http_status.HTTP_200_OK, data=data)
diff --git a/tests/test_views.py b/tests/test_views.py
index 22b1cf0..42ad0b3 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -1,6 +1,7 @@
"""
Tests for the learning assistant views.
"""
+import datetime
import json
import sys
from importlib import import_module
@@ -347,22 +348,33 @@ def test_learning_message_history_view_get(
course_id=self.course_id,
user=self.user,
role='staff',
- content='Expected content of message',
+ content='Older message',
+ created=datetime.date(2024, 10, 1)
)
- message_count = 1
+
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role='staff',
+ content='Newer message',
+ created=datetime.date(2024, 10, 3)
+ )
+
+ db_messages = LearningAssistantMessage.objects.all().order_by('created')
+ db_messages_count = len(db_messages)
+
mock_get_course_id.return_value = self.course_id
response = self.client.get(
- reverse('message-history', kwargs={'course_run_id': self.course_id})+f'?message_count={message_count}',
+ reverse('message-history', kwargs={'course_run_id': self.course_id})+f'?message_count={db_messages_count}',
content_type='application/json'
)
data = response.data
# Ensure same number of entries
- actual_message_count = LearningAssistantMessage.objects.count()
- self.assertEqual(len(data), actual_message_count)
+ self.assertEqual(len(data), db_messages_count)
# Ensure values are as expected
- actual_message = LearningAssistantMessage.objects.get(course_id=self.course_id)
- self.assertEqual(data[0]['role'], actual_message.role)
- self.assertEqual(data[0]['content'], actual_message.content)
- self.assertEqual(data[0]['timestamp'], actual_message.created.isoformat())
+ for i, expected in enumerate(db_messages):
+ self.assertEqual(expected.role, data[i]['role'])
+ self.assertEqual(expected.content, data[i]['content'])
+ self.assertEqual(expected.created.isoformat(), data[i]['timestamp'])
From 2f93c7ee2f9af9830d03f14b3ca2a45ffbaeed08 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Fri, 8 Nov 2024 17:10:04 -0300
Subject: [PATCH 093/104] chore: Updated the place where the list is reversed
---
learning_assistant/api.py | 4 ++--
learning_assistant/views.py | 2 +-
tests/test_api.py | 4 ++--
tests/test_views.py | 8 ++++----
4 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 272e201..2006b26 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -217,6 +217,6 @@ def get_message_history(courserun_key, user, message_count):
Returns a number of messages equal to the message_count value.
"""
- message_history = LearningAssistantMessage.objects.filter(
- course_id=courserun_key, user=user).order_by('-created')[:message_count]
+ message_history = list(LearningAssistantMessage.objects.filter(
+ course_id=courserun_key, user=user).order_by('-created')[:message_count])[::-1]
return message_history
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 02c3fca..b1b5c03 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -236,6 +236,6 @@ def get(self, request, course_run_id):
user = request.user
message_count = int(request.GET.get('message_count', 50))
- message_history = reversed(list(get_message_history(courserun_key, user, message_count))) # Reversing order
+ message_history = get_message_history(courserun_key, user, message_count)
data = MessageSerializer(message_history, many=True).data
return Response(status=http_status.HTTP_200_OK, data=data)
diff --git a/tests/test_api.py b/tests/test_api.py
index 8b66845..1344dea 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -365,8 +365,8 @@ def test_get_message_history(self):
return_value = get_message_history(self.course_key, self.user, message_count)
- expected_value = LearningAssistantMessage.objects.filter(
- course_id=self.course_key, user=self.user).order_by('-created')[:message_count]
+ expected_value = list(LearningAssistantMessage.objects.filter(
+ course_id=self.course_key, user=self.user).order_by('-created')[:message_count])[::-1]
# Ensure same number of entries
self.assertEqual(len(return_value), len(expected_value))
diff --git a/tests/test_views.py b/tests/test_views.py
index 42ad0b3..1d567b8 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -374,7 +374,7 @@ def test_learning_message_history_view_get(
self.assertEqual(len(data), db_messages_count)
# Ensure values are as expected
- for i, expected in enumerate(db_messages):
- self.assertEqual(expected.role, data[i]['role'])
- self.assertEqual(expected.content, data[i]['content'])
- self.assertEqual(expected.created.isoformat(), data[i]['timestamp'])
+ for i, message in enumerate(data):
+ self.assertEqual(message['role'], db_messages[i].role)
+ self.assertEqual(message['content'], db_messages[i].content)
+ self.assertEqual(message['timestamp'], db_messages[i].created.isoformat())
From 726bfdaa295c4fc64deb98e43b48d9aee9c498a7 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Tue, 12 Nov 2024 10:44:45 -0300
Subject: [PATCH 094/104] chore: Updated to use as 4.4.5 release
---
CHANGELOG.rst | 3 +++
learning_assistant/__init__.py | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 910051e..19aa431 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -13,6 +13,9 @@ Change Log
Unreleased
**********
+
+4.4.5 - 2024-11-12
+******************
* Updated Learning Assistant History payload to return in ascending order
4.4.4 - 2024-11-06
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 27b6a0f..8ef32fa 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.4.4'
+__version__ = '4.4.5'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
From 4e1ac79e2d2ca1d11044275896b67210d2bc67ce Mon Sep 17 00:00:00 2001
From: Marcos
Date: Tue, 12 Nov 2024 11:23:43 -0300
Subject: [PATCH 095/104] chore: Added comment on get_message_history()
explaining the double inversion.
---
learning_assistant/api.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 2006b26..55c73c1 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -217,6 +217,10 @@ def get_message_history(courserun_key, user, message_count):
Returns a number of messages equal to the message_count value.
"""
+ # Explanation over the double reverse: This fetches the last message_count elements ordered by creating order DESC.
+ # Slicing the list in the model is an equivalent of adding LIMIT on the query.
+ # The result is the last chat messages for that user and course but in inversed order, so in order to flip them
+ # its first turn into a list and then reversed.
message_history = list(LearningAssistantMessage.objects.filter(
course_id=courserun_key, user=user).order_by('-created')[:message_count])[::-1]
return message_history
From e3709235304bed34ddac3918497791ed517e2fb7 Mon Sep 17 00:00:00 2001
From: Isaac Lee <124631592+ilee2u@users.noreply.github.com>
Date: Thu, 14 Nov 2024 15:03:07 -0500
Subject: [PATCH 096/104] feat: add LearningAssistantAuditTrial model (#132)
* feat: add LearningAssistantAuditTrial model
* chore: Add no_pii annotation
* fix: make start_time non-nullable
* fix: also revise the migration
* fix: replace ghost tests/__init__.py
---
.../0009_learningassistantaudittrial.py | 31 +++++++++++++++++++
learning_assistant/models.py | 15 +++++++++
2 files changed, 46 insertions(+)
create mode 100644 learning_assistant/migrations/0009_learningassistantaudittrial.py
diff --git a/learning_assistant/migrations/0009_learningassistantaudittrial.py b/learning_assistant/migrations/0009_learningassistantaudittrial.py
new file mode 100644
index 0000000..30068a2
--- /dev/null
+++ b/learning_assistant/migrations/0009_learningassistantaudittrial.py
@@ -0,0 +1,31 @@
+# Generated by Django 4.2.16 on 2024-11-14 13:55
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+import model_utils.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('learning_assistant', '0008_alter_learningassistantmessage_role'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='LearningAssistantAuditTrial',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
+ ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
+ ('start_date', models.DateTimeField()),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, unique=True)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ ]
diff --git a/learning_assistant/models.py b/learning_assistant/models.py
index 482bfe2..bd05ceb 100644
--- a/learning_assistant/models.py
+++ b/learning_assistant/models.py
@@ -47,3 +47,18 @@ class LearningAssistantMessage(TimeStampedModel):
user = models.ForeignKey(USER_MODEL, db_index=True, on_delete=models.CASCADE)
role = models.CharField(choices=Roles, max_length=64)
content = models.TextField()
+
+
+class LearningAssistantAuditTrial(TimeStampedModel):
+ """
+ This model stores the trial period for an audit learner using the learning assistant.
+
+ A LearningAssistantAuditTrial instance will be created on a per user basis,
+ when an audit learner first sends a message using Xpert LA.
+
+ .. no_pii: This model has no PII.
+ """
+
+ # Unique constraint since each user should only have one trial
+ user = models.ForeignKey(USER_MODEL, db_index=True, on_delete=models.CASCADE, unique=True)
+ start_date = models.DateTimeField()
From 21fbd68880b38fe562d13631aa413749baea3b0d Mon Sep 17 00:00:00 2001
From: Bilal Qamar <59555732+BilalQamar95@users.noreply.github.com>
Date: Fri, 15 Nov 2024 20:26:35 +0500
Subject: [PATCH 097/104] feat: updated to python 3.12 (#118)
* feat: updated to python 3.12
* chore: resolved pylint errors
* chore: suppressed setup import-error
* chore: updated pylintrc_tweaks & pylintrc disabled rules
---
.github/workflows/ci.yml | 4 +-
.github/workflows/pypi-publish.yml | 2 +-
CHANGELOG.rst | 4 ++
learning_assistant/plugins.py | 1 -
pylintrc | 13 ++++--
pylintrc_tweaks | 2 +
requirements/base.txt | 33 ++++++-------
requirements/ci.txt | 14 +++---
requirements/dev.txt | 75 +++++++++++++++---------------
requirements/doc.txt | 70 +++++++++++-----------------
requirements/pip-tools.txt | 6 +--
requirements/pip.txt | 4 +-
requirements/quality.txt | 49 +++++++++----------
requirements/test.txt | 39 +++++++---------
setup.py | 2 +-
tox.ini | 2 +-
16 files changed, 149 insertions(+), 171 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4b4ceb1..3ce2313 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-20.04]
- python-version: ['3.11']
+ python-version: ['3.12']
toxenv: [quality, pii_check, django42]
steps:
@@ -37,7 +37,7 @@ jobs:
run: tox
- name: Run coverage
- if: matrix.python-version == '3.11' && matrix.toxenv == 'django42'
+ if: matrix.python-version == '3.12' && matrix.toxenv == 'django42'
uses: py-cov-action/python-coverage-comment-action@v3
with:
GITHUB_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml
index a22e9e6..d7898fd 100644
--- a/.github/workflows/pypi-publish.yml
+++ b/.github/workflows/pypi-publish.yml
@@ -15,7 +15,7 @@ jobs:
- name: setup python
uses: actions/setup-python@v2
with:
- python-version: 3.8
+ python-version: 3.12
- name: Install pip
run: pip install -r requirements/pip.txt
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 19aa431..3cf14ca 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -40,6 +40,10 @@ Unreleased
* Add LearningAssistantMessage model
* Add new GET endpoint to retrieve a user's message history in a given course.
+4.4.0 - 2024-10-25
+******************
+* Upgraded to use ``Python 3.12``
+
4.3.3 - 2024-10-15
******************
* Use `LEARNING_ASSISTANT_PROMPT_TEMPLATE` for prompt
diff --git a/learning_assistant/plugins.py b/learning_assistant/plugins.py
index 87a4e6b..f83a1a3 100644
--- a/learning_assistant/plugins.py
+++ b/learning_assistant/plugins.py
@@ -1,7 +1,6 @@
"""
Plugins for the Learning Assistant application.
"""
-# pylint: disable=import-error
from openedx.core.djangoapps.course_apps.plugins import CourseApp
from learning_assistant import plugins_api
diff --git a/pylintrc b/pylintrc
index 8d94efc..a6edcf3 100644
--- a/pylintrc
+++ b/pylintrc
@@ -2,7 +2,7 @@
# ** DO NOT EDIT THIS FILE **
# ***************************
#
-# This file was generated by edx-lint: https://github.com/edx/edx-lint
+# This file was generated by edx-lint: https://github.com/openedx/edx-lint
#
# If you want to change this file, you have two choices, depending on whether
# you want to make a local change that applies only to this repo, or whether
@@ -28,7 +28,7 @@
# CENTRAL CHANGE:
#
# 1. Edit the pylintrc file in the edx-lint repo at
-# https://github.com/edx/edx-lint/blob/master/edx_lint/files/pylintrc
+# https://github.com/openedx/edx-lint/blob/master/edx_lint/files/pylintrc
#
# 2. install the updated version of edx-lint (in edx-lint):
#
@@ -64,7 +64,7 @@
# SERIOUSLY.
#
# ------------------------------
-# Generated by edx-lint version: 5.2.4
+# Generated by edx-lint version: 5.4.0
# ------------------------------
[MASTER]
ignore = migrations
@@ -259,6 +259,7 @@ enable =
useless-suppression,
disable =
bad-indentation,
+ broad-exception-raised,
consider-using-f-string,
duplicate-code,
file-ignored,
@@ -290,6 +291,8 @@ disable =
django-not-configured,
consider-using-with,
bad-option-value,
+ import-error,
+ too-many-positional-arguments,
[REPORTS]
output-format = text
@@ -384,6 +387,6 @@ ext-import-graph =
int-import-graph =
[EXCEPTIONS]
-overgeneral-exceptions = Exception
+overgeneral-exceptions = builtins.Exception
-# d103a3939f8137bacf02447b6f210552aa7f2827
+# f8335666f17965c1c4bba3fc53bf25a343eaf3bf
diff --git a/pylintrc_tweaks b/pylintrc_tweaks
index 7b6eb35..9076787 100644
--- a/pylintrc_tweaks
+++ b/pylintrc_tweaks
@@ -9,3 +9,5 @@ disable+=
django-not-configured,
consider-using-with,
bad-option-value,
+ import-error,
+ too-many-positional-arguments,
diff --git a/requirements/base.txt b/requirements/base.txt
index fa877e3..e0b5b5b 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.11
+# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# make upgrade
@@ -10,15 +10,15 @@ attrs==24.2.0
# via -r requirements/base.in
certifi==2024.8.30
# via requests
-cffi==1.17.0
+cffi==1.17.1
# via
# cryptography
# pynacl
-charset-normalizer==3.3.2
+charset-normalizer==3.4.0
# via requests
click==8.1.7
# via edx-django-utils
-cryptography==43.0.0
+cryptography==43.0.3
# via pyjwt
django==4.2.16
# via
@@ -33,7 +33,7 @@ django==4.2.16
# edx-drf-extensions
django-crum==0.7.9
# via edx-django-utils
-django-model-utils==4.5.1
+django-model-utils==5.0.0
# via -r requirements/base.in
django-waffle==4.1.0
# via
@@ -44,33 +44,33 @@ djangorestframework==3.15.2
# -r requirements/base.in
# drf-jwt
# edx-drf-extensions
-dnspython==2.6.1
+dnspython==2.7.0
# via pymongo
drf-jwt==1.19.2
# via edx-drf-extensions
-edx-django-utils==5.15.0
+edx-django-utils==7.0.0
# via
# edx-drf-extensions
# edx-rest-api-client
edx-drf-extensions==10.4.0
# via -r requirements/base.in
-edx-opaque-keys==2.10.0
+edx-opaque-keys==2.11.0
# via
# -r requirements/base.in
# edx-drf-extensions
-edx-rest-api-client==5.7.1
+edx-rest-api-client==6.0.0
# via -r requirements/base.in
-idna==3.8
+idna==3.10
# via requests
jinja2==3.1.4
# via -r requirements/base.in
-markupsafe==2.1.5
+markupsafe==3.0.2
# via jinja2
-newrelic==9.13.0
+newrelic==10.2.0
# via edx-django-utils
pbr==6.1.0
# via stevedore
-psutil==6.0.0
+psutil==6.1.0
# via edx-django-utils
pycparser==2.22
# via cffi
@@ -79,7 +79,7 @@ pyjwt[crypto]==2.9.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==4.8.0
+pymongo==4.10.1
# via edx-opaque-keys
pynacl==1.5.0
# via edx-django-utils
@@ -87,11 +87,8 @@ requests==2.32.3
# via
# edx-drf-extensions
# edx-rest-api-client
- # slumber
semantic-version==2.10.0
# via edx-drf-extensions
-slumber==0.7.1
- # via edx-rest-api-client
sqlparse==0.5.1
# via django
stevedore==5.3.0
@@ -100,5 +97,5 @@ stevedore==5.3.0
# edx-opaque-keys
typing-extensions==4.12.2
# via edx-opaque-keys
-urllib3==2.2.2
+urllib3==2.2.3
# via requests
diff --git a/requirements/ci.txt b/requirements/ci.txt
index e39ed77..a07c43c 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.11
+# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# make upgrade
@@ -10,9 +10,9 @@ chardet==5.2.0
# via tox
colorama==0.4.6
# via tox
-distlib==0.3.8
+distlib==0.3.9
# via virtualenv
-filelock==3.15.4
+filelock==3.16.1
# via
# tox
# virtualenv
@@ -20,15 +20,15 @@ packaging==24.1
# via
# pyproject-api
# tox
-platformdirs==4.2.2
+platformdirs==4.3.6
# via
# tox
# virtualenv
pluggy==1.5.0
# via tox
-pyproject-api==1.7.1
+pyproject-api==1.8.0
# via tox
-tox==4.18.0
+tox==4.23.2
# via -r requirements/ci.in
-virtualenv==20.26.3
+virtualenv==20.27.0
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 3b85702..e100b40 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.11
+# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# make upgrade
@@ -8,14 +8,14 @@ asgiref==3.8.1
# via
# -r requirements/quality.txt
# django
-astroid==3.2.4
+astroid==3.3.5
# via
# -r requirements/quality.txt
# pylint
# pylint-celery
attrs==24.2.0
# via -r requirements/quality.txt
-build==1.2.1
+build==1.2.2.post1
# via
# -r requirements/pip-tools.txt
# pip-tools
@@ -27,7 +27,7 @@ certifi==2024.8.30
# via
# -r requirements/quality.txt
# requests
-cffi==1.17.0
+cffi==1.17.1
# via
# -r requirements/quality.txt
# cryptography
@@ -37,7 +37,7 @@ chardet==5.2.0
# -r requirements/ci.txt
# diff-cover
# tox
-charset-normalizer==3.3.2
+charset-normalizer==3.4.0
# via
# -r requirements/quality.txt
# requests
@@ -62,23 +62,23 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.6.1
+coverage[toml]==7.6.4
# via
# -r requirements/quality.txt
# pytest-cov
-cryptography==43.0.0
+cryptography==43.0.3
# via
# -r requirements/quality.txt
# pyjwt
ddt==1.7.2
# via -r requirements/quality.txt
-diff-cover==9.1.1
+diff-cover==9.2.0
# via -r requirements/dev.in
-dill==0.3.8
+dill==0.3.9
# via
# -r requirements/quality.txt
# pylint
-distlib==0.3.8
+distlib==0.3.9
# via
# -r requirements/ci.txt
# virtualenv
@@ -98,7 +98,7 @@ django-crum==0.7.9
# via
# -r requirements/quality.txt
# edx-django-utils
-django-model-utils==4.5.1
+django-model-utils==5.0.0
# via -r requirements/quality.txt
django-waffle==4.1.0
# via
@@ -110,7 +110,7 @@ djangorestframework==3.15.2
# -r requirements/quality.txt
# drf-jwt
# edx-drf-extensions
-dnspython==2.6.1
+dnspython==2.7.0
# via
# -r requirements/quality.txt
# pymongo
@@ -118,29 +118,29 @@ drf-jwt==1.19.2
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-django-utils==5.15.0
+edx-django-utils==7.0.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
# edx-rest-api-client
edx-drf-extensions==10.4.0
# via -r requirements/quality.txt
-edx-i18n-tools==1.6.2
+edx-i18n-tools==1.6.3
# via -r requirements/dev.in
edx-lint==5.4.0
# via -r requirements/quality.txt
-edx-opaque-keys==2.10.0
+edx-opaque-keys==2.11.0
# via
# -r requirements/quality.txt
# edx-drf-extensions
-edx-rest-api-client==5.7.1
+edx-rest-api-client==6.0.0
# via -r requirements/quality.txt
-filelock==3.15.4
+filelock==3.16.1
# via
# -r requirements/ci.txt
# tox
# virtualenv
-idna==3.8
+idna==3.10
# via
# -r requirements/quality.txt
# requests
@@ -157,9 +157,13 @@ jinja2==3.1.4
# -r requirements/quality.txt
# code-annotations
# diff-cover
-lxml==5.3.0
- # via edx-i18n-tools
-markupsafe==2.1.5
+lxml[html-clean,html_clean]==5.3.0
+ # via
+ # edx-i18n-tools
+ # lxml-html-clean
+lxml-html-clean==0.3.1
+ # via lxml
+markupsafe==3.0.2
# via
# -r requirements/quality.txt
# jinja2
@@ -167,7 +171,7 @@ mccabe==0.7.0
# via
# -r requirements/quality.txt
# pylint
-newrelic==9.13.0
+newrelic==10.2.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -188,7 +192,7 @@ pbr==6.1.0
# stevedore
pip-tools==7.4.1
# via -r requirements/pip-tools.txt
-platformdirs==4.2.2
+platformdirs==4.3.6
# via
# -r requirements/ci.txt
# -r requirements/quality.txt
@@ -204,7 +208,7 @@ pluggy==1.5.0
# tox
polib==1.2.0
# via edx-i18n-tools
-psutil==6.0.0
+psutil==6.1.0
# via
# -r requirements/quality.txt
# edx-django-utils
@@ -224,7 +228,7 @@ pyjwt[crypto]==2.9.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.7
+pylint==3.3.1
# via
# -r requirements/quality.txt
# edx-lint
@@ -235,7 +239,7 @@ pylint-celery==0.3
# via
# -r requirements/quality.txt
# edx-lint
-pylint-django==2.5.5
+pylint-django==2.6.1
# via
# -r requirements/quality.txt
# edx-lint
@@ -244,7 +248,7 @@ pylint-plugin-utils==0.8.2
# -r requirements/quality.txt
# pylint-celery
# pylint-django
-pymongo==4.8.0
+pymongo==4.10.1
# via
# -r requirements/quality.txt
# edx-opaque-keys
@@ -252,16 +256,16 @@ pynacl==1.5.0
# via
# -r requirements/quality.txt
# edx-django-utils
-pyproject-api==1.7.1
+pyproject-api==1.8.0
# via
# -r requirements/ci.txt
# tox
-pyproject-hooks==1.1.0
+pyproject-hooks==1.2.0
# via
# -r requirements/pip-tools.txt
# build
# pip-tools
-pytest==8.3.2
+pytest==8.3.3
# via
# -r requirements/quality.txt
# pytest-cov
@@ -286,7 +290,6 @@ requests==2.32.3
# edx-drf-extensions
# edx-rest-api-client
# responses
- # slumber
responses==0.25.3
# via -r requirements/quality.txt
semantic-version==2.10.0
@@ -297,10 +300,6 @@ six==1.16.0
# via
# -r requirements/quality.txt
# edx-lint
-slumber==0.7.1
- # via
- # -r requirements/quality.txt
- # edx-rest-api-client
snowballstemmer==2.2.0
# via
# -r requirements/quality.txt
@@ -323,18 +322,18 @@ tomlkit==0.13.2
# via
# -r requirements/quality.txt
# pylint
-tox==4.18.0
+tox==4.23.2
# via -r requirements/ci.txt
typing-extensions==4.12.2
# via
# -r requirements/quality.txt
# edx-opaque-keys
-urllib3==2.2.2
+urllib3==2.2.3
# via
# -r requirements/quality.txt
# requests
# responses
-virtualenv==20.26.3
+virtualenv==20.27.0
# via
# -r requirements/ci.txt
# tox
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 5ef1c7d..7611091 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.11
+# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# make upgrade
@@ -14,20 +14,18 @@ attrs==24.2.0
# via -r requirements/test.txt
babel==2.16.0
# via sphinx
-backports-tarfile==1.2.0
- # via jaraco-context
-build==1.2.1
+build==1.2.2.post1
# via -r requirements/doc.in
certifi==2024.8.30
# via
# -r requirements/test.txt
# requests
-cffi==1.17.0
+cffi==1.17.1
# via
# -r requirements/test.txt
# cryptography
# pynacl
-charset-normalizer==3.3.2
+charset-normalizer==3.4.0
# via
# -r requirements/test.txt
# requests
@@ -38,15 +36,14 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.txt
-coverage[toml]==7.6.1
+coverage[toml]==7.6.4
# via
# -r requirements/test.txt
# pytest-cov
-cryptography==43.0.0
+cryptography==43.0.3
# via
# -r requirements/test.txt
# pyjwt
- # secretstorage
ddt==1.7.2
# via -r requirements/test.txt
django==4.2.16
@@ -64,7 +61,7 @@ django-crum==0.7.9
# via
# -r requirements/test.txt
# edx-django-utils
-django-model-utils==4.5.1
+django-model-utils==5.0.0
# via -r requirements/test.txt
django-waffle==4.1.0
# via
@@ -76,7 +73,7 @@ djangorestframework==3.15.2
# -r requirements/test.txt
# drf-jwt
# edx-drf-extensions
-dnspython==2.6.1
+dnspython==2.7.0
# via
# -r requirements/test.txt
# pymongo
@@ -92,29 +89,27 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.15.0
+edx-django-utils==7.0.0
# via
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
edx-drf-extensions==10.4.0
# via -r requirements/test.txt
-edx-opaque-keys==2.10.0
+edx-opaque-keys==2.11.0
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-rest-api-client==5.7.1
+edx-rest-api-client==6.0.0
# via -r requirements/test.txt
-idna==3.8
+idna==3.10
# via
# -r requirements/test.txt
# requests
imagesize==1.4.1
# via sphinx
-importlib-metadata==8.4.0
- # via
- # keyring
- # twine
+importlib-metadata==8.5.0
+ # via twine
iniconfig==2.0.0
# via
# -r requirements/test.txt
@@ -123,32 +118,28 @@ jaraco-classes==3.4.0
# via keyring
jaraco-context==6.0.1
# via keyring
-jaraco-functools==4.0.2
+jaraco-functools==4.1.0
# via keyring
-jeepney==0.8.0
- # via
- # keyring
- # secretstorage
jinja2==3.1.4
# via
# -r requirements/test.txt
# code-annotations
# sphinx
-keyring==25.3.0
+keyring==25.4.1
# via twine
markdown-it-py==3.0.0
# via rich
-markupsafe==2.1.5
+markupsafe==3.0.2
# via
# -r requirements/test.txt
# jinja2
mdurl==0.1.2
# via markdown-it-py
-more-itertools==10.4.0
+more-itertools==10.5.0
# via
# jaraco-classes
# jaraco-functools
-newrelic==9.13.0
+newrelic==10.2.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -170,7 +161,7 @@ pluggy==1.5.0
# via
# -r requirements/test.txt
# pytest
-psutil==6.0.0
+psutil==6.1.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -190,7 +181,7 @@ pyjwt[crypto]==2.9.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==4.8.0
+pymongo==4.10.1
# via
# -r requirements/test.txt
# edx-opaque-keys
@@ -198,9 +189,9 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pyproject-hooks==1.1.0
+pyproject-hooks==1.2.0
# via build
-pytest==8.3.2
+pytest==8.3.3
# via
# -r requirements/test.txt
# pytest-cov
@@ -227,7 +218,6 @@ requests==2.32.3
# edx-rest-api-client
# requests-toolbelt
# responses
- # slumber
# sphinx
# twine
requests-toolbelt==1.0.0
@@ -238,21 +228,15 @@ restructuredtext-lint==1.4.0
# via doc8
rfc3986==2.0.0
# via twine
-rich==13.8.0
+rich==13.9.3
# via twine
-secretstorage==3.3.3
- # via keyring
semantic-version==2.10.0
# via
# -r requirements/test.txt
# edx-drf-extensions
-slumber==0.7.1
- # via
- # -r requirements/test.txt
- # edx-rest-api-client
snowballstemmer==2.2.0
# via sphinx
-sphinx==8.0.2
+sphinx==8.1.3
# via -r requirements/doc.in
sphinxcontrib-applehelp==2.0.0
# via sphinx
@@ -287,11 +271,11 @@ typing-extensions==4.12.2
# via
# -r requirements/test.txt
# edx-opaque-keys
-urllib3==2.2.2
+urllib3==2.2.3
# via
# -r requirements/test.txt
# requests
# responses
# twine
-zipp==3.20.1
+zipp==3.20.2
# via importlib-metadata
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index fedf88d..cf4131e 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -1,10 +1,10 @@
#
-# This file is autogenerated by pip-compile with Python 3.11
+# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# make upgrade
#
-build==1.2.1
+build==1.2.2.post1
# via pip-tools
click==8.1.7
# via pip-tools
@@ -12,7 +12,7 @@ packaging==24.1
# via build
pip-tools==7.4.1
# via -r requirements/pip-tools.in
-pyproject-hooks==1.1.0
+pyproject-hooks==1.2.0
# via
# build
# pip-tools
diff --git a/requirements/pip.txt b/requirements/pip.txt
index e7f75c5..3565563 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.11
+# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# make upgrade
@@ -10,5 +10,5 @@ wheel==0.44.0
# The following packages are considered to be unsafe in a requirements file:
pip==24.2
# via -r requirements/pip.in
-setuptools==74.1.1
+setuptools==75.2.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index 67ff2a9..ad0904d 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.11
+# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# make upgrade
@@ -8,7 +8,7 @@ asgiref==3.8.1
# via
# -r requirements/test.txt
# django
-astroid==3.2.4
+astroid==3.3.5
# via
# pylint
# pylint-celery
@@ -18,12 +18,12 @@ certifi==2024.8.30
# via
# -r requirements/test.txt
# requests
-cffi==1.17.0
+cffi==1.17.1
# via
# -r requirements/test.txt
# cryptography
# pynacl
-charset-normalizer==3.3.2
+charset-normalizer==3.4.0
# via
# -r requirements/test.txt
# requests
@@ -40,17 +40,17 @@ code-annotations==1.8.0
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.6.1
+coverage[toml]==7.6.4
# via
# -r requirements/test.txt
# pytest-cov
-cryptography==43.0.0
+cryptography==43.0.3
# via
# -r requirements/test.txt
# pyjwt
ddt==1.7.2
# via -r requirements/test.txt
-dill==0.3.8
+dill==0.3.9
# via pylint
django==4.2.16
# via
@@ -67,7 +67,7 @@ django-crum==0.7.9
# via
# -r requirements/test.txt
# edx-django-utils
-django-model-utils==4.5.1
+django-model-utils==5.0.0
# via -r requirements/test.txt
django-waffle==4.1.0
# via
@@ -79,7 +79,7 @@ djangorestframework==3.15.2
# -r requirements/test.txt
# drf-jwt
# edx-drf-extensions
-dnspython==2.6.1
+dnspython==2.7.0
# via
# -r requirements/test.txt
# pymongo
@@ -87,7 +87,7 @@ drf-jwt==1.19.2
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-django-utils==5.15.0
+edx-django-utils==7.0.0
# via
# -r requirements/test.txt
# edx-drf-extensions
@@ -96,13 +96,13 @@ edx-drf-extensions==10.4.0
# via -r requirements/test.txt
edx-lint==5.4.0
# via -r requirements/quality.in
-edx-opaque-keys==2.10.0
+edx-opaque-keys==2.11.0
# via
# -r requirements/test.txt
# edx-drf-extensions
-edx-rest-api-client==5.7.1
+edx-rest-api-client==6.0.0
# via -r requirements/test.txt
-idna==3.8
+idna==3.10
# via
# -r requirements/test.txt
# requests
@@ -118,13 +118,13 @@ jinja2==3.1.4
# via
# -r requirements/test.txt
# code-annotations
-markupsafe==2.1.5
+markupsafe==3.0.2
# via
# -r requirements/test.txt
# jinja2
mccabe==0.7.0
# via pylint
-newrelic==9.13.0
+newrelic==10.2.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -136,13 +136,13 @@ pbr==6.1.0
# via
# -r requirements/test.txt
# stevedore
-platformdirs==4.2.2
+platformdirs==4.3.6
# via pylint
pluggy==1.5.0
# via
# -r requirements/test.txt
# pytest
-psutil==6.0.0
+psutil==6.1.0
# via
# -r requirements/test.txt
# edx-django-utils
@@ -160,7 +160,7 @@ pyjwt[crypto]==2.9.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pylint==3.2.7
+pylint==3.3.1
# via
# edx-lint
# pylint-celery
@@ -168,13 +168,13 @@ pylint==3.2.7
# pylint-plugin-utils
pylint-celery==0.3
# via edx-lint
-pylint-django==2.5.5
+pylint-django==2.6.1
# via edx-lint
pylint-plugin-utils==0.8.2
# via
# pylint-celery
# pylint-django
-pymongo==4.8.0
+pymongo==4.10.1
# via
# -r requirements/test.txt
# edx-opaque-keys
@@ -182,7 +182,7 @@ pynacl==1.5.0
# via
# -r requirements/test.txt
# edx-django-utils
-pytest==8.3.2
+pytest==8.3.3
# via
# -r requirements/test.txt
# pytest-cov
@@ -206,7 +206,6 @@ requests==2.32.3
# edx-drf-extensions
# edx-rest-api-client
# responses
- # slumber
responses==0.25.3
# via -r requirements/test.txt
semantic-version==2.10.0
@@ -215,10 +214,6 @@ semantic-version==2.10.0
# edx-drf-extensions
six==1.16.0
# via edx-lint
-slumber==0.7.1
- # via
- # -r requirements/test.txt
- # edx-rest-api-client
snowballstemmer==2.2.0
# via pydocstyle
sqlparse==0.5.1
@@ -241,7 +236,7 @@ typing-extensions==4.12.2
# via
# -r requirements/test.txt
# edx-opaque-keys
-urllib3==2.2.2
+urllib3==2.2.3
# via
# -r requirements/test.txt
# requests
diff --git a/requirements/test.txt b/requirements/test.txt
index fd7013b..1cbb05e 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.11
+# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# make upgrade
@@ -14,12 +14,12 @@ certifi==2024.8.30
# via
# -r requirements/base.txt
# requests
-cffi==1.17.0
+cffi==1.17.1
# via
# -r requirements/base.txt
# cryptography
# pynacl
-charset-normalizer==3.3.2
+charset-normalizer==3.4.0
# via
# -r requirements/base.txt
# requests
@@ -30,9 +30,9 @@ click==8.1.7
# edx-django-utils
code-annotations==1.8.0
# via -r requirements/test.in
-coverage[toml]==7.6.1
+coverage[toml]==7.6.4
# via pytest-cov
-cryptography==43.0.0
+cryptography==43.0.3
# via
# -r requirements/base.txt
# pyjwt
@@ -52,7 +52,7 @@ django-crum==0.7.9
# via
# -r requirements/base.txt
# edx-django-utils
-django-model-utils==4.5.1
+django-model-utils==5.0.0
# via -r requirements/base.txt
django-waffle==4.1.0
# via
@@ -64,7 +64,7 @@ djangorestframework==3.15.2
# -r requirements/base.txt
# drf-jwt
# edx-drf-extensions
-dnspython==2.6.1
+dnspython==2.7.0
# via
# -r requirements/base.txt
# pymongo
@@ -72,20 +72,20 @@ drf-jwt==1.19.2
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-django-utils==5.15.0
+edx-django-utils==7.0.0
# via
# -r requirements/base.txt
# edx-drf-extensions
# edx-rest-api-client
edx-drf-extensions==10.4.0
# via -r requirements/base.txt
-edx-opaque-keys==2.10.0
+edx-opaque-keys==2.11.0
# via
# -r requirements/base.txt
# edx-drf-extensions
-edx-rest-api-client==5.7.1
+edx-rest-api-client==6.0.0
# via -r requirements/base.txt
-idna==3.8
+idna==3.10
# via
# -r requirements/base.txt
# requests
@@ -95,11 +95,11 @@ jinja2==3.1.4
# via
# -r requirements/base.txt
# code-annotations
-markupsafe==2.1.5
+markupsafe==3.0.2
# via
# -r requirements/base.txt
# jinja2
-newrelic==9.13.0
+newrelic==10.2.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -111,7 +111,7 @@ pbr==6.1.0
# stevedore
pluggy==1.5.0
# via pytest
-psutil==6.0.0
+psutil==6.1.0
# via
# -r requirements/base.txt
# edx-django-utils
@@ -125,7 +125,7 @@ pyjwt[crypto]==2.9.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
-pymongo==4.8.0
+pymongo==4.10.1
# via
# -r requirements/base.txt
# edx-opaque-keys
@@ -133,7 +133,7 @@ pynacl==1.5.0
# via
# -r requirements/base.txt
# edx-django-utils
-pytest==8.3.2
+pytest==8.3.3
# via
# pytest-cov
# pytest-django
@@ -153,17 +153,12 @@ requests==2.32.3
# edx-drf-extensions
# edx-rest-api-client
# responses
- # slumber
responses==0.25.3
# via -r requirements/test.in
semantic-version==2.10.0
# via
# -r requirements/base.txt
# edx-drf-extensions
-slumber==0.7.1
- # via
- # -r requirements/base.txt
- # edx-rest-api-client
sqlparse==0.5.1
# via
# -r requirements/base.txt
@@ -180,7 +175,7 @@ typing-extensions==4.12.2
# via
# -r requirements/base.txt
# edx-opaque-keys
-urllib3==2.2.2
+urllib3==2.2.3
# via
# -r requirements/base.txt
# requests
diff --git a/setup.py b/setup.py
index 817fad1..f67ace4 100755
--- a/setup.py
+++ b/setup.py
@@ -126,7 +126,7 @@ def is_requirement(line):
'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
'Natural Language :: English',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.12',
],
entry_points={
'lms.djangoapp': [
diff --git a/tox.ini b/tox.ini
index 17596e6..1bf5e95 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py311-django{42}
+envlist = py312-django{42}
[doc8]
; D001 = Line too long
From 20045dd59380f27d8beb869a3cf65010b3effc3f Mon Sep 17 00:00:00 2001
From: edX requirements bot
<49161187+edx-requirements-bot@users.noreply.github.com>
Date: Fri, 15 Nov 2024 11:19:35 -0500
Subject: [PATCH 098/104] chore: Upgrade Python requirements (#133)
---
requirements/base.txt | 6 +++---
requirements/ci.txt | 6 +++---
requirements/dev.txt | 22 +++++++++++-----------
requirements/doc.txt | 33 ++++++++++++++++++++++-----------
requirements/pip-tools.txt | 6 +++---
requirements/pip.txt | 10 ++++++----
requirements/quality.txt | 16 ++++++++--------
requirements/test.txt | 14 +++++++-------
8 files changed, 63 insertions(+), 50 deletions(-)
diff --git a/requirements/base.txt b/requirements/base.txt
index e0b5b5b..1fb82ad 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.12
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -52,7 +52,7 @@ edx-django-utils==7.0.0
# via
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.4.0
+edx-drf-extensions==10.5.0
# via -r requirements/base.in
edx-opaque-keys==2.11.0
# via
@@ -89,7 +89,7 @@ requests==2.32.3
# edx-rest-api-client
semantic-version==2.10.0
# via edx-drf-extensions
-sqlparse==0.5.1
+sqlparse==0.5.2
# via django
stevedore==5.3.0
# via
diff --git a/requirements/ci.txt b/requirements/ci.txt
index a07c43c..e128790 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.12
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -16,7 +16,7 @@ filelock==3.16.1
# via
# tox
# virtualenv
-packaging==24.1
+packaging==24.2
# via
# pyproject-api
# tox
@@ -30,5 +30,5 @@ pyproject-api==1.8.0
# via tox
tox==4.23.2
# via -r requirements/ci.in
-virtualenv==20.27.0
+virtualenv==20.27.1
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index e100b40..ca3d2d1 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.12
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -54,7 +54,7 @@ click-log==0.4.0
# via
# -r requirements/quality.txt
# edx-lint
-code-annotations==1.8.0
+code-annotations==1.8.1
# via
# -r requirements/quality.txt
# edx-lint
@@ -62,7 +62,7 @@ colorama==0.4.6
# via
# -r requirements/ci.txt
# tox
-coverage[toml]==7.6.4
+coverage[toml]==7.6.7
# via
# -r requirements/quality.txt
# pytest-cov
@@ -123,11 +123,11 @@ edx-django-utils==7.0.0
# -r requirements/quality.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.4.0
+edx-drf-extensions==10.5.0
# via -r requirements/quality.txt
edx-i18n-tools==1.6.3
# via -r requirements/dev.in
-edx-lint==5.4.0
+edx-lint==5.4.1
# via -r requirements/quality.txt
edx-opaque-keys==2.11.0
# via
@@ -161,7 +161,7 @@ lxml[html-clean,html_clean]==5.3.0
# via
# edx-i18n-tools
# lxml-html-clean
-lxml-html-clean==0.3.1
+lxml-html-clean==0.4.1
# via lxml
markupsafe==3.0.2
# via
@@ -175,7 +175,7 @@ newrelic==10.2.0
# via
# -r requirements/quality.txt
# edx-django-utils
-packaging==24.1
+packaging==24.2
# via
# -r requirements/ci.txt
# -r requirements/pip-tools.txt
@@ -270,7 +270,7 @@ pytest==8.3.3
# -r requirements/quality.txt
# pytest-cov
# pytest-django
-pytest-cov==5.0.0
+pytest-cov==6.0.0
# via -r requirements/quality.txt
pytest-django==4.9.0
# via -r requirements/quality.txt
@@ -304,7 +304,7 @@ snowballstemmer==2.2.0
# via
# -r requirements/quality.txt
# pydocstyle
-sqlparse==0.5.1
+sqlparse==0.5.2
# via
# -r requirements/quality.txt
# django
@@ -333,11 +333,11 @@ urllib3==2.2.3
# -r requirements/quality.txt
# requests
# responses
-virtualenv==20.27.0
+virtualenv==20.27.1
# via
# -r requirements/ci.txt
# tox
-wheel==0.44.0
+wheel==0.45.0
# via
# -r requirements/pip-tools.txt
# pip-tools
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 7611091..03e90e8 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.12
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -14,6 +14,8 @@ attrs==24.2.0
# via -r requirements/test.txt
babel==2.16.0
# via sphinx
+backports-tarfile==1.2.0
+ # via jaraco-context
build==1.2.2.post1
# via -r requirements/doc.in
certifi==2024.8.30
@@ -34,9 +36,9 @@ click==8.1.7
# -r requirements/test.txt
# code-annotations
# edx-django-utils
-code-annotations==1.8.0
+code-annotations==1.8.1
# via -r requirements/test.txt
-coverage[toml]==7.6.4
+coverage[toml]==7.6.7
# via
# -r requirements/test.txt
# pytest-cov
@@ -44,6 +46,7 @@ cryptography==43.0.3
# via
# -r requirements/test.txt
# pyjwt
+ # secretstorage
ddt==1.7.2
# via -r requirements/test.txt
django==4.2.16
@@ -94,7 +97,7 @@ edx-django-utils==7.0.0
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.4.0
+edx-drf-extensions==10.5.0
# via -r requirements/test.txt
edx-opaque-keys==2.11.0
# via
@@ -109,7 +112,9 @@ idna==3.10
imagesize==1.4.1
# via sphinx
importlib-metadata==8.5.0
- # via twine
+ # via
+ # keyring
+ # twine
iniconfig==2.0.0
# via
# -r requirements/test.txt
@@ -120,12 +125,16 @@ jaraco-context==6.0.1
# via keyring
jaraco-functools==4.1.0
# via keyring
+jeepney==0.8.0
+ # via
+ # keyring
+ # secretstorage
jinja2==3.1.4
# via
# -r requirements/test.txt
# code-annotations
# sphinx
-keyring==25.4.1
+keyring==25.5.0
# via twine
markdown-it-py==3.0.0
# via rich
@@ -145,7 +154,7 @@ newrelic==10.2.0
# edx-django-utils
nh3==0.2.18
# via readme-renderer
-packaging==24.1
+packaging==24.2
# via
# -r requirements/test.txt
# build
@@ -196,7 +205,7 @@ pytest==8.3.3
# -r requirements/test.txt
# pytest-cov
# pytest-django
-pytest-cov==5.0.0
+pytest-cov==6.0.0
# via -r requirements/test.txt
pytest-django==4.9.0
# via -r requirements/test.txt
@@ -228,8 +237,10 @@ restructuredtext-lint==1.4.0
# via doc8
rfc3986==2.0.0
# via twine
-rich==13.9.3
+rich==13.9.4
# via twine
+secretstorage==3.3.3
+ # via keyring
semantic-version==2.10.0
# via
# -r requirements/test.txt
@@ -250,7 +261,7 @@ sphinxcontrib-qthelp==2.0.0
# via sphinx
sphinxcontrib-serializinghtml==2.0.0
# via sphinx
-sqlparse==0.5.1
+sqlparse==0.5.2
# via
# -r requirements/test.txt
# django
@@ -277,5 +288,5 @@ urllib3==2.2.3
# requests
# responses
# twine
-zipp==3.20.2
+zipp==3.21.0
# via importlib-metadata
diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt
index cf4131e..dc539c5 100644
--- a/requirements/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.12
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -8,7 +8,7 @@ build==1.2.2.post1
# via pip-tools
click==8.1.7
# via pip-tools
-packaging==24.1
+packaging==24.2
# via build
pip-tools==7.4.1
# via -r requirements/pip-tools.in
@@ -16,7 +16,7 @@ pyproject-hooks==1.2.0
# via
# build
# pip-tools
-wheel==0.44.0
+wheel==0.45.0
# via pip-tools
# The following packages are considered to be unsafe in a requirements file:
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 3565563..e9be994 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -1,14 +1,16 @@
#
-# This file is autogenerated by pip-compile with Python 3.12
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
#
-wheel==0.44.0
+wheel==0.45.0
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
pip==24.2
- # via -r requirements/pip.in
-setuptools==75.2.0
+ # via
+ # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
+ # -r requirements/pip.in
+setuptools==75.5.0
# via -r requirements/pip.in
diff --git a/requirements/quality.txt b/requirements/quality.txt
index ad0904d..eea5bb9 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.12
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -36,11 +36,11 @@ click==8.1.7
# edx-lint
click-log==0.4.0
# via edx-lint
-code-annotations==1.8.0
+code-annotations==1.8.1
# via
# -r requirements/test.txt
# edx-lint
-coverage[toml]==7.6.4
+coverage[toml]==7.6.7
# via
# -r requirements/test.txt
# pytest-cov
@@ -92,9 +92,9 @@ edx-django-utils==7.0.0
# -r requirements/test.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.4.0
+edx-drf-extensions==10.5.0
# via -r requirements/test.txt
-edx-lint==5.4.0
+edx-lint==5.4.1
# via -r requirements/quality.in
edx-opaque-keys==2.11.0
# via
@@ -128,7 +128,7 @@ newrelic==10.2.0
# via
# -r requirements/test.txt
# edx-django-utils
-packaging==24.1
+packaging==24.2
# via
# -r requirements/test.txt
# pytest
@@ -187,7 +187,7 @@ pytest==8.3.3
# -r requirements/test.txt
# pytest-cov
# pytest-django
-pytest-cov==5.0.0
+pytest-cov==6.0.0
# via -r requirements/test.txt
pytest-django==4.9.0
# via -r requirements/test.txt
@@ -216,7 +216,7 @@ six==1.16.0
# via edx-lint
snowballstemmer==2.2.0
# via pydocstyle
-sqlparse==0.5.1
+sqlparse==0.5.2
# via
# -r requirements/test.txt
# django
diff --git a/requirements/test.txt b/requirements/test.txt
index 1cbb05e..35bcf93 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with Python 3.12
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# make upgrade
@@ -28,9 +28,9 @@ click==8.1.7
# -r requirements/base.txt
# code-annotations
# edx-django-utils
-code-annotations==1.8.0
+code-annotations==1.8.1
# via -r requirements/test.in
-coverage[toml]==7.6.4
+coverage[toml]==7.6.7
# via pytest-cov
cryptography==43.0.3
# via
@@ -77,7 +77,7 @@ edx-django-utils==7.0.0
# -r requirements/base.txt
# edx-drf-extensions
# edx-rest-api-client
-edx-drf-extensions==10.4.0
+edx-drf-extensions==10.5.0
# via -r requirements/base.txt
edx-opaque-keys==2.11.0
# via
@@ -103,7 +103,7 @@ newrelic==10.2.0
# via
# -r requirements/base.txt
# edx-django-utils
-packaging==24.1
+packaging==24.2
# via pytest
pbr==6.1.0
# via
@@ -137,7 +137,7 @@ pytest==8.3.3
# via
# pytest-cov
# pytest-django
-pytest-cov==5.0.0
+pytest-cov==6.0.0
# via -r requirements/test.in
pytest-django==4.9.0
# via -r requirements/test.in
@@ -159,7 +159,7 @@ semantic-version==2.10.0
# via
# -r requirements/base.txt
# edx-drf-extensions
-sqlparse==0.5.1
+sqlparse==0.5.2
# via
# -r requirements/base.txt
# django
From ada1330cc20dc888b1efa606ed0a26d2678c5492 Mon Sep 17 00:00:00 2001
From: Isaac Lee <124631592+ilee2u@users.noreply.github.com>
Date: Thu, 21 Nov 2024 13:30:41 -0500
Subject: [PATCH 099/104] feat: add audit gate to CourseChatView (#134)
* feat: add audit gate to CourseChatView
* test: add tests
* feat: add upgrade deadline gate
* fix: remove "created" test for trial expiration
* chore: nits
* fix: distinguish between course modes correctly
* chore: lint
* fix: correct test to cover unexpired audit trial
---
learning_assistant/api.py | 30 ++++++-
learning_assistant/constants.py | 2 +
learning_assistant/platform_imports.py | 16 ++--
learning_assistant/views.py | 105 ++++++++++++++++---------
tests/test_api.py | 44 ++++++++++-
tests/test_views.py | 93 ++++++++++++++--------
6 files changed, 208 insertions(+), 82 deletions(-)
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 55c73c1..3857945 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -2,6 +2,7 @@
Library for the learning_assistant app.
"""
import logging
+from datetime import datetime, timedelta
from django.conf import settings
from django.contrib.auth import get_user_model
@@ -10,9 +11,13 @@
from jinja2 import BaseLoader, Environment
from opaque_keys import InvalidKeyError
-from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, CATEGORY_TYPE_MAP
+from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, AUDIT_TRIAL_MAX_DAYS, CATEGORY_TYPE_MAP
from learning_assistant.data import LearningAssistantCourseEnabledData
-from learning_assistant.models import LearningAssistantCourseEnabled, LearningAssistantMessage
+from learning_assistant.models import (
+ LearningAssistantAuditTrial,
+ LearningAssistantCourseEnabled,
+ LearningAssistantMessage,
+)
from learning_assistant.platform_imports import (
block_get_children,
block_leaf_filter,
@@ -224,3 +229,24 @@ def get_message_history(courserun_key, user, message_count):
message_history = list(LearningAssistantMessage.objects.filter(
course_id=courserun_key, user=user).order_by('-created')[:message_count])[::-1]
return message_history
+
+
+def audit_trial_is_expired(user, upgrade_deadline):
+ """
+ Given a user (User), get or create the corresponding LearningAssistantAuditTrial trial object.
+ """
+ # If the upgrade deadline has passed, return "True" for expired
+ DAYS_SINCE_UPGRADE_DEADLINE = datetime.now() - upgrade_deadline
+ if DAYS_SINCE_UPGRADE_DEADLINE >= timedelta(days=0):
+ return True
+
+ audit_trial, _ = LearningAssistantAuditTrial.objects.get_or_create(
+ user=user,
+ defaults={
+ "start_date": datetime.now(),
+ },
+ )
+
+ # If the user's trial is past its expiry date, return "True" for expired. Else, return False
+ DAYS_SINCE_TRIAL_START_DATE = datetime.now() - audit_trial.start_date
+ return DAYS_SINCE_TRIAL_START_DATE >= timedelta(days=AUDIT_TRIAL_MAX_DAYS)
diff --git a/learning_assistant/constants.py b/learning_assistant/constants.py
index 7027a28..92e3717 100644
--- a/learning_assistant/constants.py
+++ b/learning_assistant/constants.py
@@ -14,3 +14,5 @@
"html": "TEXT",
"video": "VIDEO",
}
+
+AUDIT_TRIAL_MAX_DAYS = 14
diff --git a/learning_assistant/platform_imports.py b/learning_assistant/platform_imports.py
index 8c2411c..55f13d7 100644
--- a/learning_assistant/platform_imports.py
+++ b/learning_assistant/platform_imports.py
@@ -8,7 +8,7 @@
def get_text_transcript(video_block):
"""Get the transcript for a video block in text format, or None."""
- # pylint: disable=import-error, import-outside-toplevel
+ # pylint: disable=import-outside-toplevel
from xmodule.exceptions import NotFoundError
from xmodule.video_block.transcripts_utils import get_transcript
try:
@@ -21,28 +21,28 @@ def get_text_transcript(video_block):
def get_single_block(request, user_id, course_id, usage_key_string, course=None):
"""Load a single xblock."""
- # pylint: disable=import-error, import-outside-toplevel
+ # pylint: disable=import-outside-toplevel
from lms.djangoapps.courseware.block_render import load_single_xblock
return load_single_xblock(request, user_id, course_id, usage_key_string, course)
def traverse_block_pre_order(start_node, get_children, filter_func=None):
"""Traverse a DAG or tree in pre-order."""
- # pylint: disable=import-error, import-outside-toplevel
+ # pylint: disable=import-outside-toplevel
from openedx.core.lib.graph_traversals import traverse_pre_order
return traverse_pre_order(start_node, get_children, filter_func)
def block_leaf_filter(block):
"""Return only leaf nodes."""
- # pylint: disable=import-error, import-outside-toplevel
+ # pylint: disable=import-outside-toplevel
from openedx.core.lib.graph_traversals import leaf_filter
return leaf_filter(block)
def block_get_children(block):
"""Return children of a given block."""
- # pylint: disable=import-error, import-outside-toplevel
+ # pylint: disable=import-outside-toplevel
from openedx.core.lib.graph_traversals import get_children
return get_children(block)
@@ -54,7 +54,7 @@ def get_cache_course_run_data(course_run_id, fields):
This function makes use of the course run cache in the LMS, which caches data from the discovery service. This is
necessary because only the discovery service stores the relation between courseruns and courses.
"""
- # pylint: disable=import-error, import-outside-toplevel
+ # pylint: disable=import-outside-toplevel
from openedx.core.djangoapps.catalog.utils import get_course_run_data
return get_course_run_data(course_run_id, fields)
@@ -66,7 +66,7 @@ def get_cache_course_data(course_id, fields):
This function makes use of the course cache in the LMS, which caches data from the discovery service. This is
necessary because only the discovery service stores course skills data.
"""
- # pylint: disable=import-error, import-outside-toplevel
+ # pylint: disable=import-outside-toplevel
from openedx.core.djangoapps.catalog.utils import get_course_data
return get_course_data(course_id, fields)
@@ -82,6 +82,6 @@ def get_user_role(user, course_key):
Returns:
* str: the user's role
"""
- # pylint: disable=import-error, import-outside-toplevel
+ # pylint: disable=import-outside-toplevel
from lms.djangoapps.courseware.access import get_user_role as platform_get_user_role
return platform_get_user_role(user, course_key)
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index b1b5c03..b474984 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -21,6 +21,7 @@
pass
from learning_assistant.api import (
+ audit_trial_is_expired,
get_course_id,
get_message_history,
learning_assistant_enabled,
@@ -43,46 +44,10 @@ class CourseChatView(APIView):
authentication_classes = (SessionAuthentication, JwtAuthentication,)
permission_classes = (IsAuthenticated,)
- def post(self, request, course_run_id):
+ def _get_next_message(self, request, courserun_key, course_run_id):
"""
- Given a course run ID, retrieve a chat response for that course.
-
- Expected POST data: {
- [
- {'role': 'user', 'content': 'What is 2+2?'},
- {'role': 'assistant', 'content': '4'}
- ]
- }
+ Generate the next message to be returned by the learning assistant.
"""
- try:
- courserun_key = CourseKey.from_string(course_run_id)
- except InvalidKeyError:
- return Response(
- status=http_status.HTTP_400_BAD_REQUEST,
- data={'detail': 'Course ID is not a valid course ID.'}
- )
-
- if not learning_assistant_enabled(courserun_key):
- return Response(
- status=http_status.HTTP_403_FORBIDDEN,
- data={'detail': 'Learning assistant not enabled for course.'}
- )
-
- # If user does not have an enrollment record, or is not staff, they should not have access
- user_role = get_user_role(request.user, courserun_key)
- enrollment_object = CourseEnrollment.get_enrollment(request.user, courserun_key)
- enrollment_mode = enrollment_object.mode if enrollment_object else None
- if (
- (enrollment_mode not in CourseMode.VERIFIED_MODES)
- and not user_role_is_staff(user_role)
- ):
- return Response(
- status=http_status.HTTP_403_FORBIDDEN,
- data={'detail': 'Must be staff or have valid enrollment.'}
- )
-
- unit_id = request.query_params.get('unit_id')
-
message_list = request.data
# Check that the last message in the list corresponds to a user
@@ -117,8 +82,8 @@ def post(self, request, course_run_id):
)
course_id = get_course_id(course_run_id)
-
template_string = getattr(settings, 'LEARNING_ASSISTANT_PROMPT_TEMPLATE', '')
+ unit_id = request.query_params.get('unit_id')
prompt_template = render_prompt_template(
request, request.user.id, course_run_id, unit_id, course_id, template_string
@@ -130,6 +95,68 @@ def post(self, request, course_run_id):
return Response(status=status_code, data=message)
+ def post(self, request, course_run_id):
+ """
+ Given a course run ID, retrieve a chat response for that course.
+
+ Expected POST data: {
+ [
+ {'role': 'user', 'content': 'What is 2+2?'},
+ {'role': 'assistant', 'content': '4'}
+ ]
+ }
+ """
+ try:
+ courserun_key = CourseKey.from_string(course_run_id)
+ except InvalidKeyError:
+ return Response(
+ status=http_status.HTTP_400_BAD_REQUEST,
+ data={'detail': 'Course ID is not a valid course ID.'}
+ )
+
+ if not learning_assistant_enabled(courserun_key):
+ return Response(
+ status=http_status.HTTP_403_FORBIDDEN,
+ data={'detail': 'Learning assistant not enabled for course.'}
+ )
+
+ # If user does not have a verified enrollment record, or is not staff, they should not have full access
+ user_role = get_user_role(request.user, courserun_key)
+ enrollment_object = CourseEnrollment.get_enrollment(request.user, courserun_key)
+ enrollment_mode = enrollment_object.mode if enrollment_object else None
+
+ # If the user is in a verified course mode or is staff, return the next message
+ if (
+ # Here we include CREDIT_MODE and NO_ID_PROFESSIONAL_MODE, as CourseMode.VERIFIED_MODES on its own
+ # doesn't match what we count as "verified modes" in the frontend component.
+ enrollment_mode in CourseMode.VERIFIED_MODES + CourseMode.CREDIT_MODE + CourseMode.NO_ID_PROFESSIONAL_MODE
+ or user_role_is_staff(user_role)
+ ):
+ return self._get_next_message(request, courserun_key, course_run_id)
+
+ # If user has an audit enrollment record, get or create their trial. If the trial is not expired, return the
+ # next message. Otherwise, return 403
+ elif enrollment_mode in CourseMode.UPSELL_TO_VERIFIED_MODES: # AUDIT, HONOR
+ course_mode = CourseMode.objects.get(course=courserun_key)
+ upgrade_deadline = course_mode.expiration_datetime()
+
+ user_audit_trial_expired = audit_trial_is_expired(request.user, upgrade_deadline)
+ if user_audit_trial_expired:
+ return Response(
+ status=http_status.HTTP_403_FORBIDDEN,
+ data={'detail': 'The audit trial for this user has expired.'}
+ )
+ else:
+ return self._get_next_message(request, courserun_key, course_run_id)
+
+ # If user has a course mode that is not verified & not meant to access to the learning assistant, return 403
+ # This covers the other course modes: UNPAID_EXECUTIVE_EDUCATION & UNPAID_BOOTCAMP
+ else:
+ return Response(
+ status=http_status.HTTP_403_FORBIDDEN,
+ data={'detail': 'Must be staff or have valid enrollment.'}
+ )
+
class LearningAssistantEnabledView(APIView):
"""
diff --git a/tests/test_api.py b/tests/test_api.py
index 1344dea..d73af07 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -2,6 +2,7 @@
Test cases for the learning-assistant api module.
"""
import itertools
+from datetime import datetime, timedelta
from unittest.mock import MagicMock, patch
import ddt
@@ -16,6 +17,7 @@
_extract_block_contents,
_get_children_contents,
_leaf_filter,
+ audit_trial_is_expired,
get_block_content,
get_message_history,
learning_assistant_available,
@@ -24,8 +26,13 @@
save_chat_message,
set_learning_assistant_enabled,
)
+from learning_assistant.constants import AUDIT_TRIAL_MAX_DAYS
from learning_assistant.data import LearningAssistantCourseEnabledData
-from learning_assistant.models import LearningAssistantCourseEnabled, LearningAssistantMessage
+from learning_assistant.models import (
+ LearningAssistantAuditTrial,
+ LearningAssistantCourseEnabled,
+ LearningAssistantMessage,
+)
fake_transcript = 'This is the text version from the transcript'
User = get_user_model()
@@ -241,6 +248,7 @@ class TestLearningAssistantCourseEnabledApi(TestCase):
"""
Test suite for save_chat_message.
"""
+
def setUp(self):
super().setUp()
@@ -473,3 +481,37 @@ def test_get_message_course_id_differences(self):
self.assertEqual(return_value.user, expected_value[i].user)
self.assertEqual(return_value.role, expected_value[i].role)
self.assertEqual(return_value.content, expected_value[i].content)
+
+
+@ddt.ddt
+class CheckIfAuditTrialIsExpiredTests(TestCase):
+ """
+ Test suite for audit_trial_is_expired.
+ """
+
+ def setUp(self):
+ super().setUp()
+ self.course_key = CourseKey.from_string('course-v1:edx+fake+1')
+ self.user = User(username='tester', email='tester@test.com')
+ self.user.save()
+
+ self.role = 'verified'
+ self.upgrade_deadline = datetime.now() + timedelta(days=1) # 1 day from now
+
+ def test_check_if_past_upgrade_deadline(self):
+ upgrade_deadline = datetime.now() - timedelta(days=1) # yesterday
+ self.assertEqual(audit_trial_is_expired(self.user, upgrade_deadline), True)
+
+ def test_audit_trial_is_expired_audit_trial_expired(self):
+ LearningAssistantAuditTrial.objects.create(
+ user=self.user,
+ start_date=datetime.now() - timedelta(days=AUDIT_TRIAL_MAX_DAYS + 1), # 1 day more than trial deadline
+ )
+ self.assertEqual(audit_trial_is_expired(self.user, self.upgrade_deadline), True)
+
+ def test_audit_trial_is_expired_audit_trial_unexpired(self):
+ LearningAssistantAuditTrial.objects.create(
+ user=self.user,
+ start_date=datetime.now() - timedelta(days=AUDIT_TRIAL_MAX_DAYS - 0.99), # 0.99 days less than deadline
+ )
+ self.assertEqual(audit_trial_is_expired(self.user, self.upgrade_deadline), False)
diff --git a/tests/test_views.py b/tests/test_views.py
index 1d567b8..98c0ab6 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -1,9 +1,9 @@
"""
Tests for the learning assistant views.
"""
-import datetime
import json
import sys
+from datetime import date, datetime, timedelta
from importlib import import_module
from unittest.mock import MagicMock, call, patch
@@ -75,7 +75,7 @@ def setUp(self):
@ddt.ddt
-class TestCourseChatView(LoggedInTestCase):
+class CourseChatViewTests(LoggedInTestCase):
"""
Test for the CourseChatView
"""
@@ -108,32 +108,6 @@ def test_course_waffle_inactive(self, mock_waffle):
response = self.client.post(reverse('chat', kwargs={'course_run_id': self.course_id}))
self.assertEqual(response.status_code, 403)
- @patch('learning_assistant.views.learning_assistant_enabled')
- @patch('learning_assistant.views.get_user_role')
- @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
- @patch('learning_assistant.views.CourseMode')
- def test_user_no_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_role, mock_waffle):
- mock_waffle.return_value = True
- mock_role.return_value = 'student'
- mock_mode.VERIFIED_MODES = ['verified']
- mock_enrollment.return_value = None
-
- response = self.client.post(reverse('chat', kwargs={'course_run_id': self.course_id}))
- self.assertEqual(response.status_code, 403)
-
- @patch('learning_assistant.views.learning_assistant_enabled')
- @patch('learning_assistant.views.get_user_role')
- @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
- @patch('learning_assistant.views.CourseMode')
- def test_user_audit_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_role, mock_waffle):
- mock_waffle.return_value = True
- mock_role.return_value = 'student'
- mock_mode.VERIFIED_MODES = ['verified']
- mock_enrollment.return_value = MagicMock(mode='audit')
-
- response = self.client.post(reverse('chat', kwargs={'course_run_id': self.course_id}))
- self.assertEqual(response.status_code, 403)
-
@patch('learning_assistant.views.render_prompt_template')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
@@ -156,7 +130,56 @@ def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
)
self.assertEqual(response.status_code, 400)
- @ddt.data(False, True)
+ @patch('learning_assistant.views.audit_trial_is_expired')
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ @patch('learning_assistant.views.get_user_role')
+ @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseMode')
+ def test_audit_trial_expired(self, mock_mode, mock_enrollment,
+ mock_role, mock_waffle, mock_trial_expired):
+ mock_waffle.return_value = True
+ mock_role.return_value = 'student'
+ mock_mode.VERIFIED_MODES = ['verified']
+ mock_mode.CREDIT_MODE = ['credit']
+ mock_mode.NO_ID_PROFESSIONAL_MODE = ['no-id']
+ mock_mode.UPSELL_TO_VERIFIED_MODES = ['audit']
+ mock_mode.objects.get.return_value = MagicMock()
+ mock_mode.expiration_datetime.return_value = datetime.now() - timedelta(days=1)
+ mock_enrollment.return_value = MagicMock(mode='audit')
+
+ response = self.client.post(reverse('chat', kwargs={'course_run_id': self.course_id}))
+ self.assertEqual(response.status_code, 403)
+ mock_trial_expired.assert_called_once()
+
+ mock_waffle.reset_mock()
+ mock_role.reset_mock()
+ mock_mode.reset_mock()
+ mock_enrollment.reset_mock()
+ mock_trial_expired.reset_mock()
+
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ @patch('learning_assistant.views.get_user_role')
+ @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseMode')
+ def test_invalid_enrollment_mode(self, mock_mode, mock_enrollment, mock_role, mock_waffle):
+ mock_waffle.return_value = True
+ mock_role.return_value = 'student'
+ mock_mode.VERIFIED_MODES = ['verified']
+ mock_mode.CREDIT_MODE = ['credit']
+ mock_mode.NO_ID_PROFESSIONAL_MODE = ['no-id']
+ mock_mode.UPSELL_TO_VERIFIED_MODES = ['audit']
+ mock_mode.objects.get.return_value = MagicMock()
+ mock_mode.expiration_datetime.return_value = datetime.now() - timedelta(days=1)
+ mock_enrollment.return_value = MagicMock(mode='unpaid_executive_education')
+
+ response = self.client.post(reverse('chat', kwargs={'course_run_id': self.course_id}))
+ self.assertEqual(response.status_code, 403)
+
+ # Test that unexpired audit trials + vierfied track learners get the default chat response
+ @ddt.data((False, 'verified'),
+ (True, 'audit'))
+ @ddt.unpack
+ @patch('learning_assistant.views.audit_trial_is_expired')
@patch('learning_assistant.views.render_prompt_template')
@patch('learning_assistant.views.get_chat_response')
@patch('learning_assistant.views.learning_assistant_enabled')
@@ -169,6 +192,7 @@ def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
def test_chat_response_default(
self,
enabled_flag,
+ enrollment_mode,
mock_chat_history_enabled,
mock_save_chat_message,
mock_mode,
@@ -177,13 +201,18 @@ def test_chat_response_default(
mock_waffle,
mock_chat_response,
mock_render,
+ mock_trial_expired,
):
mock_waffle.return_value = True
mock_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
- mock_enrollment.return_value = MagicMock(mode='verified')
+ mock_mode.CREDIT_MODE = ['credit']
+ mock_mode.NO_ID_PROFESSIONAL_MODE = ['no-id']
+ mock_mode.UPSELL_TO_VERIFIED_MODES = ['audit']
+ mock_enrollment.return_value = MagicMock(mode=enrollment_mode)
mock_chat_response.return_value = (200, {'role': 'assistant', 'content': 'Something else'})
mock_render.return_value = 'Rendered template mock'
+ mock_trial_expired.return_value = False
test_unit_id = 'test-unit-id'
mock_chat_history_enabled.return_value = enabled_flag
@@ -349,7 +378,7 @@ def test_learning_message_history_view_get(
user=self.user,
role='staff',
content='Older message',
- created=datetime.date(2024, 10, 1)
+ created=date(2024, 10, 1)
)
LearningAssistantMessage.objects.create(
@@ -357,7 +386,7 @@ def test_learning_message_history_view_get(
user=self.user,
role='staff',
content='Newer message',
- created=datetime.date(2024, 10, 3)
+ created=date(2024, 10, 3)
)
db_messages = LearningAssistantMessage.objects.all().order_by('created')
From 010052e5beeb622feb438d43b869e98c25427f59 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Fri, 22 Nov 2024 13:08:43 -0300
Subject: [PATCH 100/104] fix: Gates the chat history endpoint behind a waffle
flag
---
learning_assistant/views.py | 4 ++
tests/test_views.py | 75 ++++++++++++++++++++++++++++++++++---
2 files changed, 73 insertions(+), 6 deletions(-)
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index b474984..764adac 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -247,6 +247,10 @@ def get(self, request, course_run_id):
data={'detail': 'Learning assistant not enabled for course.'}
)
+ # If chat history is disabled, we return no messages as response.
+ if not chat_history_enabled(courserun_key):
+ return Response(status=http_status.HTTP_200_OK, data=[])
+
# If user does not have an enrollment record, or is not staff, they should not have access
user_role = get_user_role(request.user, courserun_key)
enrollment_object = CourseEnrollment.get_enrollment(request.user, courserun_key)
diff --git a/tests/test_views.py b/tests/test_views.py
index 98c0ab6..11b8ad8 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -321,12 +321,21 @@ def test_learning_assistant_not_enabled(self, mock_learning_assistant_enabled):
self.assertEqual(response.status_code, 403)
+ @patch('learning_assistant.views.chat_history_enabled')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
@patch('learning_assistant.views.CourseEnrollment.get_enrollment')
@patch('learning_assistant.views.CourseMode')
- def test_user_no_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_role, mock_waffle):
- mock_waffle.return_value = True
+ def test_user_no_enrollment_not_staff(
+ self,
+ mock_mode,
+ mock_enrollment,
+ mock_role,
+ mock_assistant_waffle,
+ mock_history_waffle
+ ):
+ mock_assistant_waffle.return_value = True
+ mock_history_waffle.return_value = True
mock_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
mock_enrollment.return_value = None
@@ -338,12 +347,21 @@ def test_user_no_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_rol
)
self.assertEqual(response.status_code, 403)
+ @patch('learning_assistant.views.chat_history_enabled')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
@patch('learning_assistant.views.CourseEnrollment.get_enrollment')
@patch('learning_assistant.views.CourseMode')
- def test_user_audit_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_role, mock_waffle):
- mock_waffle.return_value = True
+ def test_user_audit_enrollment_not_staff(
+ self,
+ mock_mode,
+ mock_enrollment,
+ mock_role,
+ mock_assistant_waffle,
+ mock_history_waffle
+ ):
+ mock_assistant_waffle.return_value = True
+ mock_history_waffle.return_value = True
mock_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
mock_enrollment.return_value = MagicMock(mode='audit')
@@ -355,6 +373,7 @@ def test_user_audit_enrollment_not_staff(self, mock_mode, mock_enrollment, mock_
)
self.assertEqual(response.status_code, 403)
+ @patch('learning_assistant.views.chat_history_enabled')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
@patch('learning_assistant.views.CourseEnrollment.get_enrollment')
@@ -366,9 +385,11 @@ def test_learning_message_history_view_get(
mock_mode,
mock_enrollment,
mock_role,
- mock_waffle
+ mock_assistant_waffle,
+ mock_history_waffle,
):
- mock_waffle.return_value = True
+ mock_assistant_waffle.return_value = True
+ mock_history_waffle.return_value = True
mock_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
mock_enrollment.return_value = MagicMock(mode='verified')
@@ -407,3 +428,45 @@ def test_learning_message_history_view_get(
self.assertEqual(message['role'], db_messages[i].role)
self.assertEqual(message['content'], db_messages[i].content)
self.assertEqual(message['timestamp'], db_messages[i].created.isoformat())
+
+ @patch('learning_assistant.views.chat_history_enabled')
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ @patch('learning_assistant.views.get_course_id')
+ def test_learning_message_history_view_get_disabled(
+ self,
+ mock_get_course_id,
+ mock_assistant_waffle,
+ mock_history_waffle,
+ ):
+ mock_assistant_waffle.return_value = True
+ mock_history_waffle.return_value = False
+
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role='staff',
+ content='Older message',
+ created=date(2024, 10, 1)
+ )
+
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role='staff',
+ content='Newer message',
+ created=date(2024, 10, 3)
+ )
+
+ db_messages = LearningAssistantMessage.objects.all().order_by('created')
+ db_messages_count = len(db_messages)
+
+ mock_get_course_id.return_value = self.course_id
+ response = self.client.get(
+ reverse('message-history', kwargs={'course_run_id': self.course_id})+f'?message_count={db_messages_count}',
+ content_type='application/json'
+ )
+ data = response.data
+
+ # Ensure returning an empty list
+ self.assertEqual(len(data), 0)
+ self.assertEqual(data, [])
From cceafad9e86577143fc622bd07ccf6ee7e45acca Mon Sep 17 00:00:00 2001
From: Marcos
Date: Fri, 22 Nov 2024 13:40:24 -0300
Subject: [PATCH 101/104] chore: Bumped version and updated changelog
---
CHANGELOG.rst | 4 ++++
learning_assistant/__init__.py | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 3cf14ca..a9ac2da 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,10 @@ Change Log
Unreleased
**********
+4.4.6 - 2024-11-22
+******************
+* Gates the chat history endpoint behind a waffle flag
+
4.4.5 - 2024-11-12
******************
* Updated Learning Assistant History payload to return in ascending order
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 8ef32fa..b91c801 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.4.5'
+__version__ = '4.4.6'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
From 92bece4ae12b91738da8db1b3f1a1640c1cb55a1 Mon Sep 17 00:00:00 2001
From: Varsha Menon
Date: Mon, 25 Nov 2024 10:15:57 -0500
Subject: [PATCH 102/104] fix: course mode list concatenation issue
---
CHANGELOG.rst | 5 +++++
learning_assistant/__init__.py | 2 +-
learning_assistant/views.py | 3 ++-
tests/test_views.py | 12 ++++++------
4 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a9ac2da..cdcef76 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,9 +14,14 @@ Change Log
Unreleased
**********
+4.4.7 - 2024-11-25
+******************
+* Fixes the Course Chat View CourseMode concatenation issue
+
4.4.6 - 2024-11-22
******************
* Gates the chat history endpoint behind a waffle flag
+* Add LearningAssistantAuditTrial model
4.4.5 - 2024-11-12
******************
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index b91c801..25bfe14 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.4.6'
+__version__ = '4.4.7'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 764adac..8d9ed62 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -129,7 +129,8 @@ def post(self, request, course_run_id):
if (
# Here we include CREDIT_MODE and NO_ID_PROFESSIONAL_MODE, as CourseMode.VERIFIED_MODES on its own
# doesn't match what we count as "verified modes" in the frontend component.
- enrollment_mode in CourseMode.VERIFIED_MODES + CourseMode.CREDIT_MODE + CourseMode.NO_ID_PROFESSIONAL_MODE
+ enrollment_mode in CourseMode.VERIFIED_MODES + CourseMode.CREDIT_MODES
+ + [CourseMode.NO_ID_PROFESSIONAL_MODE]
or user_role_is_staff(user_role)
):
return self._get_next_message(request, courserun_key, course_run_id)
diff --git a/tests/test_views.py b/tests/test_views.py
index 11b8ad8..edde3d6 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -140,8 +140,8 @@ def test_audit_trial_expired(self, mock_mode, mock_enrollment,
mock_waffle.return_value = True
mock_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
- mock_mode.CREDIT_MODE = ['credit']
- mock_mode.NO_ID_PROFESSIONAL_MODE = ['no-id']
+ mock_mode.CREDIT_MODES = ['credit']
+ mock_mode.NO_ID_PROFESSIONAL_MODE = 'no-id'
mock_mode.UPSELL_TO_VERIFIED_MODES = ['audit']
mock_mode.objects.get.return_value = MagicMock()
mock_mode.expiration_datetime.return_value = datetime.now() - timedelta(days=1)
@@ -165,8 +165,8 @@ def test_invalid_enrollment_mode(self, mock_mode, mock_enrollment, mock_role, mo
mock_waffle.return_value = True
mock_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
- mock_mode.CREDIT_MODE = ['credit']
- mock_mode.NO_ID_PROFESSIONAL_MODE = ['no-id']
+ mock_mode.CREDIT_MODES = ['credit']
+ mock_mode.NO_ID_PROFESSIONAL_MODE = 'no-id'
mock_mode.UPSELL_TO_VERIFIED_MODES = ['audit']
mock_mode.objects.get.return_value = MagicMock()
mock_mode.expiration_datetime.return_value = datetime.now() - timedelta(days=1)
@@ -206,8 +206,8 @@ def test_chat_response_default(
mock_waffle.return_value = True
mock_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
- mock_mode.CREDIT_MODE = ['credit']
- mock_mode.NO_ID_PROFESSIONAL_MODE = ['no-id']
+ mock_mode.CREDIT_MODES = ['credit']
+ mock_mode.NO_ID_PROFESSIONAL_MODE = 'no-id'
mock_mode.UPSELL_TO_VERIFIED_MODES = ['audit']
mock_enrollment.return_value = MagicMock(mode=enrollment_mode)
mock_chat_response.return_value = (200, {'role': 'assistant', 'content': 'Something else'})
From a46ec5e550f1287314fb45780a76bdd4efca699d Mon Sep 17 00:00:00 2001
From: Varsha Menon
Date: Tue, 19 Nov 2024 10:23:12 -0500
Subject: [PATCH 103/104] docs: add local setup to readme
---
CHANGELOG.rst | 1 +
README.rst | 25 ++++++++++++++++++++++++-
2 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index cdcef76..cf031d8 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -13,6 +13,7 @@ Change Log
Unreleased
**********
+* Add local setup to readme
4.4.7 - 2024-11-25
******************
diff --git a/README.rst b/README.rst
index 69643e4..aafc186 100644
--- a/README.rst
+++ b/README.rst
@@ -26,7 +26,7 @@ One Time Setup
--------------
.. code-block::
- # Clone the repository
+ # Clone the repository (in the ../src relative to devstack repo)
git clone git@github.com:openedx/learning-assistant.git
cd learning-assistant
@@ -34,6 +34,29 @@ One Time Setup
# Here's how you might do that if you have virtualenvwrapper setup.
mkvirtualenv -p python3.8 learning-assistant
+In your ``requirements/edx/private.txt`` requirements file in edx-platform, add:
+
+.. code-block::
+
+ -e /edx/src/learning-assistant
+
+In your ``lms/envs/private.py`` settings file in edx-platform (create file if necessary), add the below settings. The value of the API key shouldn't matter, because it's not being used at this point, but the setting needs to be there.
+
+.. code-block::
+
+ CHAT_COMPLETION_API = '' # copy url from edx-internal
+ CHAT_COMPLETION_API_KEY = '' # add value though value itself does not matter
+
+ LEARNING_ASSISTANT_PROMPT_TEMPLATE = '' # copy value from edx-internal
+
+ LEARNING_ASSISTANT_AVAILABLE = True
+
+In devstack, run ``make lms-shell`` and run the following command: ``paver install_prereqs;exit``. This will install anything included in your ``private.txt`` requirements file.
+
+In django admin, add the following waffle flag ``learning_assistant.enable_course_content`` and make sure it is turned on for Everyone. The flag should be checked on for: Superusers, Staff, and Authenticated.
+
+This plugin depends on the lms and discovery - both should be running.
+
Every time you develop something in this repo
---------------------------------------------
.. code-block::
From ed0061e2b31630255185ad5bdaaa4014b1025f85 Mon Sep 17 00:00:00 2001
From: Michael Roytman
Date: Wed, 4 Dec 2024 14:25:25 -0500
Subject: [PATCH 104/104] feat: add BFFE endpoint for Learning Assistant to get
all necessary data to function (#140)
This commit adds a back-end-for-frontend (BFFE) endpoint for the Learning Assistant to get all the necessary data it needs to function.
The response from this endpoint includes the following information.
* whether the Learning Assistant is enabled
* message history information, if the learner is eligible to use the Learning Assistant
* audit trial information
---
CHANGELOG.rst | 5 +
learning_assistant/__init__.py | 2 +-
learning_assistant/api.py | 104 ++++++++-
learning_assistant/data.py | 13 ++
learning_assistant/urls.py | 12 +-
learning_assistant/views.py | 165 +++++++++++--
requirements/dev.txt | 7 +
requirements/doc.txt | 17 +-
requirements/quality.txt | 11 +-
requirements/test.in | 1 +
requirements/test.txt | 6 +
test_settings.py | 2 +
tests/__init__.py | 0
tests/test_api.py | 194 ++++++++++++++--
tests/test_views.py | 410 ++++++++++++++++++++++++++++++---
15 files changed, 864 insertions(+), 85 deletions(-)
delete mode 100644 tests/__init__.py
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index cf031d8..96feb25 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -13,7 +13,12 @@ Change Log
Unreleased
**********
+
+4.5.0 - 2024-12-04
+******************
* Add local setup to readme
+* Add a BFFE chat summary endpoint for Learning Assistant, including information about whether the Learning Assistant is
+ enabled, Learning Assistant message history, and Learning Assistant audit trial data.
4.4.7 - 2024-11-25
******************
diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py
index 25bfe14..b14e6c9 100644
--- a/learning_assistant/__init__.py
+++ b/learning_assistant/__init__.py
@@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""
-__version__ = '4.4.7'
+__version__ = '4.5.0'
default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
diff --git a/learning_assistant/api.py b/learning_assistant/api.py
index 3857945..9483888 100644
--- a/learning_assistant/api.py
+++ b/learning_assistant/api.py
@@ -1,6 +1,7 @@
"""
Library for the learning_assistant app.
"""
+import datetime
import logging
from datetime import datetime, timedelta
@@ -11,8 +12,13 @@
from jinja2 import BaseLoader, Environment
from opaque_keys import InvalidKeyError
-from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, AUDIT_TRIAL_MAX_DAYS, CATEGORY_TYPE_MAP
-from learning_assistant.data import LearningAssistantCourseEnabledData
+try:
+ from common.djangoapps.course_modes.models import CourseMode
+except ImportError:
+ CourseMode = None
+
+from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, CATEGORY_TYPE_MAP
+from learning_assistant.data import LearningAssistantAuditTrialData, LearningAssistantCourseEnabledData
from learning_assistant.models import (
LearningAssistantAuditTrial,
LearningAssistantCourseEnabled,
@@ -231,15 +237,71 @@ def get_message_history(courserun_key, user, message_count):
return message_history
-def audit_trial_is_expired(user, upgrade_deadline):
+def get_audit_trial_expiration_date(start_date):
"""
- Given a user (User), get or create the corresponding LearningAssistantAuditTrial trial object.
+ Given a start date of an audit trial, calculate the expiration date of the audit trial.
+
+ Arguments:
+ * start_date (datetime): the start date of the audit trial
+
+ Returns:
+ * expiration_date (datetime): the expiration date of the audit trial
"""
- # If the upgrade deadline has passed, return "True" for expired
- DAYS_SINCE_UPGRADE_DEADLINE = datetime.now() - upgrade_deadline
- if DAYS_SINCE_UPGRADE_DEADLINE >= timedelta(days=0):
- return True
+ default_trial_length_days = 14
+
+ trial_length_days = getattr(settings, 'LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS', default_trial_length_days)
+
+ if trial_length_days is None:
+ trial_length_days = default_trial_length_days
+
+ # If LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS is set to a negative number, assume it should be 0.
+ # pylint: disable=consider-using-max-builtin
+ if trial_length_days < 0:
+ trial_length_days = 0
+
+ expiration_datetime = start_date + timedelta(days=trial_length_days)
+ return expiration_datetime
+
+
+def get_audit_trial(user):
+ """
+ Given a user, return the associated audit trial data.
+
+ Arguments:
+ * user (User): the user
+
+ Returns:
+ * audit_trial_data (LearningAssistantAuditTrialData): the audit trial data
+ * user_id (int): the user's id
+ * start_date (datetime): the start date of the audit trial
+ * expiration_date (datetime): the expiration date of the audit trial
+ * None: if no audit trial exists for the user
+ """
+ try:
+ audit_trial = LearningAssistantAuditTrial.objects.get(user=user)
+ except LearningAssistantAuditTrial.DoesNotExist:
+ return None
+
+ return LearningAssistantAuditTrialData(
+ user_id=user.id,
+ start_date=audit_trial.start_date,
+ expiration_date=get_audit_trial_expiration_date(audit_trial.start_date),
+ )
+
+
+def get_or_create_audit_trial(user):
+ """
+ Given a user, return the associated audit trial data, creating a new audit trial for the user if one does not exist.
+ Arguments:
+ * user (User): the user
+
+ Returns:
+ * audit_trial_data (LearningAssistantAuditTrialData): the audit trial data
+ * user_id (int): the user's id
+ * start_date (datetime): the start date of the audit trial
+ * expiration_date (datetime): the expiration date of the audit trial
+ """
audit_trial, _ = LearningAssistantAuditTrial.objects.get_or_create(
user=user,
defaults={
@@ -247,6 +309,26 @@ def audit_trial_is_expired(user, upgrade_deadline):
},
)
- # If the user's trial is past its expiry date, return "True" for expired. Else, return False
- DAYS_SINCE_TRIAL_START_DATE = datetime.now() - audit_trial.start_date
- return DAYS_SINCE_TRIAL_START_DATE >= timedelta(days=AUDIT_TRIAL_MAX_DAYS)
+ return LearningAssistantAuditTrialData(
+ user_id=user.id,
+ start_date=audit_trial.start_date,
+ expiration_date=get_audit_trial_expiration_date(audit_trial.start_date),
+ )
+
+
+def audit_trial_is_expired(audit_trial_data, courserun_key):
+ """
+ Given a user (User), get or create the corresponding LearningAssistantAuditTrial trial object.
+ """
+ course_mode = CourseMode.objects.get(course=courserun_key)
+
+ upgrade_deadline = course_mode.expiration_datetime()
+
+ # If the upgrade deadline has passed, return True for expired. Upgrade deadline is an optional attribute of a
+ # CourseMode, so if it's None, then do not return True.
+ days_until_upgrade_deadline = datetime.now() - upgrade_deadline if upgrade_deadline else None
+ if days_until_upgrade_deadline is not None and days_until_upgrade_deadline >= timedelta(days=0):
+ return True
+
+ # If the user's trial is past its expiry date, return True for expired. Else, return False.
+ return audit_trial_data is None or audit_trial_data.expiration_date <= datetime.now()
diff --git a/learning_assistant/data.py b/learning_assistant/data.py
index 2e89c27..e9e923b 100644
--- a/learning_assistant/data.py
+++ b/learning_assistant/data.py
@@ -1,6 +1,8 @@
"""
Data classes for the Learning Assistant application.
"""
+from datetime import datetime
+
from attrs import field, frozen, validators
from opaque_keys.edx.keys import CourseKey
@@ -13,3 +15,14 @@ class LearningAssistantCourseEnabledData:
course_key: CourseKey = field(validator=validators.instance_of(CourseKey))
enabled: bool = field(validator=validators.instance_of(bool))
+
+
+@frozen
+class LearningAssistantAuditTrialData:
+ """
+ Data class representing an audit learner's trial of the Learning Assistant.
+ """
+
+ user_id: int = field(validator=validators.instance_of(int))
+ start_date: datetime = field(validator=validators.optional(validators.instance_of(datetime)))
+ expiration_date: datetime = field(validator=validators.optional(validators.instance_of(datetime)))
diff --git a/learning_assistant/urls.py b/learning_assistant/urls.py
index b0dfb48..31914b8 100644
--- a/learning_assistant/urls.py
+++ b/learning_assistant/urls.py
@@ -4,7 +4,12 @@
from django.urls import re_path
from learning_assistant.constants import COURSE_ID_PATTERN
-from learning_assistant.views import CourseChatView, LearningAssistantEnabledView, LearningAssistantMessageHistoryView
+from learning_assistant.views import (
+ CourseChatView,
+ LearningAssistantChatSummaryView,
+ LearningAssistantEnabledView,
+ LearningAssistantMessageHistoryView,
+)
app_name = 'learning_assistant'
@@ -24,4 +29,9 @@
LearningAssistantMessageHistoryView.as_view(),
name='message-history',
),
+ re_path(
+ fr'learning_assistant/v1/course_id/{COURSE_ID_PATTERN}/chat-summary',
+ LearningAssistantChatSummaryView.as_view(),
+ name='chat-summary',
+ ),
]
diff --git a/learning_assistant/views.py b/learning_assistant/views.py
index 8d9ed62..58351e1 100644
--- a/learning_assistant/views.py
+++ b/learning_assistant/views.py
@@ -18,12 +18,16 @@
from common.djangoapps.student.models import CourseEnrollment
from lms.djangoapps.courseware.access import get_user_role
except ImportError:
- pass
+ CourseMode = None
+ CourseEnrollment = None
+ get_user_role = None
from learning_assistant.api import (
audit_trial_is_expired,
+ get_audit_trial,
get_course_id,
get_message_history,
+ get_or_create_audit_trial,
learning_assistant_enabled,
render_prompt_template,
save_chat_message,
@@ -39,6 +43,19 @@
class CourseChatView(APIView):
"""
View to retrieve chat response.
+
+ Accepts: [POST]
+
+ Path: /learning_assistant/v1/course_id/{course_run_id}
+
+ Parameters:
+ * course_run_id: the ID of the course
+
+ Responses:
+ * 200: OK
+ * 400: Malformed Request - Course ID is not a valid course ID.
+ * 403: Forbidden - Learning assistant not enabled for course or learner does not have a valid enrollment or is
+ not staff.
"""
authentication_classes = (SessionAuthentication, JwtAuthentication,)
@@ -127,7 +144,7 @@ def post(self, request, course_run_id):
# If the user is in a verified course mode or is staff, return the next message
if (
- # Here we include CREDIT_MODE and NO_ID_PROFESSIONAL_MODE, as CourseMode.VERIFIED_MODES on its own
+ # Here we include CREDIT_MODES and NO_ID_PROFESSIONAL_MODE, as CourseMode.VERIFIED_MODES on its own
# doesn't match what we count as "verified modes" in the frontend component.
enrollment_mode in CourseMode.VERIFIED_MODES + CourseMode.CREDIT_MODES
+ [CourseMode.NO_ID_PROFESSIONAL_MODE]
@@ -138,11 +155,9 @@ def post(self, request, course_run_id):
# If user has an audit enrollment record, get or create their trial. If the trial is not expired, return the
# next message. Otherwise, return 403
elif enrollment_mode in CourseMode.UPSELL_TO_VERIFIED_MODES: # AUDIT, HONOR
- course_mode = CourseMode.objects.get(course=courserun_key)
- upgrade_deadline = course_mode.expiration_datetime()
-
- user_audit_trial_expired = audit_trial_is_expired(request.user, upgrade_deadline)
- if user_audit_trial_expired:
+ audit_trial = get_or_create_audit_trial(request.user)
+ is_user_audit_trial_expired = audit_trial_is_expired(audit_trial, courserun_key)
+ if is_user_audit_trial_expired:
return Response(
status=http_status.HTTP_403_FORBIDDEN,
data={'detail': 'The audit trial for this user has expired.'}
@@ -164,14 +179,14 @@ class LearningAssistantEnabledView(APIView):
View to retrieve whether the Learning Assistant is enabled for a course.
This endpoint returns a boolean representing whether the Learning Assistant feature is enabled in a course
- represented by the course_key, which is provided in the URL.
+ represented by the course_run_id, which is provided in the URL.
Accepts: [GET]
- Path: /learning_assistant/v1/course_id/{course_key}/enabled
+ Path: /learning_assistant/v1/course_id/{course_run_id}/enabled
Parameters:
- * course_key: the ID of the course
+ * course_run_id: the ID of the course
Responses:
* 200: OK
@@ -209,18 +224,20 @@ class LearningAssistantMessageHistoryView(APIView):
View to retrieve the message history for user in a course.
This endpoint returns the message history stored in the LearningAssistantMessage table in a course
- represented by the course_key, which is provided in the URL.
+ represented by the course_run_id, which is provided in the URL.
Accepts: [GET]
- Path: /learning_assistant/v1/course_id/{course_key}/history
+ Path: /learning_assistant/v1/course_id/{course_run_id}/history
Parameters:
- * course_key: the ID of the course
+ * course_run_id: the ID of the course
Responses:
* 200: OK
* 400: Malformed Request - Course ID is not a valid course ID.
+ * 403: Forbidden - Learning assistant not enabled for course or learner does not have a valid enrollment or is
+ not staff.
"""
authentication_classes = (SessionAuthentication, JwtAuthentication,)
@@ -271,3 +288,125 @@ def get(self, request, course_run_id):
message_history = get_message_history(courserun_key, user, message_count)
data = MessageSerializer(message_history, many=True).data
return Response(status=http_status.HTTP_200_OK, data=data)
+
+
+class LearningAssistantChatSummaryView(APIView):
+ """
+ View to retrieve data about a learner's session with the Learning Assistant.
+
+ This endpoint returns all the data necessary for the Learning Assistant to function, including the following
+ information.
+ * whether the Learning Assistant is enabled
+ * message history information, if the learner is eligible to use the Learning Assistant
+ * audit trial information
+
+ Accepts: [GET]
+
+ Path: /learning_assistant/v1/course_id/{course_run_id}/chat-summary
+
+ Parameters:
+ * course_run_id: the ID of the course
+
+ Responses:
+ * 200: OK
+ * 400: Malformed Request - Course ID is not a valid course ID.
+ """
+
+ authentication_classes = (SessionAuthentication, JwtAuthentication,)
+ permission_classes = (IsAuthenticated,)
+
+ def get(self, request, course_run_id):
+ """
+ Given a course run ID, return all the data necessary for the Learning Assistant to fuction.
+
+ The response will be in the following format.
+
+ {
+ "enabled": true,
+ "message_history": [
+ {
+ "role": "user",
+ "content": "test message from user",
+ "timestamp": "2024-12-02T15:04:17.495928Z"
+ },
+ {
+ "role": "assistant",
+ "content": "test message from assistant",
+ "timestamp": "2024-12-02T15:04:40.084584Z"
+ }
+ ],
+ "trial": {
+ "start_date": "2024-12-02T14:59:16.148236Z",
+ "expiration_date": "2024-12-16T14:59:16.148236Z"
+ }
+ }
+ """
+ try:
+ courserun_key = CourseKey.from_string(course_run_id)
+ except InvalidKeyError:
+ return Response(
+ status=http_status.HTTP_400_BAD_REQUEST,
+ data={'detail': 'Course ID is not a valid course ID.'}
+ )
+
+ data = {}
+ user = request.user
+
+ # Get whether the Learning Assistant is enabled.
+ data['enabled'] = learning_assistant_enabled(courserun_key)
+
+ # Get message history.
+ # If user does not have a verified enrollment record or is does not have an active audit trial, or is not staff,
+ # then they should not have access to the message history.
+ user_role = get_user_role(user, courserun_key)
+ enrollment_object = CourseEnrollment.get_enrollment(request.user, courserun_key)
+ enrollment_mode = enrollment_object.mode if enrollment_object else None
+
+ # Here we include CREDIT_MODES and NO_ID_PROFESSIONAL_MODE, as CourseMode.VERIFIED_MODES on its own
+ # doesn't match what we count as "verified modes" in the frontend component. We also include AUDIT and HONOR to
+ # ensure learners with audit trials see message history if the trial is non-expired.
+ valid_full_access_modes = (
+ CourseMode.VERIFIED_MODES +
+ CourseMode.CREDIT_MODES +
+ [CourseMode.NO_ID_PROFESSIONAL_MODE]
+ )
+ valid_trial_access_modes = CourseMode.UPSELL_TO_VERIFIED_MODES
+
+ # Get audit trial. Note that we do not want to create an audit trial when calling this endpoint.
+ audit_trial = get_audit_trial(request.user)
+
+ # If the learner doesn't meet criteria to use the Learning Assistant, or if the chat history is disabled, we
+ # return no messages in the response.
+ message_history_data = []
+
+ has_trial_access = (
+ enrollment_mode in valid_trial_access_modes
+ and audit_trial
+ and not audit_trial_is_expired(audit_trial, courserun_key)
+ )
+
+ if (
+ (
+ (enrollment_mode in valid_full_access_modes)
+ or has_trial_access
+ or user_role_is_staff(user_role)
+ )
+ and chat_history_enabled(courserun_key)
+ ):
+ message_count = int(request.GET.get('message_count', 50))
+ message_history = get_message_history(courserun_key, user, message_count)
+ message_history_data = MessageSerializer(message_history, many=True).data
+
+ data['message_history'] = message_history_data
+
+ # Get audit trial.
+ trial = get_audit_trial(user)
+
+ trial_data = {}
+ if trial:
+ trial_data['start_date'] = trial.start_date
+ trial_data['expiration_date'] = trial.expiration_date
+
+ data['audit_trial'] = trial_data
+
+ return Response(status=http_status.HTTP_200_OK, data=data)
diff --git a/requirements/dev.txt b/requirements/dev.txt
index ca3d2d1..5b600f6 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -140,6 +140,8 @@ filelock==3.16.1
# -r requirements/ci.txt
# tox
# virtualenv
+freezegun==1.5.1
+ # via -r requirements/quality.txt
idna==3.10
# via
# -r requirements/quality.txt
@@ -274,6 +276,10 @@ pytest-cov==6.0.0
# via -r requirements/quality.txt
pytest-django==4.9.0
# via -r requirements/quality.txt
+python-dateutil==2.9.0.post0
+ # via
+ # -r requirements/quality.txt
+ # freezegun
python-slugify==8.0.4
# via
# -r requirements/quality.txt
@@ -300,6 +306,7 @@ six==1.16.0
# via
# -r requirements/quality.txt
# edx-lint
+ # python-dateutil
snowballstemmer==2.2.0
# via
# -r requirements/quality.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 03e90e8..8bd5308 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -46,7 +46,6 @@ cryptography==43.0.3
# via
# -r requirements/test.txt
# pyjwt
- # secretstorage
ddt==1.7.2
# via -r requirements/test.txt
django==4.2.16
@@ -105,6 +104,8 @@ edx-opaque-keys==2.11.0
# edx-drf-extensions
edx-rest-api-client==6.0.0
# via -r requirements/test.txt
+freezegun==1.5.1
+ # via -r requirements/test.txt
idna==3.10
# via
# -r requirements/test.txt
@@ -125,10 +126,6 @@ jaraco-context==6.0.1
# via keyring
jaraco-functools==4.1.0
# via keyring
-jeepney==0.8.0
- # via
- # keyring
- # secretstorage
jinja2==3.1.4
# via
# -r requirements/test.txt
@@ -209,6 +206,10 @@ pytest-cov==6.0.0
# via -r requirements/test.txt
pytest-django==4.9.0
# via -r requirements/test.txt
+python-dateutil==2.9.0.post0
+ # via
+ # -r requirements/test.txt
+ # freezegun
python-slugify==8.0.4
# via
# -r requirements/test.txt
@@ -239,12 +240,14 @@ rfc3986==2.0.0
# via twine
rich==13.9.4
# via twine
-secretstorage==3.3.3
- # via keyring
semantic-version==2.10.0
# via
# -r requirements/test.txt
# edx-drf-extensions
+six==1.16.0
+ # via
+ # -r requirements/test.txt
+ # python-dateutil
snowballstemmer==2.2.0
# via sphinx
sphinx==8.1.3
diff --git a/requirements/quality.txt b/requirements/quality.txt
index eea5bb9..ed97b2c 100644
--- a/requirements/quality.txt
+++ b/requirements/quality.txt
@@ -102,6 +102,8 @@ edx-opaque-keys==2.11.0
# edx-drf-extensions
edx-rest-api-client==6.0.0
# via -r requirements/test.txt
+freezegun==1.5.1
+ # via -r requirements/test.txt
idna==3.10
# via
# -r requirements/test.txt
@@ -191,6 +193,10 @@ pytest-cov==6.0.0
# via -r requirements/test.txt
pytest-django==4.9.0
# via -r requirements/test.txt
+python-dateutil==2.9.0.post0
+ # via
+ # -r requirements/test.txt
+ # freezegun
python-slugify==8.0.4
# via
# -r requirements/test.txt
@@ -213,7 +219,10 @@ semantic-version==2.10.0
# -r requirements/test.txt
# edx-drf-extensions
six==1.16.0
- # via edx-lint
+ # via
+ # -r requirements/test.txt
+ # edx-lint
+ # python-dateutil
snowballstemmer==2.2.0
# via pydocstyle
sqlparse==0.5.2
diff --git a/requirements/test.in b/requirements/test.in
index 8b21fc7..bb09779 100644
--- a/requirements/test.in
+++ b/requirements/test.in
@@ -5,6 +5,7 @@
code-annotations # provides commands used by the pii_check make target.
ddt
+freezegun
pytest-cov # pytest extension for code coverage statistics
pytest-django # pytest extension for better Django support
responses
diff --git a/requirements/test.txt b/requirements/test.txt
index 35bcf93..ad6ed72 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -85,6 +85,8 @@ edx-opaque-keys==2.11.0
# edx-drf-extensions
edx-rest-api-client==6.0.0
# via -r requirements/base.txt
+freezegun==1.5.1
+ # via -r requirements/test.in
idna==3.10
# via
# -r requirements/base.txt
@@ -141,6 +143,8 @@ pytest-cov==6.0.0
# via -r requirements/test.in
pytest-django==4.9.0
# via -r requirements/test.in
+python-dateutil==2.9.0.post0
+ # via freezegun
python-slugify==8.0.4
# via code-annotations
pyyaml==6.0.2
@@ -159,6 +163,8 @@ semantic-version==2.10.0
# via
# -r requirements/base.txt
# edx-drf-extensions
+six==1.16.0
+ # via python-dateutil
sqlparse==0.5.2
# via
# -r requirements/base.txt
diff --git a/test_settings.py b/test_settings.py
index 78d99fc..8717a1c 100644
--- a/test_settings.py
+++ b/test_settings.py
@@ -88,3 +88,5 @@ def root(*args):
)
LEARNING_ASSISTANT_AVAILABLE = True
+
+LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS = 14
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/test_api.py b/tests/test_api.py
index d73af07..eda7602 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -10,6 +10,7 @@
from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.test import TestCase, override_settings
+from freezegun import freeze_time
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey
@@ -18,16 +19,18 @@
_get_children_contents,
_leaf_filter,
audit_trial_is_expired,
+ get_audit_trial,
+ get_audit_trial_expiration_date,
get_block_content,
get_message_history,
+ get_or_create_audit_trial,
learning_assistant_available,
learning_assistant_enabled,
render_prompt_template,
save_chat_message,
set_learning_assistant_enabled,
)
-from learning_assistant.constants import AUDIT_TRIAL_MAX_DAYS
-from learning_assistant.data import LearningAssistantCourseEnabledData
+from learning_assistant.data import LearningAssistantAuditTrialData, LearningAssistantCourseEnabledData
from learning_assistant.models import (
LearningAssistantAuditTrial,
LearningAssistantCourseEnabled,
@@ -483,6 +486,102 @@ def test_get_message_course_id_differences(self):
self.assertEqual(return_value.content, expected_value[i].content)
+@ddt.ddt
+class GetAuditTrialExpirationDateTests(TestCase):
+ """
+ Test suite for get_audit_trial_expiration_date.
+ """
+ @ddt.data(
+ (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 15, 0, 0, 0), None),
+ (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 2, 1, 0, 0, 0), None),
+ (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 15, 0, 0, 0), 14),
+ (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 2, 1, 0, 0, 0), 14),
+ (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 1, 0, 0, 0), -1),
+ (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 1, 18, 0, 0, 0), -1),
+ (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 1, 0, 0, 0), 0),
+ (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 1, 18, 0, 0, 0), 0),
+ (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 4, 0, 0, 0), 3),
+ (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 1, 21, 0, 0, 0), 3),
+ )
+ @ddt.unpack
+ def test_expiration_date(self, start_date, expected_expiration_date, trial_length_days):
+ with override_settings(LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS=trial_length_days):
+ expiration_date = get_audit_trial_expiration_date(start_date)
+ self.assertEqual(expected_expiration_date, expiration_date)
+
+
+class GetAuditTrialTests(TestCase):
+ """
+ Test suite for get_audit_trial.
+ """
+ @freeze_time('2024-01-01')
+ def setUp(self):
+ super().setUp()
+ self.user = User(username='tester', email='tester@test.com')
+ self.user.save()
+
+ def test_exists(self):
+ start_date = datetime.now()
+
+ LearningAssistantAuditTrial.objects.create(
+ user=self.user,
+ start_date=start_date
+ )
+
+ expected_return = LearningAssistantAuditTrialData(
+ user_id=self.user.id,
+ start_date=start_date,
+ expiration_date=start_date + timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS)
+ )
+ self.assertEqual(expected_return, get_audit_trial(self.user))
+
+ def test_not_exists(self):
+ other_user = User(username='other-tester', email='other-tester@test.com')
+ other_user.save()
+
+ self.assertIsNone(get_audit_trial(self.user))
+
+
+class GetOrCreateAuditTrialTests(TestCase):
+ """
+ Test suite for get_or_create_audit_trial.
+ """
+ def setUp(self):
+ super().setUp()
+ self.user = User(username='tester', email='tester@test.com')
+ self.user.save()
+
+ @freeze_time('2024-01-01')
+ def test_exists(self):
+ start_date = datetime.now()
+
+ LearningAssistantAuditTrial.objects.create(
+ user=self.user,
+ start_date=start_date
+ )
+
+ expected_return = LearningAssistantAuditTrialData(
+ user_id=self.user.id,
+ start_date=start_date,
+ expiration_date=start_date + timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS)
+ )
+ self.assertEqual(expected_return, get_or_create_audit_trial(self.user))
+
+ @freeze_time('2024-01-01')
+ def test_not_exists(self):
+ other_user = User(username='other-tester', email='other-tester@test.com')
+ other_user.save()
+
+ start_date = datetime.now()
+ expected_return = LearningAssistantAuditTrialData(
+ user_id=self.user.id,
+ start_date=start_date,
+ expiration_date=start_date + timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS)
+ )
+
+ self.assertEqual(expected_return, get_or_create_audit_trial(self.user))
+
+
@ddt.ddt
class CheckIfAuditTrialIsExpiredTests(TestCase):
"""
@@ -495,23 +594,86 @@ def setUp(self):
self.user = User(username='tester', email='tester@test.com')
self.user.save()
- self.role = 'verified'
self.upgrade_deadline = datetime.now() + timedelta(days=1) # 1 day from now
- def test_check_if_past_upgrade_deadline(self):
- upgrade_deadline = datetime.now() - timedelta(days=1) # yesterday
- self.assertEqual(audit_trial_is_expired(self.user, upgrade_deadline), True)
+ @freeze_time('2024-01-01')
+ @patch('learning_assistant.api.CourseMode')
+ def test_upgrade_deadline_expired(self, mock_course_mode):
- def test_audit_trial_is_expired_audit_trial_expired(self):
- LearningAssistantAuditTrial.objects.create(
- user=self.user,
- start_date=datetime.now() - timedelta(days=AUDIT_TRIAL_MAX_DAYS + 1), # 1 day more than trial deadline
+ mock_mode = MagicMock()
+ mock_mode.expiration_datetime.return_value = datetime.now() - timedelta(days=1) # yesterday
+ mock_course_mode.objects.get.return_value = mock_mode
+
+ start_date = datetime.now()
+ audit_trial_data = LearningAssistantAuditTrialData(
+ user_id=self.user.id,
+ start_date=start_date,
+ expiration_date=start_date + timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS),
)
- self.assertEqual(audit_trial_is_expired(self.user, self.upgrade_deadline), True)
- def test_audit_trial_is_expired_audit_trial_unexpired(self):
- LearningAssistantAuditTrial.objects.create(
- user=self.user,
- start_date=datetime.now() - timedelta(days=AUDIT_TRIAL_MAX_DAYS - 0.99), # 0.99 days less than deadline
+ self.assertEqual(audit_trial_is_expired(audit_trial_data, self.course_key), True)
+
+ @freeze_time('2024-01-01')
+ @patch('learning_assistant.api.CourseMode')
+ def test_upgrade_deadline_none(self, mock_course_mode):
+
+ mock_mode = MagicMock()
+ mock_mode.expiration_datetime.return_value = None
+ mock_course_mode.objects.get.return_value = mock_mode
+
+ # Verify that the audit trial data is considered when determing whether an audit trial is expired and not the
+ # upgrade deadline.
+ start_date = datetime.now()
+ audit_trial_data = LearningAssistantAuditTrialData(
+ user_id=self.user.id,
+ start_date=start_date,
+ expiration_date=start_date + timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS),
+ )
+
+ self.assertEqual(audit_trial_is_expired(audit_trial_data, self.course_key), False)
+
+ start_date = datetime.now() - timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS + 1)
+ audit_trial_data = LearningAssistantAuditTrialData(
+ user_id=self.user.id,
+ start_date=start_date,
+ expiration_date=start_date + timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS),
+ )
+
+ self.assertEqual(audit_trial_is_expired(audit_trial_data, self.course_key), True)
+
+ @ddt.data(
+ # exactly the trial deadline
+ datetime(year=2024, month=1, day=1) - timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS),
+ # 1 day more than trial deadline
+ datetime(year=2024, month=1, day=1) - timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS + 1),
+ )
+ @freeze_time('2024-01-01')
+ @patch('learning_assistant.api.CourseMode')
+ def test_audit_trial_expired(self, start_date, mock_course_mode):
+ mock_mode = MagicMock()
+ mock_mode.expiration_datetime.return_value = datetime.now() + timedelta(days=1) # tomorrow
+ mock_course_mode.objects.get.return_value = mock_mode
+
+ audit_trial_data = LearningAssistantAuditTrialData(
+ user_id=self.user.id,
+ start_date=start_date,
+ expiration_date=get_audit_trial_expiration_date(start_date),
+ )
+
+ self.assertEqual(audit_trial_is_expired(audit_trial_data, self.upgrade_deadline), True)
+
+ @freeze_time('2024-01-01')
+ @patch('learning_assistant.api.CourseMode')
+ def test_audit_trial_unexpired(self, mock_course_mode):
+ mock_mode = MagicMock()
+ mock_mode.expiration_datetime.return_value = datetime.now() + timedelta(days=1) # tomorrow
+ mock_course_mode.objects.get.return_value = mock_mode
+
+ start_date = datetime.now() - timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS - 1)
+ audit_trial_data = LearningAssistantAuditTrialData(
+ user_id=self.user.id,
+ start_date=start_date,
+ expiration_date=get_audit_trial_expiration_date(start_date),
)
- self.assertEqual(audit_trial_is_expired(self.user, self.upgrade_deadline), False)
+
+ self.assertEqual(audit_trial_is_expired(audit_trial_data, self.upgrade_deadline), False)
diff --git a/tests/test_views.py b/tests/test_views.py
index edde3d6..a70aad5 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -5,7 +5,9 @@
import sys
from datetime import date, datetime, timedelta
from importlib import import_module
+from itertools import product
from unittest.mock import MagicMock, call, patch
+from urllib.parse import urlencode
import ddt
from django.conf import settings
@@ -14,9 +16,10 @@
from django.test import TestCase, override_settings
from django.test.client import Client
from django.urls import reverse
+from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
-from learning_assistant.models import LearningAssistantMessage
+from learning_assistant.models import LearningAssistantAuditTrial, LearningAssistantMessage
User = get_user_model()
@@ -95,13 +98,6 @@ def setUp(self):
)
self.patcher.start()
- @patch('learning_assistant.views.learning_assistant_enabled')
- def test_invalid_course_id(self, mock_learning_assistant_enabled):
- mock_learning_assistant_enabled.return_value = True
- response = self.client.get(reverse('enabled', kwargs={'course_run_id': self.course_id+'+invalid'}))
-
- self.assertEqual(response.status_code, 400)
-
@patch('learning_assistant.views.learning_assistant_enabled')
def test_course_waffle_inactive(self, mock_waffle):
mock_waffle.return_value = False
@@ -111,9 +107,13 @@ def test_course_waffle_inactive(self, mock_waffle):
@patch('learning_assistant.views.render_prompt_template')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
- def test_invalid_messages(self, mock_role, mock_waffle, mock_render):
+ @patch('learning_assistant.views.CourseEnrollment')
+ @patch('learning_assistant.views.CourseMode')
+ def test_invalid_messages(self, mock_mode, mock_enrollment, mock_get_user_role, mock_waffle, mock_render):
mock_waffle.return_value = True
- mock_role.return_value = 'staff'
+ mock_get_user_role.return_value = 'staff'
+ mock_mode.VERIFIED_MODES = ['verified']
+ mock_enrollment.get_enrollment.return_value = MagicMock(mode='verified')
mock_render.return_value = 'This is a template'
test_unit_id = 'test-unit-id'
@@ -175,7 +175,7 @@ def test_invalid_enrollment_mode(self, mock_mode, mock_enrollment, mock_role, mo
response = self.client.post(reverse('chat', kwargs={'course_run_id': self.course_id}))
self.assertEqual(response.status_code, 403)
- # Test that unexpired audit trials + vierfied track learners get the default chat response
+ # Test that unexpired audit trials + verified track learners get the default chat response
@ddt.data((False, 'verified'),
(True, 'audit'))
@ddt.unpack
@@ -184,7 +184,7 @@ def test_invalid_enrollment_mode(self, mock_mode, mock_enrollment, mock_role, mo
@patch('learning_assistant.views.get_chat_response')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
- @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseEnrollment')
@patch('learning_assistant.views.CourseMode')
@patch('learning_assistant.views.save_chat_message')
@patch('learning_assistant.views.chat_history_enabled')
@@ -197,19 +197,19 @@ def test_chat_response_default(
mock_save_chat_message,
mock_mode,
mock_enrollment,
- mock_role,
+ mock_get_user_role,
mock_waffle,
mock_chat_response,
mock_render,
mock_trial_expired,
):
mock_waffle.return_value = True
- mock_role.return_value = 'student'
+ mock_get_user_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
mock_mode.CREDIT_MODES = ['credit']
mock_mode.NO_ID_PROFESSIONAL_MODE = 'no-id'
mock_mode.UPSELL_TO_VERIFIED_MODES = ['audit']
- mock_enrollment.return_value = MagicMock(mode=enrollment_mode)
+ mock_enrollment.get_enrollment.return_value = MagicMock(mode=enrollment_mode)
mock_chat_response.return_value = (200, {'role': 'assistant', 'content': 'Something else'})
mock_render.return_value = 'Rendered template mock'
mock_trial_expired.return_value = False
@@ -283,7 +283,6 @@ def test_invalid_course_id(self, mock_learning_assistant_enabled):
self.assertEqual(response.status_code, 400)
-@ddt.ddt
class LearningAssistantMessageHistoryViewTests(LoggedInTestCase):
"""
Tests for the LearningAssistantMessageHistoryView
@@ -293,13 +292,6 @@ def setUp(self):
super().setUp()
self.course_id = 'course-v1:edx+test+23'
- @patch('learning_assistant.views.learning_assistant_enabled')
- def test_invalid_course_id(self, mock_learning_assistant_enabled):
- mock_learning_assistant_enabled.return_value = True
- response = self.client.get(reverse('enabled', kwargs={'course_run_id': self.course_id+'+invalid'}))
-
- self.assertEqual(response.status_code, 400)
-
@patch('learning_assistant.views.learning_assistant_enabled')
def test_course_waffle_inactive(self, mock_waffle):
mock_waffle.return_value = False
@@ -324,21 +316,21 @@ def test_learning_assistant_not_enabled(self, mock_learning_assistant_enabled):
@patch('learning_assistant.views.chat_history_enabled')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
- @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseEnrollment')
@patch('learning_assistant.views.CourseMode')
def test_user_no_enrollment_not_staff(
self,
mock_mode,
mock_enrollment,
- mock_role,
+ mock_get_user_role,
mock_assistant_waffle,
mock_history_waffle
):
mock_assistant_waffle.return_value = True
mock_history_waffle.return_value = True
- mock_role.return_value = 'student'
+ mock_get_user_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
- mock_enrollment.return_value = None
+ mock_enrollment.get_enrollment = MagicMock(return_value=None)
message_count = 5
response = self.client.get(
@@ -350,21 +342,21 @@ def test_user_no_enrollment_not_staff(
@patch('learning_assistant.views.chat_history_enabled')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
- @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseEnrollment')
@patch('learning_assistant.views.CourseMode')
def test_user_audit_enrollment_not_staff(
self,
mock_mode,
mock_enrollment,
- mock_role,
+ mock_get_user_role,
mock_assistant_waffle,
mock_history_waffle
):
mock_assistant_waffle.return_value = True
mock_history_waffle.return_value = True
- mock_role.return_value = 'student'
+ mock_get_user_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
- mock_enrollment.return_value = MagicMock(mode='audit')
+ mock_enrollment.get_enrollment.return_value = MagicMock(mode='audit')
message_count = 5
response = self.client.get(
@@ -376,7 +368,7 @@ def test_user_audit_enrollment_not_staff(
@patch('learning_assistant.views.chat_history_enabled')
@patch('learning_assistant.views.learning_assistant_enabled')
@patch('learning_assistant.views.get_user_role')
- @patch('learning_assistant.views.CourseEnrollment.get_enrollment')
+ @patch('learning_assistant.views.CourseEnrollment')
@patch('learning_assistant.views.CourseMode')
@patch('learning_assistant.views.get_course_id')
def test_learning_message_history_view_get(
@@ -384,15 +376,15 @@ def test_learning_message_history_view_get(
mock_get_course_id,
mock_mode,
mock_enrollment,
- mock_role,
+ mock_get_user_role,
mock_assistant_waffle,
mock_history_waffle,
):
mock_assistant_waffle.return_value = True
mock_history_waffle.return_value = True
- mock_role.return_value = 'student'
+ mock_get_user_role.return_value = 'student'
mock_mode.VERIFIED_MODES = ['verified']
- mock_enrollment.return_value = MagicMock(mode='verified')
+ mock_enrollment.get_enrollment.return_value = MagicMock(mode='verified')
LearningAssistantMessage.objects.create(
course_id=self.course_id,
@@ -470,3 +462,351 @@ def test_learning_message_history_view_get_disabled(
# Ensure returning an empty list
self.assertEqual(len(data), 0)
self.assertEqual(data, [])
+
+
+@ddt.ddt
+class LearningAssistantChatSummaryViewTests(LoggedInTestCase):
+ """
+ Tests for the LearningAssistantChatSummaryView
+ """
+ sys.modules['lms.djangoapps.courseware.access'] = MagicMock()
+ sys.modules['lms.djangoapps.courseware.toggles'] = MagicMock()
+ sys.modules['common.djangoapps.course_modes.models'] = MagicMock()
+ sys.modules['common.djangoapps.student.models'] = MagicMock()
+
+ def setUp(self):
+ super().setUp()
+ self.course_id = 'course-v1:edx+test+23'
+
+ @patch('learning_assistant.views.CourseKey')
+ def test_invalid_course_id(self, mock_course_key):
+ mock_course_key.from_string = MagicMock(side_effect=InvalidKeyError('foo', 'bar'))
+
+ response = self.client.get(reverse('chat-summary', kwargs={'course_run_id': self.course_id+'+invalid'}))
+
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(response.data['detail'], 'Course ID is not a valid course ID.')
+
+ @ddt.data(
+ *product(
+ [True, False], # learning assistant enabled
+ [True, False], # chat history enabled
+ ['staff', 'instructor'], # user role
+ ['verified', 'credit', 'no-id', 'audit', None], # course mode
+ [True, False], # trial available
+ [True, False], # trial expired
+ )
+ )
+ @ddt.unpack
+ @patch('learning_assistant.views.audit_trial_is_expired')
+ @patch('learning_assistant.views.chat_history_enabled')
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ @patch('learning_assistant.views.get_user_role')
+ @patch('learning_assistant.views.CourseEnrollment')
+ @patch('learning_assistant.views.CourseMode')
+ def test_chat_summary_with_access_instructor(
+ self,
+ learning_assistant_enabled_mock_value,
+ chat_history_enabled_mock_value,
+ user_role_mock_value,
+ course_mode_mock_value,
+ trial_available,
+ audit_trial_is_expired_mock_value,
+ mock_mode,
+ mock_enrollment,
+ mock_get_user_role,
+ mock_learning_assistant_enabled,
+ mock_chat_history_enabled,
+ mock_audit_trial_is_expired,
+ ):
+ # Set up mocks.
+ mock_learning_assistant_enabled.return_value = learning_assistant_enabled_mock_value
+ mock_chat_history_enabled.return_value = chat_history_enabled_mock_value
+
+ mock_get_user_role.return_value = user_role_mock_value
+
+ mock_mode.VERIFIED_MODES = ['verified']
+ mock_mode.CREDIT_MODES = ['credit']
+ mock_mode.NO_ID_PROFESSIONAL_MODE = 'no-id'
+ mock_mode.UPSELL_TO_VERIFIED_MODES = ['audit']
+
+ mock_enrollment.get_enrollment.return_value = MagicMock(mode=course_mode_mock_value)
+
+ # Set up message history data.
+ if chat_history_enabled_mock_value:
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role='user',
+ content='Older message',
+ created=date(2024, 10, 1)
+ )
+
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role='user',
+ content='Newer message',
+ created=date(2024, 10, 3)
+ )
+
+ db_messages = LearningAssistantMessage.objects.all().order_by('created')
+ db_messages_count = len(db_messages)
+
+ # Set up audit trial data.
+ mock_audit_trial_is_expired.return_value = audit_trial_is_expired_mock_value
+
+ trial_start_date = datetime(2024, 1, 1, 0, 0, 0)
+ if trial_available:
+ LearningAssistantAuditTrial.objects.create(
+ user=self.user,
+ start_date=trial_start_date,
+ )
+
+ url_kwargs = {'course_run_id': self.course_id}
+ url = reverse('chat-summary', kwargs=url_kwargs)
+
+ if chat_history_enabled_mock_value:
+ query_params = {'message_count': db_messages_count}
+ url = f"{url}?{urlencode(query_params)}"
+
+ response = self.client.get(url)
+
+ # Assert message history data is correct.
+ if chat_history_enabled_mock_value:
+ data = response.data['message_history']
+
+ # Ensure same number of entries.
+ self.assertEqual(len(data), db_messages_count)
+
+ # Ensure values are as expected.
+ for i, message in enumerate(data):
+ self.assertEqual(message['role'], db_messages[i].role)
+ self.assertEqual(message['content'], db_messages[i].content)
+ self.assertEqual(message['timestamp'], db_messages[i].created.isoformat())
+ else:
+ self.assertEqual(response.data['message_history'], [])
+
+ # Assert trial data is correct.
+ expected_trial_data = {}
+ if trial_available:
+ expected_trial_data['start_date'] = trial_start_date
+ expected_trial_data['expiration_date'] = trial_start_date + timedelta(days=14)
+
+ self.assertEqual(response.data['audit_trial'], expected_trial_data)
+
+ @ddt.data(
+ *product(
+ [True, False], # learning assistant enabled
+ [True, False], # chat history enabled
+ ['student'], # user role
+ ['verified', 'credit', 'no-id'], # course mode
+ [True, False], # trial available
+ [True, False], # trial expired
+ )
+ )
+ @ddt.unpack
+ @patch('learning_assistant.views.audit_trial_is_expired')
+ @patch('learning_assistant.views.chat_history_enabled')
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ @patch('learning_assistant.views.get_user_role')
+ @patch('learning_assistant.views.CourseEnrollment')
+ @patch('learning_assistant.views.CourseMode')
+ def test_chat_summary_with_full_access_student(
+ self,
+ learning_assistant_enabled_mock_value,
+ chat_history_enabled_mock_value,
+ user_role_mock_value,
+ course_mode_mock_value,
+ trial_available,
+ audit_trial_is_expired_mock_value,
+ mock_mode,
+ mock_enrollment,
+ mock_get_user_role,
+ mock_learning_assistant_enabled,
+ mock_chat_history_enabled,
+ mock_audit_trial_is_expired,
+ ):
+ # Set up mocks.
+ mock_learning_assistant_enabled.return_value = learning_assistant_enabled_mock_value
+ mock_chat_history_enabled.return_value = chat_history_enabled_mock_value
+
+ mock_get_user_role.return_value = user_role_mock_value
+
+ mock_mode.VERIFIED_MODES = ['verified']
+ mock_mode.CREDIT_MODES = ['credit']
+ mock_mode.NO_ID_PROFESSIONAL_MODE = 'no-id'
+ mock_mode.UPSELL_TO_VERIFIED_MODES = ['audit']
+
+ mock_enrollment.get_enrollment.return_value = MagicMock(mode=course_mode_mock_value)
+
+ # Set up message history data.
+ if chat_history_enabled_mock_value:
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role='user',
+ content='Older message',
+ created=date(2024, 10, 1)
+ )
+
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role='user',
+ content='Newer message',
+ created=date(2024, 10, 3)
+ )
+
+ db_messages = LearningAssistantMessage.objects.all().order_by('created')
+ db_messages_count = len(db_messages)
+
+ # Set up audit trial data.
+ mock_audit_trial_is_expired.return_value = audit_trial_is_expired_mock_value
+
+ trial_start_date = datetime(2024, 1, 1, 0, 0, 0)
+ if trial_available:
+ LearningAssistantAuditTrial.objects.create(
+ user=self.user,
+ start_date=trial_start_date,
+ )
+
+ url_kwargs = {'course_run_id': self.course_id}
+ url = reverse('chat-summary', kwargs=url_kwargs)
+
+ if chat_history_enabled_mock_value:
+ query_params = {'message_count': db_messages_count}
+ url = f"{url}?{urlencode(query_params)}"
+
+ response = self.client.get(url)
+
+ # Assert message history data is correct.
+ if chat_history_enabled_mock_value:
+ data = response.data['message_history']
+
+ # Ensure same number of entries.
+ self.assertEqual(len(data), db_messages_count)
+
+ # Ensure values are as expected.
+ for i, message in enumerate(data):
+ self.assertEqual(message['role'], db_messages[i].role)
+ self.assertEqual(message['content'], db_messages[i].content)
+ self.assertEqual(message['timestamp'], db_messages[i].created.isoformat())
+ else:
+ self.assertEqual(response.data['message_history'], [])
+
+ # Assert trial data is correct.
+ expected_trial_data = {}
+ if trial_available:
+ expected_trial_data['start_date'] = trial_start_date
+ expected_trial_data['expiration_date'] = trial_start_date + timedelta(days=14)
+
+ self.assertEqual(response.data['audit_trial'], expected_trial_data)
+
+ @ddt.data(
+ *product(
+ [True, False], # learning assistant enabled
+ [True, False], # chat history enabled
+ ['student'], # user role
+ ['audit'], # course mode
+ [True, False], # trial available
+ [True, False], # trial expired
+ )
+ )
+ @ddt.unpack
+ @patch('learning_assistant.views.audit_trial_is_expired')
+ @patch('learning_assistant.views.chat_history_enabled')
+ @patch('learning_assistant.views.learning_assistant_enabled')
+ @patch('learning_assistant.views.get_user_role')
+ @patch('learning_assistant.views.CourseEnrollment')
+ @patch('learning_assistant.views.CourseMode')
+ def test_chat_summary_with_trial_access_student(
+ self,
+ learning_assistant_enabled_mock_value,
+ chat_history_enabled_mock_value,
+ user_role_mock_value,
+ course_mode_mock_value,
+ trial_available,
+ audit_trial_is_expired_mock_value,
+ mock_mode,
+ mock_enrollment,
+ mock_get_user_role,
+ mock_learning_assistant_enabled,
+ mock_chat_history_enabled,
+ mock_audit_trial_is_expired,
+ ):
+ # Set up mocks.
+ mock_learning_assistant_enabled.return_value = learning_assistant_enabled_mock_value
+ mock_chat_history_enabled.return_value = chat_history_enabled_mock_value
+
+ mock_get_user_role.return_value = user_role_mock_value
+
+ mock_mode.VERIFIED_MODES = ['verified']
+ mock_mode.CREDIT_MODES = ['credit']
+ mock_mode.NO_ID_PROFESSIONAL_MODE = 'no-id'
+ mock_mode.UPSELL_TO_VERIFIED_MODES = ['audit']
+
+ mock_enrollment.get_enrollment.return_value = MagicMock(mode=course_mode_mock_value)
+
+ # Set up message history data.
+ if chat_history_enabled_mock_value:
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role='user',
+ content='Older message',
+ created=date(2024, 10, 1)
+ )
+
+ LearningAssistantMessage.objects.create(
+ course_id=self.course_id,
+ user=self.user,
+ role='user',
+ content='Newer message',
+ created=date(2024, 10, 3)
+ )
+
+ db_messages = LearningAssistantMessage.objects.all().order_by('created')
+ db_messages_count = len(db_messages)
+
+ # Set up audit trial data.
+ mock_audit_trial_is_expired.return_value = audit_trial_is_expired_mock_value
+
+ trial_start_date = datetime(2024, 1, 1, 0, 0, 0)
+ if trial_available:
+ LearningAssistantAuditTrial.objects.create(
+ user=self.user,
+ start_date=trial_start_date,
+ )
+
+ url_kwargs = {'course_run_id': self.course_id}
+ url = reverse('chat-summary', kwargs=url_kwargs)
+
+ if chat_history_enabled_mock_value:
+ query_params = {'message_count': db_messages_count}
+ url = f"{url}?{urlencode(query_params)}"
+
+ response = self.client.get(url)
+
+ # Assert message history data is correct.
+ if chat_history_enabled_mock_value and trial_available and not audit_trial_is_expired_mock_value:
+ data = response.data['message_history']
+
+ # Ensure same number of entries.
+ self.assertEqual(len(data), db_messages_count)
+
+ # Ensure values are as expected.
+ for i, message in enumerate(data):
+ self.assertEqual(message['role'], db_messages[i].role)
+ self.assertEqual(message['content'], db_messages[i].content)
+ self.assertEqual(message['timestamp'], db_messages[i].created.isoformat())
+ else:
+ self.assertEqual(response.data['message_history'], [])
+
+ # Assert trial data is correct.
+ expected_trial_data = {}
+ if trial_available:
+ expected_trial_data['start_date'] = trial_start_date
+ expected_trial_data['expiration_date'] = trial_start_date + timedelta(days=14)
+
+ self.assertEqual(response.data['audit_trial'], expected_trial_data)