Clean up dead code, fix type errors, and add publish workflow
Remove unused _append_ops_call_method, fix _property_data_from_member call signature, replace sys.version_info guard with hasattr check for __buffer__ protocol, fix implicit string concatenations, add msgbus overrides for 4.0-4.4, add conformance tests, and add publish script. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+225
@@ -0,0 +1,225 @@
|
||||
"""Publish stubs to PyPI with automatic version bumping.
|
||||
|
||||
Fetches the latest published version from PyPI, bumps the revision,
|
||||
asks for confirmation, then builds and uploads.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tomllib
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
DIST_DIR = SCRIPT_DIR / "dist"
|
||||
PACKAGE_NAME = "blender-python-stubs"
|
||||
|
||||
PYPI_JSON_URL = "https://pypi.org/pypi/{package}/json"
|
||||
TEST_PYPI_JSON_URL = "https://test.pypi.org/pypi/{package}/json"
|
||||
TEST_PYPI_UPLOAD_URL = "https://test.pypi.org/legacy/"
|
||||
|
||||
|
||||
def fetch_latest_revision(blender_version: str, use_test_pypi: bool) -> int | None:
|
||||
"""Fetch the latest revision for a Blender version from PyPI.
|
||||
|
||||
Returns the revision number (the 4th component of X.Y.Z.R) or None
|
||||
if no matching version is found.
|
||||
"""
|
||||
base_url = TEST_PYPI_JSON_URL if use_test_pypi else PYPI_JSON_URL
|
||||
url = base_url.format(package=PACKAGE_NAME)
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"Accept": "application/json"})
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
data: dict[str, object] = json.loads(resp.read())
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 404:
|
||||
return None
|
||||
raise
|
||||
|
||||
raw_releases = data.get("releases")
|
||||
if not isinstance(raw_releases, dict):
|
||||
return None
|
||||
|
||||
releases = cast(dict[str, object], raw_releases)
|
||||
|
||||
# Find versions matching this Blender version (e.g., 5.1.0.*)
|
||||
prefix = blender_version + "."
|
||||
max_revision = -1
|
||||
for version_str in releases:
|
||||
if not version_str.startswith(prefix):
|
||||
continue
|
||||
parts = version_str.split(".")
|
||||
if len(parts) != 4:
|
||||
continue
|
||||
try:
|
||||
revision = int(parts[3])
|
||||
except ValueError:
|
||||
continue
|
||||
if revision > max_revision:
|
||||
max_revision = revision
|
||||
|
||||
return max_revision if max_revision >= 0 else None
|
||||
|
||||
|
||||
def read_generated_version(version_dir: Path) -> str:
|
||||
"""Read the version from a generated dist pyproject.toml."""
|
||||
pyproject_path = version_dir / "pyproject.toml"
|
||||
if not pyproject_path.exists():
|
||||
print(f"No pyproject.toml found in {version_dir}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
with pyproject_path.open("rb") as f:
|
||||
data = tomllib.load(f)
|
||||
|
||||
version = data.get("project", {}).get("version", "")
|
||||
if not isinstance(version, str) or not version:
|
||||
print(f"No version found in {pyproject_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
return version
|
||||
|
||||
|
||||
def update_version_in_pyproject(version_dir: Path, new_version: str) -> None:
|
||||
"""Update the version in the generated pyproject.toml."""
|
||||
pyproject_path = version_dir / "pyproject.toml"
|
||||
content = pyproject_path.read_text()
|
||||
|
||||
# Replace the version line
|
||||
lines: list[str] = []
|
||||
for line in content.splitlines():
|
||||
if line.startswith("version = "):
|
||||
lines.append(f'version = "{new_version}"')
|
||||
else:
|
||||
lines.append(line)
|
||||
|
||||
pyproject_path.write_text("\n".join(lines) + "\n")
|
||||
|
||||
|
||||
def publish_version(
|
||||
blender_version: str,
|
||||
publish_url: str,
|
||||
revision: int | None,
|
||||
yes: bool,
|
||||
) -> None:
|
||||
"""Publish stubs for a single Blender version."""
|
||||
# Parse blender_version into the full version from the generated stubs
|
||||
version_dir = DIST_DIR / blender_version
|
||||
if not version_dir.is_dir():
|
||||
print(
|
||||
f"No generated stubs for {blender_version}. Run 'poe generate {blender_version}' first.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
current_version = read_generated_version(version_dir)
|
||||
parts = current_version.split(".")
|
||||
if len(parts) != 4:
|
||||
print(
|
||||
f"Unexpected version format: {current_version}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
base_version = ".".join(parts[:3]) # e.g., "5.1.0"
|
||||
|
||||
use_test_pypi = "test.pypi" in publish_url
|
||||
|
||||
if revision is not None:
|
||||
new_revision = revision
|
||||
else:
|
||||
# Fetch latest from PyPI
|
||||
pypi_label = "Test PyPI" if use_test_pypi else "PyPI"
|
||||
print(f"Fetching latest version from {pypi_label}...")
|
||||
latest = fetch_latest_revision(base_version, use_test_pypi)
|
||||
|
||||
if latest is not None:
|
||||
new_revision = latest + 1
|
||||
print(f" Latest published: {base_version}.{latest}")
|
||||
else:
|
||||
new_revision = 0
|
||||
print(f" No published version found for {base_version}")
|
||||
|
||||
new_version = f"{base_version}.{new_revision}"
|
||||
|
||||
if not yes:
|
||||
answer = input(f"Publish {new_version}? [y/N] ")
|
||||
if answer.lower() not in ("y", "yes"):
|
||||
print("Aborted.")
|
||||
sys.exit(0)
|
||||
|
||||
# Update pyproject.toml with the new version
|
||||
update_version_in_pyproject(version_dir, new_version)
|
||||
print(f" Version set to {new_version}")
|
||||
|
||||
# Clean previous build artifacts
|
||||
build_dist = version_dir / "dist"
|
||||
if build_dist.exists():
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(build_dist)
|
||||
|
||||
# Build
|
||||
print(" Building...")
|
||||
result = subprocess.run(
|
||||
["uv", "build", str(version_dir)],
|
||||
cwd=str(SCRIPT_DIR),
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print("Build failed.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Publish
|
||||
print(f" Publishing to {publish_url}...")
|
||||
result = subprocess.run(
|
||||
[
|
||||
"uv",
|
||||
"publish",
|
||||
str(build_dist / "*"),
|
||||
"--publish-url",
|
||||
publish_url,
|
||||
],
|
||||
cwd=str(SCRIPT_DIR),
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print("Publish failed.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f" Published {new_version}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Publish Blender type stubs to PyPI")
|
||||
parser.add_argument(
|
||||
"version",
|
||||
help="Blender version to publish (e.g., 5.1)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--revision",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Override revision number instead of auto-detecting",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--publish-url",
|
||||
default=TEST_PYPI_UPLOAD_URL,
|
||||
help="PyPI upload URL (defaults to Test PyPI)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-y",
|
||||
"--yes",
|
||||
action="store_true",
|
||||
help="Skip confirmation prompt",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
publish_version(
|
||||
args.version,
|
||||
args.publish_url,
|
||||
args.revision,
|
||||
args.yes,
|
||||
)
|
||||
Reference in New Issue
Block a user