refactor: move transcripts_utils from xmodule to video-config (#37600)

As part of the ongoing effort to deprecate and eventually remove xmodule,
we’ve started gradually migrating the necessary code files from xmodule
to more appropriate locations within the codebase.

Ticket: https://github.com/openedx/public-engineering/issues/445

Also: this tweaks importlinter ignores & add follow-up issue links

Co-authored-by: Kyle McCormick <kyle@axim.org>
This commit is contained in:
Muhammad Farhan Khan
2025-11-14 23:26:35 +05:00
committed by GitHub
parent fcf03cc710
commit f51343c871
18 changed files with 58 additions and 42 deletions

View File

@@ -1,5 +1,10 @@
"""
Helper methods for Studio views.
Before adding more stuff here, take a look at:
https://github.com/openedx/edx-platform/issues/37637
Only Studio-specfic helper functions should be added here.
Platform-wide Python APIs should be added to an appropriate api.py file instead.
"""
from __future__ import annotations
import json
@@ -26,7 +31,7 @@ from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError
from xmodule.modulestore.django import modulestore
from xmodule.xml_block import XmlMixin
from xmodule.video_block.transcripts_utils import Transcript, build_components_import_path
from openedx.core.djangoapps.video_config.transcripts_utils import Transcript, build_components_import_path
from edxval.api import (
create_external_video,
create_or_update_video_transcript,

View File

@@ -122,7 +122,7 @@ from openedx.core.lib.api.view_utils import (
from openedx.core.djangoapps.content_libraries import api as lib_api
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.video_block.transcripts_utils import clear_transcripts
from openedx.core.djangoapps.video_config.transcripts_utils import clear_transcripts
logger = logging.getLogger(__name__)

View File

@@ -23,7 +23,7 @@ from xmodule.contentstore.django import contentstore # lint-amnesty, pylint: di
from xmodule.exceptions import NotFoundError # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.video_block import transcripts_utils # lint-amnesty, pylint: disable=wrong-import-order
from openedx.core.djangoapps.video_config import transcripts_utils # lint-amnesty, pylint: disable=wrong-import-order
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
@@ -235,7 +235,7 @@ class TestDownloadYoutubeSubs(TestYoutubeSubsBase):
self.clear_sub_content(good_youtube_sub)
language_code = 'en'
with patch('xmodule.video_block.transcripts_utils.requests.get') as mock_get:
with patch('openedx.core.djangoapps.video_config.transcripts_utils.requests.get') as mock_get:
setup_caption_responses(mock_get, language_code, caption_response_string)
transcripts_utils.download_youtube_subs(good_youtube_sub, self.course, settings)
@@ -258,7 +258,7 @@ class TestDownloadYoutubeSubs(TestYoutubeSubsBase):
self.assertEqual(html5_ids[2], 'baz.1.4')
self.assertEqual(html5_ids[3], 'foo')
@patch('xmodule.video_block.transcripts_utils.requests.get')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.requests.get')
def test_fail_downloading_subs(self, mock_get):
track_status_code = 404
@@ -459,7 +459,7 @@ class TestYoutubeTranscripts(unittest.TestCase):
"""
Tests for checking right datastructure returning when using youtube api.
"""
@patch('xmodule.video_block.transcripts_utils.requests.get')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.requests.get')
def test_youtube_bad_status_code(self, mock_get):
track_status_code = 404
setup_caption_responses(mock_get, 'en', 'test', track_status_code)
@@ -468,7 +468,7 @@ class TestYoutubeTranscripts(unittest.TestCase):
link = transcripts_utils.get_transcript_links_from_youtube(youtube_id, settings, translation)
transcripts_utils.get_transcript_from_youtube(link, youtube_id, translation)
@patch('xmodule.video_block.transcripts_utils.requests.get')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.requests.get')
def test_youtube_empty_text(self, mock_get):
setup_caption_responses(mock_get, 'en', '')
youtube_id = 'bad_youtube_id'
@@ -492,7 +492,7 @@ class TestYoutubeTranscripts(unittest.TestCase):
}
youtube_id = 'good_youtube_id'
language_code = 'en'
with patch('xmodule.video_block.transcripts_utils.requests.get') as mock_get:
with patch('openedx.core.djangoapps.video_config.transcripts_utils.requests.get') as mock_get:
setup_caption_responses(mock_get, language_code, caption_response_string)
link = transcripts_utils.get_transcript_links_from_youtube(youtube_id, settings, translation)
transcripts = transcripts_utils.get_transcript_from_youtube(link['en'], youtube_id, translation)
@@ -890,7 +890,7 @@ class TestGetTranscript(SharedModuleStoreTestCase):
self.assertEqual(filename, 'ur_video_101.sjson')
self.assertEqual(mimetype, self.sjson_mime_type)
@patch('xmodule.video_block.transcripts_utils.get_video_transcript_content')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.get_video_transcript_content')
def test_get_transcript_from_val(self, mock_get_video_transcript_content):
"""
Verify that `get_transcript` function returns correct data when transcript is in val.
@@ -952,7 +952,7 @@ class TestGetTranscript(SharedModuleStoreTestCase):
exception_message = str(no_en_transcript_exception.exception)
self.assertEqual(exception_message, 'No transcript for `en` language')
@patch('xmodule.video_block.transcripts_utils.edxval_api.get_video_transcript_data')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.edxval_api.get_video_transcript_data')
def test_get_transcript_incorrect_json_(self, mock_get_video_transcript_data):
"""
Verify that `get transcript` function returns a working json file if the original throws an error
@@ -966,7 +966,7 @@ class TestGetTranscript(SharedModuleStoreTestCase):
transcripts_utils.TranscriptsGenerationException,
UnicodeDecodeError('aliencodec', b'\x02\x01', 1, 2, 'alien codec found!')
)
@patch('xmodule.video_block.transcripts_utils.Transcript')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.Transcript')
def test_get_transcript_val_exceptions(self, exception_to_raise, mock_Transcript):
"""
Verify that `get_transcript_from_val` function raises `NotFoundError` when specified exceptions raised.
@@ -986,7 +986,7 @@ class TestGetTranscript(SharedModuleStoreTestCase):
transcripts_utils.TranscriptsGenerationException,
UnicodeDecodeError('aliencodec', b'\x02\x01', 1, 2, 'alien codec found!')
)
@patch('xmodule.video_block.transcripts_utils.Transcript')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.Transcript')
def test_get_transcript_content_store_exceptions(self, exception_to_raise, mock_Transcript):
"""
Verify that `get_transcript_from_contentstore` function raises `NotFoundError` when specified exceptions raised.
@@ -1051,7 +1051,7 @@ class TestGetEndonymOrLabel(unittest.TestCase):
"""
Helper for cleaner mocking
"""
with patch('xmodule.video_block.transcripts_utils.get_language_info') as mock_get:
with patch('openedx.core.djangoapps.video_config.transcripts_utils.get_language_info') as mock_get:
if side_effect:
mock_get.side_effect = side_effect
yield mock_get

View File

@@ -15,7 +15,7 @@ from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.utils import ProceduralCourseTestMixin
from xmodule.tests.test_transcripts_utils import YoutubeVideoHTMLResponse
from openedx.core.djangoapps.video_config.tests.test_transcripts_utils import YoutubeVideoHTMLResponse
from cms.djangoapps.contentstore.utils import reverse_url
from common.djangoapps.student.models import Registration

View File

@@ -23,7 +23,7 @@ from opaque_keys.edx.keys import CourseKey
from common.djangoapps.util.json_request import JsonResponse
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
from openedx.core.djangoapps.video_pipeline.api import update_3rd_party_transcription_service_credentials
from xmodule.video_block.transcripts_utils import Transcript, TranscriptsGenerationException # lint-amnesty, pylint: disable=wrong-import-order
from openedx.core.djangoapps.video_config.transcripts_utils import Transcript, TranscriptsGenerationException # lint-amnesty, pylint: disable=wrong-import-order
from .toggles import use_mock_video_uploads
from .video_storage_handlers import TranscriptProvider

View File

@@ -1811,7 +1811,7 @@ def get_course_videos_context(course_block, pagination_conf, course_key=None):
)
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
from openedx.core.djangoapps.video_config.toggles import use_xpert_translations_component
from xmodule.video_block.transcripts_utils import Transcript # lint-amnesty, pylint: disable=wrong-import-order
from openedx.core.djangoapps.video_config.transcripts_utils import Transcript # lint-amnesty, pylint: disable=wrong-import-order
from .video_storage_handlers import (
get_all_transcript_languages,

View File

@@ -25,7 +25,7 @@ from xmodule.contentstore.django import contentstore # lint-amnesty, pylint: di
from xmodule.exceptions import NotFoundError # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.video_block import VideoBlock # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.video_block.transcripts_utils import ( # lint-amnesty, pylint: disable=wrong-import-order
from openedx.core.djangoapps.video_config.transcripts_utils import ( # lint-amnesty, pylint: disable=wrong-import-order
GetTranscriptsFromYouTubeException,
Transcript,
get_video_transcript_content,
@@ -981,7 +981,7 @@ class TestCheckTranscripts(BaseTranscripts):
}
)
@patch('xmodule.video_block.transcripts_utils.requests.get')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.requests.get')
def test_check_youtube_with_transcript_name(self, mock_get):
"""
Test that the transcripts are fetched correctly when the the transcript name is set
@@ -1121,7 +1121,7 @@ class TestCheckTranscripts(BaseTranscripts):
'Transcripts are supported only for "video" blocks.',
)
@patch('xmodule.video_block.transcripts_utils.get_video_transcript_content')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.get_video_transcript_content')
def test_command_for_fallback_transcript(self, mock_get_video_transcript_content):
"""
Verify the command if a transcript is there in edx-val.

