blob: 8cff0b171bf32ffd53157c60a74216c692ae1778 [file] [log] [blame]
Paul Ganssle62972d92020-05-16 04:20:06 -04001import os
2import sys
3import sysconfig
4
5
6def reset_tzpath(to=None):
7 global TZPATH
8
9 tzpaths = to
10 if tzpaths is not None:
11 if isinstance(tzpaths, (str, bytes)):
12 raise TypeError(
13 f"tzpaths must be a list or tuple, "
14 + f"not {type(tzpaths)}: {tzpaths!r}"
15 )
16 elif 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
105class InvalidTZPathWarning(RuntimeWarning):
106 """Warning raised if an invalid path is specified in PYTHONTZPATH."""
107
108
109TZPATH = ()
110reset_tzpath()