|
| 1 | +""" |
| 2 | +Generates the protobuf stubs for the given protobuf version using mypy-protobuf. |
| 3 | +Generally, new minor versions are a good time to update the stubs. |
| 4 | +""" |
| 5 | + |
| 6 | +from __future__ import annotations |
| 7 | + |
| 8 | +import json |
| 9 | +import re |
| 10 | +import shutil |
| 11 | +import subprocess |
| 12 | +import sys |
| 13 | +import tempfile |
| 14 | +from pathlib import Path |
| 15 | +from typing import Any |
| 16 | + |
| 17 | +from _utils import MYPY_PROTOBUF_VERSION, REPO_ROOT, download_file, extract_archive, run_protoc, update_metadata |
| 18 | + |
| 19 | +# Whenever you update PACKAGE_VERSION here, version should be updated |
| 20 | +# in stubs/protobuf/METADATA.toml and vice-versa. |
| 21 | +PACKAGE_VERSION = "27.1" |
| 22 | + |
| 23 | +STUBS_FOLDER = REPO_ROOT / "stubs" / "protobuf" |
| 24 | +ARCHIVE_FILENAME = f"protobuf-{PACKAGE_VERSION}.zip" |
| 25 | +ARCHIVE_URL = f"https://github.com/protocolbuffers/protobuf/releases/download/v{PACKAGE_VERSION}/{ARCHIVE_FILENAME}" |
| 26 | +EXTRACTED_PACKAGE_DIR = f"protobuf-{PACKAGE_VERSION}" |
| 27 | + |
| 28 | +VERSION_PATTERN = re.compile(r'def game_version\(\):\n return "(.+?)"') |
| 29 | +PROTO_FILE_PATTERN = re.compile(r'"//:(.*)_proto"') |
| 30 | + |
| 31 | + |
| 32 | +def extract_python_version(file_path: Path) -> str: |
| 33 | + """Extract the Python version from https://github.com/protocolbuffers/protobuf/blob/main/version.json""" |
| 34 | + with open(file_path) as file: |
| 35 | + data: dict[str, Any] = json.load(file) |
| 36 | + # The root key will be the protobuf source code version |
| 37 | + version = next(iter(data.values()))["languages"]["python"] |
| 38 | + assert isinstance(version, str) |
| 39 | + return version |
| 40 | + |
| 41 | + |
| 42 | +def extract_proto_file_paths(temp_dir: Path) -> list[str]: |
| 43 | + """ |
| 44 | + Roughly reproduce the subset of .proto files on the public interface |
| 45 | + as described in py_proto_library calls in |
| 46 | + https://github.com/protocolbuffers/protobuf/blob/main/python/dist/BUILD.bazel |
| 47 | + """ |
| 48 | + with open(temp_dir / EXTRACTED_PACKAGE_DIR / "python" / "dist" / "BUILD.bazel") as file: |
| 49 | + matched_lines = filter(None, (re.search(PROTO_FILE_PATTERN, line) for line in file)) |
| 50 | + proto_files = [ |
| 51 | + EXTRACTED_PACKAGE_DIR + "/src/google/protobuf/" + match.group(1).replace("compiler_", "compiler/") + ".proto" |
| 52 | + for match in matched_lines |
| 53 | + ] |
| 54 | + return proto_files |
| 55 | + |
| 56 | + |
| 57 | +def main() -> None: |
| 58 | + temp_dir = Path(tempfile.mkdtemp()) |
| 59 | + # Fetch s2clientprotocol (which contains all the .proto files) |
| 60 | + archive_path = temp_dir / ARCHIVE_FILENAME |
| 61 | + download_file(ARCHIVE_URL, archive_path) |
| 62 | + extract_archive(archive_path, temp_dir) |
| 63 | + |
| 64 | + # Remove existing pyi |
| 65 | + for old_stub in STUBS_FOLDER.rglob("*_pb2.pyi"): |
| 66 | + old_stub.unlink() |
| 67 | + |
| 68 | + PROTOC_VERSION = run_protoc( |
| 69 | + proto_paths=(f"{EXTRACTED_PACKAGE_DIR}/src",), |
| 70 | + mypy_out=STUBS_FOLDER, |
| 71 | + proto_globs=extract_proto_file_paths(temp_dir), |
| 72 | + cwd=temp_dir, |
| 73 | + ) |
| 74 | + |
| 75 | + PYTHON_PROTOBUF_VERSION = extract_python_version(temp_dir / EXTRACTED_PACKAGE_DIR / "version.json") |
| 76 | + |
| 77 | + # Cleanup after ourselves, this is a temp dir, but it can still grow fast if run multiple times |
| 78 | + shutil.rmtree(temp_dir) |
| 79 | + |
| 80 | + update_metadata( |
| 81 | + STUBS_FOLDER, |
| 82 | + f"""Partially generated using \ |
| 83 | +[mypy-protobuf=={MYPY_PROTOBUF_VERSION}](https://github.com/nipunn1313/mypy-protobuf/tree/v{MYPY_PROTOBUF_VERSION}) \ |
| 84 | +and {PROTOC_VERSION} on \ |
| 85 | +[protobuf v{PACKAGE_VERSION}](https://github.com/protocolbuffers/protobuf/releases/tag/v{PACKAGE_VERSION}) \ |
| 86 | +(python `protobuf=={PYTHON_PROTOBUF_VERSION}`).""", |
| 87 | + ) |
| 88 | + |
| 89 | + # Run pre-commit to cleanup the stubs |
| 90 | + subprocess.run((sys.executable, "-m", "pre_commit", "run", "--files", *STUBS_FOLDER.rglob("*_pb2.pyi"))) |
| 91 | + |
| 92 | + |
| 93 | +if __name__ == "__main__": |
| 94 | + main() |
0 commit comments