forked from pandas-dev/pandas
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvalidate_min_versions_in_sync.py
executable file
·113 lines (90 loc) · 3.27 KB
/
validate_min_versions_in_sync.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#!/usr/bin/env python3
"""
Check pandas required and optional dependencies are synced across:
ci/deps/actions-.*-minimum_versions.yaml
pandas/compat/_optional.py
TODO: doc/source/getting_started/install.rst
This is meant to be run as a pre-commit hook - to run it manually, you can do:
pre-commit run validate-min-versions-in-sync --all-files
"""
from __future__ import annotations
import configparser
import pathlib
import sys
DOC_PATH = pathlib.Path("doc/source/getting_started/install.rst").resolve()
CI_PATH = next(
pathlib.Path("ci/deps").absolute().glob("actions-*-minimum_versions.yaml")
)
CODE_PATH = pathlib.Path("pandas/compat/_optional.py").resolve()
SETUP_PATH = pathlib.Path("setup.cfg").resolve()
EXCLUDE_DEPS = {"tzdata"}
# pandas package is not available
# in pre-commit environment
sys.path.append("pandas/compat")
sys.path.append("pandas/util")
import _exceptions
import version
sys.modules["pandas.util.version"] = version
sys.modules["pandas.util._exceptions"] = _exceptions
import _optional
def get_versions_from_code() -> dict[str, str]:
install_map = _optional.INSTALL_MAPPING
versions = _optional.VERSIONS
for item in EXCLUDE_DEPS:
versions.pop(item)
return {
install_map.get(k, k).casefold(): v
for k, v in versions.items()
if k != "pytest"
}
def get_versions_from_ci(content: list[str]) -> tuple[dict[str, str], dict[str, str]]:
# Don't parse with pyyaml because it ignores comments we're looking for
seen_required = False
seen_optional = False
required_deps = {}
optional_deps = {}
for line in content:
if "# required dependencies" in line:
seen_required = True
elif "# optional dependencies" in line:
seen_optional = True
elif seen_required and line.strip():
package, version = line.strip().split("=")
package = package[2:]
if package in EXCLUDE_DEPS:
continue
if not seen_optional:
required_deps[package] = version
else:
optional_deps[package] = version
return required_deps, optional_deps
def get_versions_from_setup() -> dict[str, str]:
optional_dependencies = {}
parser = configparser.ConfigParser()
parser.read(SETUP_PATH)
setup_optional = parser["options.extras_require"]["all"]
dependencies = setup_optional[1:].split("\n")
for dependency in dependencies:
package, version = dependency.strip().split(">=")
optional_dependencies[package] = version
return optional_dependencies
def main():
with open(CI_PATH, encoding="utf-8") as f:
_, ci_optional = get_versions_from_ci(f.readlines())
code_optional = get_versions_from_code()
setup_optional = get_versions_from_setup()
diff = set(
(ci_optional.items() | code_optional.items() | setup_optional.items())
- (ci_optional.items() & code_optional.items() & setup_optional.items())
)
if diff:
sys.stdout.write(
f"The follow minimum version differences were found between "
f"{CI_PATH}, {CODE_PATH} AND {SETUP_PATH}. "
f"Please ensure these are aligned: "
f"{diff}\n"
)
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()