Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dateutil/_tzdata_impl.py: 32%

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

63 statements  

1"""This is an implementation of functions that interact with the tzdata module. 

2 

3It will only live as long as :mod:`dateutil.zoneinfo` lives; when the 

4``dateutil.zoneinfo`` module is removed, this can all move under :mod:`tz`. 

5""" 

6 

7import contextlib 

8import importlib 

9import io 

10import os 

11import pkgutil 

12import sys 

13 

14import six 

15 

16if six.PY2: 

17 _TZDATA_LOAD_EXCEPTIONS = ( 

18 ImportError, 

19 IOError, 

20 UnicodeEncodeError, 

21 ValueError, 

22 ) 

23else: 

24 _TZDATA_LOAD_EXCEPTIONS = ( 

25 ImportError, 

26 FileNotFoundError, 

27 UnicodeEncodeError, 

28 IsADirectoryError, 

29 ValueError, 

30 ) 

31 

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

33 _nullcontext = getattr(contextlib, "nullcontext", None) 

34 if _nullcontext is None: 

35 

36 @contextlib.contextmanager 

37 def _nullcontext(v): 

38 yield v 

39 

40 def _get_data(package, resource): 

41 # pkgutil.get_data returns None rather than raising when the package 

42 # cannot be found (and on Python 2 raises AttributeError if the 

43 # package has been blocked by setting sys.modules[name] = None), so 

44 # import the package first to get the ImportError the callers expect 

45 # from the importlib.resources versions of these functions. 

46 importlib.import_module(package) 

47 

48 pkg_data = pkgutil.get_data(package, resource) 

49 if pkg_data is None: 

50 raise ImportError("No package named %r" % package) 

51 

52 return pkg_data 

53 

54 def _open_text(package, resource): 

55 str_package_data = _get_data(package, resource).decode("utf-8") 

56 

57 return _nullcontext(io.StringIO(str_package_data)) 

58 

59 def _open_binary(package, resource): 

60 return io.BytesIO(_get_data(package, resource)) 

61 

62else: 

63 import importlib.resources 

64 

65 if sys.version_info >= (3, 9): 

66 

67 def _open_text(package, resource): 

68 return ( 

69 importlib.resources.files(package) 

70 .joinpath(resource) 

71 .open("r", encoding="utf-8") 

72 ) 

73 

74 def _open_binary(package, resource): 

75 return ( 

76 importlib.resources.files(package).joinpath(resource).open("rb") 

77 ) 

78 

79 else: 

80 _open_text = importlib.resources.open_text 

81 _open_binary = importlib.resources.open_binary 

82 

83 

84def _is_package_directory(package, resource): 

85 """Whether ``resource`` names a directory inside ``package``. 

86 

87 Opening a directory raises IsADirectoryError on POSIX but PermissionError 

88 on Windows, and on Python < 3.9 there is no portable way to ask the 

89 resource API, so check the package's search path directly (the tzdata 

90 package is a regular directory-backed package). 

91 """ 

92 try: 

93 pkg = importlib.import_module(package) 

94 except ImportError: 

95 return False 

96 

97 for base in getattr(pkg, "__path__", ()): 

98 if os.path.isdir(os.path.join(base, resource)): 

99 return True 

100 

101 return False 

102 

103 

104def _load_tzdata(key): 

105 components = key.split("/") 

106 package_name = ".".join(["tzdata.zoneinfo"] + components[:-1]) 

107 resource_name = components[-1] 

108 

109 # Empty or relative path components cannot name a zone, and they produce 

110 # module names that the import machinery handles inconsistently across 

111 # versions, so treat them as not found up front. A key naming a directory 

112 # (a subpackage) is not a zone either, and opening it raises a 

113 # platform-dependent error, see CPython gh-85702. 

114 if any(c in ("", ".", "..") for c in components) or _is_package_directory( 

115 package_name, resource_name 

116 ): 

117 raise TZFileNotFound("Time zone not found: %s", zone_key=key) 

118 

119 try: 

120 return _open_binary(package_name, resource_name) 

121 except _TZDATA_LOAD_EXCEPTIONS: 

122 # ImportError: package does not exist (or tzdata is not installed) 

123 # FileNotFoundError: the key is not a file in the package 

124 # UnicodeEncodeError: the key is not encodable as a filename 

125 # IsADirectoryError: the key names a package rather than a resource 

126 # ValueError: the key is not a valid path (e.g. contains a NUL byte) 

127 six.raise_from( 

128 TZFileNotFound("Time zone not found: %s", zone_key=key), None 

129 ) 

130 

131 

132def _load_tzdata_keys(): 

133 with _open_text("tzdata", "zones") as f: 

134 return list(filter(None, (l.strip() for l in f))) 

135 

136 

137class TZFileNotFound(ValueError): 

138 """Indicates that a time zone file isn't present in tzdata.""" 

139 

140 def __init__(self, msg, *args, **kwargs): 

141 # TODO: Use keyword-only argument when this is Python 3-only 

142 zone_key = kwargs.pop("zone_key", None) 

143 if zone_key is None: # pragma: nocover 

144 raise TypeError("Required keyword argument missing: zone_key") 

145 

146 super(TZFileNotFound, self).__init__(msg % zone_key, *args) 

147 self.zone_key = zone_key