Skip to content

Commit 0462b77

Browse files
committed
fix version check sync logic to check pypi properly
Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
1 parent 88bdb96 commit 0462b77

3 files changed

Lines changed: 93 additions & 17 deletions

File tree

.github/workflows/version-check.yml

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,43 @@ jobs:
3535
MAIN_VERSION=$(git show origin/main:socketsecurity/__init__.py | grep -o "__version__.*" | awk '{print $3}' | tr -d "'")
3636
echo "MAIN_VERSION=$MAIN_VERSION" >> $GITHUB_ENV
3737
38-
# Compare versions using Python
39-
python3 -c "
38+
export PR_VERSION
39+
export MAIN_VERSION
40+
41+
# Compare against both main and latest published PyPI release.
42+
python3 <<'PY'
43+
import json
44+
import os
45+
import urllib.request
4046
from packaging import version
41-
pr_ver = version.parse('${PR_VERSION}')
42-
main_ver = version.parse('${MAIN_VERSION}')
43-
if pr_ver <= main_ver:
44-
print(f'❌ Version must be incremented! Main: {main_ver}, PR: {pr_ver}')
45-
exit(1)
46-
print(f'✅ Version properly incremented from {main_ver} to {pr_ver}')
47-
"
47+
48+
pr_ver = version.parse(os.environ["PR_VERSION"])
49+
main_ver = version.parse(os.environ["MAIN_VERSION"])
50+
51+
with urllib.request.urlopen("https://pypi.org/pypi/socketsecurity/json") as response:
52+
pypi_data = json.load(response)
53+
54+
published_versions = []
55+
for raw in pypi_data.get("releases", {}).keys():
56+
parsed = version.parse(raw)
57+
if not parsed.is_prerelease and not parsed.is_devrelease:
58+
published_versions.append(parsed)
59+
60+
pypi_ver = max(published_versions) if published_versions else version.parse("0.0.0")
61+
required_floor = max(main_ver, pypi_ver)
62+
63+
if pr_ver <= required_floor:
64+
print(
65+
f"❌ Version must be greater than main and PyPI! "
66+
f"Main: {main_ver}, PyPI: {pypi_ver}, PR: {pr_ver}"
67+
)
68+
raise SystemExit(1)
69+
70+
print(
71+
f"✅ Version properly incremented. "
72+
f"Main: {main_ver}, PyPI: {pypi_ver}, PR: {pr_ver}"
73+
)
74+
PY
4875
4976
- name: Require uv.lock update when pyproject changes
5077
run: |

.hooks/sync_version.py

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
VERSION_PATTERN = re.compile(r"__version__\s*=\s*['\"]([^'\"]+)['\"]")
1414
PYPROJECT_PATTERN = re.compile(r'^version\s*=\s*".*"$', re.MULTILINE)
15-
PYPI_API = "https://test.pypi.org/pypi/socketsecurity/json"
15+
STABLE_VERSION_PATTERN = re.compile(r"^\d+\.\d+\.\d+$")
16+
PYPI_PROD_API = "https://pypi.org/pypi/socketsecurity/json"
17+
PYPI_TEST_API = "https://test.pypi.org/pypi/socketsecurity/json"
1618

1719
def read_version_from_init(path: pathlib.Path) -> str:
1820
content = path.read_text()
@@ -39,24 +41,59 @@ def bump_patch_version(version: str) -> str:
3941
parts[-1] = str(int(parts[-1]) + 1)
4042
return ".".join(parts)
4143

