Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/backports/zoneinfo/_tzpath.py: 35%
103 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-30 06:11 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-30 06:11 +0000
1import os
2import sys
4PY36 = sys.version_info < (3, 7)
7def reset_tzpath(to=None):
8 global TZPATH
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 )
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 ]
33 base_tzpath.sort(key=lambda x: not os.path.exists(x))
34 else:
35 base_tzpath = ()
37 TZPATH = tuple(base_tzpath)
39 if TZPATH_CALLBACKS:
40 for callback in TZPATH_CALLBACKS:
41 callback(TZPATH)
44def _parse_python_tzpath(env_var):
45 if not env_var:
46 return ()
48 raw_tzpath = env_var.split(os.pathsep)
49 new_tzpath = tuple(filter(os.path.isabs, raw_tzpath))
51 # If anything has been filtered out, we will warn about it
52 if len(new_tzpath) != len(raw_tzpath):
53 import warnings
55 msg = _get_invalid_paths_message(raw_tzpath)
57 warnings.warn(
58 "Invalid paths specified in PYTHONTZPATH environment variable."
59 + msg,
60 InvalidTZPathWarning,
61 )
63 return new_tzpath
66def _get_invalid_paths_message(tzpaths):
67 invalid_paths = (path for path in tzpaths if not os.path.isabs(path))
69 prefix = "\n "
70 indented_str = prefix + prefix.join(invalid_paths)
72 return (
73 "Paths should be absolute but found the following relative paths:"
74 + indented_str
75 )
78if sys.version_info < (3, 8):
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
89else:
90 _isfile = os.path.isfile
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
101 return None
104_TEST_PATH = os.path.normpath(os.path.join("_", "_"))[:-1]
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 )
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 )
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 )
130del _TEST_PATH
133def available_timezones():
134 """Returns a set containing all available time zones.
136 .. caution::
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
147 valid_zones = set()
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
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
167 for tz_root in TZPATH:
168 if not os.path.exists(tz_root):
169 continue
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")
180 for file in files:
181 fpath = os.path.join(root, file)
183 key = os.path.relpath(fpath, start=tz_root)
184 if os.sep != "/": # pragma: nocover
185 key = key.replace(os.sep, "/")
187 if not key or key in valid_zones:
188 continue
190 if valid_key(fpath):
191 valid_zones.add(key)
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")
198 return valid_zones
201class InvalidTZPathWarning(RuntimeWarning):
202 """Warning raised if an invalid path is specified in PYTHONTZPATH."""
205TZPATH = ()
206TZPATH_CALLBACKS = []
207reset_tzpath()