View File

@@ -30,7 +30,7 @@ from xmodule.contentstore.django import contentstore # lint-amnesty, pylint: di
from xmodule.exceptions import NotFoundError # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.video_block.transcripts_utils import ( # lint-amnesty, pylint: disable=wrong-import-order
from openedx.core.djangoapps.video_config.transcripts_utils import ( # lint-amnesty, pylint: disable=wrong-import-order
GetTranscriptsFromYouTubeException,
Transcript,
TranscriptsGenerationException,

View File

@@ -28,7 +28,7 @@ from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disa
# noinspection PyUnresolvedReferences
from xmodule.tests.helpers import override_descriptor_system # pylint: disable=unused-import
from xmodule.video_block import VideoBlock # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.video_block.transcripts_utils import ( # lint-amnesty, pylint: disable=wrong-import-order
from openedx.core.djangoapps.video_config.transcripts_utils import ( # lint-amnesty, pylint: disable=wrong-import-order
Transcript,
edxval_api,
get_transcript,
@@ -293,7 +293,7 @@ class TestTranscriptAvailableTranslationsDispatch(TestVideo): # lint-amnesty, p
response = self.block.transcript(request=request, dispatch='available_translations')
assert json.loads(response.body.decode('utf-8')) == ['uk']
@patch('xmodule.video_block.transcripts_utils.get_video_transcript_content')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.get_video_transcript_content')
def test_multiple_available_translations(self, mock_get_video_transcript_content):
mock_get_video_transcript_content.return_value = {
'content': json.dumps({
@@ -319,8 +319,8 @@ class TestTranscriptAvailableTranslationsDispatch(TestVideo): # lint-amnesty, p
response = self.block.transcript(request=request, dispatch='available_translations')
assert sorted(json.loads(response.body.decode('utf-8'))) == sorted(['en', 'uk'])
@patch('xmodule.video_block.transcripts_utils.get_video_transcript_content')
@patch('xmodule.video_block.transcripts_utils.get_available_transcript_languages')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.get_video_transcript_content')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.get_available_transcript_languages')
@ddt.data(
(
['en', 'uk', 'ro'],
@@ -396,7 +396,7 @@ class TestTranscriptAvailableTranslationsDispatch(TestVideo): # lint-amnesty, p
response = self.block.transcript(request=request, dispatch='available_translations')
self.assertCountEqual(json.loads(response.body.decode('utf-8')), result)
@patch('xmodule.video_block.transcripts_utils.edxval_api.get_available_transcript_languages')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.edxval_api.get_available_transcript_languages')
def test_val_available_translations_feature_disabled(self, mock_get_available_transcript_languages):
"""
Tests available translations with val transcript languages when feature is disabled.
@@ -445,7 +445,7 @@ class TestTranscriptAvailableTranslationsBumperDispatch(TestVideo): # lint-amne
response = self.block.transcript(request=request, dispatch=self.dispatch)
assert json.loads(response.body.decode('utf-8')) == [lang]
@patch('xmodule.video_block.transcripts_utils.get_available_transcript_languages')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.get_available_transcript_languages')
def test_multiple_available_translations(self, mock_get_transcript_languages):
"""
Verify that available translations dispatch works as expected for multiple
@@ -534,7 +534,7 @@ class TestTranscriptDownloadDispatch(TestVideo): # lint-amnesty, pylint: disabl
get_transcript(self.block)
@patch(
'xmodule.video_block.transcripts_utils.get_transcript_for_video',
'openedx.core.djangoapps.video_config.transcripts_utils.get_transcript_for_video',
return_value=(Transcript.SRT, "", 'Subs!')
)
def test_download_non_en_non_ascii_filename(self, __):
@@ -544,7 +544,7 @@ class TestTranscriptDownloadDispatch(TestVideo): # lint-amnesty, pylint: disabl
assert response.headers['Content-Type'] == 'application/x-subrip; charset=utf-8'
assert response.headers['Content-Disposition'] == 'attachment; filename="en_塞.srt"'
@patch('xmodule.video_block.transcripts_utils.edxval_api.get_video_transcript_data')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.edxval_api.get_video_transcript_data')
@patch('xmodule.video_block.get_transcript', Mock(side_effect=NotFoundError))
def test_download_fallback_transcript(self, mock_get_video_transcript_data):
"""
@@ -814,7 +814,7 @@ class TestTranscriptTranslationGetDispatch(TestVideo): # lint-amnesty, pylint:
with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, self.course.id):
store.update_item(self.course, self.user.id)
@patch('xmodule.video_block.transcripts_utils.edxval_api.get_video_transcript_data')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.edxval_api.get_video_transcript_data')
@patch('xmodule.video_block.VideoBlock.translation', Mock(side_effect=NotFoundError))
@patch('xmodule.video_block.VideoBlock.get_static_transcript', Mock(return_value=Response(status=404)))
def test_translation_fallback_transcript(self, mock_get_video_transcript_data):

View File

@@ -50,7 +50,7 @@ from xmodule.tests.helpers import override_descriptor_system # pylint: disable=
from xmodule.tests.test_import import DummyModuleStoreRuntime
from xmodule.tests.test_video import VideoBlockTestBase
from xmodule.video_block import VideoBlock, bumper_utils, video_utils
from xmodule.video_block.transcripts_utils import Transcript, save_to_store, subs_filename
from openedx.core.djangoapps.video_config.transcripts_utils import Transcript, save_to_store, subs_filename
from xmodule.video_block.video_block import EXPORT_IMPORT_COURSE_DIR, EXPORT_IMPORT_STATIC_DIR
from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW
@@ -1755,7 +1755,7 @@ class TestVideoBlockStudentViewJson(BaseTestVideoXBlock, CacheIsolationTestCase)
({'uk': 1, 'de': 1}, 'en-subs', ['de', 'en'], ['en', 'uk', 'de']),
)
@ddt.unpack
@patch('xmodule.video_block.transcripts_utils.edxval_api.get_available_transcript_languages')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.edxval_api.get_available_transcript_languages')
def test_student_view_with_val_transcripts_enabled(self, transcripts, english_sub, val_transcripts,
expected_transcripts, mock_get_transcript_languages):
"""
@@ -1950,7 +1950,7 @@ class VideoBlockTest(TestCase, VideoBlockTestBase):
expected = etree.XML(expected_str, parser=parser)
self.assertXmlEqual(expected, actual)
@patch('xmodule.video_block.transcripts_utils.get_video_ids_info')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.get_video_ids_info')
def test_export_no_video_ids(self, mock_get_video_ids_info):
"""
Tests export when there is no video id. `export_to_xml` only works in case of video id.

View File

@@ -701,6 +701,7 @@ def get_allowed_block_types(library_key: LibraryLocatorV2): # pylint: disable=u
for block_type in enabled_block_types:
# TODO: unify the contentstore helper with the xblock.api version of
# xblock_type_display_name
# https://github.com/openedx/edx-platform/issues/37637
display_name = studio_helpers.xblock_type_display_name(block_type, None)
# For now as a crude heuristic, we exclude blocks that don't have a display_name
if display_name:

View File

@@ -11,7 +11,7 @@ from unittest import mock, TestCase
import ddt
from ..video_block.transcripts_utils import get_transcript_link_from_youtube
from openedx.core.djangoapps.video_config.transcripts_utils import get_transcript_link_from_youtube
YOUTUBE_VIDEO_ID = "z-LoKnweV6w"

View File

@@ -28,7 +28,7 @@ from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError
from .bumper_utils import get_bumper_settings
from xmodule.video_block.bumper_utils import get_bumper_settings
try:
from edxval import api as edxval_api

View File

@@ -160,7 +160,15 @@ ignore_imports =
# -> openedx.features.enterprise_support.utils
openedx.features.enterprise_support.utils -> lms.djangoapps.branding.api
cms.djangoapps.contentstore.rest_api.v1.views.settings -> lms.djangoapps.certificates.api
# We are ignoring this existing import until we can refactor contenstore/helpers.
# https://github.com/openedx/edx-platform/issues/37637
openedx.core.djangoapps.content_libraries.api.libraries -> cms.djangoapps.contentstore.helpers
openedx.core.djangoapps.content_libraries.api.blocks -> cms.djangoapps.contentstore.helpers
openedx.core.djangoapps.content_staging.serializers -> cms.djangoapps.contentstore.helpers
# These imports will become OK once we move content_libraries into CMS
# https://github.com/openedx/edx-platform/issues/33428
openedx.core.djangoapps.content_libraries.permissions -> cms.djangoapps.course_creators.views
openedx.core.djangoapps.content_libraries.tasks -> cms.djangoapps.contentstore.storage
[importlinter:contract:2]
name = Do not depend on non-public API of isolated apps.
@@ -207,7 +215,8 @@ ignore_imports =
**.tests.** -> **
# FIXME: the exceptions below are from before we added this import linting rule. Should refactor to eliminate them.
# In particular, the contentstore.helpers module is too big and has too many imports - split it up?
# In particular, the contentstore.helpers module is too big and has too many imports
# See https://github.com/openedx/edx-platform/issues/37637
# The CSV export hard-codes support for courses and libraries. Refactor to do something like learning_context.get_children()
openedx.core.djangoapps.content_tagging.helpers.objecttag_export_helpers -> openedx.core.djangoapps.content_libraries.api

View File

@@ -36,7 +36,7 @@ from xblock.fields import ScopeIds
from xmodule.tests import get_test_descriptor_system
from xmodule.validation import StudioValidationMessage
from xmodule.video_block import EXPORT_IMPORT_STATIC_DIR, VideoBlock, create_youtube_string
from xmodule.video_block.transcripts_utils import save_to_store
from openedx.core.djangoapps.video_config.transcripts_utils import save_to_store
from xblock.core import XBlockAside
from xmodule.modulestore.tests.test_asides import AsideTestType
@@ -945,9 +945,10 @@ class VideoBlockStudentViewDataTestCase(unittest.TestCase):
assert student_view_data == expected_student_view_data
@patch('xmodule.video_block.video_block.HLSPlaybackEnabledFlag.feature_enabled', Mock(return_value=True))
@patch('xmodule.video_block.transcripts_utils.get_available_transcript_languages', Mock(return_value=['es']))
@patch('openedx.core.djangoapps.video_config.transcripts_utils.get_available_transcript_languages',
Mock(return_value=['es']))
@patch('edxval.api.get_video_info_for_course_and_profiles', Mock(return_value={}))
@patch('xmodule.video_block.transcripts_utils.get_video_transcript_content')
@patch('openedx.core.djangoapps.video_config.transcripts_utils.get_video_transcript_content')
@patch('edxval.api.get_video_info')
def test_student_view_data_with_hls_flag(self, mock_get_video_info, mock_get_video_transcript_content):
mock_get_video_info.return_value = {

View File

@@ -3,6 +3,6 @@ Container for video block and its utils.
"""
from .bumper_utils import *
from .transcripts_utils import * # lint-amnesty, pylint: disable=redefined-builtin
from openedx.core.djangoapps.video_config.transcripts_utils import * # lint-amnesty, pylint: disable=redefined-builtin
from .video_block import *
from .video_utils import *

View File

@@ -52,7 +52,7 @@ from xmodule.x_module import (
)
from xmodule.xml_block import XmlMixin, deserialize_field, is_pointer_tag, name_to_pathname
from .bumper_utils import bumperize
from .transcripts_utils import (
from openedx.core.djangoapps.video_config.transcripts_utils import (
Transcript,
VideoTranscriptsMixin,
clean_video_id,

View File

@@ -22,7 +22,7 @@ from xmodule.exceptions import NotFoundError
from xmodule.fields import RelativeTime
from openedx.core.djangoapps.content_libraries import api as lib_api
from .transcripts_utils import (
from openedx.core.djangoapps.video_config.transcripts_utils import (
Transcript,
TranscriptException,
TranscriptsGenerationException,