diff --git a/Makefile b/Makefile index 5a0bb1a163..29f9a4f332 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,9 @@ VERSION_FILES := Cargo.toml \ bindings/golang/Cargo.toml \ bindings/python/Cargo.toml \ bindings/python/pyproject.toml \ - bindings/python/src/smg/version.py + bindings/python/src/smg/version.py \ + grpc_client/python/pyproject.toml \ + grpc_client/python/smg_grpc_proto/__init__.py show-version: ## Show current version across all files @echo "Current versions:" @@ -148,6 +150,8 @@ show-version: ## Show current version across all files @echo " bindings/python/Cargo.toml: $$(grep -m1 '^version = ' bindings/python/Cargo.toml | sed 's/version = "\(.*\)"/\1/')" @echo " bindings/python/pyproject.toml: $$(grep -m1 '^version = ' bindings/python/pyproject.toml | sed 's/version = "\(.*\)"/\1/')" @echo " bindings/python/.../version.py: $$(grep '__version__' bindings/python/src/smg/version.py | sed 's/__version__ = "\(.*\)"/\1/')" + @echo " grpc_client/python/pyproject.toml: $$(grep -m1 '^version = ' grpc_client/python/pyproject.toml | sed 's/version = "\(.*\)"/\1/')" + @echo " grpc_client/python/.../__init__.py: $$(grep '__version__' grpc_client/python/smg_grpc_proto/__init__.py | sed 's/__version__ = "\(.*\)"/\1/')" bump-version: ## Bump version across all files (usage: make bump-version VERSION=0.3.3) @if [ -z "$(VERSION)" ]; then \ @@ -169,12 +173,18 @@ bump-version: ## Bump version across all files (usage: make bump-version VERSION @sed -i.bak 's/^version = ".*"/version = "$(VERSION)"/' bindings/python/pyproject.toml && rm -f bindings/python/pyproject.toml.bak @# Update version.py @sed -i.bak 's/__version__ = ".*"/__version__ = "$(VERSION)"/' bindings/python/src/smg/version.py && rm -f bindings/python/src/smg/version.py.bak + @# Update grpc_client/python/pyproject.toml + @sed -i.bak 's/^version = ".*"/version = "$(VERSION)"/' grpc_client/python/pyproject.toml && rm -f grpc_client/python/pyproject.toml.bak + @# Update grpc_client/python/smg_grpc_proto/__init__.py + @sed -i.bak 's/__version__ = ".*"/__version__ = "$(VERSION)"/' grpc_client/python/smg_grpc_proto/__init__.py && rm -f grpc_client/python/smg_grpc_proto/__init__.py.bak @echo "Version updated to $(VERSION) in all files:" @echo " - Cargo.toml" @echo " - bindings/golang/Cargo.toml" @echo " - bindings/python/Cargo.toml" @echo " - bindings/python/pyproject.toml" @echo " - bindings/python/src/smg/version.py" + @echo " - grpc_client/python/pyproject.toml" + @echo " - grpc_client/python/smg_grpc_proto/__init__.py" @echo "" @echo "Verify with: make show-version" diff --git a/grpc_client/python/.gitignore b/grpc_client/python/.gitignore new file mode 100644 index 0000000000..bd1bb57dcc --- /dev/null +++ b/grpc_client/python/.gitignore @@ -0,0 +1,7 @@ +# Generated stubs (created at build time) +smg_grpc_proto/generated/ + +# Build artifacts +dist/ +*.egg-info/ +build/ diff --git a/grpc_client/python/README.md b/grpc_client/python/README.md new file mode 100644 index 0000000000..7b60976a76 --- /dev/null +++ b/grpc_client/python/README.md @@ -0,0 +1,45 @@ +# smg-grpc-proto + +Protocol Buffer definitions for SMG (Shepherd Model Gateway) gRPC services. + +This package provides the Python gRPC stubs for: +- **SGLang** scheduler service +- **vLLM** engine service +- **TRT-LLM** service + +## Installation + +```bash +pip install smg-grpc-proto +``` + +## Usage + +```python +from smg_grpc_proto import sglang_scheduler_pb2, sglang_scheduler_pb2_grpc +from smg_grpc_proto import vllm_engine_pb2, vllm_engine_pb2_grpc +from smg_grpc_proto import trtllm_service_pb2, trtllm_service_pb2_grpc +``` + +## Development + +The proto files are located in `grpc_client/proto/` in the SMG repository. A symlink at `smg_grpc_proto/proto` points to the proto source files. Python stubs are generated at build time using `grpcio-tools`. + +To install in editable mode: + +```bash +# From repo root (symlink handles proto file discovery) +pip install -e grpc_client/python/ +``` + +For CI or environments where symlinks don't work: + +```bash +mkdir -p grpc_client/python/smg_grpc_proto/proto +cp grpc_client/proto/*.proto grpc_client/python/smg_grpc_proto/proto/ +pip install -e grpc_client/python/ +``` + +## License + +Apache-2.0 diff --git a/grpc_client/python/pyproject.toml b/grpc_client/python/pyproject.toml new file mode 100644 index 0000000000..dab8f3ff0c --- /dev/null +++ b/grpc_client/python/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +requires = ["setuptools>=68.0", "grpcio-tools>=1.62.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "smg-grpc-proto" +version = "0.3.2" +description = "SMG gRPC proto definitions for SGLang, vLLM, and TRT-LLM" +requires-python = ">=3.10" +dependencies = [ + "grpcio>=1.62.0", + "protobuf>=5.26.0", +] +readme = "README.md" +license = { text = "Apache-2.0" } +authors = [ + {name = "Simo Lin", email = "linsimo.mark@gmail.com"}, + {name = "Chang Su", email = "mckvtl@gmail.com"}, + {name = "Keyang Ru", email = "rukeyang@gmail.com"} +] +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", +] + +[project.urls] +Homepage = "https://github.com/lightseekorg/smg" +Repository = "https://github.com/lightseekorg/smg" + +[tool.setuptools.packages.find] +where = ["."] +include = ["smg_grpc_proto*"] + +[tool.setuptools.package-data] +smg_grpc_proto = ["proto/*.proto", "generated/*.py", "generated/*.pyi"] diff --git a/grpc_client/python/setup.py b/grpc_client/python/setup.py new file mode 100644 index 0000000000..e55db04971 --- /dev/null +++ b/grpc_client/python/setup.py @@ -0,0 +1,103 @@ +""" +Custom setup.py to generate protobuf stubs at build time. +The generated files are NOT committed to git — they're created fresh during pip install. +""" +from pathlib import Path + +from setuptools import setup +from setuptools.command.build_py import build_py +from setuptools.command.develop import develop + + +def compile_grpc_protos(): + """Generate Python gRPC stubs from .proto files.""" + package_dir = Path(__file__).parent + proto_dir = package_dir / "smg_grpc_proto" / "proto" + output_dir = package_dir / "smg_grpc_proto" / "generated" + + output_dir.mkdir(parents=True, exist_ok=True) + (output_dir / "__init__.py").write_text( + '"""Auto-generated protobuf stubs. Do not edit."""\n' + ) + + proto_files = list(proto_dir.glob("*.proto")) + if not proto_files: + raise FileNotFoundError(f"No .proto files found in {proto_dir}") + + # Use grpc_tools.protoc Python API (same approach as vLLM) + try: + from grpc_tools import protoc + + args = [ + "grpc_tools.protoc", + f"--proto_path={proto_dir}", + f"--python_out={output_dir}", + f"--grpc_python_out={output_dir}", + f"--pyi_out={output_dir}", # Generate type stubs + ] + [str(f) for f in proto_files] + + print(f"Generating protobuf stubs from {len(proto_files)} proto files...") + result = protoc.main(args) + + if result != 0: + raise RuntimeError(f"protoc returned non-zero exit code: {result}") + + except ImportError: + raise RuntimeError( + "grpcio-tools not installed. Install with: pip install grpcio-tools" + ) + + # Fix imports in generated files (grpcio-tools generates absolute imports) + # Also add mypy ignore-errors comment for generated code + mypy_header = "# mypy: ignore-errors\n" + + for py_file in output_dir.glob("*_pb2*.py"): + content = py_file.read_text() + + # Fix imports to be relative + for proto_file in proto_files: + module_name = proto_file.stem + "_pb2" + content = content.replace( + f"import {module_name}", f"from . import {module_name}" + ) + + # Add mypy ignore-errors if not already present + if not content.startswith("# mypy:"): + content = mypy_header + content + + py_file.write_text(content) + + # Also add mypy header to .pyi files + for pyi_file in output_dir.glob("*_pb2*.pyi"): + content = pyi_file.read_text() + if not content.startswith("# mypy:"): + pyi_file.write_text(mypy_header + content) + + generated_count = len(list(output_dir.glob("*.py"))) + len( + list(output_dir.glob("*.pyi")) + ) + print(f"Generated {generated_count} files (including type stubs)") + + +class BuildPyWithProto(build_py): + """Custom build_py that generates protobuf stubs before building.""" + + def run(self): + compile_grpc_protos() + super().run() + + +class DevelopWithProto(develop): + """Custom develop that generates protobuf stubs for editable installs.""" + + def run(self): + compile_grpc_protos() + super().run() + + +setup( + cmdclass={ + "build_py": BuildPyWithProto, + "develop": DevelopWithProto, + } +) diff --git a/grpc_client/python/smg_grpc_proto/__init__.py b/grpc_client/python/smg_grpc_proto/__init__.py new file mode 100644 index 0000000000..eae1366cdf --- /dev/null +++ b/grpc_client/python/smg_grpc_proto/__init__.py @@ -0,0 +1,27 @@ +"""SMG gRPC Proto - Protocol definitions for SGLang, vLLM, and TRT-LLM.""" + +__version__ = "0.3.2" + +# Re-export generated modules for convenient access +# These imports will work after the package is built (stubs generated at build time) +try: + from smg_grpc_proto.generated import ( + sglang_scheduler_pb2, + sglang_scheduler_pb2_grpc, + trtllm_service_pb2, + trtllm_service_pb2_grpc, + vllm_engine_pb2, + vllm_engine_pb2_grpc, + ) + + __all__ = [ + "sglang_scheduler_pb2", + "sglang_scheduler_pb2_grpc", + "vllm_engine_pb2", + "vllm_engine_pb2_grpc", + "trtllm_service_pb2", + "trtllm_service_pb2_grpc", + ] +except ImportError: + # During development/build, generated modules may not exist yet + __all__ = [] diff --git a/grpc_client/python/smg_grpc_proto/proto b/grpc_client/python/smg_grpc_proto/proto new file mode 120000 index 0000000000..b943f93991 --- /dev/null +++ b/grpc_client/python/smg_grpc_proto/proto @@ -0,0 +1 @@ +../../proto \ No newline at end of file