Repository: pallets/jinja (python)

Issue:
The error message from PackageLoader could be more helpful
When following [the basics of setting up jinja2](https://jinja.palletsprojects.com/en/3.1.x/api/#basics), if you don't create the appropriate `templates` directory in your package, you get this error message:

```
ValueError: The 'mypackage' package was not installed in a way that PackageLoader understands.
```

This could be significantly improved by explaining the actual problem: The PackageLoader could not find a suitable "templates" directory.

To replicate, add this to `mypackage.py`:

```python
from jinja2 import Environment, PackageLoader


env = Environment(loader=PackageLoader("mypackage"))
```

If you run `python mypackage.py` you will see a traceback similar to this:

```python
Traceback (most recent call last):
  File "mypackage/mypackage.py", line 4, in <module>
    env = Environment(loader=PackageLoader("mypackage"))
  File ".virtualenvs/mypackage/lib/python3.10/site-packages/jinja2/loaders.py", line 291, in __init__
    import_module(package_name)
  File ".pyenv/versions/3.10.3/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "mypackage/mypackage.py", line 4, in <module>
    env = Environment(loader=PackageLoader("mypackage"))
  File ".virtualenvs/mypackage/lib/python3.10/site-packages/jinja2/loaders.py", line 323, in __init__
    raise ValueError(
ValueError: The 'mypackage' package was not installed in a way that PackageLoader understands.

```

Seeing an error message that explained that jinja2 couldn't find an appropriate "templates" directory would be much better.

Environment:

- Python version: 3.10.3
- Jinja version: 3.1.2


Pull request:
Improve the PackageLoader error message
Explain why the `PackageLoader` failed when the `package_path` directory is missing.

- fixes #1705

<!--
Ensure each step in CONTRIBUTING.rst is complete by adding an "x" to each box below.

If only docs were changed, these aren't relevant and can be removed.
-->

Checklist:

- [ ] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [ ] Add or update relevant docs, in the docs folder and in code.
- [ ] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [ ] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index cd2a4ef0..2e83ab3f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -37,6 +37,8 @@ Unreleased
 -   ``Environment.overlay(enable_async)`` is applied correctly. :pr:`2061`
 -   The error message from ``FileSystemLoader`` includes the paths that were
     searched. :issue:`1661`
+-   ``PackageLoader`` shows a clearer error message when the package does not
+    contain the templates directory. :issue:`1705`
 
 
 Version 3.1.4
diff --git a/src/jinja2/loaders.py b/src/jinja2/loaders.py
index 35799584..3913ee51 100644
--- a/src/jinja2/loaders.py
+++ b/src/jinja2/loaders.py
@@ -327,7 +327,6 @@ class PackageLoader(BaseLoader):
         assert loader is not None, "A loader was not found for the package."
         self._loader = loader
         self._archive = None
-        template_root = None
 
         if isinstance(loader, zipimport.zipimporter):
             self._archive = loader.archive
@@ -344,18 +343,23 @@ class PackageLoader(BaseLoader):
             elif spec.origin is not None:
                 roots.append(os.path.dirname(spec.origin))
 
+            if not roots:
+                raise ValueError(
+                    f"The {package_name!r} package was not installed in a"
+                    " way that PackageLoader understands."
+                )
+
             for root in roots:
                 root = os.path.join(root, package_path)
 
                 if os.path.isdir(root):
                     template_root = root
                     break
-
-        if template_root is None:
-            raise ValueError(
-                f"The {package_name!r} package was not installed in a"
-                " way that PackageLoader understands."
-            )
+            else:
+                raise ValueError(
+                    f"PackageLoader could not find a {package_path!r} directory"
+                    f" in the {package_name!r} package."
+                )
 
         self._template_root = template_root
 


Test patch:
diff --git a/tests/test_loader.py b/tests/test_loader.py
index 5a4e1a9d..377290b7 100644
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -429,3 +429,8 @@ def test_pep_451_import_hook():
         assert "test.html" in package_loader.list_templates()
     finally:
         sys.meta_path[:] = before
+
+
+def test_package_loader_no_dir() -> None:
+    with pytest.raises(ValueError, match="could not find a 'templates' directory"):
+        PackageLoader("jinja2")