42-
def fetch_existing_versions() -> set:
44+
def parse_stable_version(version: str):
45+
if not STABLE_VERSION_PATTERN.fullmatch(version):
46+
return None
47+
return tuple(int(part) for part in version.split("."))
48+
49+
50+
def format_stable_version(version_parts) -> str:
51+
return ".".join(str(part) for part in version_parts)
52+
53+
54+
def fetch_existing_versions(api_url: str) -> set:
4355
try:
44-
with urllib.request.urlopen(PYPI_API) as response:
56+
with urllib.request.urlopen(api_url) as response:
4557
data = json.load(response)
4658
return set(data.get("releases", {}).keys())
4759
except Exception as e:
48-
print(f"⚠️ Warning: Failed to fetch existing versions from Test PyPI: {e}")
60+
print(f"⚠️ Warning: Failed to fetch versions from {api_url}: {e}")
4961
return set()
5062

63+
64+
def fetch_latest_stable_pypi_version():
65+
versions = fetch_existing_versions(PYPI_PROD_API)
66+
stable_versions = []
67+
for ver in versions:
68+
parsed = parse_stable_version(ver)
69+
if parsed is not None:
70+
stable_versions.append(parsed)
71+
if not stable_versions:
72+
return None
73+
return max(stable_versions)
74+
5175
def find_next_available_dev_version(base_version: str) -> str:
52-
existing_versions = fetch_existing_versions()
76+
existing_versions = fetch_existing_versions(PYPI_TEST_API)
5377
for i in range(1, 100):
5478
candidate = f"{base_version}.dev{i}"
5579
if candidate not in existing_versions:
5680
return candidate
5781
print("❌ Could not find available .devN slot after 100 attempts.")
5882
sys.exit(1)
5983

84+
85+
def find_next_stable_patch_version(current_version: str) -> str:
86+
current_stable = current_version.split(".dev")[0] if ".dev" in current_version else current_version
87+
current_parts = parse_stable_version(current_stable)
88+
if current_parts is None:
89+
print(f"❌ Unsupported version format for stable bump: {current_version}")
90+
sys.exit(1)
91+
92+
latest_pypi_parts = fetch_latest_stable_pypi_version()
93+
base_parts = max([current_parts, latest_pypi_parts] if latest_pypi_parts else [current_parts])
94+
next_parts = (base_parts[0], base_parts[1], base_parts[2] + 1)
95+
return format_stable_version(next_parts)
96+
6097
def inject_version(version: str):
6198
print(f"🔁 Updating version to: {version}")
6299

@@ -105,13 +142,25 @@ def main():
105142
print(f"⚠️ Version was unchanged — auto-bumped. Please git add{lock_hint} + commit again.")
106143
sys.exit(0)
107144
else:
108-
new_version = bump_patch_version(current_version)
145+
new_version = find_next_stable_patch_version(current_version)
109146
inject_version(new_version)
110147
uv_lock_changed = run_uv_lock()
111148
lock_hint = " and uv.lock" if uv_lock_changed else ""
112-
print(f"⚠️ Version was unchanged — auto-bumped. Please git add{lock_hint} + commit again.")
149+
print(f"⚠️ Version was unchanged — auto-bumped to {new_version}. Please git add{lock_hint} + commit again.")
113150
sys.exit(1)
114151
else:
152+
if not dev_mode:
153+
current_parts = parse_stable_version(current_version)
154+
latest_pypi_parts = fetch_latest_stable_pypi_version()
155+
if current_parts is not None and latest_pypi_parts is not None and current_parts <= latest_pypi_parts:
156+
next_parts = (latest_pypi_parts[0], latest_pypi_parts[1], latest_pypi_parts[2] + 1)
157+
new_version = format_stable_version(next_parts)
158+
inject_version(new_version)
159+
uv_lock_changed = run_uv_lock()
160+
lock_hint = " and uv.lock" if uv_lock_changed else ""
161+
print(f"⚠️ Version {current_version} is already published on PyPI — auto-bumped to {new_version}. Please git add{lock_hint} + commit again.")
162+
sys.exit(1)
163+
115164
uv_lock_changed = run_uv_lock()
116165
if uv_lock_changed:
117166
print("⚠️ Version already bumped, but uv.lock was out of date and has been updated. Please git add uv.lock + commit again.")

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)