Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/docutils/languages/__init__.py: 61%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

69 statements  

1# $Id: __init__.py 10398 2026-09-02 15:46:37Z milde $ 

2# Author: David Goodger <goodger@python.org> 

3# Copyright: This module has been placed in the public domain. 

4 

5# Internationalization details are documented in 

6# <https://docutils.sourceforge.io/docs/howto/i18n.html>. 

7 

8""" 

9This package contains modules for language-dependent features of Docutils. 

10""" 

11 

12from __future__ import annotations 

13 

14__docformat__ = 'reStructuredText' 

15 

16from importlib import import_module 

17 

18from docutils.utils import normalize_language_tag 

19 

20TYPE_CHECKING = False 

21if TYPE_CHECKING: 

22 import types 

23 from typing import NoReturn, Protocol, TypeVar, overload 

24 

25 from docutils.utils import Reporter 

26 

27 class LanguageModule(Protocol): 

28 __name__: str 

29 

30 labels: dict[str, str] 

31 bibliographic_fields: dict[str, str] 

32 author_separators: list[str] 

33 

34 LanguageModuleT = TypeVar('LanguageModuleT') 

35else: 

36 from docutils.utils._typing import overload 

37 

38 

39class LanguageImporter: 

40 """Import language modules. 

41 

42 When called with a BCP 47 language tag, instances return a module 

43 with localisations from `docutils.languages` or the PYTHONPATH. 

44 

45 If there is no matching module, warn (if a `reporter` is passed) 

46 and fall back to English. 

47 """ 

48 packages = ('docutils.languages.', '') 

49 warn_msg = ('Language "%s" not supported: ' 

50 'Docutils-generated text will be in English.') 

51 fallback = 'en' 

52 # TODO: use a dummy module returning empty strings?, configurable? 

53 

54 def __init__(self) -> None: 

55 self.cache: dict[str, LanguageModuleT] = {} 

56 

57 def import_from_packages(self, name: str, reporter: Reporter = None 

58 ) -> LanguageModuleT|None: 

59 """Try loading language module `name` from `self.packages`.""" 

60 module = None 

61 for package in self.packages: 

62 try: 

63 module = import_module(package + name) 

64 self.check_content(module) 

65 except (ImportError, AttributeError): 

66 if reporter and module: 

67 reporter.info(f'{module} is no complete ' 

68 'Docutils language module.') 

69 elif reporter: 

70 reporter.info(f'Module "{package+name}" not found.') 

71 continue 

72 break 

73 else: 

74 module = None 

75 return module 

76 

77 @overload 

78 def check_content(self, module: LanguageModule) -> None: 

79 ... 

80 

81 @overload 

82 def check_content(self, module: types.ModuleType) -> NoReturn: 

83 ... 

84 

85 def check_content(self, module: LanguageModule | types.ModuleType) -> None: 

86 """Check if we got a Docutils language module.""" 

87 if not ( 

88 isinstance(module.labels, dict) 

89 and isinstance(module.bibliographic_fields, dict) 

90 and isinstance(module.author_separators, list) 

91 ): 

92 raise ImportError 

93 

94 def __call__(self, language_code: str, reporter: Reporter = None 

95 ) -> LanguageModuleT: 

96 try: 

97 return self.cache[language_code] 

98 except KeyError: 

99 pass 

100 for tag in normalize_language_tag(language_code): 

101 tag = tag.replace('-', '_') # '-' not valid in module names 

102 module = self.import_from_packages(tag, reporter) 

103 if module is not None: 

104 break 

105 else: 

106 if reporter: 

107 reporter.warning(self.warn_msg % language_code) 

108 if self.fallback: 

109 module = self.import_from_packages(self.fallback) 

110 if reporter and (language_code != 'en'): 

111 reporter.info(f'Using {module} for language "{language_code}".') 

112 self.cache[language_code] = module 

113 return module 

114 

115 def __class_getitem__(cls, name): 

116 return cls 

117 

118 

119get_language: LanguageImporter[LanguageModule] = LanguageImporter()