Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/_config/localization.py: 36%

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

53 statements  

1""" 

2Helpers for configuring locale settings. 

3 

4Name `localization` is chosen to avoid overlap with builtin `locale` module. 

5""" 

6 

7from __future__ import annotations 

8 

9from contextlib import contextmanager 

10import functools 

11import locale 

12import platform 

13import re 

14import subprocess 

15from typing import ( 

16 TYPE_CHECKING, 

17 cast, 

18) 

19 

20from pandas._config.config import options 

21 

22if TYPE_CHECKING: 

23 from collections.abc import Generator 

24 

25 

26@contextmanager 

27def set_locale( 

28 new_locale: str | tuple[str, str], lc_var: int = locale.LC_ALL 

29) -> Generator[str | tuple[str, str]]: 

30 """ 

31 Context manager for temporarily setting a locale. 

32 

33 Parameters 

34 ---------- 

35 new_locale : str or tuple 

36 A string of the form <language_country>.<encoding>. For example to set 

37 the current locale to US English with a UTF8 encoding, you would pass 

38 "en_US.UTF-8". 

39 lc_var : int, default `locale.LC_ALL` 

40 The category of the locale being set. 

41 

42 Notes 

43 ----- 

44 This is useful when you want to run a particular block of code under a 

45 particular locale, without globally setting the locale. This probably isn't 

46 thread-safe. 

47 """ 

48 # getlocale is not always compliant with setlocale, use setlocale. GH#46595 

49 current_locale = locale.setlocale(lc_var) 

50 

51 try: 

52 locale.setlocale(lc_var, new_locale) 

53 normalized_code, normalized_encoding = locale.getlocale() 

54 if normalized_code is not None and normalized_encoding is not None: 

55 yield f"{normalized_code}.{normalized_encoding}" 

56 else: 

57 yield new_locale 

58 finally: 

59 locale.setlocale(lc_var, current_locale) 

60 

61 

62def can_set_locale(lc: str, lc_var: int = locale.LC_ALL) -> bool: 

63 """ 

64 Check to see if we can set a locale, and subsequently get the locale, 

65 without raising an Exception. 

66 

67 Parameters 

68 ---------- 

69 lc : str 

70 The locale to attempt to set. 

71 lc_var : int, default `locale.LC_ALL` 

72 The category of the locale being set. 

73 

74 Returns 

75 ------- 

76 bool 

77 Whether the passed locale can be set 

78 """ 

79 try: 

80 with set_locale(lc, lc_var=lc_var): 

81 pass 

82 except (ValueError, locale.Error): 

83 # horrible name for an Exception subclass 

84 return False 

85 else: 

86 return True 

87 

88 

89def _valid_locales(locales: list[str] | str, normalize: bool) -> list[str]: 

90 """ 

91 Return a list of normalized locales that do not throw an ``Exception`` 

92 when set. 

93 

94 Parameters 

95 ---------- 

96 locales : str 

97 A string where each locale is separated by a newline. 

98 normalize : bool 

99 Whether to call ``locale.normalize`` on each locale. 

100 

101 Returns 

102 ------- 

103 valid_locales : list 

104 A list of valid locales. 

105 """ 

106 return [ 

107 loc 

108 for loc in ( 

109 locale.normalize(loc.strip()) if normalize else loc.strip() 

110 for loc in locales 

111 ) 

112 if can_set_locale(loc) 

113 ] 

114 

115 

116# Windows has no "locale -a" to enumerate with, so we hardcode a few values. 

117# GH#46597 

118_WINDOWS_LOCALES = [ 

119 "de-DE.UTF-8", # Latin with diacritics 

120 "el-GR.UTF-8", # Greek, where capitalize() has to handle final sigma 

121 "en-US.UTF-8", # ASCII baseline 

122 "ja-JP.UTF-8", # caseless script, capitalize() is a no-op 

123 "tr-TR.UTF-8", # dotted and dotless i 

124] 

125 

126 

127@functools.cache 

128def _get_locales(prefix: str | None, normalize: bool) -> tuple[str, ...]: 

129 if platform.system() in ("Linux", "Darwin"): 

130 raw_locales = subprocess.check_output(["locale", "-a"]) 

131 # raw_locales is "\n" separated list of locales 

132 # it may contain non-decodable parts, so split 

133 # extract what we can and then rejoin. 

134 out_locales = [] 

135 for raw_locale in raw_locales.split(b"\n"): 

136 try: 

137 out_locales.append( 

138 str(raw_locale, encoding=cast("str", options.display.encoding)) 

139 ) 

140 except UnicodeError: 

141 # 'locale -a' is used to populated 'raw_locales' and on 

142 # Redhat 7 Linux (and maybe others) prints locale names 

143 # using windows-1252 encoding. Bug only triggered by 

144 # a few special characters and when there is an 

145 # extensive list of installed locales. 

146 out_locales.append(str(raw_locale, encoding="windows-1252")) 

147 elif platform.system() == "Windows": 

148 # Note: is_platform_windows causes circular import here 

149 out_locales = _WINDOWS_LOCALES 

150 # These are already spelled the way the CRT wants them; running them 

151 # through the POSIX alias table would rename them ("ar-SA" -> "ar_AA"). 

152 normalize = False 

153 else: 

154 return () 

155 

156 if prefix is None: 

157 return tuple(_valid_locales(out_locales, normalize)) 

158 

159 pattern = re.compile(f"{prefix}.*") 

160 found = pattern.findall("\n".join(out_locales)) 

161 return tuple(_valid_locales(found, normalize)) 

162 

163 

164def get_locales( 

165 prefix: str | None = None, 

166 normalize: bool = True, 

167) -> list[str]: 

168 """ 

169 Get all the locales that are available on the system. 

170 

171 Parameters 

172 ---------- 

173 prefix : str 

174 If not ``None`` then return only those locales with the prefix 

175 provided. For example to get all English language locales (those that 

176 start with ``"en"``), pass ``prefix="en"``. 

177 normalize : bool 

178 Call ``locale.normalize`` on the resulting list of available locales. 

179 If ``True``, only locales that can be set without throwing an 

180 ``Exception`` are returned. 

181 

182 Returns 

183 ------- 

184 locales : list of strings 

185 A list of locale strings that can be set with ``locale.setlocale()``. 

186 For example:: 

187 

188 locale.setlocale(locale.LC_ALL, locale_string) 

189 

190 On error will return an empty list (no locale available) 

191 

192 """ 

193 # Every candidate is probed with setlocale, and several test modules call 

194 # this at collection time, so the result is cached. 

195 return list(_get_locales(prefix, normalize))