Initial SDK Template Scaffold#1
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces the initial scaffold for a multilingual SDK/CLI template that supports Python, Rust, and C++ backends. The template provides a production-ready starting point with development tooling, testing infrastructure, and pre-commit hooks.
Key changes:
- Python CLI implementation with argparse-based command routing and optional native backend integration
- Rust native backend with cargo workspace configuration and comprehensive test coverage
- C++ native backend with CMake build system and custom test framework
- Development environment setup with VS Code integration, pre-commit hooks, and linting/formatting tools
Reviewed changes
Copilot reviewed 48 out of 51 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
pyproject.toml |
Python project configuration with dev dependencies and tooling setup |
uv.lock |
Python dependency lock file with package versions |
src/sdk_template/ |
Main Python package with CLI, native bridge, and version info |
tests/test_cli.py |
Comprehensive Python test suite for CLI functionality |
native/rust/ |
Rust backend implementation with Cargo configuration |
native/cpp/ |
C++ backend with CMake build system and tests |
.pre-commit-config.yaml |
Pre-commit hooks for code quality |
.vscode/ |
VS Code workspace settings and tasks |
| Documentation files | README, CHANGELOG, LICENSE, and guidance documents |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
|
|
||
| def _repo_root() -> Path: | ||
| env_root = os.environ.get("SDK_TEMPLATE_REPO_ROOT") |
There was a problem hiding this comment.
The environment variable name mismatch could cause runtime errors. The code checks for "SDK_TEMPLATE_REPO_ROOT" but the tests and documentation reference "CLI_TEMPLATE_REPO_ROOT". This inconsistency needs to be resolved - choose one naming convention and use it throughout the codebase.
| from sdk_template import __version__ | ||
| from sdk_template._native import ( | ||
| NativeBackendError, | ||
| _cpp_bin_path, | ||
| _repo_root, | ||
| _rust_bin_path, | ||
| run_cpp, | ||
| run_rust, | ||
| ) | ||
| from sdk_template.cli import main |
There was a problem hiding this comment.
The test imports reference "cli_template" module but the actual package is named "sdk_template" (as shown in pyproject.toml and the src directory structure). Update all test imports to use "sdk_template" instead of "cli_template".
| with mock.patch("cli_template.cli.run_rust") as mock_run: | ||
| mock_run.return_value = 0 | ||
| result = main(["--impl", "rust", "hello"]) | ||
| assert result == 0 | ||
| mock_run.assert_called_once_with(["hello", "world"]) | ||
|
|
||
| def test_impl_cpp_hello_success(self) -> None: | ||
| """Test cpp impl hello succeeds when binary exists.""" | ||
| with mock.patch("cli_template.cli.run_cpp") as mock_run: | ||
| mock_run.return_value = 0 | ||
| result = main(["--impl", "cpp", "hello"]) | ||
| assert result == 0 | ||
| mock_run.assert_called_once_with(["hello", "world"]) | ||
|
|
||
| def test_impl_rust_echo_success(self) -> None: | ||
| """Test rust impl echo succeeds when binary exists.""" | ||
| with mock.patch("cli_template.cli.run_rust") as mock_run: | ||
| mock_run.return_value = 0 | ||
| result = main(["--impl", "rust", "echo", "hello", "world"]) | ||
| assert result == 0 | ||
| mock_run.assert_called_once_with(["echo", "hello", "world"]) | ||
|
|
||
| def test_impl_cpp_echo_success(self) -> None: | ||
| """Test cpp impl echo succeeds when binary exists.""" | ||
| with mock.patch("cli_template.cli.run_cpp") as mock_run: | ||
| mock_run.return_value = 0 | ||
| result = main(["--impl", "cpp", "echo", "hello", "world"]) | ||
| assert result == 0 | ||
| mock_run.assert_called_once_with(["echo", "hello", "world"]) | ||
|
|
||
| def test_impl_rust_echo_error(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """Test rust impl echo fails gracefully when binary missing.""" | ||
| monkeypatch.setenv("CLI_TEMPLATE_REPO_ROOT", "/nonexistent") | ||
| with pytest.raises(SystemExit) as exc_info: | ||
| main(["--impl", "rust", "echo", "test"]) | ||
| assert exc_info.value.code == 2 | ||
|
|
||
| def test_impl_cpp_echo_error(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """Test cpp impl echo fails gracefully when binary missing.""" | ||
| monkeypatch.setenv("CLI_TEMPLATE_REPO_ROOT", "/nonexistent") | ||
| with pytest.raises(SystemExit) as exc_info: | ||
| main(["--impl", "cpp", "echo", "test"]) | ||
| assert exc_info.value.code == 2 | ||
|
|
||
| def test_impl_rust_sum_success(self) -> None: | ||
| """Test rust impl sum succeeds when binary exists.""" | ||
| with mock.patch("cli_template.cli.run_rust") as mock_run: | ||
| mock_run.return_value = 0 | ||
| result = main(["--impl", "rust", "sum", "1", "2"]) | ||
| assert result == 0 | ||
| mock_run.assert_called_once_with(["sum", "1", "2"]) | ||
|
|
||
| def test_impl_cpp_sum_success(self) -> None: | ||
| """Test cpp impl sum succeeds when binary exists.""" | ||
| with mock.patch("cli_template.cli.run_cpp") as mock_run: | ||
| mock_run.return_value = 0 | ||
| result = main(["--impl", "cpp", "sum", "1", "2"]) | ||
| assert result == 0 | ||
| mock_run.assert_called_once_with(["sum", "1", "2"]) | ||
|
|
||
| def test_impl_rust_sum_error(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """Test rust impl sum fails gracefully when binary missing.""" | ||
| monkeypatch.setenv("CLI_TEMPLATE_REPO_ROOT", "/nonexistent") | ||
| with pytest.raises(SystemExit) as exc_info: | ||
| main(["--impl", "rust", "sum", "1", "2"]) | ||
| assert exc_info.value.code == 2 | ||
|
|
||
| def test_impl_cpp_sum_error(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """Test cpp impl sum fails gracefully when binary missing.""" | ||
| monkeypatch.setenv("CLI_TEMPLATE_REPO_ROOT", "/nonexistent") | ||
| with pytest.raises(SystemExit) as exc_info: | ||
| main(["--impl", "cpp", "sum", "1", "2"]) | ||
| assert exc_info.value.code == 2 | ||
|
|
||
| def test_impl_rust_info_success(self) -> None: | ||
| """Test rust impl info succeeds when binary exists.""" | ||
| with mock.patch("cli_template.cli.run_rust") as mock_run: | ||
| mock_run.return_value = 0 | ||
| result = main(["--impl", "rust", "info"]) | ||
| assert result == 0 | ||
| mock_run.assert_called_once_with(["info"]) | ||
|
|
||
| def test_impl_cpp_info_success(self) -> None: | ||
| """Test cpp impl info succeeds when binary exists.""" | ||
| with mock.patch("cli_template.cli.run_cpp") as mock_run: | ||
| mock_run.return_value = 0 | ||
| result = main(["--impl", "cpp", "info"]) | ||
| assert result == 0 | ||
| mock_run.assert_called_once_with(["info"]) | ||
|
|
||
| def test_impl_rust_info_error(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """Test rust impl info fails gracefully when binary missing.""" | ||
| monkeypatch.setenv("CLI_TEMPLATE_REPO_ROOT", "/nonexistent") | ||
| with pytest.raises(SystemExit) as exc_info: | ||
| main(["--impl", "rust", "info"]) | ||
| assert exc_info.value.code == 2 | ||
|
|
||
| def test_impl_cpp_info_error(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """Test cpp impl info fails gracefully when binary missing.""" | ||
| monkeypatch.setenv("CLI_TEMPLATE_REPO_ROOT", "/nonexistent") | ||
| with pytest.raises(SystemExit) as exc_info: | ||
| main(["--impl", "cpp", "info"]) | ||
| assert exc_info.value.code == 2 | ||
|
|
||
|
|
||
| class TestInvalidInput: | ||
| """Tests for invalid input handling.""" | ||
|
|
||
| def test_missing_command(self) -> None: | ||
| """Test that missing command exits with error.""" | ||
| with pytest.raises(SystemExit) as exc_info: | ||
| main([]) | ||
| assert exc_info.value.code == 2 | ||
|
|
||
| def test_invalid_command(self) -> None: | ||
| """Test that invalid command exits with error.""" | ||
| with pytest.raises(SystemExit) as exc_info: | ||
| main(["invalid_command"]) | ||
| assert exc_info.value.code == 2 | ||
|
|
||
| def test_invalid_sum_argument(self) -> None: | ||
| """Test that invalid sum argument exits with error.""" | ||
| with pytest.raises(SystemExit) as exc_info: | ||
| main(["sum", "not_a_number", "3"]) | ||
| assert exc_info.value.code == 2 | ||
|
|
||
| def test_invalid_impl_choice(self) -> None: | ||
| """Test that invalid impl choice exits with error.""" | ||
| with pytest.raises(SystemExit) as exc_info: | ||
| main(["--impl", "invalid", "hello"]) | ||
| assert exc_info.value.code == 2 | ||
|
|
||
|
|
||
| class TestNativeBackend: | ||
| """Tests for native backend utilities.""" | ||
|
|
||
| def test_repo_root_found(self) -> None: | ||
| """Test that repo root can be found.""" | ||
| root = _repo_root() | ||
| assert (root / "pyproject.toml").is_file() | ||
|
|
||
| def test_repo_root_from_env(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """Test repo root from environment variable.""" | ||
| # Create a fake pyproject.toml | ||
| fake_root = tmp_path / "fake_project" | ||
| fake_root.mkdir() | ||
| (fake_root / "pyproject.toml").write_text("[project]\nname = 'test'\n") | ||
|
|
||
| monkeypatch.setenv("CLI_TEMPLATE_REPO_ROOT", str(fake_root)) | ||
| root = _repo_root() | ||
| assert root == fake_root | ||
|
|
||
| def test_repo_root_not_found(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """Test that repo root raises error when not found.""" | ||
| # Change to a directory without pyproject.toml and clear env var | ||
| monkeypatch.delenv("CLI_TEMPLATE_REPO_ROOT", raising=False) | ||
| monkeypatch.chdir(tmp_path) | ||
| with pytest.raises(NativeBackendError, match="Could not locate repo root"): | ||
| _repo_root() | ||
|
|
||
| def test_native_backend_error(self) -> None: | ||
| """Test NativeBackendError can be raised.""" | ||
| with pytest.raises(NativeBackendError): | ||
| raise NativeBackendError("test error") | ||
|
|
||
| def test_rust_bin_path(self) -> None: | ||
| """Test Rust binary path construction.""" | ||
| root = Path("/fake/root") | ||
| path = _rust_bin_path(root) | ||
| assert path == root / "native" / "rust" / "target" / "release" / "cli-template-native-rs" | ||
|
|
||
| def test_cpp_bin_path(self) -> None: | ||
| """Test C++ binary path construction.""" | ||
| root = Path("/fake/root") | ||
| path = _cpp_bin_path(root) | ||
| assert path == root / "build" / "native-cpp" / "cli-template-native-cpp" | ||
|
|
||
| def test_run_rust_success(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """Test successful Rust binary execution.""" | ||
| # Create a fake binary | ||
| fake_root = tmp_path / "fake_project" | ||
| fake_root.mkdir(parents=True) | ||
| (fake_root / "pyproject.toml").write_text("[project]\nname = 'test'\n") | ||
|
|
||
| binary_dir = fake_root / "native" / "rust" / "target" / "release" | ||
| binary_dir.mkdir(parents=True) | ||
| binary = binary_dir / "cli-template-native-rs" | ||
| binary.write_text("#!/bin/sh\necho 'fake rust'\n") | ||
| binary.chmod(0o755) | ||
|
|
||
| monkeypatch.setenv("CLI_TEMPLATE_REPO_ROOT", str(fake_root)) | ||
|
|
||
| # Mock subprocess.run to avoid actually running anything | ||
| with mock.patch("cli_template._native.subprocess.run") as mock_run: | ||
| mock_run.return_value = mock.Mock(returncode=0) | ||
| result = run_rust(["hello"]) | ||
| assert result == 0 | ||
| mock_run.assert_called_once() | ||
|
|
||
| def test_run_cpp_success(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """Test successful C++ binary execution.""" | ||
| # Create a fake binary | ||
| fake_root = tmp_path / "fake_project" | ||
| fake_root.mkdir(parents=True) | ||
| (fake_root / "pyproject.toml").write_text("[project]\nname = 'test'\n") | ||
|
|
||
| binary_dir = fake_root / "build" / "native-cpp" | ||
| binary_dir.mkdir(parents=True) | ||
| binary = binary_dir / "cli-template-native-cpp" | ||
| binary.write_text("#!/bin/sh\necho 'fake cpp'\n") | ||
| binary.chmod(0o755) | ||
|
|
||
| monkeypatch.setenv("CLI_TEMPLATE_REPO_ROOT", str(fake_root)) | ||
|
|
||
| # Mock subprocess.run to avoid actually running anything | ||
| with mock.patch("cli_template._native.subprocess.run") as mock_run: | ||
| mock_run.return_value = mock.Mock(returncode=0) | ||
| result = run_cpp(["hello"]) | ||
| assert result == 0 | ||
| mock_run.assert_called_once() | ||
|
|
||
| def test_run_cpp_windows_path(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """Test C++ binary path has .exe suffix on Windows.""" | ||
| # Create a fake binary with .exe suffix | ||
| fake_root = tmp_path / "fake_project" | ||
| fake_root.mkdir(parents=True) | ||
| (fake_root / "pyproject.toml").write_text("[project]\nname = 'test'\n") | ||
|
|
||
| binary_dir = fake_root / "build" / "native-cpp" | ||
| binary_dir.mkdir(parents=True) | ||
| binary = binary_dir / "cli-template-native-cpp.exe" | ||
| binary.write_text("fake windows exe") | ||
|
|
||
| monkeypatch.setenv("CLI_TEMPLATE_REPO_ROOT", str(fake_root)) | ||
| monkeypatch.setattr("cli_template._native.sys.platform", "win32") |
There was a problem hiding this comment.
The mock patch paths reference "cli_template" module but should reference "sdk_template" to match the actual package name defined in pyproject.toml.
| import cli_template.__main__ as main_module | ||
|
|
||
| # Verify the module imports main from cli | ||
| assert hasattr(main_module, "main") | ||
|
|
||
| # Test that main is callable | ||
| with mock.patch.object(main_module, "main", return_value=0) as mock_main: | ||
| # The module-level code runs at import, so we test the imported function | ||
| assert callable(main_module.main) | ||
|
|
||
| def test_main_module_direct_execution(self, capsys: pytest.CaptureFixture[str]) -> None: | ||
| """Test the main() function directly from __main__ module.""" | ||
| import cli_template.__main__ as main_module |
There was a problem hiding this comment.
The test attempts to import "cli_template.main" but the actual module is "sdk_template.main". Update the import to match the actual package name.
No description provided.