Coverage for /pythoncovmergedfiles/medio/medio/usr/lib/python3.9/zoneinfo/_tzpath.py: 40%

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

85 statements  

1import os 

2import sysconfig 

3 

4 

5def reset_tzpath(to=None): 

6 global TZPATH 

7 

8 tzpaths = to 

9 if tzpaths is not None: 

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

11 raise TypeError( 

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

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

14 ) 

15 

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

17 raise ValueError(_get_invalid_paths_message(tzpaths)) 

18 base_tzpath = tzpaths 

19 else: 

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

21 if env_var is not None: 

22 base_tzpath = _parse_python_tzpath(env_var) 

23 else: 

24 base_tzpath = _parse_python_tzpath( 

25 sysconfig.get_config_var("TZPATH") 

26 ) 

27 

28 TZPATH = tuple(base_tzpath) 

29 

30 

31def _parse_python_tzpath(env_var): 

32 if not env_var: 

33 return () 

34 

35 raw_tzpath = env_var.split(os.pathsep) 

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

37 

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

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

40 import warnings 

41 

42 msg = _get_invalid_paths_message(raw_tzpath) 

43 

44 warnings.warn( 

45 "Invalid paths specified in PYTHONTZPATH environment variable. " 

46 + msg, 

47 InvalidTZPathWarning, 

48 ) 

49 

50 return new_tzpath 

51 

52 

53def _get_invalid_paths_message(tzpaths): 

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

55 

56 prefix = "\n " 

57 indented_str = prefix + prefix.join(invalid_paths) 

58 

59 return ( 

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

61 + indented_str 

62 ) 

63 

64 

65def find_tzfile(key): 

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

67 _validate_tzfile_path(key) 

68 for search_path in TZPATH: 

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

70 if os.path.isfile(filepath): 

71 return filepath 

72 

73 return None 

74 

75 

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

77 

78 

79def _validate_tzfile_path(path, _base=_TEST_PATH): 

80 if os.path.isabs(path): 

81 raise ValueError( 

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

83 ) 

84 

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

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

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

88 # the length. 

89 new_path = os.path.normpath(path) 

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

91 raise ValueError( 

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

93 ) 

94 

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

96 if not resolved.startswith(_base): 

97 raise ValueError( 

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

99 ) 

100 

101 

102del _TEST_PATH 

103 

104 

105def available_timezones(): 

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

107 

108 .. caution:: 

109 

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

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

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

113 """ 

114 from importlib import resources 

115 

116 valid_zones = set() 

117 

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

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

120 try: 

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

122 for zone in f: 

123 zone = zone.strip() 

124 if zone: 

125 valid_zones.add(zone) 

126 except (ImportError, FileNotFoundError): 

127 pass 

128 

129 def valid_key(fpath): 

130 try: 

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

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

133 except Exception: # pragma: nocover 

134 return False 

135 

136 for tz_root in TZPATH: 

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

138 continue 

139 

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

141 if root == tz_root: 

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

143 # included in the output of available zones 

144 if "right" in dirnames: 

145 dirnames.remove("right") 

146 if "posix" in dirnames: 

147 dirnames.remove("posix") 

148 

149 for file in files: 

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

151 

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

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

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

155 

156 if not key or key in valid_zones: 

157 continue 

158 

159 if valid_key(fpath): 

160 valid_zones.add(key) 

161 

162 if "posixrules" in valid_zones: 

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

164 # should not be included in the output 

165 valid_zones.remove("posixrules") 

166 

167 return valid_zones 

168 

169 

170class InvalidTZPathWarning(RuntimeWarning): 

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

172 

173 

174TZPATH = () 

175reset_tzpath()