Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/backports/zoneinfo/_tzpath.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

103 statements  

1import os 

2import sys 

3 

4PY36 = sys.version_info < (3, 7) 

5 

6 

7def reset_tzpath(to=None): 

8 global TZPATH 

9 

10 tzpaths = to 

11 if tzpaths is not None: 

12 if isinstance(tzpaths, (str, bytes)): 

13 raise TypeError( 

14 f"tzpaths must be a list or tuple, " 

15 + f"not {type(tzpaths)}: {tzpaths!r}" 

16 ) 

17 

18 if not all(map(os.path.isabs, tzpaths)): 

19 raise ValueError(_get_invalid_paths_message(tzpaths)) 

20 base_tzpath = tzpaths 

21 else: 

22 env_var = os.environ.get("PYTHONTZPATH", None) 

23 if env_var is not None: 

24 base_tzpath = _parse_python_tzpath(env_var) 

25 elif sys.platform != "win32": 

26 base_tzpath = [ 

27 "/usr/share/zoneinfo", 

28 "/usr/lib/zoneinfo", 

29 "/usr/share/lib/zoneinfo", 

30 "/etc/zoneinfo", 

31 ] 

32 

33 base_tzpath.sort(key=lambda x: not os.path.exists(x)) 

34 else: 

35 base_tzpath = () 

36 

37 TZPATH = tuple(base_tzpath) 

38 

39 if TZPATH_CALLBACKS: 

40 for callback in TZPATH_CALLBACKS: 

41 callback(TZPATH) 

42 

43 

44def _parse_python_tzpath(env_var): 

45 if not env_var: 

46 return () 

47 

48 raw_tzpath = env_var.split(os.pathsep) 

49 new_tzpath = tuple(filter(os.path.isabs, raw_tzpath)) 

50 

51 # If anything has been filtered out, we will warn about it 

52 if len(new_tzpath) != len(raw_tzpath): 

53 import warnings 

54 

55 msg = _get_invalid_paths_message(raw_tzpath) 

56 

57 warnings.warn( 

58 "Invalid paths specified in PYTHONTZPATH environment variable." 

59 + msg, 

60 InvalidTZPathWarning, 

61 ) 

62 

63 return new_tzpath 

64 

65 

66def _get_invalid_paths_message(tzpaths): 

67 invalid_paths = (path for path in tzpaths if not os.path.isabs(path)) 

68 

69 prefix = "\n " 

70 indented_str = prefix + prefix.join(invalid_paths) 

71 

72 return ( 

73 "Paths should be absolute but found the following relative paths:" 

74 + indented_str 

75 ) 

76 

77 

78if sys.version_info < (3, 8): 

79 

80 def _isfile(path): 

81 # bpo-33721: In Python 3.8 non-UTF8 paths return False rather than 

82 # raising an error. See https://bugs.python.org/issue33721 

83 try: 

84 return os.path.isfile(path) 

85 except ValueError: 

86 return False 

87 

88 

89else: 

90 _isfile = os.path.isfile 

91 

92 

93def find_tzfile(key): 

94 """Retrieve the path to a TZif file from a key.""" 

95 _validate_tzfile_path(key) 

96 for search_path in TZPATH: 

97 filepath = os.path.join(search_path, key) 

98 if _isfile(filepath): 

99 return filepath 

100 

101 return None 

102 

103 

104_TEST_PATH = os.path.normpath(os.path.join("_", "_"))[:-1] 

105 

106 

107def _validate_tzfile_path(path, _base=_TEST_PATH): 

108 if os.path.isabs(path): 

109 raise ValueError( 

110 f"ZoneInfo keys may not be absolute paths, got: {path}" 

111 ) 

112 

113 # We only care about the kinds of path normalizations that would change the 

114 # length of the key - e.g. a/../b -> a/b, or a/b/ -> a/b. On Windows, 

115 # normpath will also change from a/b to a\b, but that would still preserve 

116 # the length. 

117 new_path = os.path.normpath(path) 

118 if len(new_path) != len(path): 

119 raise ValueError( 

120 f"ZoneInfo keys must be normalized relative paths, got: {path}" 

121 ) 

122 

123 resolved = os.path.normpath(os.path.join(_base, new_path)) 

124 if not resolved.startswith(_base): 

125 raise ValueError( 

126 f"ZoneInfo keys must refer to subdirectories of TZPATH, got: {path}" 

127 ) 

128 

129 

130del _TEST_PATH 

131 

132 

133def available_timezones(): 

134 """Returns a set containing all available time zones. 

135 

136 .. caution:: 

137 

138 This may attempt to open a large number of files, since the best way to 

139 determine if a given file on the time zone search path is to open it 

140 and check for the "magic string" at the beginning. 

141 """ 

142 try: 

143 from importlib import resources 

144 except ImportError: 

145 import importlib_resources as resources 

146 

147 valid_zones = set() 

148 

149 # Start with loading from the tzdata package if it exists: this has a 

150 # pre-assembled list of zones that only requires opening one file. 

151 try: 

152 with resources.open_text("tzdata", "zones") as f: 

153 for zone in f: 

154 zone = zone.strip() 

155 if zone: 

156 valid_zones.add(zone) 

157 except (ImportError, FileNotFoundError): 

158 pass 

159 

160 def valid_key(fpath): 

161 try: 

162 with open(fpath, "rb") as f: 

163 return f.read(4) == b"TZif" 

164 except Exception: # pragma: nocover 

165 return False 

166 

167 for tz_root in TZPATH: 

168 if not os.path.exists(tz_root): 

169 continue 

170 

171 for root, dirnames, files in os.walk(tz_root): 

172 if root == tz_root: 

173 # right/ and posix/ are special directories and shouldn't be 

174 # included in the output of available zones 

175 if "right" in dirnames: 

176 dirnames.remove("right") 

177 if "posix" in dirnames: 

178 dirnames.remove("posix") 

179 

180 for file in files: 

181 fpath = os.path.join(root, file) 

182 

183 key = os.path.relpath(fpath, start=tz_root) 

184 if os.sep != "/": # pragma: nocover 

185 key = key.replace(os.sep, "/") 

186 

187 if not key or key in valid_zones: 

188 continue 

189 

190 if valid_key(fpath): 

191 valid_zones.add(key) 

192 

193 if "posixrules" in valid_zones: 

194 # posixrules is a special symlink-only time zone where it exists, it 

195 # should not be included in the output 

196 valid_zones.remove("posixrules") 

197 

198 return valid_zones 

199 

200 

201class InvalidTZPathWarning(RuntimeWarning): 

202 """Warning raised if an invalid path is specified in PYTHONTZPATH.""" 

203 

204 

205TZPATH = () 

206TZPATH_CALLBACKS = [] 

207reset_tzpath()