Merge branch 'master' into final-dj52
This commit is contained in:
23
.github/workflows/docker-compose.yml.mysqldbdump
vendored
23
.github/workflows/docker-compose.yml.mysqldbdump
vendored
@@ -1,23 +0,0 @@
|
||||
version: '3'
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
container_name: edx.devstack.mysql80
|
||||
ports:
|
||||
- '3306:3306'
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ""
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
|
||||
volumes:
|
||||
- ./init:/docker-entrypoint-initdb.d
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
edxapp:
|
||||
image: edxops/edxapp:latest
|
||||
command: bash -c 'source /edx/app/edxapp/edxapp_env && cd /edx/app/edxapp/edx-platform/ && make migrate'
|
||||
volumes:
|
||||
- ../../:/edx/app/edxapp/edx-platform
|
||||
depends_on:
|
||||
- mysql
|
||||
3
.github/workflows/init/01.sql
vendored
3
.github/workflows/init/01.sql
vendored
@@ -1,3 +0,0 @@
|
||||
CREATE DATABASE IF NOT EXISTS `edxapp`;
|
||||
CREATE DATABASE IF NOT EXISTS `edxapp_csmh`;
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'edxapp001'@'%' IDENTIFIED BY 'password';
|
||||
@@ -46,6 +46,7 @@ class ModulestoreMigrationSerializer(serializers.ModelSerializer):
|
||||
help_text="The target collection slug within the library to import into. Optional.",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default=None,
|
||||
)
|
||||
forward_source_to_target = serializers.BooleanField(
|
||||
help_text="Forward references of this block source over to the target of this block migration.",
|
||||
|
||||
@@ -9,6 +9,7 @@ import typing as t
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from itertools import groupby
|
||||
|
||||
from celery import shared_task
|
||||
from celery.utils.log import get_task_logger
|
||||
@@ -20,8 +21,11 @@ from lxml.etree import _ElementTree as XmlTree
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from opaque_keys.edx.locator import (
|
||||
CourseLocator, LibraryLocator,
|
||||
LibraryLocatorV2, LibraryUsageLocatorV2, LibraryContainerLocator
|
||||
CourseLocator,
|
||||
LibraryContainerLocator,
|
||||
LibraryLocator,
|
||||
LibraryLocatorV2,
|
||||
LibraryUsageLocatorV2
|
||||
)
|
||||
from openedx_learning.api import authoring as authoring_api
|
||||
from openedx_learning.api.authoring_models import (
|
||||
@@ -30,21 +34,20 @@ from openedx_learning.api.authoring_models import (
|
||||
ComponentType,
|
||||
LearningPackage,
|
||||
PublishableEntity,
|
||||
PublishableEntityVersion,
|
||||
PublishableEntityVersion
|
||||
)
|
||||
from user_tasks.tasks import UserTask, UserTaskStatus
|
||||
|
||||
from openedx.core.djangoapps.content_libraries.api import ContainerType, get_library
|
||||
from common.djangoapps.split_modulestore_django.models import SplitModulestoreCourseIndex
|
||||
from openedx.core.djangoapps.content_libraries import api as libraries_api
|
||||
from openedx.core.djangoapps.content_libraries.api import ContainerType, get_library
|
||||
from openedx.core.djangoapps.content_staging import api as staging_api
|
||||
from xmodule.modulestore import exceptions as modulestore_exceptions
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from common.djangoapps.split_modulestore_django.models import SplitModulestoreCourseIndex
|
||||
|
||||
from .constants import CONTENT_STAGING_PURPOSE_TEMPLATE
|
||||
from .data import CompositionLevel, RepeatHandlingStrategy
|
||||
from .models import ModulestoreSource, ModulestoreMigration, ModulestoreBlockSource, ModulestoreBlockMigration
|
||||
|
||||
from .models import ModulestoreBlockMigration, ModulestoreBlockSource, ModulestoreMigration, ModulestoreSource
|
||||
|
||||
log = get_task_logger(__name__)
|
||||
|
||||
@@ -89,7 +92,7 @@ class _MigrationContext:
|
||||
Context for the migration process.
|
||||
"""
|
||||
existing_source_to_target_keys: dict[ # Note: It's intended to be mutable to reflect changes during migration.
|
||||
UsageKey, PublishableEntity
|
||||
UsageKey, list[PublishableEntity]
|
||||
]
|
||||
target_package_id: int
|
||||
target_library_key: LibraryLocatorV2
|
||||
@@ -105,16 +108,30 @@ class _MigrationContext:
|
||||
return source_key in self.existing_source_to_target_keys
|
||||
|
||||
def get_existing_target(self, source_key: UsageKey) -> PublishableEntity:
|
||||
return self.existing_source_to_target_keys[source_key]
|
||||
"""
|
||||
Get the target entity for a given source key.
|
||||
|
||||
If the source key is already migrated, return the FIRST target entity.
|
||||
If the source key is not found, raise a KeyError.
|
||||
"""
|
||||
if source_key not in self.existing_source_to_target_keys:
|
||||
raise KeyError(f"Source key {source_key} not found in existing source to target keys")
|
||||
|
||||
# NOTE: This is a list of PublishableEntities, but we always return the first one.
|
||||
return self.existing_source_to_target_keys[source_key][0]
|
||||
|
||||
def add_migration(self, source_key: UsageKey, target: PublishableEntity) -> None:
|
||||
"""Update the context with a new migration (keeps it current)"""
|
||||
self.existing_source_to_target_keys[source_key] = target
|
||||
if source_key not in self.existing_source_to_target_keys:
|
||||
self.existing_source_to_target_keys[source_key] = [target]
|
||||
else:
|
||||
self.existing_source_to_target_keys[source_key].append(target)
|
||||
|
||||
def get_existing_target_entity_keys(self, base_key: str) -> set[str]:
|
||||
return set(
|
||||
publishable_entity.key for _, publishable_entity in
|
||||
self.existing_source_to_target_keys.items()
|
||||
publishable_entity.key
|
||||
for publishable_entity_list in self.existing_source_to_target_keys.values()
|
||||
for publishable_entity in publishable_entity_list
|
||||
if publishable_entity.key.startswith(base_key)
|
||||
)
|
||||
|
||||
@@ -285,10 +302,13 @@ def migrate_from_modulestore(
|
||||
# a given LearningPackage.
|
||||
# We use this mapping to ensure that we don't create duplicate
|
||||
# PublishableEntities during the migration process for a given LearningPackage.
|
||||
existing_source_to_target_keys: dict[UsageKey, list[PublishableEntity]] = {}
|
||||
modulestore_blocks = (
|
||||
ModulestoreBlockMigration.objects.filter(overall_migration__target=migration.target.id).order_by("source__key")
|
||||
)
|
||||
existing_source_to_target_keys = {
|
||||
block.source.key: block.target for block in ModulestoreBlockMigration.objects.filter(
|
||||
overall_migration__target=migration.target.id
|
||||
)
|
||||
source_key: list(block.target for block in group) for source_key, group in groupby(
|
||||
modulestore_blocks, key=lambda x: x.source.key)
|
||||
}
|
||||
|
||||
migration_context = _MigrationContext(
|
||||
@@ -657,7 +677,7 @@ def _get_distinct_target_usage_key(
|
||||
# Check if we already processed this block and we are not forking. If we are forking, we will
|
||||
# want a new target key.
|
||||
if context.is_already_migrated(source_key) and not context.should_fork_strategy:
|
||||
log.debug(f"Block {source_key} already exists, reusing existing target")
|
||||
log.debug(f"Block {source_key} already exists, reusing first existing target")
|
||||
existing_target = context.get_existing_target(source_key)
|
||||
block_id = existing_target.component.local_key
|
||||
|
||||
|
||||
@@ -276,6 +276,43 @@ class TestModulestoreMigratorAPI(LibraryTestCase):
|
||||
)
|
||||
assert second_component.display_name == "Updated Block"
|
||||
|
||||
# Update the block again, changing its name
|
||||
library_block.display_name = "Updated Block Again"
|
||||
self.store.update_item(library_block, user.id)
|
||||
|
||||
# Migrate again using the Fork strategy
|
||||
api.start_migration_to_library(
|
||||
user=user,
|
||||
source_key=source.key,
|
||||
target_library_key=self.library_v2.library_key,
|
||||
composition_level=CompositionLevel.Component.value,
|
||||
repeat_handling_strategy=RepeatHandlingStrategy.Fork.value,
|
||||
preserve_url_slugs=True,
|
||||
forward_source_to_target=False,
|
||||
)
|
||||
|
||||
modulestoremigration = ModulestoreMigration.objects.last()
|
||||
assert modulestoremigration is not None
|
||||
assert modulestoremigration.repeat_handling_strategy == RepeatHandlingStrategy.Fork.value
|
||||
|
||||
migrated_components_fork = lib_api.get_library_components(self.library_v2.library_key)
|
||||
assert len(migrated_components_fork) == 3
|
||||
|
||||
first_component = lib_api.LibraryXBlockMetadata.from_component(
|
||||
self.library_v2.library_key, migrated_components_fork[0]
|
||||
)
|
||||
assert first_component.display_name == "Original Block"
|
||||
|
||||
second_component = lib_api.LibraryXBlockMetadata.from_component(
|
||||
self.library_v2.library_key, migrated_components_fork[1]
|
||||
)
|
||||
assert second_component.display_name == "Updated Block"
|
||||
|
||||
third_component = lib_api.LibraryXBlockMetadata.from_component(
|
||||
self.library_v2.library_key, migrated_components_fork[2]
|
||||
)
|
||||
assert third_component.display_name == "Updated Block Again"
|
||||
|
||||
def test_get_migration_info(self):
|
||||
"""
|
||||
Test that the API can retrieve migration info.
|
||||
|
||||
@@ -447,7 +447,7 @@ class TestMigrateFromModulestore(ModuleStoreTestCase):
|
||||
title="test_problem"
|
||||
)
|
||||
|
||||
context.existing_source_to_target_keys[source_key] = first_result.entity
|
||||
context.existing_source_to_target_keys[source_key] = [first_result.entity]
|
||||
|
||||
second_result = _migrate_component(
|
||||
context=context,
|
||||
@@ -489,7 +489,7 @@ class TestMigrateFromModulestore(ModuleStoreTestCase):
|
||||
title="test_problem"
|
||||
)
|
||||
|
||||
context.existing_source_to_target_keys[source_key_1] = first_result.entity
|
||||
context.existing_source_to_target_keys[source_key_1] = [first_result.entity]
|
||||
|
||||
second_result = _migrate_component(
|
||||
context=context,
|
||||
@@ -527,7 +527,7 @@ class TestMigrateFromModulestore(ModuleStoreTestCase):
|
||||
title="original"
|
||||
)
|
||||
|
||||
context.existing_source_to_target_keys[source_key] = first_result.entity
|
||||
context.existing_source_to_target_keys[source_key] = [first_result.entity]
|
||||
|
||||
updated_olx = '<problem display_name="Updated"><multiplechoiceresponse></multiplechoiceresponse></problem>'
|
||||
second_result = _migrate_component(
|
||||
@@ -708,7 +708,7 @@ class TestMigrateFromModulestore(ModuleStoreTestCase):
|
||||
title="test_problem"
|
||||
)
|
||||
|
||||
context.existing_source_to_target_keys[source_key] = first_result.entity
|
||||
context.existing_source_to_target_keys[source_key] = [first_result.entity]
|
||||
|
||||
second_result = _migrate_component(
|
||||
context=context,
|
||||
@@ -863,7 +863,7 @@ class TestMigrateFromModulestore(ModuleStoreTestCase):
|
||||
children=[],
|
||||
)
|
||||
|
||||
context.existing_source_to_target_keys[source_key] = first_result.entity
|
||||
context.existing_source_to_target_keys[source_key] = [first_result.entity]
|
||||
|
||||
second_result = _migrate_container(
|
||||
context=context,
|
||||
@@ -909,7 +909,7 @@ class TestMigrateFromModulestore(ModuleStoreTestCase):
|
||||
children=[],
|
||||
)
|
||||
|
||||
context.existing_source_to_target_keys[source_key_1] = first_result.entity
|
||||
context.existing_source_to_target_keys[source_key_1] = [first_result.entity]
|
||||
|
||||
second_result = _migrate_container(
|
||||
context=context,
|
||||
@@ -969,7 +969,7 @@ class TestMigrateFromModulestore(ModuleStoreTestCase):
|
||||
children=[],
|
||||
)
|
||||
|
||||
context.existing_source_to_target_keys[source_key] = first_result.entity
|
||||
context.existing_source_to_target_keys[source_key] = [first_result.entity]
|
||||
|
||||
second_result = _migrate_container(
|
||||
context=context,
|
||||
|
||||
@@ -139,12 +139,6 @@ if STATIC_ROOT_BASE:
|
||||
|
||||
DATA_DIR = path(DATA_DIR)
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
# TODO: bbeggs remove this before prod, temp fix to get load testing running
|
||||
"*",
|
||||
CMS_BASE,
|
||||
]
|
||||
|
||||
# Cache used for location mapping -- called many times with the same key/value
|
||||
# in a given request.
|
||||
if 'loc_cache' not in CACHES:
|
||||
|
||||
@@ -533,15 +533,19 @@ class UpstreamTestCase(ModuleStoreTestCase):
|
||||
"""
|
||||
Does sever_upstream_link correctly disconnect a block from its upstream?
|
||||
"""
|
||||
# Start with a course block that is linked+synced to a content library block.
|
||||
# Start with a course block that is linked+synced to a content library block
|
||||
# and has a customizred title.
|
||||
downstream = BlockFactory.create(category='html', parent=self.unit, upstream=str(self.upstream_key))
|
||||
sync_from_upstream_block(downstream, self.user)
|
||||
downstream.display_name = "Downstream Title"
|
||||
save_xblock_with_callback(downstream, self.user)
|
||||
|
||||
# (sanity checks)
|
||||
assert downstream.upstream == str(self.upstream_key)
|
||||
assert downstream.upstream_version == 2
|
||||
assert downstream.upstream_display_name == "Upstream Title V2"
|
||||
assert downstream.display_name == "Upstream Title V2"
|
||||
assert downstream.display_name == "Downstream Title"
|
||||
assert downstream.downstream_customized == ["display_name"]
|
||||
assert downstream.data == "<html><body>Upstream content V2</body></html>"
|
||||
assert downstream.copied_from_block is None
|
||||
|
||||
@@ -552,14 +556,21 @@ class UpstreamTestCase(ModuleStoreTestCase):
|
||||
assert downstream.upstream is None
|
||||
assert downstream.upstream_version is None
|
||||
assert downstream.upstream_display_name is None
|
||||
assert downstream.downstream_customized == []
|
||||
|
||||
# BUT, the content which was synced into the upstream remains.
|
||||
assert downstream.display_name == "Upstream Title V2"
|
||||
# BUT, the content remains.
|
||||
assert downstream.display_name == "Downstream Title"
|
||||
assert downstream.data == "<html><body>Upstream content V2</body></html>"
|
||||
|
||||
# AND, we have recorded the old upstream as our copied_from_block.
|
||||
assert downstream.copied_from_block == str(self.upstream_key)
|
||||
|
||||
# Finally... unlike an upstream-linked block, our unlinked block should not
|
||||
# have its downstream_customized updated when the title changes.
|
||||
downstream.display_name = "Downstream Title II"
|
||||
save_xblock_with_callback(downstream, self.user)
|
||||
assert downstream.downstream_customized == []
|
||||
|
||||
def test_sync_library_block_tags(self):
|
||||
upstream_lib_block_key = libs.create_library_block(self.library.key, "html", "upstream").usage_key
|
||||
upstream_lib_block = xblock.load_block(upstream_lib_block_key, self.user)
|
||||
|
||||
@@ -386,6 +386,7 @@ def sever_upstream_link(downstream: XBlock) -> list[XBlock]:
|
||||
downstream.copied_from_block = downstream.upstream
|
||||
downstream.upstream = None
|
||||
downstream.upstream_version = None
|
||||
downstream.downstream_customized = []
|
||||
for _, fetched_upstream_field in downstream.get_customizable_fields().items():
|
||||
# Downstream-only fields don't have an upstream fetch field
|
||||
if fetched_upstream_field is None:
|
||||
@@ -527,6 +528,10 @@ class UpstreamSyncMixin(XBlockMixin):
|
||||
Update `downstream_customized` when a customizable field is modified.
|
||||
"""
|
||||
super().editor_saved(user, old_metadata, old_content)
|
||||
if not self.upstream:
|
||||
# If a block does not have an upstream, then we do not need to track its
|
||||
# customizations.
|
||||
return
|
||||
customizable_fields = self.get_customizable_fields()
|
||||
new_data = (
|
||||
self.get_explicitly_set_fields_by_scope(Scope.settings)
|
||||
|
||||
@@ -44,7 +44,7 @@ from eventtracking import tracker
|
||||
from model_utils.models import TimeStampedModel
|
||||
from opaque_keys.edx.django.models import CourseKeyField, LearningContextKeyField
|
||||
from pytz import UTC, timezone
|
||||
from user_util import user_util
|
||||
from openedx.core.lib import user_util
|
||||
|
||||
import openedx.core.djangoapps.django_comment_common.comment_client as cc
|
||||
from common.djangoapps.util.model_utils import emit_field_changed_events, get_changed_fields_dict
|
||||
|
||||
@@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from model_utils.models import TimeStampedModel
|
||||
from opaque_keys.edx.django.models import CourseKeyField
|
||||
from simple_history.models import HistoricalRecords
|
||||
from user_util import user_util
|
||||
from openedx.core.lib import user_util
|
||||
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
|
||||
|
||||
@@ -141,11 +141,6 @@ SESSION_COOKIE_SAMESITE = DCS_SESSION_COOKIE_SAMESITE
|
||||
for feature, value in _YAML_TOKENS.get('FEATURES', {}).items():
|
||||
FEATURES[feature] = value
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
"*",
|
||||
_YAML_TOKENS.get('LMS_BASE'),
|
||||
]
|
||||
|
||||
# Cache used for location mapping -- called many times with the same key/value
|
||||
# in a given request.
|
||||
if 'loc_cache' not in CACHES:
|
||||
|
||||
@@ -41,9 +41,10 @@ could be promoted to the core XBlock API and made generic.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field as dataclass_field
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import field as dataclass_field
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser, AnonymousUser, Group
|
||||
@@ -53,29 +54,24 @@ from django.db import IntegrityError, transaction
|
||||
from django.db.models import Q, QuerySet
|
||||
from django.utils.translation import gettext as _
|
||||
from opaque_keys.edx.locator import LibraryLocatorV2, LibraryUsageLocatorV2
|
||||
from openedx_events.content_authoring.data import (
|
||||
ContentLibraryData,
|
||||
)
|
||||
from openedx_events.content_authoring.data import ContentLibraryData
|
||||
from openedx_events.content_authoring.signals import (
|
||||
CONTENT_LIBRARY_CREATED,
|
||||
CONTENT_LIBRARY_DELETED,
|
||||
CONTENT_LIBRARY_UPDATED,
|
||||
CONTENT_LIBRARY_UPDATED
|
||||
)
|
||||
from openedx_learning.api import authoring as authoring_api
|
||||
from openedx_learning.api.authoring_models import Component
|
||||
from organizations.models import Organization
|
||||
from user_tasks.models import UserTaskArtifact, UserTaskStatus
|
||||
from xblock.core import XBlock
|
||||
|
||||
from openedx.core.types import User as UserType
|
||||
|
||||
from .. import permissions
|
||||
from .. import permissions, tasks
|
||||
from ..constants import ALL_RIGHTS_RESERVED
|
||||
from ..models import ContentLibrary, ContentLibraryPermission
|
||||
from .. import tasks
|
||||
from .exceptions import (
|
||||
LibraryAlreadyExists,
|
||||
LibraryPermissionIntegrityError,
|
||||
)
|
||||
from .exceptions import LibraryAlreadyExists, LibraryPermissionIntegrityError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -105,6 +101,7 @@ __all__ = [
|
||||
"get_allowed_block_types",
|
||||
"publish_changes",
|
||||
"revert_changes",
|
||||
"get_backup_task_status",
|
||||
]
|
||||
|
||||
|
||||
@@ -692,3 +689,30 @@ def revert_changes(library_key: LibraryLocatorV2, user_id: int | None = None) ->
|
||||
|
||||
# Call the event handlers as needed.
|
||||
tasks.wait_for_post_revert_events(draft_change_log, library_key)
|
||||
|
||||
|
||||
def get_backup_task_status(
|
||||
user_id: int,
|
||||
task_id: str
|
||||
) -> dict | None:
|
||||
"""
|
||||
Get the status of a library backup task.
|
||||
|
||||
Returns a dictionary with the following keys:
|
||||
- state: One of "Pending", "Exporting", "Succeeded", "Failed"
|
||||
- url: If state is "Succeeded", the URL where the exported .zip file can be downloaded. Otherwise, None.
|
||||
If no task is found, returns None.
|
||||
"""
|
||||
|
||||
try:
|
||||
task_status = UserTaskStatus.objects.get(task_id=task_id, user_id=user_id)
|
||||
except UserTaskStatus.DoesNotExist:
|
||||
return None
|
||||
|
||||
result = {'state': task_status.state, 'url': None}
|
||||
|
||||
if task_status.state == UserTaskStatus.SUCCEEDED:
|
||||
artifact = UserTaskArtifact.objects.get(status=task_status, name='Output')
|
||||
result['url'] = artifact.file.storage.url(artifact.file.name)
|
||||
|
||||
return result
|
||||
|
||||
@@ -66,6 +66,7 @@ import itertools
|
||||
import json
|
||||
import logging
|
||||
|
||||
import edx_api_doc_tools as apidocs
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate, get_user_model, login
|
||||
from django.contrib.auth.models import Group
|
||||
@@ -78,14 +79,12 @@ from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic.base import TemplateResponseMixin, View
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from pylti1p3.contrib.django import DjangoCacheDataStorage, DjangoDbToolConf, DjangoMessageLaunch, DjangoOIDCLogin
|
||||
from pylti1p3.exception import LtiException, OIDCException
|
||||
|
||||
import edx_api_doc_tools as apidocs
|
||||
from opaque_keys.edx.locator import LibraryLocatorV2, LibraryUsageLocatorV2
|
||||
from organizations.api import ensure_organization
|
||||
from organizations.exceptions import InvalidOrganizationException
|
||||
from organizations.models import Organization
|
||||
from pylti1p3.contrib.django import DjangoCacheDataStorage, DjangoDbToolConf, DjangoMessageLaunch, DjangoOIDCLogin
|
||||
from pylti1p3.exception import LtiException, OIDCException
|
||||
from rest_framework import status
|
||||
from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError
|
||||
from rest_framework.generics import GenericAPIView
|
||||
@@ -93,12 +92,15 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
import openedx.core.djangoapps.site_configuration.helpers as configuration_helpers
|
||||
from cms.djangoapps.contentstore.views.course import (
|
||||
get_allowed_organizations_for_libraries,
|
||||
user_can_create_organizations,
|
||||
user_can_create_organizations
|
||||
)
|
||||
from openedx.core.djangoapps.content_libraries import api, permissions
|
||||
from openedx.core.djangoapps.content_libraries.api.libraries import get_backup_task_status
|
||||
from openedx.core.djangoapps.content_libraries.rest_api.serializers import (
|
||||
ContentLibraryAddPermissionByEmailSerializer,
|
||||
ContentLibraryBlockImportTaskCreateSerializer,
|
||||
ContentLibraryBlockImportTaskSerializer,
|
||||
ContentLibraryFilterSerializer,
|
||||
@@ -106,20 +108,20 @@ from openedx.core.djangoapps.content_libraries.rest_api.serializers import (
|
||||
ContentLibraryPermissionLevelSerializer,
|
||||
ContentLibraryPermissionSerializer,
|
||||
ContentLibraryUpdateSerializer,
|
||||
LibraryBackupResponseSerializer,
|
||||
LibraryBackupTaskStatusSerializer,
|
||||
LibraryXBlockCreationSerializer,
|
||||
LibraryXBlockMetadataSerializer,
|
||||
LibraryXBlockTypeSerializer,
|
||||
ContentLibraryAddPermissionByEmailSerializer,
|
||||
PublishableItemSerializer,
|
||||
PublishableItemSerializer
|
||||
)
|
||||
import openedx.core.djangoapps.site_configuration.helpers as configuration_helpers
|
||||
from openedx.core.lib.api.view_utils import view_auth_classes
|
||||
from openedx.core.djangoapps.content_libraries.tasks import backup_library
|
||||
from openedx.core.djangoapps.safe_sessions.middleware import mark_user_change_as_expected
|
||||
from openedx.core.djangoapps.xblock import api as xblock_api
|
||||
from openedx.core.lib.api.view_utils import view_auth_classes
|
||||
|
||||
from .utils import convert_exceptions
|
||||
from ..models import ContentLibrary, LtiGradedResource, LtiProfile
|
||||
|
||||
from .utils import convert_exceptions
|
||||
|
||||
User = get_user_model()
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -685,6 +687,109 @@ class LibraryImportTaskViewSet(GenericViewSet):
|
||||
return Response(ContentLibraryBlockImportTaskSerializer(import_task).data)
|
||||
|
||||
|
||||
# Library Backup Views
|
||||
# ====================
|
||||
|
||||
@method_decorator(non_atomic_requests, name="dispatch")
|
||||
@view_auth_classes()
|
||||
class LibraryBackupView(APIView):
|
||||
"""
|
||||
**Use Case**
|
||||
* Start an asynchronous task to back up the content of a library to a .zip file
|
||||
* Get a status on an asynchronous export task
|
||||
|
||||
**Example Requests**
|
||||
POST /api/libraries/v2/{library_id}/backup/
|
||||
GET /api/libraries/v2/{library_id}/backup/?task_id={task_id}
|
||||
|
||||
**POST Response Values**
|
||||
|
||||
If the import task is started successfully, an HTTP 200 "OK" response is
|
||||
returned.
|
||||
|
||||
The HTTP 200 response has the following values:
|
||||
|
||||
* task_id: UUID of the created task, usable for checking status
|
||||
|
||||
**Example POST Response**
|
||||
|
||||
{
|
||||
"task_id": "7069b95b-ccea-4214-b6db-e00f27065bf7"
|
||||
}
|
||||
|
||||
**GET Parameters**
|
||||
|
||||
A GET request must include the following parameters:
|
||||
|
||||
* task_id: (required) The UUID of the task to check.
|
||||
|
||||
**GET Response Values**
|
||||
|
||||
If the import task is found successfully by the UUID provided, an HTTP
|
||||
200 "OK" response is returned.
|
||||
|
||||
The HTTP 200 response has the following values:
|
||||
|
||||
* state: String description of the state of the task.
|
||||
Possible states: "Pending", "Exporting", "Succeeded", "Failed".
|
||||
* url: (may be null) If the task is complete, a URL to download the .zip file
|
||||
|
||||
**Example GET Response**
|
||||
{
|
||||
"state": "Succeeded",
|
||||
"url": "/media/user_tasks/2025/10/03/lib-wgu-csprob-2025-10-03-153633.zip"
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
@apidocs.schema(
|
||||
body=None,
|
||||
responses={200: LibraryBackupResponseSerializer}
|
||||
)
|
||||
@convert_exceptions
|
||||
def post(self, request, lib_key_str):
|
||||
"""
|
||||
Start backup task for the specified library.
|
||||
"""
|
||||
library_key = LibraryLocatorV2.from_string(lib_key_str)
|
||||
# Using CAN_EDIT_THIS_CONTENT_LIBRARY permission for now. This should eventually become its own permission
|
||||
api.require_permission_for_library_key(library_key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
|
||||
|
||||
async_result = backup_library.delay(request.user.id, str(library_key))
|
||||
result = {'task_id': async_result.task_id}
|
||||
|
||||
return Response(LibraryBackupResponseSerializer(result).data)
|
||||
|
||||
@apidocs.schema(
|
||||
parameters=[
|
||||
apidocs.query_parameter(
|
||||
'task_id',
|
||||
str,
|
||||
description="The ID of the backup task to retrieve."
|
||||
),
|
||||
],
|
||||
responses={200: LibraryBackupTaskStatusSerializer}
|
||||
)
|
||||
@convert_exceptions
|
||||
def get(self, request, lib_key_str):
|
||||
"""
|
||||
Get the status of the specified backup task for the specified library.
|
||||
"""
|
||||
library_key = LibraryLocatorV2.from_string(lib_key_str)
|
||||
# Using CAN_EDIT_THIS_CONTENT_LIBRARY permission for now. This should eventually become its own permission
|
||||
api.require_permission_for_library_key(library_key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
|
||||
|
||||
task_id = request.query_params.get('task_id', None)
|
||||
if not task_id:
|
||||
raise ValidationError(detail={'task_id': _('This field is required.')})
|
||||
result = get_backup_task_status(request.user.id, task_id)
|
||||
|
||||
if not result:
|
||||
raise NotFound(detail="No backup found for this library.")
|
||||
|
||||
return Response(LibraryBackupTaskStatusSerializer(result).data)
|
||||
|
||||
|
||||
# LTI 1.3 Views
|
||||
# =============
|
||||
|
||||
|
||||
@@ -3,26 +3,22 @@ Serializers for the content libraries REST API
|
||||
"""
|
||||
# pylint: disable=abstract-method
|
||||
from django.core.validators import validate_unicode_slug
|
||||
from opaque_keys import InvalidKeyError, OpaqueKey
|
||||
from opaque_keys.edx.locator import LibraryContainerLocator, LibraryUsageLocatorV2
|
||||
from openedx_learning.api.authoring_models import Collection
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from opaque_keys import OpaqueKey
|
||||
from opaque_keys.edx.locator import LibraryContainerLocator, LibraryUsageLocatorV2
|
||||
from opaque_keys import InvalidKeyError
|
||||
|
||||
from openedx_learning.api.authoring_models import Collection
|
||||
from openedx.core.djangoapps.content_libraries.api.containers import ContainerType
|
||||
from openedx.core.djangoapps.content_libraries.constants import (
|
||||
ALL_RIGHTS_RESERVED,
|
||||
LICENSE_OPTIONS,
|
||||
)
|
||||
from openedx.core.djangoapps.content_libraries.constants import ALL_RIGHTS_RESERVED, LICENSE_OPTIONS
|
||||
from openedx.core.djangoapps.content_libraries.models import (
|
||||
ContentLibraryPermission, ContentLibraryBlockImportTask,
|
||||
ContentLibrary
|
||||
ContentLibrary,
|
||||
ContentLibraryBlockImportTask,
|
||||
ContentLibraryPermission
|
||||
)
|
||||
from openedx.core.lib.api.serializers import CourseKeyField
|
||||
from .. import permissions
|
||||
|
||||
from .. import permissions
|
||||
|
||||
DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
|
||||
|
||||
@@ -416,3 +412,18 @@ class ContainerHierarchySerializer(serializers.Serializer):
|
||||
units = serializers.ListField(child=ContainerHierarchyMemberSerializer(), allow_empty=True)
|
||||
components = serializers.ListField(child=ContainerHierarchyMemberSerializer(), allow_empty=True)
|
||||
object_key = OpaqueKeySerializer()
|
||||
|
||||
|
||||
class LibraryBackupResponseSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for the response after requesting a backup of a content library.
|
||||
"""
|
||||
task_id = serializers.CharField()
|
||||
|
||||
|
||||
class LibraryBackupTaskStatusSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for checking the status of a library backup task.
|
||||
"""
|
||||
state = serializers.CharField()
|
||||
url = serializers.URLField(allow_null=True)
|
||||
|
||||
@@ -17,37 +17,44 @@ Architecture note:
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
from tempfile import mkdtemp
|
||||
|
||||
from celery import shared_task
|
||||
from celery_utils.logged_task import LoggedTask
|
||||
from celery.utils.log import get_task_logger
|
||||
from edx_django_utils.monitoring import set_code_owner_attribute, set_code_owner_attribute_from_module
|
||||
from celery_utils.logged_task import LoggedTask
|
||||
from django.core.files import File
|
||||
from django.utils.text import slugify
|
||||
from edx_django_utils.monitoring import (
|
||||
set_code_owner_attribute,
|
||||
set_code_owner_attribute_from_module,
|
||||
set_custom_attribute
|
||||
)
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys.edx.locator import (
|
||||
BlockUsageLocator,
|
||||
LibraryCollectionLocator,
|
||||
LibraryContainerLocator,
|
||||
LibraryLocatorV2,
|
||||
)
|
||||
from openedx_learning.api import authoring as authoring_api
|
||||
from openedx_learning.api.authoring_models import DraftChangeLog, PublishLog
|
||||
from openedx_events.content_authoring.data import (
|
||||
LibraryBlockData,
|
||||
LibraryCollectionData,
|
||||
LibraryContainerData,
|
||||
LibraryLocatorV2
|
||||
)
|
||||
from openedx_events.content_authoring.data import LibraryBlockData, LibraryCollectionData, LibraryContainerData
|
||||
from openedx_events.content_authoring.signals import (
|
||||
LIBRARY_BLOCK_CREATED,
|
||||
LIBRARY_BLOCK_DELETED,
|
||||
LIBRARY_BLOCK_UPDATED,
|
||||
LIBRARY_BLOCK_PUBLISHED,
|
||||
LIBRARY_BLOCK_UPDATED,
|
||||
LIBRARY_COLLECTION_UPDATED,
|
||||
LIBRARY_CONTAINER_CREATED,
|
||||
LIBRARY_CONTAINER_DELETED,
|
||||
LIBRARY_CONTAINER_UPDATED,
|
||||
LIBRARY_CONTAINER_PUBLISHED,
|
||||
LIBRARY_CONTAINER_UPDATED
|
||||
)
|
||||
|
||||
from openedx_learning.api import authoring as authoring_api
|
||||
from openedx_learning.api.authoring import create_zip_file as create_lib_zip_file
|
||||
from openedx_learning.api.authoring_models import DraftChangeLog, PublishLog
|
||||
from path import Path
|
||||
from user_tasks.models import UserTaskArtifact
|
||||
from user_tasks.tasks import UserTask, UserTaskStatus
|
||||
from xblock.fields import Scope
|
||||
|
||||
@@ -477,3 +484,66 @@ def _copy_overrides(
|
||||
dest_block=store.get_item(dest_child_key),
|
||||
)
|
||||
store.update_item(dest_block, user_id)
|
||||
|
||||
|
||||
class LibraryBackupTask(UserTask): # pylint: disable=abstract-method
|
||||
"""
|
||||
Base class for tasks related with Library backup functionality.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def generate_name(cls, arguments_dict) -> str:
|
||||
"""
|
||||
Create a name for this particular backup task instance.
|
||||
|
||||
Should be both:
|
||||
a. semi human-friendly
|
||||
b. something we can query in order to determine whether the library has a task in progress
|
||||
|
||||
Arguments:
|
||||
arguments_dict (dict): The arguments given to the task function
|
||||
|
||||
Returns:
|
||||
str: The generated name
|
||||
"""
|
||||
key = arguments_dict['library_key_str']
|
||||
return f'Backup of {key}'
|
||||
|
||||
|
||||
@shared_task(base=LibraryBackupTask, bind=True)
|
||||
# Note: The decorator @set_code_owner_attribute cannot be used here because the UserTaskMixin
|
||||
# does stack inspection and can't handle additional decorators.
|
||||
def backup_library(self, user_id: int, library_key_str: str) -> None:
|
||||
"""
|
||||
Export a library to a .zip archive and prepare it for download.
|
||||
Possible Task states:
|
||||
- Pending: Task is created but not started yet.
|
||||
- Exporting: Task is running and the library is being exported.
|
||||
- Succeeded: Task completed successfully and the exported file is available for download.
|
||||
- Failed: Task failed and the export did not complete.
|
||||
"""
|
||||
ensure_cms("backup_library may only be executed in a CMS context")
|
||||
set_code_owner_attribute_from_module(__name__)
|
||||
library_key = LibraryLocatorV2.from_string(library_key_str)
|
||||
|
||||
try:
|
||||
self.status.set_state('Exporting')
|
||||
set_custom_attribute("exporting_started", str(library_key))
|
||||
|
||||
root_dir = Path(mkdtemp())
|
||||
sanitized_lib_key = str(library_key).replace(":", "-")
|
||||
sanitized_lib_key = slugify(sanitized_lib_key, allow_unicode=True)
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d-%H%M%S")
|
||||
filename = f'{sanitized_lib_key}-{timestamp}.zip'
|
||||
file_path = os.path.join(root_dir, filename)
|
||||
create_lib_zip_file(lp_key=str(library_key), path=file_path)
|
||||
set_custom_attribute("exporting_completed", str(library_key))
|
||||
|
||||
with open(file_path, 'rb') as zipfile:
|
||||
artifact = UserTaskArtifact(status=self.status, name='Output')
|
||||
artifact.file.save(name=os.path.basename(zipfile.name), content=File(zipfile))
|
||||
artifact.save()
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
TASK_LOGGER.exception('Error exporting library %s', library_key, exc_info=True)
|
||||
if self.status.state != UserTaskStatus.FAILED:
|
||||
self.status.fail({'raw_error_msg': str(exception)})
|
||||
|
||||
@@ -32,6 +32,8 @@ URL_LIB_TEAM = URL_LIB_DETAIL + 'team/' # Get the list of users/groups authoriz
|
||||
URL_LIB_TEAM_USER = URL_LIB_TEAM + 'user/{username}/' # Add/edit/remove a user's permission to use this library
|
||||
URL_LIB_TEAM_GROUP = URL_LIB_TEAM + 'group/{group_name}/' # Add/edit/remove a group's permission to use this library
|
||||
URL_LIB_PASTE_CLIPBOARD = URL_LIB_DETAIL + 'paste_clipboard/' # Paste user clipboard (POST) containing Xblock data
|
||||
URL_LIB_BACKUP = URL_LIB_DETAIL + 'backup/' # Start a backup task for this library
|
||||
URL_LIB_BACKUP_GET = URL_LIB_BACKUP + '?{query_params}' # Get status on a backup task for this library
|
||||
URL_LIB_BLOCK = URL_PREFIX + 'blocks/{block_key}/' # Get data about a block, or delete it
|
||||
URL_LIB_BLOCK_PUBLISH = URL_LIB_BLOCK + 'publish/' # Publish changes from a specified XBlock
|
||||
URL_LIB_BLOCK_OLX = URL_LIB_BLOCK + 'olx/' # Get or set the OLX of the specified XBlock
|
||||
@@ -319,6 +321,17 @@ class ContentLibrariesRestApiTest(APITransactionTestCase):
|
||||
url = URL_LIB_PASTE_CLIPBOARD.format(lib_key=lib_key)
|
||||
return self._api('post', url, {}, expect_response)
|
||||
|
||||
def _start_library_backup_task(self, lib_key, expect_response=200):
|
||||
""" Start a backup task for this library """
|
||||
url = URL_LIB_BACKUP.format(lib_key=lib_key)
|
||||
return self._api('post', url, {}, expect_response)
|
||||
|
||||
def _get_library_backup_task(self, lib_key, task_id, expect_response=200):
|
||||
""" Get the status of a backup task for this library """
|
||||
query_params = urlencode({"task_id": task_id})
|
||||
url = URL_LIB_BACKUP_GET.format(lib_key=lib_key, query_params=query_params)
|
||||
return self._api('get', url, None, expect_response)
|
||||
|
||||
def _render_block_view(self, block_key, view_name, version=None, expect_response=200):
|
||||
"""
|
||||
Render an XBlock's view in the active application's runtime.
|
||||
|
||||
@@ -4,9 +4,11 @@ Tests for Content Library internal api.
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import uuid
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from user_tasks.models import UserTaskStatus
|
||||
|
||||
from opaque_keys.edx.keys import (
|
||||
CourseKey,
|
||||
@@ -1309,3 +1311,95 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest):
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class ContentLibraryExportTest(ContentLibrariesRestApiTest):
|
||||
"""
|
||||
Tests for Content Library API export methods.
|
||||
"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
# Create Content Libraries
|
||||
self._create_library("test-lib-exp-1", "Test Library Export 1")
|
||||
|
||||
# Fetch the created ContentLibrary objects so we can access their learning_package.id
|
||||
self.lib1 = ContentLibrary.objects.get(slug="test-lib-exp-1")
|
||||
self.wrong_task_id = '11111111-1111-1111-1111-111111111111'
|
||||
|
||||
def test_get_backup_task_status_no_task(self) -> None:
|
||||
status = api.get_backup_task_status(self.user.id, "")
|
||||
assert status is None
|
||||
|
||||
def test_get_backup_task_status_wrong_task_id(self) -> None:
|
||||
status = api.get_backup_task_status(self.user.id, task_id=self.wrong_task_id)
|
||||
assert status is None
|
||||
|
||||
def test_get_backup_task_status_in_progress(self) -> None:
|
||||
# Create a mock UserTaskStatus in IN_PROGRESS state
|
||||
task_id = str(uuid.uuid4())
|
||||
mock_task = UserTaskStatus(
|
||||
task_id=task_id,
|
||||
user_id=self.user.id,
|
||||
name=f"Export of {self.lib1.library_key}",
|
||||
state=UserTaskStatus.IN_PROGRESS
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
'openedx.core.djangoapps.content_libraries.api.libraries.UserTaskStatus.objects.get'
|
||||
) as mock_get:
|
||||
mock_get.return_value = mock_task
|
||||
|
||||
status = api.get_backup_task_status(self.user.id, task_id=task_id)
|
||||
assert status is not None
|
||||
assert status['state'] == UserTaskStatus.IN_PROGRESS
|
||||
assert status['url'] is None
|
||||
|
||||
def test_get_backup_task_status_succeeded(self) -> None:
|
||||
# Create a mock UserTaskStatus in SUCCEEDED state
|
||||
task_id = str(uuid.uuid4())
|
||||
mock_task = UserTaskStatus(
|
||||
task_id=task_id,
|
||||
user_id=self.user.id,
|
||||
name=f"Export of {self.lib1.library_key}",
|
||||
state=UserTaskStatus.SUCCEEDED
|
||||
)
|
||||
|
||||
# Create a mock UserTaskArtifact
|
||||
mock_artifact = mock.Mock()
|
||||
mock_artifact.file.storage.url.return_value = "/media/user_tasks/2025/10/01/library-libOEXCSPROB_mOw1rPL.zip"
|
||||
|
||||
with mock.patch(
|
||||
'openedx.core.djangoapps.content_libraries.api.libraries.UserTaskStatus.objects.get'
|
||||
) as mock_get, mock.patch(
|
||||
'openedx.core.djangoapps.content_libraries.api.libraries.UserTaskArtifact.objects.get'
|
||||
) as mock_artifact_get:
|
||||
|
||||
mock_get.return_value = mock_task
|
||||
mock_artifact_get.return_value = mock_artifact
|
||||
|
||||
status = api.get_backup_task_status(self.user.id, task_id=task_id)
|
||||
assert status is not None
|
||||
assert status['state'] == UserTaskStatus.SUCCEEDED
|
||||
assert status['url'] == "/media/user_tasks/2025/10/01/library-libOEXCSPROB_mOw1rPL.zip"
|
||||
|
||||
def test_get_backup_task_status_failed(self) -> None:
|
||||
# Create a mock UserTaskStatus in FAILED state
|
||||
task_id = str(uuid.uuid4())
|
||||
mock_task = UserTaskStatus(
|
||||
task_id=task_id,
|
||||
user_id=self.user.id,
|
||||
name=f"Export of {self.lib1.library_key}",
|
||||
state=UserTaskStatus.FAILED
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
'openedx.core.djangoapps.content_libraries.api.libraries.UserTaskStatus.objects.get'
|
||||
) as mock_get:
|
||||
mock_get.return_value = mock_task
|
||||
|
||||
status = api.get_backup_task_status(self.user.id, task_id=task_id)
|
||||
assert status is not None
|
||||
assert status['state'] == UserTaskStatus.FAILED
|
||||
assert status['url'] is None
|
||||
|
||||
@@ -823,6 +823,40 @@ class ContentLibrariesTestCase(ContentLibrariesRestApiTest):
|
||||
"id": f"lb:CL-TEST:test_lib_paste_clipboard:problem:{pasted_usage_key.block_id}",
|
||||
})
|
||||
|
||||
def test_start_library_backup(self):
|
||||
"""
|
||||
Test starting a backup operation on a content library.
|
||||
"""
|
||||
author = UserFactory.create(username="Author", email="author@example.com", is_staff=True)
|
||||
with self.as_user(author):
|
||||
lib = self._create_library(
|
||||
slug="test_lib_backup",
|
||||
title="Backup Test Library",
|
||||
description="Testing backup for library"
|
||||
)
|
||||
lib_id = lib["id"]
|
||||
response = self._start_library_backup_task(lib_id)
|
||||
assert response["task_id"] is not None
|
||||
|
||||
def test_get_library_backup_status(self):
|
||||
"""
|
||||
Test getting the status of a backup operation on a content library.
|
||||
"""
|
||||
author = UserFactory.create(username="Author", email="author@example.com", is_staff=True)
|
||||
with self.as_user(author):
|
||||
lib = self._create_library(
|
||||
slug="test_lib_backup_status",
|
||||
title="Backup Status Test Library",
|
||||
description="Testing backup status for library"
|
||||
)
|
||||
lib_id = lib["id"]
|
||||
response = self._start_library_backup_task(lib_id)
|
||||
task_id = response["task_id"]
|
||||
|
||||
# Now check the status of the backup task
|
||||
status_response = self._get_library_backup_task(lib_id, task_id)
|
||||
assert status_response["state"] in ["Pending", "Exporting", "Succeeded", "Failed"]
|
||||
|
||||
@override_settings(LIBRARY_ENABLED_BLOCKS=['problem', 'video', 'html'])
|
||||
def test_library_get_enabled_blocks(self):
|
||||
expected = [
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Unit tests for content libraries Celery tasks
|
||||
"""
|
||||
|
||||
from ..models import ContentLibrary
|
||||
from .base import ContentLibrariesRestApiTest
|
||||
|
||||
from openedx.core.djangoapps.content_libraries.tasks import backup_library
|
||||
from user_tasks.models import UserTaskArtifact
|
||||
|
||||
|
||||
class ContentLibraryBackupTaskTest(ContentLibrariesRestApiTest):
|
||||
"""
|
||||
Tests for Content Library export task.
|
||||
"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
# Create Content Libraries
|
||||
self._create_library("test-lib-task-1", "Test Library Task 1")
|
||||
|
||||
# Fetch the created ContentLibrary objects so we can access their learning_package.id
|
||||
self.lib1 = ContentLibrary.objects.get(slug="test-lib-task-1")
|
||||
self.wrong_task_id = '11111111-1111-1111-1111-111111111111'
|
||||
|
||||
def test_backup_task_returns_task_id(self):
|
||||
result = backup_library.delay(self.user.id, str(self.lib1.library_key))
|
||||
assert result.task_id is not None
|
||||
|
||||
def test_backup_task_success(self):
|
||||
result = backup_library.delay(self.user.id, str(self.lib1.library_key))
|
||||
assert result.state == 'SUCCESS'
|
||||
# Ensure an artifact was created with the output file
|
||||
artifact = UserTaskArtifact.objects.filter(status__task_id=result.task_id, name='Output').first()
|
||||
assert artifact is not None
|
||||
assert artifact.file.name.endswith('.zip')
|
||||
|
||||
def test_backup_task_failure(self):
|
||||
result = backup_library.delay(self.user.id, self.wrong_task_id)
|
||||
assert result.state == 'FAILURE'
|
||||
# Ensure an error artifact was created
|
||||
artifact = UserTaskArtifact.objects.filter(status__task_id=result.task_id, name='Error').first()
|
||||
assert artifact is not None
|
||||
assert artifact.text is not None
|
||||
@@ -54,6 +54,8 @@ urlpatterns = [
|
||||
path('import_blocks/', include(import_blocks_router.urls)),
|
||||
# Paste contents of clipboard into library
|
||||
path('paste_clipboard/', libraries.LibraryPasteClipboardView.as_view()),
|
||||
# Start a backup task for this library
|
||||
path('backup/', libraries.LibraryBackupView.as_view()),
|
||||
# Library Collections
|
||||
path('', include(library_collections_router.urls)),
|
||||
])),
|
||||
|
||||
244
openedx/core/lib/tests/test_user_util.py
Normal file
244
openedx/core/lib/tests/test_user_util.py
Normal file
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Tests for `user_util` package."""
|
||||
|
||||
import pytest
|
||||
from types import GeneratorType
|
||||
|
||||
from openedx.core.lib import user_util
|
||||
|
||||
VALID_SALT_LIST_ONE_SALT = ['gsw@&2p)$^p2hdk&ou0e%c=ou80o=%!+tv7(u(ircv@+96jl6$']
|
||||
VALID_SALT_LIST_THREE_SALTS = [
|
||||
'^==!0%=z4s!v7!yl0#+m6-st^*946aop6$0i+hu13&h_$a$vq8',
|
||||
'wdwhs@(f=jnlky4up8p0#04t$jp%ip)nfp@de6rr9i)j7nf',
|
||||
')h1^pu8a!rh=%$_4f7sx*5^46ln_pujw6y*s0=dl6i$_#&#io1',
|
||||
]
|
||||
VALID_SALT_LIST_FIVE_SALTS = [
|
||||
'8rv!7iy4a7mdvs_kudis6&oycj0_b(mj0s^@*e5p)(o+m(c-cb',
|
||||
'xp)43m+d_!f!-)c=ki_8oc2w9(^r^umy73%dp@z7sknn#800z$',
|
||||
'some_salt_that_is_not_very_random',
|
||||
'$=ldtvagk$qwc)cz%2%edaa_id45^(xg*1rs#t0inywla*)3+x',
|
||||
'4eyp*!%nz&g@8(tm!236ykbg2xzwcix!=)06q&=d2rh@3n1o+8',
|
||||
]
|
||||
VALID_SALT_LISTS = (
|
||||
VALID_SALT_LIST_ONE_SALT,
|
||||
VALID_SALT_LIST_THREE_SALTS,
|
||||
VALID_SALT_LIST_FIVE_SALTS,
|
||||
)
|
||||
INVALID_SALT_LIST = (
|
||||
'gsw@&2p)$^p2hdk&ou0e%c=ou80o=%!+tv7(u(ircv@+96jl6$',
|
||||
None,
|
||||
[],
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Username retirement tests
|
||||
#
|
||||
@pytest.mark.parametrize('salt_list', VALID_SALT_LISTS)
|
||||
def test_username_to_hash(salt_list):
|
||||
username = 'ALearnerUserName'
|
||||
retired_username = user_util.get_retired_username(username, salt_list)
|
||||
assert retired_username != username
|
||||
assert retired_username.startswith('_'.join(user_util.RETIRED_USERNAME_DEFAULT_FMT.split('_')[0:-1]))
|
||||
# Since SHA1 is used, the hexadecimal digest length should be 40.
|
||||
assert len(retired_username.split('_')[-1]) == 40
|
||||
|
||||
|
||||
@pytest.mark.parametrize('salt_list', VALID_SALT_LISTS)
|
||||
def test_username_to_hash_is_normalized(salt_list):
|
||||
"""
|
||||
Make sure identical usernames with different cases map to the same retired username.
|
||||
"""
|
||||
username_mixed = 'ALearnerUserName'
|
||||
username_lower = username_mixed.lower()
|
||||
retired_username_mixed = user_util.get_retired_username(username_mixed, salt_list)
|
||||
retired_username_lower = user_util.get_retired_username(username_lower, salt_list)
|
||||
# No matter the case of the input username, the retired username hash should be identical.
|
||||
assert retired_username_mixed == retired_username_lower
|
||||
|
||||
|
||||
def test_unicode_username_to_hash():
|
||||
username = 'ÁĹéáŕńéŕŰśéŕŃáḿéẂíthŰńíćődé'
|
||||
retired_username = user_util.get_retired_username(username, VALID_SALT_LIST_ONE_SALT)
|
||||
assert retired_username != username
|
||||
# Since SHA1 is used, the hexadecimal digest length should be 40.
|
||||
assert len(retired_username.split('_')[-1]) == 40
|
||||
|
||||
|
||||
@pytest.mark.parametrize('salt_list', (VALID_SALT_LIST_THREE_SALTS,))
|
||||
def test_correct_username_hash(salt_list):
|
||||
"""
|
||||
Verify that get_retired_username uses the current salt and returns the expected hash.
|
||||
"""
|
||||
username = 'ALearnerUserName'
|
||||
# Valid retired usernames for the above username when using VALID_SALT_LIST_THREE_SALTS.
|
||||
valid_retired_usernames = [
|
||||
# pylint: disable=protected-access
|
||||
user_util.RETIRED_USERNAME_DEFAULT_FMT.format(user_util._compute_retired_hash(username.lower(), salt))
|
||||
for salt in salt_list
|
||||
]
|
||||
retired_username = user_util.get_retired_username(username, salt_list)
|
||||
assert retired_username == valid_retired_usernames[-1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('salt_list', (VALID_SALT_LIST_FIVE_SALTS,))
|
||||
def test_all_usernames_to_hash(salt_list):
|
||||
username = 'ALearnerUserName'
|
||||
retired_username_generator = user_util.get_all_retired_usernames(username, salt_list)
|
||||
assert isinstance(retired_username_generator, GeneratorType)
|
||||
assert len(list(retired_username_generator)) == len(VALID_SALT_LIST_FIVE_SALTS)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('salt_list', VALID_SALT_LISTS)
|
||||
def test_username_to_hash_with_different_format(salt_list):
|
||||
username = 'ALearnerUserName'
|
||||
retired_username_fmt = "{}_is_now_the_retired_username"
|
||||
retired_username = user_util.get_retired_username(username, salt_list, retired_username_fmt=retired_username_fmt)
|
||||
assert retired_username.endswith('_'.join(retired_username_fmt.split('_')[1:]))
|
||||
# Since SHA1 is used, the hexadecimal digest length should be 40.
|
||||
assert len(retired_username.split('_')[0]) == 40
|
||||
|
||||
|
||||
#
|
||||
# Email address retirement tests
|
||||
#
|
||||
@pytest.mark.parametrize('salt_list', VALID_SALT_LISTS)
|
||||
def test_email_to_hash(salt_list):
|
||||
email = 'a.learner@example.com'
|
||||
retired_email = user_util.get_retired_email(email, salt_list)
|
||||
assert retired_email != email
|
||||
assert retired_email.startswith('_'.join(user_util.RETIRED_EMAIL_DEFAULT_FMT.split('_')[0:2]))
|
||||
assert retired_email.endswith(user_util.RETIRED_EMAIL_DEFAULT_FMT.split('@')[-1])
|
||||
# Since SHA1 is used, the hexadecimal digest length should be 40.
|
||||
assert len(retired_email.split('@')[0]) == len('retired_email_') + 40
|
||||
|
||||
|
||||
@pytest.mark.parametrize('salt_list', VALID_SALT_LISTS)
|
||||
def test_email_to_hash_is_normalized(salt_list):
|
||||
"""
|
||||
Make sure identical emails with different cases map to the same retired email.
|
||||
"""
|
||||
email_mixed = 'A.Learner@example.com'
|
||||
email_lower = email_mixed.lower()
|
||||
retired_email_mixed = user_util.get_retired_email(email_mixed, salt_list)
|
||||
retired_email_lower = user_util.get_retired_email(email_lower, salt_list)
|
||||
# No matter the case of the input email, the retired email hash should be identical.
|
||||
assert retired_email_mixed == retired_email_lower
|
||||
|
||||
|
||||
def test_unicode_email_to_hash():
|
||||
email = '🅐.🅛🅔🅐🅡🅝🅔🅡r@example.com'
|
||||
retired_email = user_util.get_retired_email(email, VALID_SALT_LIST_ONE_SALT)
|
||||
assert retired_email != email
|
||||
# Since SHA1 is used, the hexadecimal digest length should be 40.
|
||||
assert len(retired_email.split('@')[0]) == len('retired_email_') + 40
|
||||
|
||||
|
||||
@pytest.mark.parametrize('salt_list', (VALID_SALT_LIST_THREE_SALTS,))
|
||||
def test_correct_email_hash(salt_list):
|
||||
"""
|
||||
Verify that get_retired_email uses the current salt and returns the expected hash.
|
||||
"""
|
||||
email = 'a.learner@example.com'
|
||||
# Valid retired emails for the above email address when using VALID_SALT_LIST_THREE_SALTS.
|
||||
valid_retired_emails = [
|
||||
# pylint: disable=protected-access
|
||||
user_util.RETIRED_EMAIL_DEFAULT_FMT.format(user_util._compute_retired_hash(email.lower(), salt))
|
||||
for salt in salt_list
|
||||
]
|
||||
retired_email = user_util.get_retired_email(email, salt_list)
|
||||
assert retired_email == valid_retired_emails[-1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('salt_list', (VALID_SALT_LIST_FIVE_SALTS,))
|
||||
def test_all_emails_to_hash(salt_list):
|
||||
email = 'a.learner@example.com'
|
||||
retired_email_generator = user_util.get_all_retired_emails(email, salt_list)
|
||||
assert isinstance(retired_email_generator, GeneratorType)
|
||||
assert len(list(retired_email_generator)) == len(VALID_SALT_LIST_FIVE_SALTS)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('salt_list', VALID_SALT_LISTS)
|
||||
def test_email_to_hash_with_different_format(salt_list):
|
||||
email = 'a.learner@example.com'
|
||||
retired_email_fmt = "{}_is_now_the_retired_email@devnull.example.com"
|
||||
retired_email = user_util.get_retired_email(email, salt_list, retired_email_fmt=retired_email_fmt)
|
||||
assert retired_email.endswith('_'.join(retired_email_fmt.split('_')[1:]))
|
||||
# Since SHA1 is used, the hexadecimal digest length should be 40.
|
||||
assert len(retired_email.split('_')[0]) == 40
|
||||
|
||||
|
||||
#
|
||||
# Bad salt tests.
|
||||
#
|
||||
@pytest.mark.parametrize('salt', INVALID_SALT_LIST)
|
||||
def test_username_to_hash_bad_salt(salt):
|
||||
"""
|
||||
Salts that are *not* lists/tuples should fail.
|
||||
"""
|
||||
with pytest.raises((ValueError, IndexError)):
|
||||
_ = user_util.get_retired_username('AnotherLearnerUserName', salt)
|
||||
|
||||
|
||||
#
|
||||
# External user retirement tests
|
||||
#
|
||||
|
||||
@pytest.mark.parametrize('salt_list', VALID_SALT_LISTS)
|
||||
def test_external_key_to_hash(salt_list):
|
||||
external_key = '343ni3hr3ifh3fgghg'
|
||||
retired_external_key = user_util.get_retired_external_key(external_key, salt_list)
|
||||
assert retired_external_key != external_key
|
||||
assert retired_external_key.startswith(
|
||||
'_'.join(user_util.RETIRED_EXTERNAL_KEY_DEFAULT_FMT.split('_')[0:3])
|
||||
)
|
||||
# Since SHA1 is used, the hexadecimal digest length should be 40.
|
||||
assert len(retired_external_key) == len('retired_external_key_') + 40
|
||||
|
||||
|
||||
def test_unicode_external_key_to_hash():
|
||||
unicode_external_key = '🅐.🅛🅔🅐🅡🅝🅔🅡'
|
||||
retired_external_key = user_util.get_retired_external_key(unicode_external_key, VALID_SALT_LIST_ONE_SALT)
|
||||
assert retired_external_key != unicode_external_key
|
||||
# Since SHA1 is used, the hexadecimal digest length should be 40.
|
||||
assert len(retired_external_key) == len('retired_external_key_') + 40
|
||||
|
||||
|
||||
@pytest.mark.parametrize('salt_list', (VALID_SALT_LIST_THREE_SALTS,))
|
||||
def test_correct_external_key_hash(salt_list):
|
||||
"""
|
||||
Verify that get_retired_external_key uses the current salt and returns the expected hash.
|
||||
"""
|
||||
external_key = 'S34839GEF3'
|
||||
valid_retired_external_keys = [
|
||||
# pylint: disable=protected-access
|
||||
user_util.RETIRED_EXTERNAL_KEY_DEFAULT_FMT.format(
|
||||
user_util._compute_retired_hash(external_key.lower(), salt)
|
||||
)
|
||||
for salt in salt_list
|
||||
]
|
||||
retired_email = user_util.get_retired_external_key(external_key, salt_list)
|
||||
assert retired_email == valid_retired_external_keys[-1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('salt_list', (VALID_SALT_LIST_FIVE_SALTS,))
|
||||
def test_all_external_keys_to_hash(salt_list):
|
||||
external_key = 'S34839GEF3'
|
||||
retired_external_key_generator = user_util.get_all_retired_external_keys(external_key, salt_list)
|
||||
assert isinstance(retired_external_key_generator, GeneratorType)
|
||||
assert len(list(retired_external_key_generator)) == len(VALID_SALT_LIST_FIVE_SALTS)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('salt_list', VALID_SALT_LISTS)
|
||||
def test_external_key_to_hash_with_different_format(salt_list):
|
||||
external_key = 'S34839GEF3'
|
||||
retired_external_key_fmt = "{}_is_now_the_retired_external_key"
|
||||
retired_external_key = user_util.get_retired_external_key(
|
||||
external_key,
|
||||
salt_list,
|
||||
retired_external_key_fmt=retired_external_key_fmt
|
||||
)
|
||||
assert retired_external_key.endswith('_is_now_the_retired_external_key')
|
||||
# Since SHA1 is used, the hexadecimal digest length should be 40.
|
||||
assert len(retired_external_key.split('_')[0]) == 40
|
||||
157
openedx/core/lib/user_util.py
Normal file
157
openedx/core/lib/user_util.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""Main module."""
|
||||
import hashlib
|
||||
|
||||
|
||||
RETIRED_USERNAME_DEFAULT_FMT = 'retired_username_{}'
|
||||
RETIRED_EMAIL_DEFAULT_FMT = 'retired_email_{}@retired.edx.org'
|
||||
RETIRED_EXTERNAL_KEY_DEFAULT_FMT = 'retired_external_key_{}'
|
||||
SALT_LIST_EXCEPTION = ValueError("Salt must be a list -or- tuple of all historical salts.")
|
||||
|
||||
|
||||
def _compute_retired_hash(value_to_retire, salt):
|
||||
"""
|
||||
Returns a retired value given a value to retire and a hash.
|
||||
|
||||
Arguments:
|
||||
value_to_retire (str): Value to be retired.
|
||||
salt (str): Salt string used to modify the retired value before hashing.
|
||||
"""
|
||||
return hashlib.sha1(
|
||||
salt.encode() + value_to_retire.encode('utf-8')
|
||||
).hexdigest()
|
||||
|
||||
|
||||
def get_all_retired_usernames(username, salt_list, retired_username_fmt=RETIRED_USERNAME_DEFAULT_FMT):
|
||||
"""
|
||||
Returns a generator of possible retired usernames based on the original
|
||||
lowercased username and all the historical salts, from oldest to current.
|
||||
The current salt is assumed to be the last salt in the list.
|
||||
|
||||
Raises :class:`~ValueError` if the salt isn't a list of salts.
|
||||
|
||||
Arguments:
|
||||
username (str): The name of the user to be retired.
|
||||
salt_list (list/tuple): List of all historical salts.
|
||||
|
||||
Yields:
|
||||
Returns a generator of possible retired usernames based on the original username
|
||||
and all the historical salts, including the current salt, from oldest to current.
|
||||
"""
|
||||
if not isinstance(salt_list, (list, tuple)):
|
||||
raise SALT_LIST_EXCEPTION
|
||||
|
||||
for salt in salt_list:
|
||||
yield retired_username_fmt.format(_compute_retired_hash(username.lower(), salt))
|
||||
|
||||
|
||||
def get_all_retired_emails(email, salt_list, retired_email_fmt=RETIRED_EMAIL_DEFAULT_FMT):
|
||||
"""
|
||||
Returns a generator of possible retired email addresses based on the
|
||||
original lowercased email and all the historical salts, from oldest to
|
||||
current. The current salt is assumed to be the last salt in the list.
|
||||
|
||||
Raises :class:`~ValueError` if the salt isn't a list of salts.
|
||||
|
||||
Arguments:
|
||||
email (str): Email address of the user to be retired.
|
||||
salt_list (list/tuple): List of all historical salts.
|
||||
|
||||
Yields:
|
||||
Returns a generator of possible retired email addresses based on the original email
|
||||
and all the historical salts, including the current salt, from oldest to current.
|
||||
"""
|
||||
if not isinstance(salt_list, (list, tuple)):
|
||||
raise SALT_LIST_EXCEPTION
|
||||
|
||||
for salt in salt_list:
|
||||
yield retired_email_fmt.format(_compute_retired_hash(email.lower(), salt))
|
||||
|
||||
|
||||
def get_all_retired_external_keys(external_key, salt_list, retired_external_key_fmt=RETIRED_EXTERNAL_KEY_DEFAULT_FMT):
|
||||
"""
|
||||
Returns a generator of possible retired external user key based on the
|
||||
original external user key and all the historical salts, from oldest to
|
||||
current. The current salt is assumed to be the last salt in the list.
|
||||
|
||||
Raises :class:`~ValueError` if the salt isn't a list of salts.
|
||||
|
||||
Arguments:
|
||||
external_key (str): External user key of the user to be retired.
|
||||
salt_list (list/tuple): List of all historical salts.
|
||||
|
||||
Yields:
|
||||
Returns a generator of possible retired external user keys based on the original external key
|
||||
and all the historical salts, including the current salt, from oldest to current.
|
||||
"""
|
||||
if not isinstance(salt_list, (list, tuple)):
|
||||
raise SALT_LIST_EXCEPTION
|
||||
|
||||
for salt in salt_list:
|
||||
yield retired_external_key_fmt.format(_compute_retired_hash(external_key.lower(), salt))
|
||||
|
||||
|
||||
def get_retired_username(username, salt_list, retired_username_fmt=RETIRED_USERNAME_DEFAULT_FMT):
|
||||
"""
|
||||
Returns a retired username based on the original lowercased username and
|
||||
all the historical salts, from oldest to current. The current salt is
|
||||
assumed to be the last salt in the list.
|
||||
|
||||
Raises :class:`~ValueError` if the salt isn't a list of salts.
|
||||
|
||||
Arguments:
|
||||
username (str): The name of the user to be retired.
|
||||
salt_list (list/tuple): List of all historical salts.
|
||||
|
||||
Yields:
|
||||
Returns a retired username based on the original username
|
||||
and all the historical salts, including the current salt.
|
||||
"""
|
||||
if not isinstance(salt_list, (list, tuple)):
|
||||
raise SALT_LIST_EXCEPTION
|
||||
|
||||
return retired_username_fmt.format(_compute_retired_hash(username.lower(), salt_list[-1]))
|
||||
|
||||
|
||||
def get_retired_email(email, salt_list, retired_email_fmt=RETIRED_EMAIL_DEFAULT_FMT):
|
||||
"""
|
||||
Returns a retired email address based on the original lowercased email
|
||||
address and the current salt. The current salt is assumed to be the last
|
||||
salt in the list.
|
||||
|
||||
Raises :class:`~ValueError` if salt_list isn't a list of salts.
|
||||
|
||||
Arguments:
|
||||
email (str): Email address of the user to be retired.
|
||||
salt_list (list/tuple): List of all historical salts.
|
||||
|
||||
Yields:
|
||||
Returns a retired email address based on the original email
|
||||
and the current salt
|
||||
"""
|
||||
if not isinstance(salt_list, (list, tuple)):
|
||||
raise SALT_LIST_EXCEPTION
|
||||
|
||||
return retired_email_fmt.format(_compute_retired_hash(email.lower(), salt_list[-1]))
|
||||
|
||||
|
||||
def get_retired_external_key(external_key, salt_list, retired_external_key_fmt=RETIRED_EXTERNAL_KEY_DEFAULT_FMT):
|
||||
"""
|
||||
Returns a retired external user key based on the original external key and the current salt.
|
||||
The current salt is assumed to be the last salt in the list.
|
||||
|
||||
Raises :class:`~ValueError` if salt_list isn't a list of salts.
|
||||
|
||||
Arguments:
|
||||
external_key (str): External user key of the user to be retired.
|
||||
salt_list (list/tuple): List of all historical salts.
|
||||
|
||||
Yields:
|
||||
Returns a retired external user key based on the original external_user_key
|
||||
and the current salt
|
||||
"""
|
||||
if not isinstance(salt_list, (list, tuple)):
|
||||
raise SALT_LIST_EXCEPTION
|
||||
|
||||
return retired_external_key_fmt.format(
|
||||
_compute_retired_hash(external_key.lower(), salt_list[-1])
|
||||
)
|
||||
@@ -2245,6 +2245,10 @@ AI_TRANSLATIONS_API_URL = 'http://localhost:18760/api/v1'
|
||||
def should_send_learning_badge_events(settings):
|
||||
return settings.BADGES_ENABLED
|
||||
|
||||
############################## ALLOWED_HOSTS ###############################
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
############################## Miscellaneous ###############################
|
||||
|
||||
COURSE_MODE_DEFAULTS = {
|
||||
|
||||
@@ -22,7 +22,3 @@
|
||||
# elastic search changelog: https://www.elastic.co/guide/en/enterprise-search/master/release-notes-7.14.0.html
|
||||
# See https://github.com/openedx/edx-platform/issues/35126 for more info
|
||||
elasticsearch<7.14.0
|
||||
|
||||
# Cause: https://github.com/openedx/edx-lint/issues/458
|
||||
# This can be unpinned once https://github.com/openedx/edx-lint/issues/459 has been resolved.
|
||||
|
||||
|
||||
@@ -121,3 +121,14 @@ xmlsec==1.3.14
|
||||
# https://github.com/django-commons/django-debug-toolbar/issues/2172
|
||||
# Pin this back to the previous version until that bug is fixed.
|
||||
django-debug-toolbar<6.0.0
|
||||
|
||||
# Date 2025-10-07
|
||||
# Cryptography 46.0.0 conflicts with system dependencies needed for snowflake-connector-python
|
||||
# snowflake-connector-python comes as a dependency of edx-enterprise so it can not be directly pinned here.
|
||||
# See issue https://github.com/openedx/edx-platform/issues/37417 for details on this.
|
||||
# This can be unpinned once snowflake-connector-python==4.0.0 is available (contains the fix).
|
||||
# pact-python==3.0.0 also removes cffi dependency and is causing the upgrade build to fail
|
||||
# This should also be removed together with cryptography constraint.
|
||||
# Issue: https://github.com/openedx/edx-platform/issues/37435
|
||||
cryptography<46.0.0
|
||||
pact-python<3.0.0
|
||||
|
||||
@@ -8,17 +8,19 @@ cffi==2.0.0
|
||||
# via cryptography
|
||||
chem==2.0.0
|
||||
# via -r requirements/edx-sandbox/base.in
|
||||
click==8.2.1
|
||||
click==8.3.0
|
||||
# via nltk
|
||||
codejail-includes==2.0.0
|
||||
# via -r requirements/edx-sandbox/base.in
|
||||
contourpy==1.3.3
|
||||
# via matplotlib
|
||||
cryptography==45.0.7
|
||||
# via -r requirements/edx-sandbox/base.in
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx-sandbox/base.in
|
||||
cycler==0.12.1
|
||||
# via matplotlib
|
||||
fonttools==4.59.2
|
||||
fonttools==4.60.1
|
||||
# via matplotlib
|
||||
joblib==1.5.2
|
||||
# via nltk
|
||||
@@ -30,9 +32,9 @@ lxml[html-clean]==5.3.2
|
||||
# -r requirements/edx-sandbox/base.in
|
||||
# lxml-html-clean
|
||||
# openedx-calc
|
||||
lxml-html-clean==0.4.2
|
||||
lxml-html-clean==0.4.3
|
||||
# via lxml
|
||||
markupsafe==3.0.2
|
||||
markupsafe==3.0.3
|
||||
# via
|
||||
# chem
|
||||
# openedx-calc
|
||||
@@ -42,7 +44,7 @@ mpmath==1.3.0
|
||||
# via sympy
|
||||
networkx==3.5
|
||||
# via -r requirements/edx-sandbox/base.in
|
||||
nltk==3.9.1
|
||||
nltk==3.9.2
|
||||
# via
|
||||
# -r requirements/edx-sandbox/base.in
|
||||
# chem
|
||||
@@ -62,7 +64,7 @@ pillow==11.3.0
|
||||
# via matplotlib
|
||||
pycparser==2.23
|
||||
# via cffi
|
||||
pyparsing==3.2.4
|
||||
pyparsing==3.2.5
|
||||
# via
|
||||
# -r requirements/edx-sandbox/base.in
|
||||
# chem
|
||||
@@ -72,7 +74,7 @@ python-dateutil==2.9.0.post0
|
||||
# via matplotlib
|
||||
random2==1.0.2
|
||||
# via -r requirements/edx-sandbox/base.in
|
||||
regex==2025.9.1
|
||||
regex==2025.9.18
|
||||
# via nltk
|
||||
scipy==1.16.2
|
||||
# via
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# make upgrade
|
||||
#
|
||||
click==8.2.1
|
||||
click==8.3.0
|
||||
# via -r requirements/edx/assets.in
|
||||
libsass==0.10.0
|
||||
# via
|
||||
|
||||
@@ -8,7 +8,7 @@ acid-xblock==0.4.1
|
||||
# via -r requirements/edx/kernel.in
|
||||
aiohappyeyeballs==2.6.1
|
||||
# via aiohttp
|
||||
aiohttp==3.12.15
|
||||
aiohttp==3.13.0
|
||||
# via
|
||||
# geoip2
|
||||
# openai
|
||||
@@ -22,18 +22,18 @@ aniso8601==10.0.1
|
||||
# via edx-tincan-py35
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
anyio==4.10.0
|
||||
anyio==4.11.0
|
||||
# via httpx
|
||||
appdirs==1.4.4
|
||||
# via fs
|
||||
asgiref==3.9.1
|
||||
asgiref==3.10.0
|
||||
# via
|
||||
# django
|
||||
# django-cors-headers
|
||||
# django-countries
|
||||
asn1crypto==1.5.1
|
||||
# via snowflake-connector-python
|
||||
attrs==25.3.0
|
||||
attrs==25.4.0
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# aiohttp
|
||||
@@ -50,13 +50,13 @@ babel==2.17.0
|
||||
# enmerkar-underscore
|
||||
backoff==1.10.0
|
||||
# via analytics-python
|
||||
bcrypt==4.3.0
|
||||
bcrypt==5.0.0
|
||||
# via paramiko
|
||||
beautifulsoup4==4.13.5
|
||||
beautifulsoup4==4.14.2
|
||||
# via
|
||||
# openedx-forum
|
||||
# pynliner
|
||||
billiard==4.2.1
|
||||
billiard==4.2.2
|
||||
# via celery
|
||||
bleach[css]==6.2.0
|
||||
# via
|
||||
@@ -68,14 +68,14 @@ bleach[css]==6.2.0
|
||||
# xblock-poll
|
||||
boto==2.49.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
boto3==1.40.31
|
||||
boto3==1.40.46
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# django-ses
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
# snowflake-connector-python
|
||||
botocore==1.40.31
|
||||
botocore==1.40.46
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# boto3
|
||||
@@ -85,7 +85,7 @@ bridgekeeper==0.9
|
||||
# via -r requirements/edx/kernel.in
|
||||
cachecontrol==0.14.3
|
||||
# via firebase-admin
|
||||
cachetools==5.5.2
|
||||
cachetools==6.2.0
|
||||
# via
|
||||
# edxval
|
||||
# google-auth
|
||||
@@ -102,7 +102,7 @@ celery==5.5.3
|
||||
# enterprise-integrated-channels
|
||||
# event-tracking
|
||||
# openedx-learning
|
||||
certifi==2025.8.3
|
||||
certifi==2025.10.5
|
||||
# via
|
||||
# elasticsearch
|
||||
# httpcore
|
||||
@@ -122,7 +122,7 @@ charset-normalizer==3.4.3
|
||||
# snowflake-connector-python
|
||||
chem==2.0.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
click==8.2.1
|
||||
click==8.3.0
|
||||
# via
|
||||
# celery
|
||||
# click-didyoumean
|
||||
@@ -131,7 +131,6 @@ click==8.2.1
|
||||
# code-annotations
|
||||
# edx-django-utils
|
||||
# nltk
|
||||
# user-util
|
||||
click-didyoumean==0.3.1
|
||||
# via celery
|
||||
click-plugins==1.1.1.2
|
||||
@@ -148,6 +147,7 @@ crowdsourcehinter-xblock==0.8
|
||||
# via -r requirements/edx/bundled.in
|
||||
cryptography==45.0.7
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
# django-fernet-fields-v2
|
||||
# edx-enterprise
|
||||
@@ -257,7 +257,7 @@ django-config-models==2.9.0
|
||||
# edx-name-affirmation
|
||||
# enterprise-integrated-channels
|
||||
# lti-consumer-xblock
|
||||
django-cors-headers==4.8.0
|
||||
django-cors-headers==4.9.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
django-countries==7.6.1
|
||||
# via
|
||||
@@ -315,7 +315,7 @@ django-mptt==0.18.0
|
||||
# openedx-django-wiki
|
||||
django-multi-email-field==0.8.0
|
||||
# via edx-enterprise
|
||||
django-mysql==4.18.0
|
||||
django-mysql==4.19.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
django-oauth-toolkit==1.7.1
|
||||
# via
|
||||
@@ -403,7 +403,7 @@ drf-jwt==1.19.2
|
||||
# via edx-drf-extensions
|
||||
drf-spectacular==0.28.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
drf-yasg==1.21.10
|
||||
drf-yasg==1.21.11
|
||||
# via
|
||||
# django-user-tasks
|
||||
# edx-api-doc-tools
|
||||
@@ -413,7 +413,7 @@ edx-api-doc-tools==2.1.0
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# edx-name-affirmation
|
||||
edx-auth-backends==4.6.0
|
||||
edx-auth-backends==4.6.1
|
||||
# via -r requirements/edx/kernel.in
|
||||
edx-bulk-grades==1.2.0
|
||||
# via
|
||||
@@ -440,7 +440,7 @@ edx-django-release-util==1.5.0
|
||||
# edxval
|
||||
edx-django-sites-extensions==5.1.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
edx-django-utils==8.0.0
|
||||
edx-django-utils==8.0.1
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# django-config-models
|
||||
@@ -527,7 +527,7 @@ edx-search==4.3.0
|
||||
# openedx-forum
|
||||
edx-sga==0.26.0
|
||||
# via -r requirements/edx/bundled.in
|
||||
edx-submissions==3.11.1
|
||||
edx-submissions==3.12.0
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# ora2
|
||||
@@ -564,7 +564,7 @@ enmerkar==0.7.1
|
||||
# via enmerkar-underscore
|
||||
enmerkar-underscore==2.4.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
enterprise-integrated-channels==0.1.16
|
||||
enterprise-integrated-channels==0.1.18
|
||||
# via -r requirements/edx/bundled.in
|
||||
event-tracking==3.3.0
|
||||
# via
|
||||
@@ -578,7 +578,7 @@ filelock==3.19.1
|
||||
# via snowflake-connector-python
|
||||
firebase-admin==7.1.0
|
||||
# via edx-ace
|
||||
frozenlist==1.7.0
|
||||
frozenlist==1.8.0
|
||||
# via
|
||||
# aiohttp
|
||||
# aiosignal
|
||||
@@ -596,13 +596,13 @@ geoip2==5.1.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
glob2==0.7
|
||||
# via -r requirements/edx/kernel.in
|
||||
google-api-core[grpc]==2.25.1
|
||||
google-api-core[grpc]==2.25.2
|
||||
# via
|
||||
# firebase-admin
|
||||
# google-cloud-core
|
||||
# google-cloud-firestore
|
||||
# google-cloud-storage
|
||||
google-auth==2.40.3
|
||||
google-auth==2.41.1
|
||||
# via
|
||||
# google-api-core
|
||||
# google-cloud-core
|
||||
@@ -626,11 +626,11 @@ googleapis-common-protos==1.70.0
|
||||
# via
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
grpcio==1.74.0
|
||||
grpcio==1.75.1
|
||||
# via
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
grpcio-status==1.74.0
|
||||
grpcio-status==1.75.1
|
||||
# via google-api-core
|
||||
gunicorn==23.0.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
@@ -732,7 +732,7 @@ lxml[html-clean]==5.3.2
|
||||
# python3-saml
|
||||
# xblock
|
||||
# xmlsec
|
||||
lxml-html-clean==0.4.2
|
||||
lxml-html-clean==0.4.3
|
||||
# via lxml
|
||||
mailsnake==1.6.4
|
||||
# via -r requirements/edx/bundled.in
|
||||
@@ -749,7 +749,7 @@ markdown==3.9
|
||||
# openedx-django-wiki
|
||||
# staff-graded-xblock
|
||||
# xblock-poll
|
||||
markupsafe==3.0.2
|
||||
markupsafe==3.0.3
|
||||
# via
|
||||
# chem
|
||||
# jinja2
|
||||
@@ -772,7 +772,7 @@ mpmath==1.3.0
|
||||
# via sympy
|
||||
msgpack==1.1.1
|
||||
# via cachecontrol
|
||||
multidict==6.6.4
|
||||
multidict==6.7.0
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
@@ -784,7 +784,7 @@ nh3==0.3.0
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# xblocks-contrib
|
||||
nltk==3.9.1
|
||||
nltk==3.9.2
|
||||
# via chem
|
||||
nodeenv==1.9.1
|
||||
# via -r requirements/edx/kernel.in
|
||||
@@ -884,7 +884,7 @@ polib==1.2.0
|
||||
# via edx-i18n-tools
|
||||
prompt-toolkit==3.0.52
|
||||
# via click-repl
|
||||
propcache==0.3.2
|
||||
propcache==0.4.0
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
@@ -899,7 +899,7 @@ protobuf==6.32.1
|
||||
# googleapis-common-protos
|
||||
# grpcio-status
|
||||
# proto-plus
|
||||
psutil==7.0.0
|
||||
psutil==7.1.0
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# edx-django-utils
|
||||
@@ -919,7 +919,7 @@ pycryptodomex==3.23.0
|
||||
# -r requirements/edx/kernel.in
|
||||
# edx-proctoring
|
||||
# lti-consumer-xblock
|
||||
pydantic==2.11.9
|
||||
pydantic==2.11.10
|
||||
# via camel-converter
|
||||
pydantic-core==2.33.2
|
||||
# via pydantic
|
||||
@@ -956,9 +956,9 @@ pynacl==1.6.0
|
||||
# paramiko
|
||||
pynliner==0.8.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
pyopenssl==25.2.0
|
||||
pyopenssl==25.3.0
|
||||
# via snowflake-connector-python
|
||||
pyparsing==3.2.4
|
||||
pyparsing==3.2.5
|
||||
# via
|
||||
# chem
|
||||
# openedx-calc
|
||||
@@ -1010,7 +1010,7 @@ pytz==2025.2
|
||||
# xblock
|
||||
pyuca==1.2
|
||||
# via -r requirements/edx/kernel.in
|
||||
pyyaml==6.0.2
|
||||
pyyaml==6.0.3
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# code-annotations
|
||||
@@ -1032,7 +1032,7 @@ referencing==0.36.2
|
||||
# via
|
||||
# jsonschema
|
||||
# jsonschema-specifications
|
||||
regex==2025.9.1
|
||||
regex==2025.9.18
|
||||
# via nltk
|
||||
requests==2.32.5
|
||||
# via
|
||||
@@ -1084,9 +1084,9 @@ scipy==1.16.2
|
||||
# via chem
|
||||
semantic-version==2.10.0
|
||||
# via edx-drf-extensions
|
||||
shapely==2.1.1
|
||||
shapely==2.1.2
|
||||
# via -r requirements/edx/kernel.in
|
||||
simplejson==3.20.1
|
||||
simplejson==3.20.2
|
||||
# via
|
||||
# -r requirements/edx/kernel.in
|
||||
# sailthru-client
|
||||
@@ -1118,7 +1118,7 @@ slumber==0.7.1
|
||||
# enterprise-integrated-channels
|
||||
sniffio==1.3.1
|
||||
# via anyio
|
||||
snowflake-connector-python==3.17.3
|
||||
snowflake-connector-python==3.18.0
|
||||
# via edx-enterprise
|
||||
social-auth-app-django==5.4.1
|
||||
# via
|
||||
@@ -1177,6 +1177,7 @@ typing-extensions==4.15.0
|
||||
# beautifulsoup4
|
||||
# django-countries
|
||||
# edx-opaque-keys
|
||||
# grpcio
|
||||
# jwcrypto
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
@@ -1185,7 +1186,7 @@ typing-extensions==4.15.0
|
||||
# referencing
|
||||
# snowflake-connector-python
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
tzdata==2025.2
|
||||
# via
|
||||
@@ -1207,8 +1208,6 @@ urllib3==2.5.0
|
||||
# botocore
|
||||
# elasticsearch
|
||||
# requests
|
||||
user-util==2.0.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
vine==5.1.0
|
||||
# via
|
||||
# amqp
|
||||
@@ -1218,7 +1217,7 @@ voluptuous==0.15.2
|
||||
# via ora2
|
||||
walrus==0.9.5
|
||||
# via edx-event-bus-redis
|
||||
wcwidth==0.2.13
|
||||
wcwidth==0.2.14
|
||||
# via prompt-toolkit
|
||||
web-fragments==3.1.0
|
||||
# via
|
||||
@@ -1275,7 +1274,7 @@ xmlsec==1.3.14
|
||||
# python3-saml
|
||||
xss-utils==0.8.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
yarl==1.20.1
|
||||
yarl==1.22.0
|
||||
# via aiohttp
|
||||
zipp==3.23.0
|
||||
# via importlib-metadata
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
#
|
||||
chardet==5.2.0
|
||||
# via diff-cover
|
||||
coverage==7.10.6
|
||||
coverage==7.10.7
|
||||
# via -r requirements/edx/coverage.in
|
||||
diff-cover==9.6.0
|
||||
diff-cover==9.7.1
|
||||
# via -r requirements/edx/coverage.in
|
||||
jinja2==3.1.6
|
||||
# via diff-cover
|
||||
markupsafe==3.0.2
|
||||
markupsafe==3.0.3
|
||||
# via jinja2
|
||||
pluggy==1.6.0
|
||||
# via diff-cover
|
||||
|
||||
@@ -17,7 +17,7 @@ aiohappyeyeballs==2.6.1
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# aiohttp
|
||||
aiohttp==3.12.15
|
||||
aiohttp==3.13.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -51,7 +51,7 @@ annotated-types==0.7.0
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# pydantic
|
||||
anyio==4.10.0
|
||||
anyio==4.11.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -62,7 +62,7 @@ appdirs==1.4.4
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# fs
|
||||
asgiref==3.9.1
|
||||
asgiref==3.10.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -81,7 +81,7 @@ astroid==3.3.11
|
||||
# pylint
|
||||
# pylint-celery
|
||||
# sphinx-autoapi
|
||||
attrs==25.3.0
|
||||
attrs==25.4.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -105,19 +105,19 @@ backoff==1.10.0
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# analytics-python
|
||||
bcrypt==4.3.0
|
||||
bcrypt==5.0.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# paramiko
|
||||
beautifulsoup4==4.13.5
|
||||
beautifulsoup4==4.14.2
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# openedx-forum
|
||||
# pydata-sphinx-theme
|
||||
# pynliner
|
||||
billiard==4.2.1
|
||||
billiard==4.2.2
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -136,7 +136,7 @@ boto==2.49.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
boto3==1.40.31
|
||||
boto3==1.40.46
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -144,7 +144,7 @@ boto3==1.40.31
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
# snowflake-connector-python
|
||||
botocore==1.40.31
|
||||
botocore==1.40.46
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -164,7 +164,7 @@ cachecontrol==0.14.3
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# firebase-admin
|
||||
cachetools==5.5.2
|
||||
cachetools==6.2.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -188,7 +188,7 @@ celery==5.5.3
|
||||
# enterprise-integrated-channels
|
||||
# event-tracking
|
||||
# openedx-learning
|
||||
certifi==2025.8.3
|
||||
certifi==2025.10.5
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -222,7 +222,7 @@ chem==2.0.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
click==8.2.1
|
||||
click==8.3.0
|
||||
# via
|
||||
# -r requirements/edx/assets.txt
|
||||
# -r requirements/edx/development.in
|
||||
@@ -241,7 +241,6 @@ click==8.2.1
|
||||
# nltk
|
||||
# pact-python
|
||||
# pip-tools
|
||||
# user-util
|
||||
# uvicorn
|
||||
click-didyoumean==0.3.1
|
||||
# via
|
||||
@@ -277,7 +276,7 @@ colorama==0.4.6
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# tox
|
||||
coverage[toml]==7.10.6
|
||||
coverage[toml]==7.10.7
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# pytest-cov
|
||||
@@ -287,6 +286,7 @@ crowdsourcehinter-xblock==0.8
|
||||
# -r requirements/edx/testing.txt
|
||||
cryptography==45.0.7
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# django-fernet-fields-v2
|
||||
@@ -321,7 +321,7 @@ defusedxml==0.7.1
|
||||
# ora2
|
||||
# python3-openid
|
||||
# social-auth-core
|
||||
diff-cover==9.6.0
|
||||
diff-cover==9.7.1
|
||||
# via -r requirements/edx/testing.txt
|
||||
dill==0.4.0
|
||||
# via
|
||||
@@ -439,7 +439,7 @@ django-config-models==2.9.0
|
||||
# edx-name-affirmation
|
||||
# enterprise-integrated-channels
|
||||
# lti-consumer-xblock
|
||||
django-cors-headers==4.8.0
|
||||
django-cors-headers==4.9.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -519,7 +519,7 @@ django-multi-email-field==0.8.0
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# edx-enterprise
|
||||
django-mysql==4.18.0
|
||||
django-mysql==4.19.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -581,12 +581,12 @@ django-storages==1.14.6
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# edxval
|
||||
django-stubs[compatible-mypy]==5.2.5
|
||||
django-stubs[compatible-mypy]==5.2.6
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx/development.in
|
||||
# djangorestframework-stubs
|
||||
django-stubs-ext==5.2.5
|
||||
django-stubs-ext==5.2.6
|
||||
# via django-stubs
|
||||
django-user-tasks==3.4.3
|
||||
# via
|
||||
@@ -627,7 +627,7 @@ djangorestframework==3.16.1
|
||||
# openedx-learning
|
||||
# ora2
|
||||
# super-csv
|
||||
djangorestframework-stubs==3.16.2
|
||||
djangorestframework-stubs==3.16.4
|
||||
# via -r requirements/edx/development.in
|
||||
djangorestframework-xml==2.0.0
|
||||
# via
|
||||
@@ -658,7 +658,7 @@ drf-spectacular==0.28.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
drf-yasg==1.21.10
|
||||
drf-yasg==1.21.11
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -673,7 +673,7 @@ edx-api-doc-tools==2.1.0
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# edx-name-affirmation
|
||||
edx-auth-backends==4.6.0
|
||||
edx-auth-backends==4.6.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -712,7 +712,7 @@ edx-django-sites-extensions==5.1.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
edx-django-utils==8.0.0
|
||||
edx-django-utils==8.0.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -824,7 +824,7 @@ edx-sga==0.26.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
edx-submissions==3.11.1
|
||||
edx-submissions==3.12.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -875,7 +875,7 @@ enmerkar-underscore==2.4.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
enterprise-integrated-channels==0.1.16
|
||||
enterprise-integrated-channels==0.1.18
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -896,7 +896,7 @@ faker==37.8.0
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# factory-boy
|
||||
fastapi==0.116.1
|
||||
fastapi==0.118.0
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# pact-python
|
||||
@@ -919,7 +919,7 @@ firebase-admin==7.1.0
|
||||
# edx-ace
|
||||
freezegun==1.5.5
|
||||
# via -r requirements/edx/testing.txt
|
||||
frozenlist==1.7.0
|
||||
frozenlist==1.8.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -951,7 +951,7 @@ glob2==0.7
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
google-api-core[grpc]==2.25.1
|
||||
google-api-core[grpc]==2.25.2
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -959,7 +959,7 @@ google-api-core[grpc]==2.25.1
|
||||
# google-cloud-core
|
||||
# google-cloud-firestore
|
||||
# google-cloud-storage
|
||||
google-auth==2.40.3
|
||||
google-auth==2.41.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1004,13 +1004,13 @@ grimp==3.11
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# import-linter
|
||||
grpcio==1.74.0
|
||||
grpcio==1.75.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
grpcio-status==1.74.0
|
||||
grpcio-status==1.75.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1109,7 +1109,7 @@ isodate==0.7.2
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# python3-saml
|
||||
isort==6.0.1
|
||||
isort==6.1.0
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# pylint
|
||||
@@ -1212,7 +1212,7 @@ lxml[html-clean]==5.3.2
|
||||
# python3-saml
|
||||
# xblock
|
||||
# xmlsec
|
||||
lxml-html-clean==0.4.2
|
||||
lxml-html-clean==0.4.3
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1236,7 +1236,7 @@ markdown==3.9
|
||||
# openedx-django-wiki
|
||||
# staff-graded-xblock
|
||||
# xblock-poll
|
||||
markupsafe==3.0.2
|
||||
markupsafe==3.0.3
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1289,13 +1289,13 @@ msgpack==1.1.1
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# cachecontrol
|
||||
multidict==6.6.4
|
||||
multidict==6.7.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# aiohttp
|
||||
# yarl
|
||||
mypy==1.18.1
|
||||
mypy==1.18.2
|
||||
# via
|
||||
# -r requirements/edx/development.in
|
||||
# django-stubs
|
||||
@@ -1311,7 +1311,7 @@ nh3==0.3.0
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# xblocks-contrib
|
||||
nltk==3.9.1
|
||||
nltk==3.9.2
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1423,7 +1423,9 @@ packaging==25.0
|
||||
# sphinx
|
||||
# tox
|
||||
pact-python==2.3.3
|
||||
# via -r requirements/edx/testing.txt
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
paramiko==4.0.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
@@ -1465,7 +1467,7 @@ pillow==11.3.0
|
||||
# edx-enterprise
|
||||
# edx-organizations
|
||||
# edxval
|
||||
pip-tools==7.5.0
|
||||
pip-tools==7.5.1
|
||||
# via -r requirements/pip-tools.txt
|
||||
platformdirs==4.4.0
|
||||
# via
|
||||
@@ -1492,7 +1494,7 @@ prompt-toolkit==3.0.52
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# click-repl
|
||||
propcache==0.3.2
|
||||
propcache==0.4.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1513,7 +1515,7 @@ protobuf==6.32.1
|
||||
# googleapis-common-protos
|
||||
# grpcio-status
|
||||
# proto-plus
|
||||
psutil==7.0.0
|
||||
psutil==7.1.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1553,7 +1555,7 @@ pycryptodomex==3.23.0
|
||||
# -r requirements/edx/testing.txt
|
||||
# edx-proctoring
|
||||
# lti-consumer-xblock
|
||||
pydantic==2.11.9
|
||||
pydantic==2.11.10
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1596,7 +1598,7 @@ pylatexenc==2.10
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# olxcleaner
|
||||
pylint==3.3.8
|
||||
pylint==3.3.9
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# edx-lint
|
||||
@@ -1646,12 +1648,12 @@ pynliner==0.8.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
pyopenssl==25.2.0
|
||||
pyopenssl==25.3.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# snowflake-connector-python
|
||||
pyparsing==3.2.4
|
||||
pyparsing==3.2.5
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1766,7 +1768,7 @@ pyuca==1.2
|
||||
# -r requirements/edx/testing.txt
|
||||
pywatchman==3.0.0
|
||||
# via -r requirements/edx/development.in
|
||||
pyyaml==6.0.2
|
||||
pyyaml==6.0.3
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1798,7 +1800,7 @@ referencing==0.36.2
|
||||
# -r requirements/edx/testing.txt
|
||||
# jsonschema
|
||||
# jsonschema-specifications
|
||||
regex==2025.9.1
|
||||
regex==2025.9.18
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1881,11 +1883,11 @@ semantic-version==2.10.0
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
# edx-drf-extensions
|
||||
shapely==2.1.1
|
||||
shapely==2.1.2
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
simplejson==3.20.1
|
||||
simplejson==3.20.2
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1938,7 +1940,7 @@ snowballstemmer==3.0.1
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# sphinx
|
||||
snowflake-connector-python==3.17.3
|
||||
snowflake-connector-python==3.18.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -1982,7 +1984,7 @@ sphinx==8.2.3
|
||||
# sphinxcontrib-httpdomain
|
||||
# sphinxcontrib-openapi
|
||||
# sphinxext-rediraffe
|
||||
sphinx-autoapi==3.6.0
|
||||
sphinx-autoapi==3.6.1
|
||||
# via -r requirements/edx/doc.txt
|
||||
sphinx-book-theme==1.1.4
|
||||
# via -r requirements/edx/doc.txt
|
||||
@@ -2024,7 +2026,7 @@ sphinxcontrib-serializinghtml==2.0.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# sphinx
|
||||
sphinxext-rediraffe==0.2.7
|
||||
sphinxext-rediraffe==0.3.0
|
||||
# via -r requirements/edx/doc.txt
|
||||
sqlparse==0.5.3
|
||||
# via
|
||||
@@ -2036,7 +2038,7 @@ staff-graded-xblock==3.1.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
starlette==0.47.3
|
||||
starlette==0.48.0
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# fastapi
|
||||
@@ -2081,7 +2083,7 @@ tomlkit==0.13.3
|
||||
# openedx-learning
|
||||
# pylint
|
||||
# snowflake-connector-python
|
||||
tox==4.27.0
|
||||
tox==4.30.3
|
||||
# via -r requirements/edx/testing.txt
|
||||
tqdm==4.67.1
|
||||
# via
|
||||
@@ -2109,6 +2111,7 @@ typing-extensions==4.15.0
|
||||
# edx-opaque-keys
|
||||
# fastapi
|
||||
# grimp
|
||||
# grpcio
|
||||
# import-linter
|
||||
# jwcrypto
|
||||
# mypy
|
||||
@@ -2121,7 +2124,7 @@ typing-extensions==4.15.0
|
||||
# snowflake-connector-python
|
||||
# starlette
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
typing-inspection==0.4.2
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -2159,11 +2162,7 @@ urllib3==2.5.0
|
||||
# elasticsearch
|
||||
# requests
|
||||
# types-requests
|
||||
user-util==2.0.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
uvicorn==0.35.0
|
||||
uvicorn==0.37.0
|
||||
# via
|
||||
# -r requirements/edx/testing.txt
|
||||
# pact-python
|
||||
@@ -2192,7 +2191,7 @@ walrus==0.9.5
|
||||
# edx-event-bus-redis
|
||||
watchdog==6.0.0
|
||||
# via -r requirements/edx/development.in
|
||||
wcwidth==0.2.13
|
||||
wcwidth==0.2.14
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
@@ -2278,7 +2277,7 @@ xss-utils==0.8.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
yarl==1.20.1
|
||||
yarl==1.22.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
|
||||
@@ -12,7 +12,7 @@ aiohappyeyeballs==2.6.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
aiohttp==3.12.15
|
||||
aiohttp==3.13.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# geoip2
|
||||
@@ -37,7 +37,7 @@ annotated-types==0.7.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# pydantic
|
||||
anyio==4.10.0
|
||||
anyio==4.11.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# httpx
|
||||
@@ -45,7 +45,7 @@ appdirs==1.4.4
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# fs
|
||||
asgiref==3.9.1
|
||||
asgiref==3.10.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django
|
||||
@@ -57,7 +57,7 @@ asn1crypto==1.5.1
|
||||
# snowflake-connector-python
|
||||
astroid==3.3.11
|
||||
# via sphinx-autoapi
|
||||
attrs==25.3.0
|
||||
attrs==25.4.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
@@ -78,17 +78,17 @@ backoff==1.10.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# analytics-python
|
||||
bcrypt==4.3.0
|
||||
bcrypt==5.0.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# paramiko
|
||||
beautifulsoup4==4.13.5
|
||||
beautifulsoup4==4.14.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# openedx-forum
|
||||
# pydata-sphinx-theme
|
||||
# pynliner
|
||||
billiard==4.2.1
|
||||
billiard==4.2.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# celery
|
||||
@@ -103,14 +103,14 @@ bleach[css]==6.2.0
|
||||
# xblock-poll
|
||||
boto==2.49.0
|
||||
# via -r requirements/edx/base.txt
|
||||
boto3==1.40.31
|
||||
boto3==1.40.46
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django-ses
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
# snowflake-connector-python
|
||||
botocore==1.40.31
|
||||
botocore==1.40.46
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
@@ -122,7 +122,7 @@ cachecontrol==0.14.3
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# firebase-admin
|
||||
cachetools==5.5.2
|
||||
cachetools==6.2.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edxval
|
||||
@@ -142,7 +142,7 @@ celery==5.5.3
|
||||
# enterprise-integrated-channels
|
||||
# event-tracking
|
||||
# openedx-learning
|
||||
certifi==2025.8.3
|
||||
certifi==2025.10.5
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# elasticsearch
|
||||
@@ -167,7 +167,7 @@ charset-normalizer==3.4.3
|
||||
# snowflake-connector-python
|
||||
chem==2.0.0
|
||||
# via -r requirements/edx/base.txt
|
||||
click==8.2.1
|
||||
click==8.3.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# celery
|
||||
@@ -177,7 +177,6 @@ click==8.2.1
|
||||
# code-annotations
|
||||
# edx-django-utils
|
||||
# nltk
|
||||
# user-util
|
||||
click-didyoumean==0.3.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -202,6 +201,7 @@ crowdsourcehinter-xblock==0.8
|
||||
# via -r requirements/edx/base.txt
|
||||
cryptography==45.0.7
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# django-fernet-fields-v2
|
||||
# edx-enterprise
|
||||
@@ -321,7 +321,7 @@ django-config-models==2.9.0
|
||||
# edx-name-affirmation
|
||||
# enterprise-integrated-channels
|
||||
# lti-consumer-xblock
|
||||
django-cors-headers==4.8.0
|
||||
django-cors-headers==4.9.0
|
||||
# via -r requirements/edx/base.txt
|
||||
django-countries==7.6.1
|
||||
# via
|
||||
@@ -384,7 +384,7 @@ django-multi-email-field==0.8.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
django-mysql==4.18.0
|
||||
django-mysql==4.19.0
|
||||
# via -r requirements/edx/base.txt
|
||||
django-oauth-toolkit==1.7.1
|
||||
# via
|
||||
@@ -486,7 +486,7 @@ drf-jwt==1.19.2
|
||||
# edx-drf-extensions
|
||||
drf-spectacular==0.28.0
|
||||
# via -r requirements/edx/base.txt
|
||||
drf-yasg==1.21.10
|
||||
drf-yasg==1.21.11
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django-user-tasks
|
||||
@@ -497,7 +497,7 @@ edx-api-doc-tools==2.1.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-name-affirmation
|
||||
edx-auth-backends==4.6.0
|
||||
edx-auth-backends==4.6.1
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-bulk-grades==1.2.0
|
||||
# via
|
||||
@@ -524,7 +524,7 @@ edx-django-release-util==1.5.0
|
||||
# edxval
|
||||
edx-django-sites-extensions==5.1.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-django-utils==8.0.0
|
||||
edx-django-utils==8.0.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django-config-models
|
||||
@@ -612,7 +612,7 @@ edx-search==4.3.0
|
||||
# openedx-forum
|
||||
edx-sga==0.26.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-submissions==3.11.1
|
||||
edx-submissions==3.12.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# ora2
|
||||
@@ -653,7 +653,7 @@ enmerkar==0.7.1
|
||||
# enmerkar-underscore
|
||||
enmerkar-underscore==2.4.0
|
||||
# via -r requirements/edx/base.txt
|
||||
enterprise-integrated-channels==0.1.16
|
||||
enterprise-integrated-channels==0.1.18
|
||||
# via -r requirements/edx/base.txt
|
||||
event-tracking==3.3.0
|
||||
# via
|
||||
@@ -673,7 +673,7 @@ firebase-admin==7.1.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-ace
|
||||
frozenlist==1.7.0
|
||||
frozenlist==1.8.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
@@ -696,14 +696,14 @@ gitpython==3.1.45
|
||||
# via -r requirements/edx/doc.in
|
||||
glob2==0.7
|
||||
# via -r requirements/edx/base.txt
|
||||
google-api-core[grpc]==2.25.1
|
||||
google-api-core[grpc]==2.25.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# firebase-admin
|
||||
# google-cloud-core
|
||||
# google-cloud-firestore
|
||||
# google-cloud-storage
|
||||
google-auth==2.40.3
|
||||
google-auth==2.41.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# google-api-core
|
||||
@@ -737,12 +737,12 @@ googleapis-common-protos==1.70.0
|
||||
# -r requirements/edx/base.txt
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
grpcio==1.74.0
|
||||
grpcio==1.75.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
grpcio-status==1.74.0
|
||||
grpcio-status==1.75.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# google-api-core
|
||||
@@ -885,7 +885,7 @@ lxml[html-clean]==5.3.2
|
||||
# python3-saml
|
||||
# xblock
|
||||
# xmlsec
|
||||
lxml-html-clean==0.4.2
|
||||
lxml-html-clean==0.4.3
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# lxml
|
||||
@@ -904,7 +904,7 @@ markdown==3.9
|
||||
# openedx-django-wiki
|
||||
# staff-graded-xblock
|
||||
# xblock-poll
|
||||
markupsafe==3.0.2
|
||||
markupsafe==3.0.3
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# chem
|
||||
@@ -940,7 +940,7 @@ msgpack==1.1.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# cachecontrol
|
||||
multidict==6.6.4
|
||||
multidict==6.7.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
@@ -953,7 +953,7 @@ nh3==0.3.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# xblocks-contrib
|
||||
nltk==3.9.1
|
||||
nltk==3.9.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# chem
|
||||
@@ -1074,7 +1074,7 @@ prompt-toolkit==3.0.52
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# click-repl
|
||||
propcache==0.3.2
|
||||
propcache==0.4.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
@@ -1092,7 +1092,7 @@ protobuf==6.32.1
|
||||
# googleapis-common-protos
|
||||
# grpcio-status
|
||||
# proto-plus
|
||||
psutil==7.0.0
|
||||
psutil==7.1.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-django-utils
|
||||
@@ -1117,7 +1117,7 @@ pycryptodomex==3.23.0
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-proctoring
|
||||
# lti-consumer-xblock
|
||||
pydantic==2.11.9
|
||||
pydantic==2.11.10
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# camel-converter
|
||||
@@ -1169,11 +1169,11 @@ pynacl==1.6.0
|
||||
# paramiko
|
||||
pynliner==0.8.0
|
||||
# via -r requirements/edx/base.txt
|
||||
pyopenssl==25.2.0
|
||||
pyopenssl==25.3.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# snowflake-connector-python
|
||||
pyparsing==3.2.4
|
||||
pyparsing==3.2.5
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# chem
|
||||
@@ -1234,7 +1234,7 @@ pytz==2025.2
|
||||
# xblock
|
||||
pyuca==1.2
|
||||
# via -r requirements/edx/base.txt
|
||||
pyyaml==6.0.2
|
||||
pyyaml==6.0.3
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# code-annotations
|
||||
@@ -1259,7 +1259,7 @@ referencing==0.36.2
|
||||
# -r requirements/edx/base.txt
|
||||
# jsonschema
|
||||
# jsonschema-specifications
|
||||
regex==2025.9.1
|
||||
regex==2025.9.18
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# nltk
|
||||
@@ -1328,9 +1328,9 @@ semantic-version==2.10.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-drf-extensions
|
||||
shapely==2.1.1
|
||||
shapely==2.1.2
|
||||
# via -r requirements/edx/base.txt
|
||||
simplejson==3.20.1
|
||||
simplejson==3.20.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# sailthru-client
|
||||
@@ -1369,7 +1369,7 @@ sniffio==1.3.1
|
||||
# anyio
|
||||
snowballstemmer==3.0.1
|
||||
# via sphinx
|
||||
snowflake-connector-python==3.17.3
|
||||
snowflake-connector-python==3.18.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
@@ -1407,7 +1407,7 @@ sphinx==8.2.3
|
||||
# sphinxcontrib-httpdomain
|
||||
# sphinxcontrib-openapi
|
||||
# sphinxext-rediraffe
|
||||
sphinx-autoapi==3.6.0
|
||||
sphinx-autoapi==3.6.1
|
||||
# via -r requirements/edx/doc.in
|
||||
sphinx-book-theme==1.1.4
|
||||
# via -r requirements/edx/doc.in
|
||||
@@ -1433,7 +1433,7 @@ sphinxcontrib-qthelp==2.0.0
|
||||
# via sphinx
|
||||
sphinxcontrib-serializinghtml==2.0.0
|
||||
# via sphinx
|
||||
sphinxext-rediraffe==0.2.7
|
||||
sphinxext-rediraffe==0.3.0
|
||||
# via -r requirements/edx/doc.in
|
||||
sqlparse==0.5.3
|
||||
# via
|
||||
@@ -1487,6 +1487,7 @@ typing-extensions==4.15.0
|
||||
# beautifulsoup4
|
||||
# django-countries
|
||||
# edx-opaque-keys
|
||||
# grpcio
|
||||
# jwcrypto
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
@@ -1496,7 +1497,7 @@ typing-extensions==4.15.0
|
||||
# referencing
|
||||
# snowflake-connector-python
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
typing-inspection==0.4.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# pydantic
|
||||
@@ -1523,8 +1524,6 @@ urllib3==2.5.0
|
||||
# botocore
|
||||
# elasticsearch
|
||||
# requests
|
||||
user-util==2.0.0
|
||||
# via -r requirements/edx/base.txt
|
||||
vine==5.1.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -1539,7 +1538,7 @@ walrus==0.9.5
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-event-bus-redis
|
||||
wcwidth==0.2.13
|
||||
wcwidth==0.2.14
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# prompt-toolkit
|
||||
@@ -1603,7 +1602,7 @@ xmlsec==1.3.14
|
||||
# python3-saml
|
||||
xss-utils==0.8.0
|
||||
# via -r requirements/edx/base.txt
|
||||
yarl==1.20.1
|
||||
yarl==1.22.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
|
||||
@@ -155,7 +155,6 @@ sorl-thumbnail
|
||||
sortedcontainers # Provides SortedKeyList, used for lists of XBlock assets
|
||||
stevedore # Support for runtime plugins, used for XBlocks and edx-platform Django app plugins
|
||||
unicodecsv # Easier support for CSV files with unicode text
|
||||
user-util # Functionality for retiring users (GDPR compliance)
|
||||
webob
|
||||
web-fragments # Provides the ability to render fragments of web pages
|
||||
wrapt # Better functools.wrapped. TODO: functools has since improved, maybe we can switch?
|
||||
|
||||
@@ -4,7 +4,15 @@
|
||||
#
|
||||
# make upgrade
|
||||
#
|
||||
attrs==25.3.0
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
anyio==4.11.0
|
||||
# via
|
||||
# httpx
|
||||
# mcp
|
||||
# sse-starlette
|
||||
# starlette
|
||||
attrs==25.4.0
|
||||
# via
|
||||
# glom
|
||||
# jsonschema
|
||||
@@ -17,24 +25,24 @@ boltons==21.0.0
|
||||
# semgrep
|
||||
bracex==2.6
|
||||
# via wcmatch
|
||||
certifi==2025.8.3
|
||||
# via requests
|
||||
certifi==2025.10.5
|
||||
# via
|
||||
# httpcore
|
||||
# httpx
|
||||
# requests
|
||||
charset-normalizer==3.4.3
|
||||
# via requests
|
||||
click==8.1.8
|
||||
# via
|
||||
# click-option-group
|
||||
# semgrep
|
||||
click-option-group==0.5.7
|
||||
# uvicorn
|
||||
click-option-group==0.5.8
|
||||
# via semgrep
|
||||
colorama==0.4.6
|
||||
# via semgrep
|
||||
defusedxml==0.7.1
|
||||
# via semgrep
|
||||
deprecated==1.2.18
|
||||
# via
|
||||
# opentelemetry-api
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
exceptiongroup==1.2.2
|
||||
# via semgrep
|
||||
face==24.0.0
|
||||
@@ -43,19 +51,36 @@ glom==22.1.0
|
||||
# via semgrep
|
||||
googleapis-common-protos==1.70.0
|
||||
# via opentelemetry-exporter-otlp-proto-http
|
||||
h11==0.16.0
|
||||
# via
|
||||
# httpcore
|
||||
# uvicorn
|
||||
httpcore==1.0.9
|
||||
# via httpx
|
||||
httpx==0.28.1
|
||||
# via mcp
|
||||
httpx-sse==0.4.1
|
||||
# via mcp
|
||||
idna==3.10
|
||||
# via requests
|
||||
importlib-metadata==7.1.0
|
||||
# via
|
||||
# anyio
|
||||
# httpx
|
||||
# requests
|
||||
importlib-metadata==8.7.0
|
||||
# via opentelemetry-api
|
||||
jsonschema==4.25.1
|
||||
# via semgrep
|
||||
jsonschema==4.20.0
|
||||
# via
|
||||
# mcp
|
||||
# semgrep
|
||||
jsonschema-specifications==2025.9.1
|
||||
# via jsonschema
|
||||
markdown-it-py==4.0.0
|
||||
# via rich
|
||||
mcp==1.12.2
|
||||
# via semgrep
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
opentelemetry-api==1.25.0
|
||||
opentelemetry-api==1.37.0
|
||||
# via
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
# opentelemetry-instrumentation
|
||||
@@ -63,38 +88,53 @@ opentelemetry-api==1.25.0
|
||||
# opentelemetry-sdk
|
||||
# opentelemetry-semantic-conventions
|
||||
# semgrep
|
||||
opentelemetry-exporter-otlp-proto-common==1.25.0
|
||||
opentelemetry-exporter-otlp-proto-common==1.37.0
|
||||
# via opentelemetry-exporter-otlp-proto-http
|
||||
opentelemetry-exporter-otlp-proto-http==1.25.0
|
||||
opentelemetry-exporter-otlp-proto-http==1.37.0
|
||||
# via semgrep
|
||||
opentelemetry-instrumentation==0.46b0
|
||||
opentelemetry-instrumentation==0.58b0
|
||||
# via opentelemetry-instrumentation-requests
|
||||
opentelemetry-instrumentation-requests==0.46b0
|
||||
opentelemetry-instrumentation-requests==0.58b0
|
||||
# via semgrep
|
||||
opentelemetry-proto==1.25.0
|
||||
opentelemetry-proto==1.37.0
|
||||
# via
|
||||
# opentelemetry-exporter-otlp-proto-common
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
opentelemetry-sdk==1.25.0
|
||||
opentelemetry-sdk==1.37.0
|
||||
# via
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
# semgrep
|
||||
opentelemetry-semantic-conventions==0.46b0
|
||||
opentelemetry-semantic-conventions==0.58b0
|
||||
# via
|
||||
# opentelemetry-instrumentation
|
||||
# opentelemetry-instrumentation-requests
|
||||
# opentelemetry-sdk
|
||||
opentelemetry-util-http==0.46b0
|
||||
opentelemetry-util-http==0.58b0
|
||||
# via opentelemetry-instrumentation-requests
|
||||
packaging==25.0
|
||||
# via semgrep
|
||||
# via
|
||||
# opentelemetry-instrumentation
|
||||
# semgrep
|
||||
peewee==3.18.2
|
||||
# via semgrep
|
||||
protobuf==4.25.8
|
||||
protobuf==6.32.1
|
||||
# via
|
||||
# googleapis-common-protos
|
||||
# opentelemetry-proto
|
||||
pydantic==2.11.10
|
||||
# via
|
||||
# mcp
|
||||
# pydantic-settings
|
||||
pydantic-core==2.33.2
|
||||
# via pydantic
|
||||
pydantic-settings==2.11.0
|
||||
# via mcp
|
||||
pygments==2.19.2
|
||||
# via rich
|
||||
python-dotenv==1.1.1
|
||||
# via pydantic-settings
|
||||
python-multipart==0.0.20
|
||||
# via mcp
|
||||
referencing==0.36.2
|
||||
# via
|
||||
# jsonschema
|
||||
@@ -112,28 +152,45 @@ rpds-py==0.27.1
|
||||
ruamel-yaml==0.18.15
|
||||
# via semgrep
|
||||
ruamel-yaml-clib==0.2.12
|
||||
# via ruamel-yaml
|
||||
semgrep==1.136.0
|
||||
# via
|
||||
# ruamel-yaml
|
||||
# semgrep
|
||||
semgrep==1.139.0
|
||||
# via -r requirements/edx/semgrep.in
|
||||
sniffio==1.3.1
|
||||
# via anyio
|
||||
sse-starlette==3.0.2
|
||||
# via mcp
|
||||
starlette==0.48.0
|
||||
# via mcp
|
||||
tomli==2.0.2
|
||||
# via semgrep
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# anyio
|
||||
# opentelemetry-api
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
# opentelemetry-sdk
|
||||
# opentelemetry-semantic-conventions
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# referencing
|
||||
# semgrep
|
||||
# starlette
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.2
|
||||
# via
|
||||
# pydantic
|
||||
# pydantic-settings
|
||||
urllib3==2.5.0
|
||||
# via
|
||||
# requests
|
||||
# semgrep
|
||||
uvicorn==0.37.0
|
||||
# via mcp
|
||||
wcmatch==8.5.2
|
||||
# via semgrep
|
||||
wrapt==1.17.3
|
||||
# via
|
||||
# deprecated
|
||||
# opentelemetry-instrumentation
|
||||
# via opentelemetry-instrumentation
|
||||
zipp==3.23.0
|
||||
# via importlib-metadata
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# setuptools
|
||||
|
||||
@@ -10,7 +10,7 @@ aiohappyeyeballs==2.6.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
aiohttp==3.12.15
|
||||
aiohttp==3.13.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# geoip2
|
||||
@@ -33,7 +33,7 @@ annotated-types==0.7.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# pydantic
|
||||
anyio==4.10.0
|
||||
anyio==4.11.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# httpx
|
||||
@@ -42,7 +42,7 @@ appdirs==1.4.4
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# fs
|
||||
asgiref==3.9.1
|
||||
asgiref==3.10.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django
|
||||
@@ -56,7 +56,7 @@ astroid==3.3.11
|
||||
# via
|
||||
# pylint
|
||||
# pylint-celery
|
||||
attrs==25.3.0
|
||||
attrs==25.4.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
@@ -75,17 +75,17 @@ backoff==1.10.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# analytics-python
|
||||
bcrypt==4.3.0
|
||||
bcrypt==5.0.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# paramiko
|
||||
beautifulsoup4==4.13.5
|
||||
beautifulsoup4==4.14.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# -r requirements/edx/testing.in
|
||||
# openedx-forum
|
||||
# pynliner
|
||||
billiard==4.2.1
|
||||
billiard==4.2.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# celery
|
||||
@@ -100,14 +100,14 @@ bleach[css]==6.2.0
|
||||
# xblock-poll
|
||||
boto==2.49.0
|
||||
# via -r requirements/edx/base.txt
|
||||
boto3==1.40.31
|
||||
boto3==1.40.46
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django-ses
|
||||
# fs-s3fs
|
||||
# ora2
|
||||
# snowflake-connector-python
|
||||
botocore==1.40.31
|
||||
botocore==1.40.46
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# boto3
|
||||
@@ -119,7 +119,7 @@ cachecontrol==0.14.3
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# firebase-admin
|
||||
cachetools==5.5.2
|
||||
cachetools==6.2.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edxval
|
||||
@@ -140,7 +140,7 @@ celery==5.5.3
|
||||
# enterprise-integrated-channels
|
||||
# event-tracking
|
||||
# openedx-learning
|
||||
certifi==2025.8.3
|
||||
certifi==2025.10.5
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# elasticsearch
|
||||
@@ -169,7 +169,7 @@ charset-normalizer==3.4.3
|
||||
# snowflake-connector-python
|
||||
chem==2.0.0
|
||||
# via -r requirements/edx/base.txt
|
||||
click==8.2.1
|
||||
click==8.3.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# celery
|
||||
@@ -183,7 +183,6 @@ click==8.2.1
|
||||
# import-linter
|
||||
# nltk
|
||||
# pact-python
|
||||
# user-util
|
||||
# uvicorn
|
||||
click-didyoumean==0.3.1
|
||||
# via
|
||||
@@ -210,7 +209,7 @@ codejail-includes==2.0.0
|
||||
# via -r requirements/edx/base.txt
|
||||
colorama==0.4.6
|
||||
# via tox
|
||||
coverage[toml]==7.10.6
|
||||
coverage[toml]==7.10.7
|
||||
# via
|
||||
# -r requirements/edx/coverage.txt
|
||||
# pytest-cov
|
||||
@@ -218,6 +217,7 @@ crowdsourcehinter-xblock==0.8
|
||||
# via -r requirements/edx/base.txt
|
||||
cryptography==45.0.7
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
# django-fernet-fields-v2
|
||||
# edx-enterprise
|
||||
@@ -245,7 +245,7 @@ defusedxml==0.7.1
|
||||
# ora2
|
||||
# python3-openid
|
||||
# social-auth-core
|
||||
diff-cover==9.6.0
|
||||
diff-cover==9.7.1
|
||||
# via -r requirements/edx/coverage.txt
|
||||
dill==0.4.0
|
||||
# via pylint
|
||||
@@ -347,7 +347,7 @@ django-config-models==2.9.0
|
||||
# edx-name-affirmation
|
||||
# enterprise-integrated-channels
|
||||
# lti-consumer-xblock
|
||||
django-cors-headers==4.8.0
|
||||
django-cors-headers==4.9.0
|
||||
# via -r requirements/edx/base.txt
|
||||
django-countries==7.6.1
|
||||
# via
|
||||
@@ -410,7 +410,7 @@ django-multi-email-field==0.8.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
django-mysql==4.18.0
|
||||
django-mysql==4.19.0
|
||||
# via -r requirements/edx/base.txt
|
||||
django-oauth-toolkit==1.7.1
|
||||
# via
|
||||
@@ -507,7 +507,7 @@ drf-jwt==1.19.2
|
||||
# edx-drf-extensions
|
||||
drf-spectacular==0.28.0
|
||||
# via -r requirements/edx/base.txt
|
||||
drf-yasg==1.21.10
|
||||
drf-yasg==1.21.11
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django-user-tasks
|
||||
@@ -518,7 +518,7 @@ edx-api-doc-tools==2.1.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-name-affirmation
|
||||
edx-auth-backends==4.6.0
|
||||
edx-auth-backends==4.6.1
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-bulk-grades==1.2.0
|
||||
# via
|
||||
@@ -545,7 +545,7 @@ edx-django-release-util==1.5.0
|
||||
# edxval
|
||||
edx-django-sites-extensions==5.1.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-django-utils==8.0.0
|
||||
edx-django-utils==8.0.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# django-config-models
|
||||
@@ -635,7 +635,7 @@ edx-search==4.3.0
|
||||
# openedx-forum
|
||||
edx-sga==0.26.0
|
||||
# via -r requirements/edx/base.txt
|
||||
edx-submissions==3.11.1
|
||||
edx-submissions==3.12.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# ora2
|
||||
@@ -676,7 +676,7 @@ enmerkar==0.7.1
|
||||
# enmerkar-underscore
|
||||
enmerkar-underscore==2.4.0
|
||||
# via -r requirements/edx/base.txt
|
||||
enterprise-integrated-channels==0.1.16
|
||||
enterprise-integrated-channels==0.1.18
|
||||
# via -r requirements/edx/base.txt
|
||||
event-tracking==3.3.0
|
||||
# via
|
||||
@@ -690,7 +690,7 @@ factory-boy==3.3.3
|
||||
# via -r requirements/edx/testing.in
|
||||
faker==37.8.0
|
||||
# via factory-boy
|
||||
fastapi==0.116.1
|
||||
fastapi==0.118.0
|
||||
# via pact-python
|
||||
fastavro==1.12.0
|
||||
# via
|
||||
@@ -708,7 +708,7 @@ firebase-admin==7.1.0
|
||||
# edx-ace
|
||||
freezegun==1.5.5
|
||||
# via -r requirements/edx/testing.in
|
||||
frozenlist==1.7.0
|
||||
frozenlist==1.8.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
@@ -727,14 +727,14 @@ geoip2==5.1.0
|
||||
# via -r requirements/edx/base.txt
|
||||
glob2==0.7
|
||||
# via -r requirements/edx/base.txt
|
||||
google-api-core[grpc]==2.25.1
|
||||
google-api-core[grpc]==2.25.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# firebase-admin
|
||||
# google-cloud-core
|
||||
# google-cloud-firestore
|
||||
# google-cloud-storage
|
||||
google-auth==2.40.3
|
||||
google-auth==2.41.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# google-api-core
|
||||
@@ -770,12 +770,12 @@ googleapis-common-protos==1.70.0
|
||||
# grpcio-status
|
||||
grimp==3.11
|
||||
# via import-linter
|
||||
grpcio==1.74.0
|
||||
grpcio==1.75.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
grpcio-status==1.74.0
|
||||
grpcio-status==1.75.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# google-api-core
|
||||
@@ -846,7 +846,7 @@ isodate==0.7.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# python3-saml
|
||||
isort==6.0.1
|
||||
isort==6.1.0
|
||||
# via
|
||||
# -r requirements/edx/testing.in
|
||||
# pylint
|
||||
@@ -927,7 +927,7 @@ lxml[html-clean]==5.3.2
|
||||
# python3-saml
|
||||
# xblock
|
||||
# xmlsec
|
||||
lxml-html-clean==0.4.2
|
||||
lxml-html-clean==0.4.3
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# lxml
|
||||
@@ -946,7 +946,7 @@ markdown==3.9
|
||||
# openedx-django-wiki
|
||||
# staff-graded-xblock
|
||||
# xblock-poll
|
||||
markupsafe==3.0.2
|
||||
markupsafe==3.0.3
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# -r requirements/edx/coverage.txt
|
||||
@@ -985,7 +985,7 @@ msgpack==1.1.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# cachecontrol
|
||||
multidict==6.6.4
|
||||
multidict==6.7.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
@@ -998,7 +998,7 @@ nh3==0.3.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# xblocks-contrib
|
||||
nltk==3.9.1
|
||||
nltk==3.9.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# chem
|
||||
@@ -1079,7 +1079,9 @@ packaging==25.0
|
||||
# snowflake-connector-python
|
||||
# tox
|
||||
pact-python==2.3.3
|
||||
# via -r requirements/edx/testing.in
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx/testing.in
|
||||
paramiko==4.0.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
@@ -1131,7 +1133,7 @@ prompt-toolkit==3.0.52
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# click-repl
|
||||
propcache==0.3.2
|
||||
propcache==0.4.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
@@ -1149,7 +1151,7 @@ protobuf==6.32.1
|
||||
# googleapis-common-protos
|
||||
# grpcio-status
|
||||
# proto-plus
|
||||
psutil==7.0.0
|
||||
psutil==7.1.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-django-utils
|
||||
@@ -1182,7 +1184,7 @@ pycryptodomex==3.23.0
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-proctoring
|
||||
# lti-consumer-xblock
|
||||
pydantic==2.11.9
|
||||
pydantic==2.11.10
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# camel-converter
|
||||
@@ -1212,7 +1214,7 @@ pylatexenc==2.10
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# olxcleaner
|
||||
pylint==3.3.8
|
||||
pylint==3.3.9
|
||||
# via
|
||||
# edx-lint
|
||||
# pylint-celery
|
||||
@@ -1248,11 +1250,11 @@ pynacl==1.6.0
|
||||
# paramiko
|
||||
pynliner==0.8.0
|
||||
# via -r requirements/edx/base.txt
|
||||
pyopenssl==25.2.0
|
||||
pyopenssl==25.3.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# snowflake-connector-python
|
||||
pyparsing==3.2.4
|
||||
pyparsing==3.2.5
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# chem
|
||||
@@ -1345,7 +1347,7 @@ pytz==2025.2
|
||||
# xblock
|
||||
pyuca==1.2
|
||||
# via -r requirements/edx/base.txt
|
||||
pyyaml==6.0.2
|
||||
pyyaml==6.0.3
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# code-annotations
|
||||
@@ -1368,7 +1370,7 @@ referencing==0.36.2
|
||||
# -r requirements/edx/base.txt
|
||||
# jsonschema
|
||||
# jsonschema-specifications
|
||||
regex==2025.9.1
|
||||
regex==2025.9.18
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# nltk
|
||||
@@ -1435,9 +1437,9 @@ semantic-version==2.10.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-drf-extensions
|
||||
shapely==2.1.1
|
||||
shapely==2.1.2
|
||||
# via -r requirements/edx/base.txt
|
||||
simplejson==3.20.1
|
||||
simplejson==3.20.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# sailthru-client
|
||||
@@ -1475,7 +1477,7 @@ sniffio==1.3.1
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# anyio
|
||||
snowflake-connector-python==3.17.3
|
||||
snowflake-connector-python==3.18.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-enterprise
|
||||
@@ -1507,7 +1509,7 @@ sqlparse==0.5.3
|
||||
# django
|
||||
staff-graded-xblock==3.1.0
|
||||
# via -r requirements/edx/base.txt
|
||||
starlette==0.47.3
|
||||
starlette==0.48.0
|
||||
# via fastapi
|
||||
stevedore==5.5.0
|
||||
# via
|
||||
@@ -1544,7 +1546,7 @@ tomlkit==0.13.3
|
||||
# openedx-learning
|
||||
# pylint
|
||||
# snowflake-connector-python
|
||||
tox==4.27.0
|
||||
tox==4.30.3
|
||||
# via -r requirements/edx/testing.in
|
||||
tqdm==4.67.1
|
||||
# via
|
||||
@@ -1561,6 +1563,7 @@ typing-extensions==4.15.0
|
||||
# edx-opaque-keys
|
||||
# fastapi
|
||||
# grimp
|
||||
# grpcio
|
||||
# import-linter
|
||||
# jwcrypto
|
||||
# pydantic
|
||||
@@ -1571,7 +1574,7 @@ typing-extensions==4.15.0
|
||||
# snowflake-connector-python
|
||||
# starlette
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
typing-inspection==0.4.2
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# pydantic
|
||||
@@ -1601,9 +1604,7 @@ urllib3==2.5.0
|
||||
# botocore
|
||||
# elasticsearch
|
||||
# requests
|
||||
user-util==2.0.0
|
||||
# via -r requirements/edx/base.txt
|
||||
uvicorn==0.35.0
|
||||
uvicorn==0.37.0
|
||||
# via pact-python
|
||||
vine==5.1.0
|
||||
# via
|
||||
@@ -1621,7 +1622,7 @@ walrus==0.9.5
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# edx-event-bus-redis
|
||||
wcwidth==0.2.13
|
||||
wcwidth==0.2.14
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# prompt-toolkit
|
||||
@@ -1685,7 +1686,7 @@ xmlsec==1.3.14
|
||||
# python3-saml
|
||||
xss-utils==0.8.0
|
||||
# via -r requirements/edx/base.txt
|
||||
yarl==1.20.1
|
||||
yarl==1.22.0
|
||||
# via
|
||||
# -r requirements/edx/base.txt
|
||||
# aiohttp
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
#
|
||||
build==1.3.0
|
||||
# via pip-tools
|
||||
click==8.2.1
|
||||
click==8.3.0
|
||||
# via pip-tools
|
||||
packaging==25.0
|
||||
# via build
|
||||
pip-tools==7.5.0
|
||||
pip-tools==7.5.1
|
||||
# via -r requirements/pip-tools.in
|
||||
pyproject-hooks==1.2.0
|
||||
# via
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# make upgrade
|
||||
#
|
||||
click==8.2.1
|
||||
click==8.3.0
|
||||
# via
|
||||
# -r scripts/structures_pruning/requirements/base.in
|
||||
# click-log
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# make upgrade
|
||||
#
|
||||
click==8.2.1
|
||||
click==8.3.0
|
||||
# via
|
||||
# -r scripts/structures_pruning/requirements/base.txt
|
||||
# click-log
|
||||
|
||||
@@ -4,21 +4,21 @@
|
||||
#
|
||||
# make upgrade
|
||||
#
|
||||
asgiref==3.9.1
|
||||
asgiref==3.10.0
|
||||
# via django
|
||||
attrs==25.3.0
|
||||
attrs==25.4.0
|
||||
# via zeep
|
||||
backoff==2.2.1
|
||||
# via -r scripts/user_retirement/requirements/base.in
|
||||
boto3==1.40.31
|
||||
boto3==1.40.46
|
||||
# via -r scripts/user_retirement/requirements/base.in
|
||||
botocore==1.40.31
|
||||
botocore==1.40.46
|
||||
# via
|
||||
# boto3
|
||||
# s3transfer
|
||||
cachetools==5.5.2
|
||||
cachetools==6.2.0
|
||||
# via google-auth
|
||||
certifi==2025.8.3
|
||||
certifi==2025.10.5
|
||||
# via requests
|
||||
cffi==2.0.0
|
||||
# via
|
||||
@@ -26,7 +26,7 @@ cffi==2.0.0
|
||||
# pynacl
|
||||
charset-normalizer==3.4.3
|
||||
# via requests
|
||||
click==8.2.1
|
||||
click==8.3.0
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.in
|
||||
# edx-django-utils
|
||||
@@ -34,6 +34,7 @@ cryptography==45.0.7
|
||||
# via pyjwt
|
||||
django==5.2.6
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# django-crum
|
||||
# django-waffle
|
||||
# edx-django-utils
|
||||
@@ -41,15 +42,15 @@ django-crum==0.7.9
|
||||
# via edx-django-utils
|
||||
django-waffle==5.0.0
|
||||
# via edx-django-utils
|
||||
edx-django-utils==8.0.0
|
||||
edx-django-utils==8.0.1
|
||||
# via edx-rest-api-client
|
||||
edx-rest-api-client==6.2.0
|
||||
# via -r scripts/user_retirement/requirements/base.in
|
||||
google-api-core==2.25.1
|
||||
google-api-core==2.25.2
|
||||
# via google-api-python-client
|
||||
google-api-python-client==2.181.0
|
||||
google-api-python-client==2.184.0
|
||||
# via -r scripts/user_retirement/requirements/base.in
|
||||
google-auth==2.40.3
|
||||
google-auth==2.41.1
|
||||
# via
|
||||
# google-api-core
|
||||
# google-api-python-client
|
||||
@@ -87,7 +88,7 @@ protobuf==6.32.1
|
||||
# google-api-core
|
||||
# googleapis-common-protos
|
||||
# proto-plus
|
||||
psutil==7.0.0
|
||||
psutil==7.1.0
|
||||
# via edx-django-utils
|
||||
pyasn1==0.6.1
|
||||
# via
|
||||
@@ -103,7 +104,7 @@ pyjwt[crypto]==2.10.1
|
||||
# simple-salesforce
|
||||
pynacl==1.6.0
|
||||
# via edx-django-utils
|
||||
pyparsing==3.2.4
|
||||
pyparsing==3.2.5
|
||||
# via httplib2
|
||||
python-dateutil==2.9.0.post0
|
||||
# via botocore
|
||||
@@ -111,7 +112,7 @@ pytz==2025.2
|
||||
# via
|
||||
# jenkinsapi
|
||||
# zeep
|
||||
pyyaml==6.0.2
|
||||
pyyaml==6.0.3
|
||||
# via -r scripts/user_retirement/requirements/base.in
|
||||
requests==2.32.5
|
||||
# via
|
||||
@@ -133,7 +134,7 @@ s3transfer==0.14.0
|
||||
# via boto3
|
||||
simple-salesforce==1.12.9
|
||||
# via -r scripts/user_retirement/requirements/base.in
|
||||
simplejson==3.20.1
|
||||
simplejson==3.20.2
|
||||
# via -r scripts/user_retirement/requirements/base.in
|
||||
six==1.17.0
|
||||
# via python-dateutil
|
||||
|
||||
@@ -4,31 +4,31 @@
|
||||
#
|
||||
# make upgrade
|
||||
#
|
||||
asgiref==3.9.1
|
||||
asgiref==3.10.0
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# django
|
||||
attrs==25.3.0
|
||||
attrs==25.4.0
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# zeep
|
||||
backoff==2.2.1
|
||||
# via -r scripts/user_retirement/requirements/base.txt
|
||||
boto3==1.40.31
|
||||
boto3==1.40.46
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# moto
|
||||
botocore==1.40.31
|
||||
botocore==1.40.46
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# boto3
|
||||
# moto
|
||||
# s3transfer
|
||||
cachetools==5.5.2
|
||||
cachetools==6.2.0
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# google-auth
|
||||
certifi==2025.8.3
|
||||
certifi==2025.10.5
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# requests
|
||||
@@ -41,7 +41,7 @@ charset-normalizer==3.4.3
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# requests
|
||||
click==8.2.1
|
||||
click==8.3.0
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# edx-django-utils
|
||||
@@ -66,19 +66,19 @@ django-waffle==5.0.0
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# edx-django-utils
|
||||
edx-django-utils==8.0.0
|
||||
edx-django-utils==8.0.1
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# edx-rest-api-client
|
||||
edx-rest-api-client==6.2.0
|
||||
# via -r scripts/user_retirement/requirements/base.txt
|
||||
google-api-core==2.25.1
|
||||
google-api-core==2.25.2
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# google-api-python-client
|
||||
google-api-python-client==2.181.0
|
||||
google-api-python-client==2.184.0
|
||||
# via -r scripts/user_retirement/requirements/base.txt
|
||||
google-auth==2.40.3
|
||||
google-auth==2.41.1
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# google-api-core
|
||||
@@ -120,7 +120,7 @@ lxml==5.3.2
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# zeep
|
||||
markupsafe==3.0.2
|
||||
markupsafe==3.0.3
|
||||
# via
|
||||
# jinja2
|
||||
# werkzeug
|
||||
@@ -130,7 +130,7 @@ more-itertools==10.8.0
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# simple-salesforce
|
||||
moto==5.1.12
|
||||
moto==5.1.14
|
||||
# via -r scripts/user_retirement/requirements/testing.in
|
||||
packaging==25.0
|
||||
# via pytest
|
||||
@@ -150,7 +150,7 @@ protobuf==6.32.1
|
||||
# google-api-core
|
||||
# googleapis-common-protos
|
||||
# proto-plus
|
||||
psutil==7.0.0
|
||||
psutil==7.1.0
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# edx-django-utils
|
||||
@@ -178,7 +178,7 @@ pynacl==1.6.0
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# edx-django-utils
|
||||
pyparsing==3.2.4
|
||||
pyparsing==3.2.5
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# httplib2
|
||||
@@ -194,7 +194,7 @@ pytz==2025.2
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# jenkinsapi
|
||||
# zeep
|
||||
pyyaml==6.0.2
|
||||
pyyaml==6.0.3
|
||||
# via
|
||||
# -r scripts/user_retirement/requirements/base.txt
|
||||
# responses
|
||||
@@ -235,7 +235,7 @@ s3transfer==0.14.0
|
||||
# boto3
|
||||
simple-salesforce==1.12.9
|
||||
# via -r scripts/user_retirement/requirements/base.txt
|
||||
simplejson==3.20.1
|
||||
simplejson==3.20.2
|
||||
# via -r scripts/user_retirement/requirements/base.txt
|
||||
six==1.17.0
|
||||
# via
|
||||
@@ -268,7 +268,7 @@ urllib3==2.5.0
|
||||
# responses
|
||||
werkzeug==3.1.3
|
||||
# via moto
|
||||
xmltodict==1.0.0
|
||||
xmltodict==1.0.2
|
||||
# via moto
|
||||
zeep==4.3.2
|
||||
# via
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# make upgrade
|
||||
#
|
||||
certifi==2025.8.3
|
||||
certifi==2025.10.5
|
||||
# via requests
|
||||
charset-normalizer==3.4.3
|
||||
# via requests
|
||||
|
||||
Reference in New Issue
Block a user