-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathverify_python_versions.py
More file actions
executable file
·134 lines (104 loc) · 4.23 KB
/
verify_python_versions.py
File metadata and controls
executable file
·134 lines (104 loc) · 4.23 KB
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python3
"""
A script to verify Python version consistency across different project configuration files.
This script ensures that Python versions specified in different project files
(Dockerfile, pyproject.toml, and GitHub workflow files) are consistent with each other.
It extracts the Python version from the Dockerfile as the source of truth and compares
it with versions specified in other configuration files.
The script checks:
- Python version in Dockerfile (source of truth)
- Python version in pyproject.toml (mypy configuration)
- Python versions in GitHub workflow files (comma separated list of paths)
Usage:
python verify_python_versions.py <dockerfile_path> <pyproject_path> <workflow_paths_csv>
Returns:
Exit code 0 if all versions are consistent
Exit code 1 if any inconsistencies are found or errors occur
"""
import re
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, cast
def extract_version_from_dockerfile(dockerfile_path: Path) -> tuple[str, str]:
"""Extract the Python version from the Dockerfile (source of truth).
Finds all instances of 'FROM python:*', verifies they're all the same,
and returns the common version.
"""
if not dockerfile_path.exists():
print(f"Error: {dockerfile_path} not found")
sys.exit(1)
dockerfile_content = dockerfile_path.read_text()
# Find all Python image references in the Dockerfile
python_images = re.findall(r"^FROM python:(\d+)\.?(\d+)?.*", dockerfile_content)
if not python_images:
print(f"Error: No Python images found in {dockerfile_path}")
sys.exit(1)
base_version = cast(tuple[str, str | None], python_images[0])
if base_version[1] is None:
print("Dockerfile python does not specify minor version.")
sys.exit(1)
if not all(version == base_version for version in python_images):
print(
f"Error: Inconsistent Python images in {dockerfile_path}: {python_images}"
)
sys.exit(1)
return cast(tuple[str, str], base_version)
@dataclass(frozen=True)
class VersionCheckResult:
success: bool
message: str | None
def check_for_version_in_file(
path: Path,
extract_versions: Callable[[str], list[tuple[str, str]]],
source_version: tuple[str, str],
) -> VersionCheckResult:
"""
Checks if a given file declares the correct python versions.
Accepts a path and a lambda which defines how to extract versions from that path,
as well as a source version to compare against
"""
if not path.exists():
return VersionCheckResult(success=False, message=f"{path.name} not found.")
python_versions = extract_versions(path.read_text())
if not python_versions:
return VersionCheckResult(
success=False, message=f"{path.name} has no python versions."
)
errors = [
f"{path.name} has mismatched version: {python_version}"
for python_version in python_versions
if source_version != python_version
]
return VersionCheckResult(
success=len(errors) == 0, message="\n".join(errors) if errors else None
)
def main(dockerfile_path: str, pyproject_path: str, workflow_paths_csv: str) -> None:
source_version = extract_version_from_dockerfile(Path(dockerfile_path.strip()))
print(f"Source of truth Python version: {source_version}")
results = [
check_for_version_in_file(
Path(pyproject_path.strip()),
lambda content: re.findall(
r'python_version\s*=\s*"(\d+)\.?(\d+)?.*"', content
),
source_version,
),
*(
check_for_version_in_file(
Path(workflow_path.strip()),
lambda content: re.findall(
r"python-version:\s*(\d+)\.?(\d+)?.*", content
),
source_version,
)
for workflow_path in workflow_paths_csv.split(",")
),
]
print("\n".join(result.message for result in results if result.message is not None))
if not all(result.success for result in results):
sys.exit(1)
else:
print("All Python version references are consistent!")
if __name__ == "__main__":
main(*sys.argv[1:])