diff --git a/requirements.txt b/requirements.txt index 4a991f0b..29e5385f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,8 +7,7 @@ docutils>=0.14 # testing tools pylint>=2.3.1 coverage>=4.5.4 -# https://github.com/pytest-dev/pytest/issues/5392 -pytest==4.5.0 +pytest>=5.3.5 pytest-cov>=2.7.1 pytest-random-order>=1.0.4 hypothesis>=4.32.3 diff --git a/setup.py b/setup.py index 5df37bcf..c1777461 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ def find_version(*file_paths): "boto3>=1.10.20", "Jinja2>=2.10", "jsonschema>=3.0.1", - "pytest==4.5.0", + "pytest>=4.5.0", "Werkzeug>=0.15", "PyYAML>=5.1", "requests>=2.22", diff --git a/src/rpdk/core/plugin_base.py b/src/rpdk/core/plugin_base.py index 386dde29..25683ec5 100644 --- a/src/rpdk/core/plugin_base.py +++ b/src/rpdk/core/plugin_base.py @@ -1,6 +1,14 @@ +import importlib.util from abc import ABC, abstractmethod +from pathlib import Path -from jinja2 import ChoiceLoader, Environment, PackageLoader, select_autoescape +from jinja2 import ( + ChoiceLoader, + Environment, + FileSystemLoader, + PackageLoader, + select_autoescape, +) from .filters import FILTER_REGISTRY @@ -15,13 +23,18 @@ def _module_name(self): return self.MODULE_NAME def _setup_jinja_env(self, **options): + if "loader" not in options: - options["loader"] = ChoiceLoader( - [ - PackageLoader(self._module_name, "templates/"), - PackageLoader(__name__, "templates/"), - ] - ) + # Try loading module with PEP 451 loaders + spec = importlib.util.find_spec(self._module_name) + + if spec is None or spec.origin is None: + loader = PackageLoader(self._module_name) + else: + path = Path(spec.origin).resolve(strict=True) + loader = FileSystemLoader(str(path.parent / "templates")) + + options["loader"] = ChoiceLoader([loader, PackageLoader(__name__)]) if "autoescape" not in options: options["autoescape"] = select_autoescape(["html", "htm", "xml"]) diff --git a/tests/test_plugin_base.py b/tests/test_plugin_base.py index 7ff43038..63ea2900 100644 --- a/tests/test_plugin_base.py +++ b/tests/test_plugin_base.py @@ -1,11 +1,11 @@ # fixture and parameter have the same name # pylint: disable=redefined-outer-name,useless-super-delegation,protected-access -from unittest.mock import patch +from unittest.mock import call, patch import pytest from rpdk.core.filters import FILTER_REGISTRY -from rpdk.core.plugin_base import LanguagePlugin +from rpdk.core.plugin_base import LanguagePlugin, __name__ as plugin_base_name class TestLanguagePlugin(LanguagePlugin): @@ -66,3 +66,19 @@ def test_language_plugin_setup_jinja_env_overrides(plugin): for name in FILTER_REGISTRY: assert name in env.filters + + +def test_language_plugin_setup_jinja_env_no_spec(plugin): + with patch( + "rpdk.core.plugin_base.importlib.util.find_spec", return_value=None + ) as mock_spec, patch("rpdk.core.plugin_base.PackageLoader") as mock_loader: + env = plugin._setup_jinja_env() + + mock_spec.assert_called_once_with(plugin._module_name) + mock_loader.assert_has_calls([call(plugin._module_name), call(plugin_base_name)]) + + assert env.loader + assert env.autoescape + + for name in FILTER_REGISTRY: + assert name in env.filters