Skip to content

Commit

Permalink
chore: Transition from pkg_resources API to importlib-resources API
Browse files Browse the repository at this point in the history
  • Loading branch information
farhan committed May 20, 2024
1 parent 003ae98 commit d7e9ba2
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 36 deletions.
2 changes: 1 addition & 1 deletion xblock/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
XBlock Courseware Components
"""

__version__ = '4.1.0'
__version__ = '4.1.1'
6 changes: 4 additions & 2 deletions xblock/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import warnings
from collections import OrderedDict, defaultdict

import pkg_resources
import importlib.resources
from lxml import etree
from webob import Response

Expand Down Expand Up @@ -157,7 +157,9 @@ def open_local_resource(cls, uri):
if "/." in uri:
raise DisallowedFileError("Only safe file names are allowed: %r" % uri)

return pkg_resources.resource_stream(cls.__module__, os.path.join(cls.resources_dir, uri))
return importlib.resources.files(inspect.getmodule(cls).__package__).joinpath(
os.path.join(cls.resources_dir, uri)
).open('rb')

@classmethod
def json_handler(cls, func):
Expand Down
10 changes: 7 additions & 3 deletions xblock/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
This code is in the Runtime layer.
"""
import functools
import importlib.metadata
import itertools
import logging
import pkg_resources

from xblock.internal import class_lazy

Expand Down Expand Up @@ -100,7 +100,11 @@ def select(identifier, all_entry_points):
if select is None:
select = default_select

all_entry_points = list(pkg_resources.iter_entry_points(cls.entry_point, name=identifier))
all_entry_points = [
entry_point
for entry_point in importlib.metadata.entry_points(group=cls.entry_point)
if entry_point.name == identifier
]
for extra_identifier, extra_entry_point in iter(cls.extra_entry_points):
if identifier == extra_identifier:
all_entry_points.append(extra_entry_point)
Expand Down Expand Up @@ -133,7 +137,7 @@ def load_classes(cls, fail_silently=True):
contexts. Hence, the flag.
"""
all_classes = itertools.chain(
pkg_resources.iter_entry_points(cls.entry_point),
importlib.metadata.entry_points(group=cls.entry_point),
(entry_point for identifier, entry_point in iter(cls.extra_entry_points)),
)
for class_ in all_classes:
Expand Down
20 changes: 10 additions & 10 deletions xblock/test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,11 +961,6 @@ class UnloadableXBlock(XBlock):
"""Just something to load resources from."""
resources_dir = None

def stub_resource_stream(self, module, name):
"""Act like pkg_resources.resource_stream, for testing."""
assert module == "xblock.test.test_core"
return "!" + name + "!"

@ddt.data(
"public/hey.js",
"public/sub/hey.js",
Expand All @@ -976,7 +971,8 @@ def stub_resource_stream(self, module, name):
)
def test_open_good_local_resource(self, uri):
loadable = self.LoadableXBlock(None, scope_ids=Mock())
with patch('pkg_resources.resource_stream', self.stub_resource_stream):
with patch('importlib.resources.files') as mock_files:
mock_files.return_value.joinpath.return_value.open.return_value = "!" + uri + "!"
assert loadable.open_local_resource(uri) == "!" + uri + "!"
assert loadable.open_local_resource(uri.encode('utf-8')) == "!" + uri + "!"

Expand All @@ -990,7 +986,8 @@ def test_open_good_local_resource(self, uri):
)
def test_open_good_local_resource_binary(self, uri):
loadable = self.LoadableXBlock(None, scope_ids=Mock())
with patch('pkg_resources.resource_stream', self.stub_resource_stream):
with patch('importlib.resources.files') as mock_files:
mock_files.return_value.joinpath.return_value.open.return_value = "!" + uri.decode('utf-8') + "!"
assert loadable.open_local_resource(uri) == "!" + uri.decode('utf-8') + "!"

@ddt.data(
Expand All @@ -1004,7 +1001,8 @@ def test_open_good_local_resource_binary(self, uri):
)
def test_open_bad_local_resource(self, uri):
loadable = self.LoadableXBlock(None, scope_ids=Mock())
with patch('pkg_resources.resource_stream', self.stub_resource_stream):
with patch('importlib.resources.files') as mock_files:
mock_files.return_value.joinpath.return_value.open.return_value = "!" + uri + "!"
msg_pattern = ".*: %s" % re.escape(repr(uri))
with pytest.raises(DisallowedFileError, match=msg_pattern):
loadable.open_local_resource(uri)
Expand All @@ -1020,7 +1018,8 @@ def test_open_bad_local_resource(self, uri):
)
def test_open_bad_local_resource_binary(self, uri):
loadable = self.LoadableXBlock(None, scope_ids=Mock())
with patch('pkg_resources.resource_stream', self.stub_resource_stream):
with patch('importlib.resources.files') as mock_files:
mock_files.return_value.joinpath.return_value.open.return_value = "!" + str(uri) + "!"
msg = ".*: %s" % re.escape(repr(uri.decode('utf-8')))
with pytest.raises(DisallowedFileError, match=msg):
loadable.open_local_resource(uri)
Expand All @@ -1043,7 +1042,8 @@ def test_open_bad_local_resource_binary(self, uri):
def test_open_local_resource_with_no_resources_dir(self, uri):
unloadable = self.UnloadableXBlock(None, scope_ids=Mock())

with patch('pkg_resources.resource_stream', self.stub_resource_stream):
with patch('importlib.resources.files') as mock_files:
mock_files.return_value.joinpath.return_value.open.return_value = "!" + uri + "!"
msg = "not configured to serve local resources"
with pytest.raises(DisallowedFileError, match=msg):
unloadable.open_local_resource(uri)
Expand Down
28 changes: 14 additions & 14 deletions xblock/test/utils/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

import gettext
import unittest
from unittest.mock import patch, DEFAULT
from unittest.mock import DEFAULT, patch

from pkg_resources import resource_filename
import importlib.resources

from xblock.utils.resources import ResourceLoader

Expand Down Expand Up @@ -136,7 +136,7 @@ class MockI18nService:
def __init__(self):

locale_dir = 'data/translations'
locale_path = resource_filename(__name__, locale_dir)
locale_path = str(importlib.resources.files(__package__) / locale_dir)
domain = 'text'
self.mock_translator = gettext.translation(
domain,
Expand All @@ -154,20 +154,20 @@ class TestResourceLoader(unittest.TestCase):
"""

