forked from UiPath/uipath-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck_version_uniqueness.py
More file actions
120 lines (99 loc) · 3.83 KB
/
Copy pathcheck_version_uniqueness.py
File metadata and controls
120 lines (99 loc) · 3.83 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
#!/usr/bin/env python3
"""Ensure every changed package has a version not yet published to PyPI.
For each package with file changes in the PR, we read its version from
pyproject.toml and check whether that exact version exists on PyPI.
If it does, the check fails — the developer must bump the version.
This catches two scenarios:
1. Developer changed code but forgot to bump the version.
2. Two PRs raced to the same version — after rebase the first PR's
version is already on PyPI, so this check forces the second PR
to pick a new one.
"""
import os
import subprocess
import sys
import urllib.error
import urllib.request
from pathlib import Path
try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib # type: ignore[no-redef]
def version_exists_on_pypi(package_name: str, version: str) -> bool | None:
url = f"https://pypi.org/pypi/{package_name}/{version}/json"
try:
req = urllib.request.Request(url, method="HEAD")
with urllib.request.urlopen(req, timeout=10):
return True
except urllib.error.HTTPError as e:
if e.code == 404:
return False
print(f"Warning: PyPI returned HTTP {e.code} for {package_name}=={version}", file=sys.stderr)
return None
except Exception as e:
print(f"Warning: Could not reach PyPI for {package_name}=={version}: {e}", file=sys.stderr)
return None
def get_package_info(package_dir: str) -> tuple[str, str] | None:
pyproject = Path("packages") / package_dir / "pyproject.toml"
if not pyproject.exists():
return None
with open(pyproject, "rb") as f:
data = tomllib.load(f)
project = data.get("project", {})
name = project.get("name")
version = project.get("version")
if name and version:
return name, version
return None
def get_changed_packages() -> list[str]:
base_sha = os.getenv("BASE_SHA", "")
head_sha = os.getenv("HEAD_SHA", "")
diff_spec = f"{base_sha}...{head_sha}" if base_sha and head_sha else "origin/main...HEAD"
try:
result = subprocess.run(
["git", "diff", "--name-only", diff_spec],
capture_output=True,
text=True,
check=True,
)
except subprocess.CalledProcessError as e:
print(f"Error running git diff: {e}", file=sys.stderr)
return []
changed = set()
for file_path in result.stdout.strip().split("\n"):
if file_path.startswith("packages/"):
parts = file_path.split("/")
if len(parts) >= 3 and parts[2] == "src" and (Path("packages") / parts[1] / "pyproject.toml").exists():
changed.add(parts[1])
return sorted(changed)
def main() -> int:
changed = get_changed_packages()
if not changed:
print("No changed packages detected.")
return 0
conflicts = []
failures = []
for pkg_dir in changed:
info = get_package_info(pkg_dir)
if not info:
continue
name, version = info
exists = version_exists_on_pypi(name, version)
if exists is True:
print(f"FAIL: {name}=={version} already exists on PyPI")
conflicts.append(f"{name}=={version}")
elif exists is False:
print(f"OK: {name}=={version} is available")
else:
print(f"FAIL: could not verify {name}=={version}")
failures.append(f"{name}=={version}")
success = len(conflicts) + len(failures) == 0
if not success:
if conflicts:
print(f"\nPlease bump the version in pyproject.toml for: {', '.join(conflicts)}", file=sys.stderr)
if failures:
print(f"\nError while trying to check the following packages on pypi index: {', '.join(failures)}. Please retry.", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())