]> git.ipfire.org Git - thirdparty/babel.git/commitdiff
Support importlib.metadata for finding entrypoints
authorAarni Koskela <akx@iki.fi>
Thu, 18 Jul 2024 07:41:03 +0000 (10:41 +0300)
committerAarni Koskela <akx@iki.fi>
Thu, 25 Jul 2024 09:39:08 +0000 (12:39 +0300)
For Python 3.12 compatibility!

Co-authored-by: podgorniy94 <podgorniy.inc@gmail.com>
babel/messages/_compat.py [new file with mode: 0644]
babel/messages/checkers.py
babel/messages/extract.py

diff --git a/babel/messages/_compat.py b/babel/messages/_compat.py
new file mode 100644 (file)
index 0000000..cf82245
--- /dev/null
@@ -0,0 +1,32 @@
+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.
+    """
+    try:
+        from importlib.metadata import entry_points
+    except ImportError:
+        pass
+    else:
+        eps = entry_points()
+        if isinstance(eps, dict):  # Old structure before Python 3.10
+            group_eps = eps.get(group_name, [])
+        else:  # New structure in Python 3.10+
+            group_eps = (ep for ep in eps if ep.group == group_name)
+        for entry_point in group_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))
index df1159dedf2e064f8af1b50abb3eaba609e8c358..2889b4e6c0d3a5c6c698824643930de11ed116e4 100644 (file)
@@ -155,16 +155,11 @@ def _validate_format(format: str, alternative: str) -> None:
 
 
 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
index 26d736e7a887c74f3aa4a70d5d5a8d31da5a10ad..8d4bbeaf8c67a8d2bc4c4a4ae86cef69de4e2947 100644 (file)
@@ -30,11 +30,13 @@ from collections.abc import (
     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:
@@ -363,6 +365,14 @@ def _match_messages_against_spec(lineno: int, messages: list[str|None], comments
     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,
@@ -421,25 +431,11 @@ def extract(
             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}")
@@ -838,3 +834,10 @@ def parse_template_string(
                     lineno += len(line_re.findall(expression_contents))
                     expression_contents = ''
         prev_character = character
+
+
+_BUILTIN_EXTRACTORS = {
+    'ignore': extract_nothing,
+    'python': extract_python,
+    'javascript': extract_javascript,
+}