def test_load_unicode(self):
s = ResourceLoader(__name__).load_unicode("data/simple_django_template.txt")
s = ResourceLoader(__package__).load_unicode("data/simple_django_template.txt")
self.assertEqual(s, expected_string)

def test_load_unicode_from_another_module(self):
s = ResourceLoader("xblock.test.utils.data").load_unicode("simple_django_template.txt")
self.assertEqual(s, expected_string)

def test_render_django_template(self):
loader = ResourceLoader(__name__)
loader = ResourceLoader(__package__)
s = loader.render_django_template("data/simple_django_template.txt", example_context)
self.assertEqual(s, expected_filled_template)

def test_render_django_template_translated(self):
loader = ResourceLoader(__name__)
loader = ResourceLoader(__package__)
s = loader.render_django_template("data/trans_django_template.txt",
context=example_context,
i18n_service=MockI18nService())
Expand All @@ -179,31 +179,31 @@ def test_render_django_template_translated(self):

def test_render_django_template_localized(self):
# Test that default template tags like l10n are loaded
loader = ResourceLoader(__name__)
loader = ResourceLoader(__package__)
s = loader.render_django_template("data/l10n_django_template.txt",
context=example_context,
i18n_service=MockI18nService())
self.assertEqual(s, expected_localized_template)

def test_render_mako_template(self):
loader = ResourceLoader(__name__)
loader = ResourceLoader(__package__)
s = loader.render_mako_template("data/simple_mako_template.txt", example_context)
self.assertEqual(s, expected_filled_template)

@patch('warnings.warn', DEFAULT)
def test_render_template_deprecated(self, mock_warn):
loader = ResourceLoader(__name__)
loader = ResourceLoader(__package__)
s = loader.render_template("data/simple_django_template.txt", example_context)
self.assertTrue(mock_warn.called)
self.assertEqual(s, expected_filled_template)

def test_render_js_template(self):
loader = ResourceLoader(__name__)
loader = ResourceLoader(__package__)
s = loader.render_js_template("data/simple_django_template.txt", example_id, example_context)
self.assertEqual(s, expected_filled_js_template)

def test_render_js_template_translated(self):
loader = ResourceLoader(__name__)
loader = ResourceLoader(__package__)
s = loader.render_js_template("data/trans_django_template.txt",
example_id,
context=example_context,
Expand All @@ -216,19 +216,19 @@ def test_render_js_template_translated(self):

def test_render_js_template_localized(self):
# Test that default template tags like l10n are loaded
loader = ResourceLoader(__name__)
loader = ResourceLoader(__package__)
s = loader.render_js_template("data/l10n_django_template.txt",
example_id,
context=example_context,
i18n_service=MockI18nService())
self.assertEqual(s, expected_filled_localized_js_template)

def test_load_scenarios(self):
loader = ResourceLoader(__name__)
loader = ResourceLoader(__package__)
scenarios = loader.load_scenarios_from_path("data")
self.assertEqual(scenarios, expected_scenarios)

def test_load_scenarios_with_identifiers(self):
loader = ResourceLoader(__name__)
loader = ResourceLoader(__package__)
scenarios = loader.load_scenarios_from_path("data", include_identifier=True)
self.assertEqual(scenarios, expected_scenarios_with_identifiers)
10 changes: 5 additions & 5 deletions xblock/utils/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import sys
import warnings

import pkg_resources
from django.template import Context, Template, Engine
import importlib.resources
from django.template import Context, Engine, Template
from django.template.backends.django import get_installed_libraries
from mako.lookup import TemplateLookup as MakoTemplateLookup
from mako.template import Template as MakoTemplate
Expand All @@ -22,8 +22,7 @@ def load_unicode(self, resource_path):
"""
Gets the content of a resource
"""
resource_content = pkg_resources.resource_string(self.module_name, resource_path)
return resource_content.decode('utf-8')
return importlib.resources.files(self.module_name).joinpath(resource_path).read_text()

def render_django_template(self, template_path, context=None, i18n_service=None):
"""
Expand Down Expand Up @@ -57,7 +56,8 @@ def render_mako_template(self, template_path, context=None):
)
context = context or {}
template_str = self.load_unicode(template_path)
lookup = MakoTemplateLookup(directories=[pkg_resources.resource_filename(self.module_name, '')])
directory = str(importlib.resources.as_file(importlib.resources.files(self.module_name) / ''))
lookup = MakoTemplateLookup(directories=[directory])
template = MakoTemplate(template_str, lookup=lookup)
return template.render(**context)

Expand Down
2 changes: 1 addition & 1 deletion xblock/utils/studio_editable.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# Globals ###########################################################

log = logging.getLogger(__name__)
loader = ResourceLoader(__name__)
loader = ResourceLoader(__package__)


# Classes ###########################################################
Expand Down

0 comments on commit d7e9ba2

Please sign in to comment.