--- /dev/null
+import sys
+from functools import partial
+
+
+def find_entrypoints(group_name: str):
+ """
+ Find entrypoints of a given group using either `importlib.metadata` or the
+ older `pkg_resources` mechanism.
+
+ Yields tuples of the entrypoint name and a callable function that will
+ load the actual entrypoint.
+ """
+ if sys.version_info >= (3, 10):
+ # "Changed in version 3.10: importlib.metadata is no longer provisional."
+ try:
+ from importlib.metadata import entry_points
+ except ImportError:
+ pass
+ else:
+ eps = entry_points(group=group_name)
+ # Only do this if this implementation of `importlib.metadata` is
+ # modern enough to not return a dict.
+ if not isinstance(eps, dict):
+ for entry_point in eps:
+ yield (entry_point.name, entry_point.load)
+ return
+
+ try:
+ from pkg_resources import working_set
+ except ImportError:
+ pass
+ else:
+ for entry_point in working_set.iter_entry_points(group_name):
+ yield (entry_point.name, partial(entry_point.load, require=True))
def _find_checkers() -> list[Callable[[Catalog | None, Message], object]]:
+ from babel.messages._compat import find_entrypoints
checkers: list[Callable[[Catalog | None, Message], object]] = []
- try:
- from pkg_resources import working_set
- except ImportError:
- pass
- else:
- for entry_point in working_set.iter_entry_points('babel.checkers'):
- checkers.append(entry_point.load())
+ checkers.extend(load() for (name, load) in find_entrypoints('babel.checkers'))
if len(checkers) == 0:
- # if pkg_resources is not available or no usable egg-info was found
+ # if entrypoints are not available or no usable egg-info was found
# (see #230), just resort to hard-coded checkers
return [num_plurals, python_format]
return checkers
Mapping,
MutableSequence,
)
+from functools import lru_cache
from os.path import relpath
from textwrap import dedent
from tokenize import COMMENT, NAME, OP, STRING, generate_tokens
from typing import TYPE_CHECKING, Any
+from babel.messages._compat import find_entrypoints
from babel.util import parse_encoding, parse_future_flags, pathmatch
if TYPE_CHECKING:
return lineno, translatable, comments, context
+@lru_cache(maxsize=None)
+def _find_extractor(name: str):
+ for ep_name, load in find_entrypoints(GROUP_NAME):
+ if ep_name == name:
+ return load()
+ return None
+
+
def extract(
method: _ExtractionMethod,
fileobj: _FileObj,
module, attrname = method.split(':', 1)
func = getattr(__import__(module, {}, {}, [attrname]), attrname)
else:
- try:
- from pkg_resources import working_set
- except ImportError:
- pass
- else:
- for entry_point in working_set.iter_entry_points(GROUP_NAME,
- method):
- func = entry_point.load(require=True)
- break
+ func = _find_extractor(method)
if func is None:
- # if pkg_resources is not available or no usable egg-info was found
- # (see #230), we resort to looking up the builtin extractors
- # directly
- builtin = {
- 'ignore': extract_nothing,
- 'python': extract_python,
- 'javascript': extract_javascript,
- }
- func = builtin.get(method)
+ # if no named entry point was found,
+ # we resort to looking up a builtin extractor
+ func = _BUILTIN_EXTRACTORS.get(method)
if func is None:
raise ValueError(f"Unknown extraction method {method!r}")
lineno += len(line_re.findall(expression_contents))
expression_contents = ''
prev_character = character
+
+
+_BUILTIN_EXTRACTORS = {
+ 'ignore': extract_nothing,
+ 'python': extract_python,
+ 'javascript': extract_javascript,
+}
--- /dev/null
+{% trans %}Hello, {{ name }}!{% endtrans %}
--- /dev/null
+[jinja2: *.html]
--- /dev/null
+import pathlib
+
+import pytest
+
+from babel.messages import frontend
+
+jinja2 = pytest.importorskip("jinja2")
+
+jinja2_data_path = pathlib.Path(__file__).parent / "jinja2_data"
+
+
+def test_jinja2_interop(monkeypatch, tmp_path):
+ """
+ Test that babel can extract messages from Jinja2 templates.
+ """
+ monkeypatch.chdir(jinja2_data_path)
+ cli = frontend.CommandLineInterface()
+ pot_file = tmp_path / "messages.pot"
+ cli.run(['pybabel', 'extract', '--mapping', 'mapping.cfg', '-o', str(pot_file), '.'])
+ assert '"Hello, %(name)s!"' in pot_file.read_text()
pypy3
py{38}-pytz
py{311,312}-setuptools
+ py312-jinja
[testenv]
extras =
tzdata;sys_platform == 'win32'
pytz: pytz
setuptools: setuptools
+ jinja: jinja2>=3.0
allowlist_externals = make
commands = make clean-cldr test
setenv =