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. +

> 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 = '''\ + + ''' + expected_text = dedent('') + text = html_to_text(html_content) + self.assertEqual(text, expected_text) + + +if __name__ == '__main__': + unittest.main() From 7213dba7c20155997510a875c2dc907ca93d7467 Mon Sep 17 00:00:00 2001 From: edX requirements bot <49161187+edx-requirements-bot@users.noreply.github.com> Date: Fri, 1 Dec 2023 14:18:51 -0500 Subject: [PATCH 016/104] chore: Updating Python Requirements (#36) --- requirements/base.txt | 8 ++++---- requirements/ci.txt | 6 +++--- requirements/dev.txt | 16 ++++++++-------- requirements/doc.txt | 10 +++++----- requirements/pip-tools.txt | 2 +- requirements/pip.txt | 4 ++-- requirements/quality.txt | 8 ++++---- requirements/test.txt | 8 ++++---- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index d5b64f8..437fba7 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.5 +cryptography==41.0.7 # 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.8.0 +edx-django-utils==5.9.0 # via # edx-drf-extensions # edx-rest-api-client -edx-drf-extensions==8.13.1 +edx-drf-extensions==9.0.0 # via -r requirements/base.in edx-opaque-keys==2.5.1 # via @@ -56,7 +56,7 @@ edx-opaque-keys==2.5.1 # edx-drf-extensions edx-rest-api-client==5.6.1 # via -r requirements/base.in -idna==3.4 +idna==3.6 # via requests newrelic==9.2.0 # via edx-django-utils diff --git a/requirements/ci.txt b/requirements/ci.txt index 04b193f..fe4aa1f 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -24,7 +24,7 @@ filelock==3.13.1 # via # tox # virtualenv -idna==3.4 +idna==3.6 # via requests packaging==23.2 # via @@ -45,9 +45,9 @@ tomli==2.0.1 # via # pyproject-api # tox -tox==4.11.3 +tox==4.11.4 # via -r requirements/ci.in urllib3==2.1.0 # via requests -virtualenv==20.24.6 +virtualenv==20.24.7 # via tox diff --git a/requirements/dev.txt b/requirements/dev.txt index 9a5893c..202708a 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -71,7 +71,7 @@ coverage[toml]==7.3.2 # codecov # coverage # pytest-cov -cryptography==41.0.5 +cryptography==41.0.7 # via # -r requirements/quality.txt # pyjwt @@ -119,12 +119,12 @@ drf-jwt==1.19.2 # via # -r requirements/quality.txt # edx-drf-extensions -edx-django-utils==5.8.0 +edx-django-utils==5.9.0 # via # -r requirements/quality.txt # edx-drf-extensions # edx-rest-api-client -edx-drf-extensions==8.13.1 +edx-drf-extensions==9.0.0 # via -r requirements/quality.txt edx-i18n-tools==1.3.0 # via -r requirements/dev.in @@ -145,7 +145,7 @@ filelock==3.13.1 # -r requirements/ci.txt # tox # virtualenv -idna==3.4 +idna==3.6 # via # -r requirements/ci.txt # -r requirements/quality.txt @@ -227,7 +227,7 @@ pycparser==2.21 # cffi pydocstyle==6.3.0 # via -r requirements/quality.txt -pygments==2.17.1 +pygments==2.17.2 # via diff-cover pyjwt[crypto]==2.8.0 # via @@ -354,7 +354,7 @@ tomlkit==0.12.3 # via # -r requirements/quality.txt # pylint -tox==4.11.3 +tox==4.11.4 # via -r requirements/ci.txt typing-extensions==4.8.0 # via @@ -369,11 +369,11 @@ urllib3==2.1.0 # -r requirements/quality.txt # requests # responses -virtualenv==20.24.6 +virtualenv==20.24.7 # via # -r requirements/ci.txt # tox -wheel==0.41.3 +wheel==0.42.0 # via # -r requirements/pip-tools.txt # pip-tools diff --git a/requirements/doc.txt b/requirements/doc.txt index 4c8f896..5d7f8a1 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.5 +cryptography==41.0.7 # via # -r requirements/test.txt # pyjwt @@ -85,12 +85,12 @@ drf-jwt==1.19.2 # via # -r requirements/test.txt # edx-drf-extensions -edx-django-utils==5.8.0 +edx-django-utils==5.9.0 # via # -r requirements/test.txt # edx-drf-extensions # edx-rest-api-client -edx-drf-extensions==8.13.1 +edx-drf-extensions==9.0.0 # via -r requirements/test.txt edx-opaque-keys==2.5.1 # via @@ -102,7 +102,7 @@ exceptiongroup==1.2.0 # via # -r requirements/test.txt # pytest -idna==3.4 +idna==3.6 # via # -r requirements/test.txt # requests @@ -173,7 +173,7 @@ pycparser==2.21 # via # -r requirements/test.txt # cffi -pygments==2.17.1 +pygments==2.17.2 # via # doc8 # readme-renderer diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt index ea34731..41203fd 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.3 +wheel==0.42.0 # via pip-tools zipp==3.17.0 # via importlib-metadata diff --git a/requirements/pip.txt b/requirements/pip.txt index 9465bd4..14cb99c 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -4,11 +4,11 @@ # # make upgrade # -wheel==0.41.3 +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 # via -r requirements/pip.in -setuptools==69.0.1 +setuptools==69.0.2 # via -r requirements/pip.in diff --git a/requirements/quality.txt b/requirements/quality.txt index e308ffc..91ca32e 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.5 +cryptography==41.0.7 # via # -r requirements/test.txt # pyjwt @@ -82,12 +82,12 @@ drf-jwt==1.19.2 # via # -r requirements/test.txt # edx-drf-extensions -edx-django-utils==5.8.0 +edx-django-utils==5.9.0 # via # -r requirements/test.txt # edx-drf-extensions # edx-rest-api-client -edx-drf-extensions==8.13.1 +edx-drf-extensions==9.0.0 # via -r requirements/test.txt edx-lint==5.3.6 # via -r requirements/quality.in @@ -101,7 +101,7 @@ exceptiongroup==1.2.0 # via # -r requirements/test.txt # pytest -idna==3.4 +idna==3.6 # via # -r requirements/test.txt # requests diff --git a/requirements/test.txt b/requirements/test.txt index 030e8e7..56f20ad 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.5 +cryptography==41.0.7 # via # -r requirements/base.txt # pyjwt @@ -68,12 +68,12 @@ drf-jwt==1.19.2 # via # -r requirements/base.txt # edx-drf-extensions -edx-django-utils==5.8.0 +edx-django-utils==5.9.0 # via # -r requirements/base.txt # edx-drf-extensions # edx-rest-api-client -edx-drf-extensions==8.13.1 +edx-drf-extensions==9.0.0 # via -r requirements/base.txt edx-opaque-keys==2.5.1 # via @@ -83,7 +83,7 @@ edx-rest-api-client==5.6.1 # via -r requirements/base.txt exceptiongroup==1.2.0 # via pytest -idna==3.4 +idna==3.6 # via # -r requirements/base.txt # requests From 0000980eea42b8f021c2c6e52e51ce48135baaae Mon Sep 17 00:00:00 2001 From: Alie Langston 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)