forked from pandas-dev/pandas
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathvalidate_min_versions_in_sync.py
executable file
·81 lines (67 loc) · 2.58 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
#!/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 ast
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()
def get_versions_from_code(content: str) -> dict[str, str]:
num_dicts = 0
for node in ast.walk(ast.parse(content)):
if isinstance(node, ast.Dict):
if num_dicts == 0:
version_dict_ast = node
num_dicts += 1
elif num_dicts == 1:
install_map = {k.value: v.value for k, v in zip(node.keys, node.values)}
return {
install_map.get(k.value, k.value).casefold(): v.value
for k, v in zip(version_dict_ast.keys, version_dict_ast.values)
if k.value != "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 not seen_optional:
required_deps[package] = version
else:
optional_deps[package] = version
return required_deps, optional_deps
def main():
with open(CI_PATH) as f:
_, ci_optional = get_versions_from_ci(f.readlines())
with open(CODE_PATH) as f:
code_optional = get_versions_from_code(f.read())
diff = set(ci_optional.items()).symmetric_difference(code_optional.items())
if diff:
sys.stdout.write(
f"The follow minimum version differences were found between "
f"{CI_PATH} and {CODE_PATH}. Please ensure these are aligned: "
f"{diff}"
)
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()