├── .bazelignore ├── .bazelrc ├── .github └── workflows │ ├── amd64_linux_bazel.yml │ ├── amd64_linux_cmake.yml │ ├── amd64_macos_bazel.yml │ ├── amd64_macos_cmake.yml │ ├── amd64_windows_bazel.yml │ ├── amd64_windows_cmake.yml │ ├── arm64_macos_bazel.yml │ ├── arm64_macos_cmake.yml │ ├── pre-commit.yml │ └── ubuntu-build.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CMakeLists.txt ├── LICENSE ├── MODULE.bazel ├── README.md ├── WORKSPACE ├── WORKSPACE.bzlmod ├── cmake └── dependencies │ └── CMakeLists.txt ├── pybind11_protobuf ├── BUILD ├── CMakeLists.txt ├── check_unknown_fields.cc ├── check_unknown_fields.h ├── disallow_extensions_with_unknown_fields.cc ├── enum_type_caster.h ├── native_proto_caster.h ├── proto_cast_util.cc ├── proto_cast_util.h ├── proto_caster_impl.h ├── proto_utils.cc ├── proto_utils.h ├── requirements │ ├── BUILD │ ├── requirements.in │ ├── requirements_lock_3_10.txt │ ├── requirements_lock_3_11.txt │ ├── requirements_lock_3_12.txt │ ├── requirements_lock_3_8.txt │ └── requirements_lock_3_9.txt ├── tests │ ├── BUILD │ ├── CMakeLists.txt │ ├── compare.py │ ├── dynamic_message_module.cc │ ├── dynamic_message_test.py │ ├── extension.proto │ ├── extension_in_other_file.proto │ ├── extension_in_other_file_in_deps.proto │ ├── extension_module.cc │ ├── extension_nest_repeated.proto │ ├── extension_test.py │ ├── message_module.cc │ ├── message_test.py │ ├── pass_by_module.cc │ ├── pass_by_test.py │ ├── pass_proto2_message_module.cc │ ├── proto_enum_module.cc │ ├── proto_enum_test.py │ ├── regression_wrappers_module.cc │ ├── regression_wrappers_test.py │ ├── test.proto │ ├── thread_module.cc │ ├── thread_module_test.py │ ├── very_large_proto_test.py │ ├── we-love-dashes.proto │ ├── we_love_dashes_cc_and_py_in_deps_test.py │ ├── we_love_dashes_cc_only_module.cc │ ├── we_love_dashes_cc_only_test.py │ ├── we_love_dashes_py_only_test.py │ ├── wrapped_proto_module.cc │ └── wrapped_proto_module_test.py └── wrapped_proto_caster.h └── scripts └── build_and_run_tests.sh /.bazelignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | # Enable logging rc options. 2 | common --announce_rc 3 | 4 | # Enable verbose failures for testing only. 5 | build --verbose_failures 6 | 7 | # Abseil requires C++14 at minimum. 8 | build --enable_platform_specific_config 9 | build:linux --cxxopt=-std=c++17 --host_cxxopt=-std=c++17 10 | build:macos --cxxopt=-std=c++17 --host_cxxopt=-std=c++17 11 | build:windows --cxxopt=/std:c++17 --host_cxxopt=/std:c++17 12 | 13 | # Enable logging error output. 14 | test --test_output=errors 15 | test --test_summary=detailed 16 | 17 | # https://bazel.build/configure/best-practices#bazelrc-file 18 | try-import %workspace%/user.bazelrc 19 | -------------------------------------------------------------------------------- /.github/workflows/amd64_linux_bazel.yml: -------------------------------------------------------------------------------- 1 | # ref: https://github.com/actions/runner-images 2 | name: amd64 Linux Bazel 3 | 4 | on: [push, pull_request, workflow_dispatch] 5 | 6 | # Building using the github runner environement directly. 7 | jobs: 8 | native: 9 | strategy: 10 | matrix: 11 | bazel: [ 12 | {compilation_mode: opt}, 13 | {compilation_mode: dbg}, 14 | ] 15 | cpp: [ 16 | {version: 14, flags: "-std=c++14"}, 17 | {version: 17, flags: "-std=c++17"}, 18 | {version: 20, flags: "-std=c++20"}, 19 | ] 20 | python: [ 21 | #{version: '3.11'}, 22 | {version: '3.12'}, 23 | ] 24 | exclude: 25 | # only test `-c dbg` build with C++17 26 | - cpp: {version: 14} 27 | bazel: {compilation_mode: dbg} 28 | - cpp: {version: 20} 29 | bazel: {compilation_mode: dbg} 30 | fail-fast: false 31 | name: Linux•Bazel(${{ matrix.bazel.compilation_mode }})•C++${{ matrix.cpp.version }}•Python${{ matrix.python.version }} 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Check Java 36 | run: java -version 37 | - name: Setup Python 38 | uses: actions/setup-python@v5 39 | with: 40 | python-version: ${{ matrix.python.version }} 41 | - name: Check Python 42 | run: | 43 | python --version 44 | python -m platform 45 | - uses: bazel-contrib/setup-bazel@0.8.4 46 | with: 47 | bazelisk-cache: true 48 | disk-cache: ${{ github.workflow }} 49 | repository-cache: true 50 | - name: Check Bazel 51 | run: bazel version 52 | - name: Build 53 | run: > 54 | bazel build 55 | -c ${{ matrix.bazel.compilation_mode }} 56 | --cxxopt=${{ matrix.cpp.flags }} --host_cxxopt=${{ matrix.cpp.flags }} 57 | --subcommands=pretty_print 58 | --enable_bzlmod 59 | //... 60 | - name: Test 61 | run: > 62 | bazel test 63 | -c ${{ matrix.bazel.compilation_mode }} 64 | --cxxopt=${{ matrix.cpp.flags }} --host_cxxopt=${{ matrix.cpp.flags }} 65 | --subcommands=pretty_print 66 | --enable_bzlmod 67 | //... 68 | 69 | amd64_linux_bazel: 70 | runs-on: ubuntu-latest 71 | needs: native 72 | steps: 73 | - uses: actions/checkout@v4 74 | -------------------------------------------------------------------------------- /.github/workflows/amd64_linux_cmake.yml: -------------------------------------------------------------------------------- 1 | # ref: https://github.com/actions/runner-images 2 | name: amd64 Linux CMake 3 | 4 | on: [push, pull_request, workflow_dispatch] 5 | 6 | # Building using the github runner environement directly. 7 | jobs: 8 | native: 9 | strategy: 10 | matrix: 11 | python: [ 12 | {version: '3.9'}, 13 | {version: '3.10'}, 14 | {version: '3.11'}, 15 | {version: '3.12'}, 16 | #{version: '3.13'}, 17 | ] 18 | cmake: [ 19 | {generator: "Unix Makefiles", config: "Release"}, 20 | {generator: "Ninja", config: "Release"}, 21 | #{generator: "Ninja Multi-Config", config: "Release"}, 22 | ] 23 | fail-fast: false 24 | name: Linux•CMake(${{ matrix.cmake.generator }})•Python${{ matrix.python.version }} 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Setup Python 29 | uses: actions/setup-python@v5 30 | with: 31 | python-version: ${{ matrix.python.version }} 32 | - name: Check Python 33 | run: | 34 | python --version 35 | python -m platform 36 | - name: Install Python requirements 37 | run: python -m pip install --upgrade -r $(python -c 'import sys; print("./pybind11_protobuf/requirements/requirements_lock_%d_%d.txt" % (sys.version_info[:2]))') 38 | - name: Install Ninja 39 | run: | 40 | sudo apt-get update 41 | sudo apt-get install ninja-build 42 | - name: Check CMake 43 | run: cmake --version 44 | - name: Configure 45 | run: > 46 | cmake -S. -Bbuild 47 | -G "${{ matrix.cmake.generator }}" 48 | -DCMAKE_BUILD_TYPE=${{ matrix.cmake.config }} 49 | -DCMAKE_INSTALL_PREFIX=install 50 | - name: Build 51 | run: > 52 | cmake --build build 53 | --config ${{ matrix.cmake.config }} 54 | --target all 55 | -v -j2 56 | - name: Test 57 | run: > 58 | CTEST_OUTPUT_ON_FAILURE=1 59 | cmake --build build 60 | --config ${{ matrix.cmake.config }} 61 | --target test 62 | -v 63 | - name: Install 64 | run: > 65 | cmake --build build 66 | --config ${{ matrix.cmake.config }} 67 | --target install 68 | -v 69 | 70 | amd64_linux_cmake: 71 | runs-on: ubuntu-latest 72 | needs: native 73 | steps: 74 | - uses: actions/checkout@v4 75 | -------------------------------------------------------------------------------- /.github/workflows/amd64_macos_bazel.yml: -------------------------------------------------------------------------------- 1 | # ref: https://github.com/actions/runner-images 2 | name: amd64 MacOS Bazel 3 | 4 | on: [push, pull_request, workflow_dispatch] 5 | 6 | # Building using the github runner environement directly. 7 | jobs: 8 | native: 9 | strategy: 10 | matrix: 11 | bazel: [ 12 | {compilation_mode: opt}, 13 | {compilation_mode: dbg}, 14 | ] 15 | cpp: [ 16 | #{version: 14, flags: "-std=c++14"}, 17 | {version: 17, flags: "-std=c++17"}, 18 | #{version: 20, flags: "-std=c++20"}, 19 | ] 20 | python: [ 21 | #{version: '3.11'}, 22 | {version: '3.12'}, 23 | ] 24 | exclude: 25 | # only test `-c dbg` build with C++17 26 | - cpp: {version: 14} 27 | bazel: {compilation_mode: dbg} 28 | - cpp: {version: 20} 29 | bazel: {compilation_mode: dbg} 30 | fail-fast: false 31 | name: MacOS•Bazel(${{ matrix.bazel.compilation_mode }})•C++${{ matrix.cpp.version }}•Python${{ matrix.python.version }} 32 | runs-on: macos-13 # last macos intel based runner 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Set Java to OpenJDK 17 (Temurin) 36 | uses: actions/setup-java@v3 37 | with: 38 | distribution: 'temurin' 39 | java-version: '17' 40 | - name: Setup Python 41 | uses: actions/setup-python@v5 42 | with: 43 | python-version: ${{ matrix.python.version }} 44 | - name: Check Python 45 | run: | 46 | python --version 47 | python -m platform 48 | - name: Check Bazel 49 | run: bazel version 50 | - name: Change Python in MODULE.bazel 51 | run: | 52 | sed -i '' -e 's/\(DEFAULT_PYTHON =\) "3.[0-9]*"/\1 "${{ matrix.python.version }}"/g' MODULE.bazel 53 | cat MODULE.bazel 54 | - name: Build 55 | run: > 56 | bazel build 57 | -c ${{ matrix.bazel.compilation_mode }} 58 | --cxxopt=${{ matrix.cpp.flags }} --host_cxxopt=${{ matrix.cpp.flags }} 59 | --subcommands=pretty_print 60 | --enable_bzlmod 61 | //... 62 | - name: Test 63 | run: > 64 | bazel test 65 | -c ${{ matrix.bazel.compilation_mode }} 66 | --cxxopt=${{ matrix.cpp.flags }} --host_cxxopt=${{ matrix.cpp.flags }} 67 | --subcommands=pretty_print 68 | --enable_bzlmod 69 | //... 70 | 71 | amd64_macos_bazel: 72 | runs-on: ubuntu-latest 73 | needs: native 74 | steps: 75 | - uses: actions/checkout@v4 76 | -------------------------------------------------------------------------------- /.github/workflows/amd64_macos_cmake.yml: -------------------------------------------------------------------------------- 1 | # ref: https://github.com/actions/runner-images 2 | name: amd64 MacOS CMake 3 | 4 | on: [push, pull_request, workflow_dispatch] 5 | 6 | # Building using the github runner environement directly. 7 | jobs: 8 | native: 9 | strategy: 10 | matrix: 11 | python: [ 12 | {version: '3.9'}, 13 | {version: '3.10'}, 14 | {version: '3.11'}, 15 | {version: '3.12'}, 16 | #{version: '3.13'}, 17 | ] 18 | cmake: [ 19 | #{generator: "Xcode", config: Release, build_target: ALL_BUILD, test_target: RUN_TESTS, install_target: install}, 20 | {generator: "Unix Makefiles", config: Release, build_target: all, test_target: test, install_target: install}, 21 | ] 22 | fail-fast: false 23 | name: MacOS•CMake(${{ matrix.cmake.generator }})•Python${{ matrix.python.version }} 24 | runs-on: macos-13 # last macos intel based runner 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Setup Python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: ${{ matrix.python.version }} 31 | - name: Update Path 32 | run: | 33 | echo "$HOME/Library/Python/${{ matrix.python.version }}/bin" >> $GITHUB_PATH 34 | echo "$HOME/.local/bin" >> $GITHUB_PATH 35 | - name: Check Python 36 | run: python --version 37 | - name: Install Python requirements 38 | run: python -m pip install --upgrade -r $(python -c 'import sys; print("./pybind11_protobuf/requirements/requirements_lock_%d_%d.txt" % (sys.version_info[:2]))') 39 | - name: Check CMake 40 | run: cmake --version 41 | - name: Configure 42 | run: > 43 | cmake -S. -Bbuild 44 | -G "${{ matrix.cmake.generator }}" 45 | -DCMAKE_BUILD_TYPE=${{ matrix.cmake.config }} 46 | -DCMAKE_INSTALL_PREFIX=install 47 | - name: Build 48 | run: > 49 | cmake --build build 50 | --config ${{ matrix.cmake.config }} 51 | --target ${{ matrix.cmake.build_target }} 52 | -v -j2 53 | - name: Test 54 | run: > 55 | CTEST_OUTPUT_ON_FAILURE=1 56 | cmake --build build 57 | --config ${{ matrix.cmake.config }} 58 | --target ${{ matrix.cmake.test_target }} 59 | -v 60 | - name: Install 61 | run: > 62 | cmake --build build 63 | --config ${{ matrix.cmake.config }} 64 | --target ${{ matrix.cmake.install_target }} 65 | -v 66 | 67 | amd64_macos_cmake: 68 | runs-on: ubuntu-latest 69 | needs: native 70 | steps: 71 | - uses: actions/checkout@v4 72 | -------------------------------------------------------------------------------- /.github/workflows/amd64_windows_bazel.yml: -------------------------------------------------------------------------------- 1 | # ref: https://github.com/actions/runner-images 2 | name: amd64 Windows Bazel 3 | 4 | on: [push, pull_request, workflow_dispatch] 5 | 6 | # Building using the github runner environement directly. 7 | jobs: 8 | native: 9 | strategy: 10 | matrix: 11 | runner: [ 12 | windows-2022, 13 | #windows-2019, 14 | ] 15 | bazel: [ 16 | {compilation_mode: opt}, 17 | {compilation_mode: dbg}, 18 | ] 19 | cpp: [ 20 | #{version: 14, flags: "/std:c++14"}, 21 | {version: 17, flags: "/std:c++17"}, 22 | #{version: 20, flags: "/std:c++20"}, 23 | ] 24 | python: [ 25 | #{version: '3.11'}, 26 | {version: '3.12'}, 27 | ] 28 | exclude: 29 | - runner: windows-2019 30 | cpp: {version: 20} 31 | # only test -c dbg with VS 2022 version 17 to save compute time 32 | - runner: windows-2019 33 | bazel: {compilation_mode: dbg} 34 | - cpp: {version: 14} 35 | bazel: {compilation_mode: dbg} 36 | - cpp: {version: 20} 37 | bazel: {compilation_mode: dbg} 38 | fail-fast: false # Don't cancel all jobs if one fails. 39 | name: ${{ matrix.runner }}•Bazel(${{ matrix.bazel.compilation_mode }})•C++${{ matrix.cpp.version }}•Python${{ matrix.python.version }} 40 | runs-on: ${{ matrix.runner }} 41 | steps: 42 | - uses: actions/checkout@v4 43 | - name: Setup Python 44 | uses: actions/setup-python@v5 45 | with: 46 | python-version: ${{ matrix.python.version }} 47 | - name: Check Python 48 | run: | 49 | python --version 50 | python -m platform 51 | - name: Install Bazel 52 | run: choco install bazel 53 | - name: Check Bazel 54 | run: bazel version 55 | - name: Build 56 | run: > 57 | bazel build 58 | -c ${{ matrix.bazel.compilation_mode }} 59 | --cxxopt=${{ matrix.cpp.flags }} --host_cxxopt=${{ matrix.cpp.flags }} 60 | --subcommands=pretty_print 61 | --enable_bzlmod 62 | //... 63 | - name: Test 64 | run: > 65 | bazel test 66 | -c ${{ matrix.bazel.compilation_mode }} 67 | --cxxopt=${{ matrix.cpp.flags }} --host_cxxopt=${{ matrix.cpp.flags }} 68 | --subcommands=pretty_print 69 | --enable_bzlmod 70 | //... 71 | 72 | amd64_windows_bazel: 73 | runs-on: ubuntu-latest 74 | needs: native 75 | steps: 76 | - uses: actions/checkout@v4 77 | -------------------------------------------------------------------------------- /.github/workflows/amd64_windows_cmake.yml: -------------------------------------------------------------------------------- 1 | # ref: https://github.com/actions/runner-images 2 | name: amd64 Windows CMake 3 | 4 | on: [push, pull_request, workflow_dispatch] 5 | 6 | # Building using the github runner environement directly. 7 | jobs: 8 | native: 9 | strategy: 10 | matrix: 11 | python: [ 12 | #{version: '3.11'}, 13 | {version: '3.12'}, 14 | #{version: '3.13'}, 15 | ] 16 | cmake: [ 17 | {generator: "Visual Studio 17 2022", config: Release, build_target: ALL_BUILD, test_target: RUN_TESTS, install_target: INSTALL}, 18 | ] 19 | fail-fast: false 20 | name: Windows•CMake(${{ matrix.cmake.generator }})•Python${{ matrix.python.version }} 21 | runs-on: windows-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Setup Python 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: ${{ matrix.python.version }} 28 | - name: Check Python 29 | run: | 30 | python --version 31 | python -m platform 32 | - name: Install Python requirements 33 | run: python -m pip install --upgrade -r $(python -c 'import sys; print("./pybind11_protobuf/requirements/requirements_lock_%d_%d.txt" % (sys.version_info[:2]))') 34 | - name: Check CMake 35 | run: | 36 | cmake --version 37 | cmake -G || true 38 | - name: Configure 39 | run: > 40 | cmake -S. -Bbuild 41 | -G "${{ matrix.cmake.generator }}" 42 | -DCMAKE_CONFIGURATION_TYPES=${{ matrix.cmake.config }} 43 | -DCMAKE_INSTALL_PREFIX=install 44 | - name: Build 45 | run: > 46 | cmake --build build 47 | --config ${{ matrix.cmake.config }} 48 | --target ${{ matrix.cmake.build_target }} 49 | -v -j2 50 | - name: Test 51 | shell: bash 52 | run: > 53 | CTEST_OUTPUT_ON_FAILURE=1 54 | cmake --build build 55 | --config ${{ matrix.cmake.config }} 56 | --target ${{ matrix.cmake.test_target }} 57 | -v 58 | - name: Install 59 | run: > 60 | cmake --build build 61 | --config ${{ matrix.cmake.config }} 62 | --target ${{ matrix.cmake.install_target }} 63 | -v 64 | 65 | amd64_windows_cmake: 66 | runs-on: ubuntu-latest 67 | needs: native 68 | steps: 69 | - uses: actions/checkout@v4 70 | -------------------------------------------------------------------------------- /.github/workflows/arm64_macos_bazel.yml: -------------------------------------------------------------------------------- 1 | # ref: https://github.com/actions/runner-images 2 | name: arm64 MacOS Bazel 3 | 4 | on: [push, pull_request, workflow_dispatch] 5 | 6 | # Building using the github runner environement directly. 7 | jobs: 8 | native: 9 | strategy: 10 | matrix: 11 | bazel: [ 12 | {compilation_mode: opt}, 13 | {compilation_mode: dbg}, 14 | ] 15 | cpp: [ 16 | #{version: 14, flags: "-std=c++14"}, 17 | {version: 17, flags: "-std=c++17"}, 18 | #{version: 20, flags: "-std=c++20"}, 19 | ] 20 | python: [ 21 | #{version: '3.11'}, 22 | {version: '3.12'}, 23 | ] 24 | exclude: 25 | # only test `-c dbg` build with C++17 26 | - cpp: {version: 14} 27 | bazel: {compilation_mode: dbg} 28 | - cpp: {version: 20} 29 | bazel: {compilation_mode: dbg} 30 | fail-fast: false 31 | name: MacOS•Bazel(${{ matrix.bazel.compilation_mode }})•C++${{ matrix.cpp.version }}•Python${{ matrix.python.version }} 32 | runs-on: macos-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Set Java to OpenJDK 17 (Temurin) 36 | uses: actions/setup-java@v3 37 | with: 38 | distribution: 'temurin' 39 | java-version: '17' 40 | - name: Setup Python 41 | uses: actions/setup-python@v5 42 | with: 43 | python-version: ${{ matrix.python.version }} 44 | - name: Check Python 45 | run: | 46 | python --version 47 | python -m platform 48 | - name: Check Bazel 49 | run: bazel version 50 | - name: Change Python in MODULE.bazel 51 | run: | 52 | sed -i '' -e 's/\(DEFAULT_PYTHON =\) "3.[0-9]*"/\1 "${{ matrix.python.version }}"/g' MODULE.bazel 53 | cat MODULE.bazel 54 | - name: Build 55 | run: > 56 | bazel build 57 | -c ${{ matrix.bazel.compilation_mode }} 58 | --cxxopt=${{ matrix.cpp.flags }} --host_cxxopt=${{ matrix.cpp.flags }} 59 | --subcommands=pretty_print 60 | --enable_bzlmod 61 | //... 62 | - name: Test 63 | run: > 64 | bazel test 65 | -c ${{ matrix.bazel.compilation_mode }} 66 | --cxxopt=${{ matrix.cpp.flags }} --host_cxxopt=${{ matrix.cpp.flags }} 67 | --subcommands=pretty_print 68 | --enable_bzlmod 69 | //... 70 | 71 | arm64_macos_bazel: 72 | runs-on: ubuntu-latest 73 | needs: native 74 | steps: 75 | - uses: actions/checkout@v4 76 | -------------------------------------------------------------------------------- /.github/workflows/arm64_macos_cmake.yml: -------------------------------------------------------------------------------- 1 | # ref: https://github.com/actions/runner-images 2 | name: arm64 MacOS CMake 3 | 4 | on: [push, pull_request, workflow_dispatch] 5 | 6 | # Building using the github runner environement directly. 7 | jobs: 8 | native: 9 | strategy: 10 | matrix: 11 | python: [ 12 | {version: '3.9'}, 13 | {version: '3.10'}, 14 | {version: '3.11'}, 15 | {version: '3.12'}, 16 | #{version: '3.13'}, 17 | ] 18 | cmake: [ 19 | #{generator: "Xcode", config: Release, build_target: ALL_BUILD, test_target: RUN_TESTS, install_target: install}, 20 | {generator: "Unix Makefiles", config: Release, build_target: all, test_target: test, install_target: install}, 21 | ] 22 | fail-fast: false 23 | name: MacOS•CMake(${{ matrix.cmake.generator }})•Python${{ matrix.python.version }} 24 | runs-on: macos-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Setup Python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: ${{ matrix.python.version }} 31 | - name: Update Path 32 | run: | 33 | echo "$HOME/Library/Python/${{ matrix.python.version }}/bin" >> $GITHUB_PATH 34 | echo "$HOME/.local/bin" >> $GITHUB_PATH 35 | - name: Check Python 36 | run: python --version 37 | - name: Install Python requirements 38 | run: python -m pip install --upgrade -r $(python -c 'import sys; print("./pybind11_protobuf/requirements/requirements_lock_%d_%d.txt" % (sys.version_info[:2]))') 39 | - name: Check CMake 40 | run: cmake --version 41 | - name: Configure 42 | run: > 43 | cmake -S. -Bbuild 44 | -G "${{ matrix.cmake.generator }}" 45 | -DCMAKE_BUILD_TYPE=${{ matrix.cmake.config }} 46 | -DCMAKE_INSTALL_PREFIX=install 47 | - name: Build 48 | run: > 49 | cmake --build build 50 | --config ${{ matrix.cmake.config }} 51 | --target ${{ matrix.cmake.build_target }} 52 | -v -j2 53 | - name: Test 54 | run: > 55 | CTEST_OUTPUT_ON_FAILURE=1 56 | cmake --build build 57 | --config ${{ matrix.cmake.config }} 58 | --target ${{ matrix.cmake.test_target }} 59 | -v 60 | - name: Install 61 | run: > 62 | cmake --build build 63 | --config ${{ matrix.cmake.config }} 64 | --target ${{ matrix.cmake.install_target }} 65 | -v 66 | 67 | arm64_macos_cmake: 68 | runs-on: ubuntu-latest 69 | needs: native 70 | steps: 71 | - uses: actions/checkout@v4 72 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | # ref: https://github.com/actions/runner-images 2 | name: pre-commit 3 | 4 | # Controls when the action will run. 5 | on: [push, pull_request, workflow_dispatch] 6 | 7 | jobs: 8 | pre-commit: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-python@v5 13 | - uses: pre-commit/action@v3.0.0 14 | with: 15 | # Slow hooks are marked with manual - slow is okay here, run them too 16 | extra_args: --hook-stage manual --all-files 17 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu-build.yml: -------------------------------------------------------------------------------- 1 | # ref: https://github.com/actions/runner-images 2 | name: ubuntu-build 3 | 4 | on: [push, pull_request, workflow_dispatch] 5 | 6 | concurrency: 7 | group: ci-${{github.workflow}}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | cmake: 12 | name: cmake 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - name: Show env 16 | run: env 17 | - uses: actions/checkout@v4 18 | - name: Install Build Dependencies 19 | run: > 20 | sudo apt update && 21 | sudo apt install --no-install-recommends -y 22 | cmake make 23 | - name: Show Python version and platform info 24 | run: | 25 | python --version 26 | python -m platform 27 | - name: Show CMake version 28 | run: cmake --version 29 | - name: CMake Configure 30 | run: cmake -S. -Bbuild -DCMAKE_VERBOSE_MAKEFILE=ON 31 | - name: CMake Build 32 | run: cmake --build build -j$(nproc) 33 | 34 | bazel_inner: 35 | strategy: 36 | matrix: 37 | options: [ 38 | {version: 14, flags: "-std=c++14"}, 39 | {version: 17, flags: "-std=c++17"}, 40 | {version: 20, flags: "-std=c++20"}, 41 | ] 42 | fail-fast: false # Don't cancel all jobs if one fails. 43 | name: bazel c++${{ matrix.options.version }} 44 | runs-on: ubuntu-22.04 45 | steps: 46 | - uses: actions/checkout@v4 47 | - name: Setup Python 48 | uses: actions/setup-python@v5 49 | with: 50 | python-version: 3.12 # Current default version in MODULE.bazel 51 | - name: Show Python version and platform info 52 | run: | 53 | python --version 54 | python -m platform 55 | - uses: bazel-contrib/setup-bazel@0.8.4 56 | with: 57 | bazelisk-cache: true 58 | disk-cache: ${{ github.workflow }} 59 | repository-cache: true 60 | - name: Show Bazel version 61 | run: bazel --version 62 | - name: Bazel Build 63 | shell: bash 64 | run: > 65 | bazel build 66 | --cxxopt=${{ matrix.options.flags }} --host_cxxopt=${{ matrix.options.flags }} 67 | --subcommands=pretty_print 68 | --enable_bzlmod 69 | //... 70 | - name: Bazel Test 71 | shell: bash 72 | run: > 73 | bazel test 74 | --cxxopt=${{ matrix.options.flags }} --host_cxxopt=${{ matrix.options.flags }} 75 | --subcommands=pretty_print 76 | --enable_bzlmod 77 | //... 78 | 79 | bazel: 80 | runs-on: ubuntu-latest 81 | needs: bazel_inner 82 | steps: 83 | - uses: actions/checkout@v4 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bazel-* 2 | /build 3 | /tmp_build 4 | 5 | user.bazelrc 6 | .*.swp 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | #################################################################### 2 | # PLEASE KEEP IN SYNC WITH pybind11_abseil/.pre-commit-config.yaml # 3 | #################################################################### 4 | # 5 | # To use: 6 | # 7 | # pre-commit run -a 8 | # 9 | # Or: 10 | # 11 | # pre-commit install # (runs every time you commit in git) 12 | # 13 | # To update this file: 14 | # 15 | # pre-commit autoupdate 16 | # 17 | # See https://github.com/pre-commit/pre-commit 18 | 19 | 20 | ci: 21 | autoupdate_commit_msg: "chore(deps): update pre-commit hooks" 22 | autofix_commit_msg: "style: pre-commit fixes" 23 | autoupdate_schedule: quarterly 24 | 25 | repos: 26 | 27 | - repo: https://github.com/pre-commit/pre-commit-hooks 28 | rev: "v4.4.0" 29 | hooks: 30 | - id: check-added-large-files 31 | - id: check-case-conflict 32 | - id: check-docstring-first 33 | - id: check-merge-conflict 34 | - id: check-symlinks 35 | - id: check-toml 36 | - id: check-yaml 37 | - id: debug-statements 38 | - id: end-of-file-fixer 39 | - id: mixed-line-ending 40 | - id: requirements-txt-fixer 41 | - id: trailing-whitespace 42 | exclude: \.patch?$ 43 | 44 | - repo: https://github.com/cheshirekow/cmake-format-precommit 45 | rev: "v0.6.13" 46 | hooks: 47 | - id: cmake-format 48 | additional_dependencies: [pyyaml] 49 | types: [file] 50 | files: (\.cmake|CMakeLists.txt)(.in)?$ 51 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 3 | 4 | project(pybind11_protobuf LANGUAGES CXX) 5 | 6 | if(NOT DEFINED CMAKE_CXX_STANDARD) 7 | if(MSVC) 8 | set(CMAKE_CXX_STANDARD 20) 9 | else() 10 | set(CMAKE_CXX_STANDARD 17) 11 | endif() 12 | endif() 13 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 14 | set(CMAKE_CXX_EXTENSIONS OFF) 15 | 16 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 17 | 18 | # ============================================================================ 19 | # Options 20 | 21 | option(USE_SYSTEM_ABSEIL "Force usage of system provided abseil-cpp" OFF) 22 | option(USE_SYSTEM_PROTOBUF "Force usage of system provided Protobuf" OFF) 23 | option(USE_SYSTEM_PYBIND "Force usage of system provided pybind11" OFF) 24 | 25 | # ============================================================================ 26 | # Testing 27 | include(CTest) 28 | option(PYBIND11_PROTOBUF_BUILD_TESTING 29 | "If ON, build all of pybind11_protobuf's own tests." ${BUILD_TESTING}) 30 | 31 | # ============================================================================ 32 | # Find Python 33 | find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module) 34 | 35 | # ============================================================================ 36 | # Build dependencies 37 | add_subdirectory(cmake/dependencies dependencies) 38 | 39 | set(TOP_LEVEL_DIR ${CMAKE_CURRENT_LIST_DIR}) 40 | include_directories(${TOP_LEVEL_DIR} ${pybind11_INCLUDE_DIRS}) 41 | add_subdirectory(pybind11_protobuf) 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2021 The Pybind Development Team. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | You are under no obligation whatsoever to provide any bug fixes, patches, or 29 | upgrades to the features, functionality or performance of the source code 30 | ("Enhancements") to anyone; however, if you choose to make your Enhancements 31 | available either publicly, or directly to the author of this software, without 32 | imposing a separate written license agreement for such Enhancements, then you 33 | hereby grant the following license: a non-exclusive, royalty-free perpetual 34 | license to install, use, modify, prepare derivative works, incorporate into 35 | other computer software, distribute, and sublicense such enhancements or 36 | derivative works thereof, in binary and source code form. 37 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "pybind11_protobuf", 3 | version = "head", 4 | ) 5 | 6 | # Only direct dependencies need to be listed below. 7 | # Please keep the versions in sync with the versions in the WORKSPACE file. 8 | # see https://registry.bazel.build/ 9 | bazel_dep(name = "bazel_skylib", version = "1.7.1") 10 | bazel_dep(name = "abseil-cpp", version = "20240722.0.bcr.2", repo_name = "com_google_absl") 11 | bazel_dep(name = "abseil-py", version = "2.1.0", repo_name = "com_google_absl_py") 12 | bazel_dep(name = "platforms", version = "0.0.10") 13 | bazel_dep(name = "rules_cc", version = "0.0.15") 14 | bazel_dep(name = "rules_python", version = "0.40.0") 15 | bazel_dep(name = "pybind11_bazel", version = "2.13.6") 16 | bazel_dep(name = "pybind11_abseil", version = "202402.0") 17 | bazel_dep(name = "protobuf", version = "29.2", repo_name = "com_google_protobuf") 18 | bazel_dep(name = "grpc", version = "1.56.3.bcr.1", repo_name = "com_github_grpc_grpc") 19 | 20 | SUPPORTED_PYTHON_VERSIONS = [ 21 | "3.12", 22 | "3.11", 23 | "3.10", 24 | "3.9", 25 | ] 26 | 27 | DEFAULT_PYTHON = "3.12" 28 | 29 | python = use_extension("@rules_python//python/extensions:python.bzl", "python") 30 | 31 | [ 32 | python.toolchain( 33 | is_default = version == DEFAULT_PYTHON, 34 | python_version = version, 35 | ) 36 | for version in SUPPORTED_PYTHON_VERSIONS 37 | ] 38 | 39 | use_repo(python, python = "python_versions") 40 | 41 | #### DEV ONLY DEPENDENCIES BELOW HERE #### 42 | 43 | pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip", dev_dependency = True) 44 | 45 | [ 46 | pip.parse( 47 | hub_name = "pypi_protobuf", 48 | python_version = version, 49 | requirements_lock = "//pybind11_protobuf/requirements:requirements_lock_" + version.replace(".", "_") + ".txt", 50 | ) 51 | for version in SUPPORTED_PYTHON_VERSIONS 52 | ] 53 | 54 | use_repo(pip, pypi = "pypi_protobuf") 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pybind11 bindings for Google's Protocol Buffers 2 | 3 | Github-CI: 4 | | OS \ Build system | Bazel | CMake | 5 | |:------- | :---: | :---: | 6 | | Linux (`amd64`) | [![Build Status][amd64_linux_bazel_status]][amd64_linux_bazel_link] | [![Build Status][amd64_linux_cmake_status]][amd64_linux_cmake_link] | 7 | | MacOS (`amd64`) | [![Build Status][amd64_macos_bazel_status]][amd64_macos_bazel_link] | [![Build Status][amd64_macos_cmake_status]][amd64_macos_cmake_link] | 8 | | MacOS (`arm64`) | [![Build Status][arm64_macos_bazel_status]][arm64_macos_bazel_link] | [![Build Status][arm64_macos_cmake_status]][arm64_macos_cmake_link] | 9 | | Windows (`amd64`) | [![Build Status][amd64_windows_bazel_status]][amd64_windows_bazel_link] | [![Build Status][amd64_windows_cmake_status]][amd64_windows_cmake_link] | 10 | 11 | [amd64_linux_bazel_status]: ./../../actions/workflows/amd64_linux_bazel.yml/badge.svg 12 | [amd64_linux_bazel_link]: ./../../actions/workflows/amd64_linux_bazel.yml 13 | [amd64_macos_bazel_status]: ./../../actions/workflows/amd64_macos_bazel.yml/badge.svg 14 | [amd64_macos_bazel_link]: ./../../actions/workflows/amd64_macos_bazel.yml 15 | [arm64_macos_bazel_status]: ./../../actions/workflows/arm64_macos_bazel.yml/badge.svg 16 | [arm64_macos_bazel_link]: ./../../actions/workflows/arm64_macos_bazel.yml 17 | [amd64_windows_bazel_status]: ./../../actions/workflows/amd64_windows_bazel.yml/badge.svg 18 | [amd64_windows_bazel_link]: ./../../actions/workflows/amd64_windows_bazel.yml 19 | 20 | [amd64_linux_cmake_status]: ./../../actions/workflows/amd64_linux_cmake.yml/badge.svg 21 | [amd64_linux_cmake_link]: ./../../actions/workflows/amd64_linux_cmake.yml 22 | [amd64_macos_cmake_status]: ./../../actions/workflows/amd64_macos_cmake.yml/badge.svg 23 | [amd64_macos_cmake_link]: ./../../actions/workflows/amd64_macos_cmake.yml 24 | [arm64_macos_cmake_status]: ./../../actions/workflows/arm64_macos_cmake.yml/badge.svg 25 | [arm64_macos_cmake_link]: ./../../actions/workflows/arm64_macos_cmake.yml 26 | [amd64_windows_cmake_status]: ./../../actions/workflows/amd64_windows_cmake.yml/badge.svg 27 | [amd64_windows_cmake_link]: ./../../actions/workflows/amd64_windows_cmake.yml 28 | 29 | ## Overview 30 | 31 | These adapters make Protocol Buffer message types work with Pybind11 bindings. 32 | 33 | To use the proto messages with pybind11: 34 | 35 | 1. Include the header file `pybind11_protobuf/native_proto_caster.h` 36 | in the .cc file with your bindings. 37 | 1. Call `pybind11_protobuf::ImportNativeProtoCasters();` in your `PYBIND11_MODULE` definition. 38 | 1. Ensure `"@com_google_protobuf//:protobuf_python"` is a python dependency. 39 | When using Bazel, a common strategy is to add a python library that "wraps" 40 | the extension along with any required python dependencies. 41 | 42 | Any arguments or return values which are a protocol buffer (including the base 43 | class, `proto2::Message`) will be automatically converted to python native 44 | protocol buffers. 45 | 46 | ### Basic Example 47 | 48 | ```cpp 49 | #include 50 | 51 | #include "path/to/my/my_message.proto.h" 52 | #include "pybind11_protobuf/native_proto_caster.h" 53 | 54 | // In real use, these two functions would probably be defined in a python-agnostic library. 55 | MyMessage ReturnMyMessage() { ... } 56 | void TakeMyMessage(const MyMessage& in) { ... } 57 | 58 | PYBIND11_MODULE(my_module, m) { 59 | pybind11_protobuf::ImportNativeProtoCasters(); 60 | 61 | m.def("return_my_message", &ReturnMyMessage); 62 | m.def("take_my_message", &TakeMyMessage, pybind11::arg("in")); 63 | } 64 | ``` 65 | 66 | ## C++ Native vs Python Native Types 67 | 68 | When passing protos between C++ and Python, the `native_proto_caster.h` 69 | bindings will convert protobuf objects to the native type on either side. 70 | 71 | While C++ has only one native type, Python has two native types 72 | (https://rules-proto-grpc.com/en/latest/lang/python.html): 73 | 74 | * `--define=use_fast_cpp_protos=false` (aka `use_pure_python_protos`) 75 | * `--define=use_fast_cpp_protos=true` 76 | 77 | With `use_pure_python_protos`, protobuf objects passed between C++ and Python 78 | are always serialized/deserialized between the native C++ type and the pure 79 | Python native type. This is very safe but also slow. 80 | 81 | With `use_fast_cpp_protos`, the native Python type is internally backed by 82 | the native C++ type, which unlocks various performance benefits, even when 83 | only used from Python. When passing protobuf objects between Python and C++, 84 | in certain situations the serialization/deserialization overhead can be 85 | avoided, but not universally. Fundamentally, sharing C++ native protobuf 86 | objects between C++ and Python is unsafe because C++ assumes that it has 87 | exclusive ownership and may manipulate references in a way that undermines 88 | Python's much safer ownership semantics. Because of this, sharing mutable 89 | references or pointers between C++ and Python is not allowed. 90 | However, when passing a Python protobuf object to 91 | C++, and with `PYBIND11_PROTOBUF_ASSUME_FULL_ABI_COMPATIBILITY` defined 92 | (see proto_cast_util.h), 93 | the bindings will share the underlying C++ native protobuf object with C++ when 94 | passed by `const &` or `const *`. 95 | 96 | ### Protobuf Extensions 97 | 98 | When `use_fast_cpp_protos` is in use, and 99 | protobuf extensions 100 | are involved, a well-known pitfall is that extensions are silently moved 101 | to the `proto2::UnknownFieldSet` when a message is deserialized in C++, 102 | but the `cc_proto_library` for the extensions is not linked in. The root 103 | cause is an asymmetry in the handling of Python protos vs C++ 104 | protos: 105 | when a Python proto is deserialized, both the Python descriptor pool and the 106 | C++ descriptor pool are inspected, but when a C++ proto is deserialized, only 107 | the C++ descriptor pool is inspected. Until this asymmetry is resolved, the 108 | `cc_proto_library` for all extensions involved must be added to the `deps` of 109 | the relevant `pybind_library` or `pybind_extension`, or if this is impractial, 110 | `pybind11_protobuf::check_unknown_fields::ExtensionsWithUnknownFieldsPolicy::WeakEnableFallbackToSerializeParse` 111 | or `pybind11_protobuf::AllowUnknownFieldsFor` can be used. 112 | 113 | The pitfall is sufficiently unobvious to be a setup for regular accidents, 114 | potentially with critical 115 | consequences. 116 | 117 | To guard against the most common type of accident, native_proto_caster.h 118 | includes a safety mechanism that raises "Proto Message has an Unknown Field" 119 | in certain situations: 120 | 121 | * When `use_fast_cpp_protos` is in use, 122 | * a protobuf message is returned from C++ to Python, 123 | * the message involves protobuf extensions (recursively), 124 | * and the `proto2::UnknownFieldSet` for the message or any of its submessages 125 | is not empty. 126 | 127 | `pybind11_protobuf::check_unknown_fields::ExtensionsWithUnknownFieldsPolicy::WeakEnableFallbackToSerializeParse` 128 | is a **global** escape hatch trading off convenience and runtime overhead: the 129 | convenience is that it is not necessary to determine what `cc_proto_library` 130 | dependencies need to be added, the runtime overhead is that 131 | `SerializePartialToString`/`ParseFromString` is used for messages with unknown 132 | fields, instead of the much faster `CopyFrom`. 133 | 134 | Another escape hatch is `pybind11_protobuf::AllowUnknownFieldsFor`, which 135 | simply disables the safety mechanism for **specific message types**, without 136 | a runtime overhead. This is useful for situations in which unknown fields 137 | are acceptable. 138 | 139 | An example of a full error message generated by the safety mechanism 140 | (with lines breaks here for readability): 141 | 142 | ``` 143 | Proto Message of type pybind11.test.NestRepeated has an Unknown Field with 144 | parent of type pybind11.test.BaseMessage: base_msgs.1003 145 | (pybind11_protobuf/tests/extension_nest_repeated.proto, 146 | pybind11_protobuf/tests/extension.proto). 147 | Please add the required `cc_proto_library` `deps`. 148 | Only if there is no alternative to suppressing this error, use 149 | `pybind11_protobuf::AllowUnknownFieldsFor("pybind11.test.NestRepeated", "base_msgs");` 150 | (Warning: suppressions may mask critical bugs.) 151 | ``` 152 | 153 | Note that the current implementation of the safety mechanism is a compromise 154 | solution, trading off simplicity of implementation, runtime performance, 155 | and precision. Alerting developers of new code to unknown fields is assumed 156 | to be generally helpful, but the unknown fields detection is limited to 157 | messages with extensions, to avoid the runtime overhead for the presumably 158 | much more common case that no extensions are involved. Because of this, 159 | the runtime overhead for the safety mechanism is expected to be very small. 160 | 161 | ### Enumerations 162 | 163 | Enumerations are passed and returned as integers. You may use the enum values 164 | from the native python proto module to set and check the enum values used 165 | by a bound proto enum (see `tests/proto_enum_test.py` for an example). 166 | 167 | ### In / Out Parameters 168 | 169 | In cases where a protocol buffer is used as an in/out parameter in C++, 170 | additional logic will be required in the wrapper. For example: 171 | 172 | ```cpp 173 | #include 174 | 175 | #include "path/to/my/my_message.proto.h" 176 | #include "pybind11_protobuf/native_proto_caster.h" 177 | 178 | void MutateMessage(MyMessage* in_out) { ... } 179 | 180 | PYBIND11_MODULE(my_module, m) { 181 | pybind11_protobuf::ImportNativeProtoCasters(); 182 | 183 | m.def("mutate_message", [](MyMessage in) { 184 | MutateMessage(&in); 185 | return in; 186 | }, 187 | pybind11::arg("in")); 188 | } 189 | ``` 190 | 191 | ### `pybind11_protobuf/wrapped_proto_caster.h` 192 | 193 | TL;DR: Ignore `wrapped_proto_caster.h` if you can, this header was added as 194 | a migration aid before the removal of `proto_casters.h`. 195 | 196 | Historic background: Due to the nature of pybind11, extension modules 197 | built using `native_proto_caster.h` could not interoperate with the 198 | older `proto_casters.h` bindings, as that would have led to C++ ODR 199 | violations. `wrapped_proto_caster.h` is a nearly-transparent wrapper for 200 | `native_proto_caster.h` to work around the ODR issue. With the migration to 201 | `native_proto_caster.h` now completed, `wrapped_proto_caster.h` is obsolete 202 | and will be removed in the future. 203 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "pybind11_protobuf") 2 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 3 | load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository", "new_git_repository") 4 | 5 | ################################################################################ 6 | # 7 | # WORKSPACE is being deprecated in favor of the new Bzlmod dependency system. 8 | # It will be removed at some point in the future. 9 | # 10 | ################################################################################ 11 | 12 | ## `bazel_skylib` 13 | # Needed for Abseil. 14 | git_repository( 15 | name = "bazel_skylib", 16 | commit = "27d429d8d036af3d010be837cc5924de1ca8d163", 17 | #tag = "1.7.1", 18 | remote = "https://github.com/bazelbuild/bazel-skylib.git", 19 | ) 20 | load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") 21 | bazel_skylib_workspace() 22 | 23 | ## Bazel rules... 24 | git_repository( 25 | name = "platforms", 26 | commit = "05ec3a3df23fde62471f8288e344cc021dd87bab", 27 | #tag = "0.0.10", 28 | remote = "https://github.com/bazelbuild/platforms.git", 29 | ) 30 | 31 | ## abseil-cpp 32 | # https://github.com/abseil/abseil-cpp 33 | ## Abseil-cpp 34 | git_repository( 35 | name = "com_google_absl", 36 | commit = "4447c7562e3bc702ade25105912dce503f0c4010", 37 | #tag = "20240722.0", 38 | remote = "https://github.com/abseil/abseil-cpp.git", 39 | ) 40 | 41 | http_archive( 42 | name = "com_google_absl_py", 43 | sha256 = "8a3d0830e4eb4f66c4fa907c06edf6ce1c719ced811a12e26d9d3162f8471758", 44 | strip_prefix = "abseil-py-2.1.0", 45 | urls = [ 46 | "https://github.com/abseil/abseil-py/archive/refs/tags/v2.1.0.tar.gz", 47 | ], 48 | ) 49 | 50 | git_repository( 51 | name = "rules_python", 52 | commit = "1944874f6ba507f70d8c5e70df84622e0c783254", 53 | #tag = "0.40.0", 54 | remote = "https://github.com/bazelbuild/rules_python.git", 55 | ) 56 | 57 | load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_multi_toolchains") 58 | py_repositories() 59 | 60 | load("@rules_python//python/pip_install:repositories.bzl", "pip_install_dependencies") 61 | pip_install_dependencies() 62 | 63 | DEFAULT_PYTHON = "3.11" 64 | 65 | python_register_multi_toolchains( 66 | name = "python", 67 | default_version = DEFAULT_PYTHON, 68 | python_versions = [ 69 | "3.12", 70 | "3.11", 71 | "3.10", 72 | "3.9", 73 | "3.8" 74 | ], 75 | ) 76 | 77 | load("@python//:pip.bzl", "multi_pip_parse") 78 | 79 | multi_pip_parse( 80 | name = "pypi", 81 | default_version = DEFAULT_PYTHON, 82 | python_interpreter_target = { 83 | "3.12": "@python_3_12_host//:python", 84 | "3.11": "@python_3_11_host//:python", 85 | "3.10": "@python_3_10_host//:python", 86 | "3.9": "@python_3_9_host//:python", 87 | "3.8": "@python_3_8_host//:python", 88 | }, 89 | requirements_lock = { 90 | "3.12": "//pybind11_protobuf/requirements:requirements_lock_3_12.txt", 91 | "3.11": "//pybind11_protobuf/requirements:requirements_lock_3_11.txt", 92 | "3.10": "//pybind11_protobuf/requirements:requirements_lock_3_10.txt", 93 | "3.9": "//pybind11_protobuf/requirements:requirements_lock_3_9.txt", 94 | "3.8": "//pybind11_protobuf/requirements:requirements_lock_3_8.txt", 95 | }, 96 | ) 97 | 98 | load("@pypi//:requirements.bzl", "install_deps") 99 | install_deps() 100 | 101 | ## `pybind11_bazel` 102 | # https://github.com/pybind/pybind11_bazel 103 | git_repository( 104 | name = "pybind11_bazel", 105 | commit = "2b6082a4d9d163a52299718113fa41e4b7978db5", 106 | #tag = "v2.13.6", # 2024/10/21 107 | remote = "https://github.com/pybind/pybind11_bazel.git", 108 | ) 109 | 110 | ## `pybind11` 111 | # https://github.com/pybind/pybind11 112 | new_git_repository( 113 | name = "pybind11", 114 | build_file = "@pybind11_bazel//:pybind11-BUILD.bazel", 115 | commit = "a2e59f0e7065404b44dfe92a28aca47ba1378dc4", 116 | #tag = "v2.13.6", 117 | remote = "https://github.com/pybind/pybind11.git", 118 | ) 119 | 120 | # proto_library, cc_proto_library, and java_proto_library rules implicitly 121 | # depend on @com_google_protobuf for protoc and proto runtimes. 122 | git_repository( 123 | name = "com_google_protobuf", 124 | commit = "233098326bc268fc03b28725c941519fc77703e6", 125 | #tag = "v29.2", 126 | remote = "https://github.com/protocolbuffers/protobuf.git", 127 | ) 128 | 129 | load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") 130 | protobuf_deps() 131 | 132 | # GRPC, for proto rules. 133 | # For a related discussion of the pro/cons of various open-source py proto rule 134 | # repositories, see b/189457935. 135 | git_repository( 136 | name = "com_github_grpc_grpc", 137 | commit = "d3286610f703a339149c3f9be69f0d7d0abb130a", 138 | #tag = "v1.67.1", 139 | remote = "https://github.com/grpc/grpc.git", 140 | ) 141 | 142 | load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps") 143 | grpc_deps() 144 | -------------------------------------------------------------------------------- /WORKSPACE.bzlmod: -------------------------------------------------------------------------------- 1 | # Empty file for cross-compatibility between MODULE.bazel and WORKSPACE 2 | -------------------------------------------------------------------------------- /cmake/dependencies/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | set(BUILD_SHARED_LIBS ON) 3 | set(BUILD_TESTING OFF) 4 | 5 | message(CHECK_START "Checking for external dependencies") 6 | list(APPEND CMAKE_MESSAGE_INDENT " ") 7 | 8 | if(NOT TARGET absl::base) 9 | if(USE_SYSTEM_ABSEIL) 10 | # Version omitted, as absl only allows EXACT version matches 11 | find_package(absl REQUIRED) 12 | else() 13 | message(CHECK_START "Fetching Abseil-cpp") 14 | list(APPEND CMAKE_MESSAGE_INDENT " ") 15 | # ensure that abseil also installs itself, since we are using it in our 16 | # public API 17 | set(ABSL_ENABLE_INSTALL ON) 18 | set(ABSL_PROPAGATE_CXX_STD ON) 19 | set(ABSL_USE_SYSTEM_INCLUDES ON) 20 | set(ABSL_BUILD_TESTING OFF) 21 | FetchContent_Declare( 22 | absl 23 | GIT_REPOSITORY "https://github.com/abseil/abseil-cpp.git" 24 | GIT_TAG "20240722.0" 25 | GIT_SHALLOW TRUE) 26 | FetchContent_MakeAvailable(absl) 27 | list(POP_BACK CMAKE_MESSAGE_INDENT) 28 | message(CHECK_PASS "fetched") 29 | endif() 30 | endif() 31 | 32 | if(NOT TARGET protobuf::libprotobuf) 33 | if(USE_SYSTEM_PROTOBUF) 34 | find_package(protobuf 5.29.2 REQUIRED) 35 | else() 36 | message(CHECK_START "Fetching Protobuf") 37 | list(APPEND CMAKE_MESSAGE_INDENT " ") 38 | set(protobuf_BUILD_TESTS OFF) 39 | FetchContent_Declare( 40 | Protobuf 41 | GIT_REPOSITORY "https://github.com/protocolbuffers/protobuf.git" 42 | GIT_TAG "v29.2" 43 | GIT_SHALLOW TRUE) 44 | FetchContent_MakeAvailable(Protobuf) 45 | list(POP_BACK CMAKE_MESSAGE_INDENT) 46 | message(CHECK_PASS "fetched") 47 | endif() 48 | endif() 49 | 50 | if(NOT TARGET pybind11::pybind11_headers) 51 | if(USE_SYSTEM_PYBIND) 52 | find_package(pybind11 2.13.6 REQUIRED) 53 | else() 54 | message(CHECK_START "Fetching pybind11") 55 | list(APPEND CMAKE_MESSAGE_INDENT " ") 56 | FetchContent_Declare( 57 | pybind11 58 | GIT_REPOSITORY "https://github.com/pybind/pybind11.git" 59 | GIT_TAG "v2.13.6" 60 | GIT_SHALLOW TRUE) 61 | FetchContent_MakeAvailable(pybind11) 62 | list(POP_BACK CMAKE_MESSAGE_INDENT) 63 | message(CHECK_PASS "fetched") 64 | endif() 65 | endif() 66 | 67 | if(PYBIND11_PROTOBUF_BUILD_TESTING AND NOT TARGET pybind11_abseil::absl_casters) 68 | message(CHECK_START "Fetching pybind11_abseil") 69 | list(APPEND CMAKE_MESSAGE_INDENT " ") 70 | FetchContent_Declare( 71 | pybind11_abseil 72 | GIT_REPOSITORY "https://github.com/pybind/pybind11_abseil.git" 73 | # TODO(mizux) use a release version tag once available. 74 | GIT_TAG "master" 75 | GIT_SHALLOW TRUE) 76 | FetchContent_MakeAvailable(pybind11_abseil) 77 | list(POP_BACK CMAKE_MESSAGE_INDENT) 78 | message(CHECK_PASS "fetched") 79 | endif() 80 | 81 | list(POP_BACK CMAKE_MESSAGE_INDENT) 82 | message(CHECK_PASS "all dependencies found") 83 | -------------------------------------------------------------------------------- /pybind11_protobuf/BUILD: -------------------------------------------------------------------------------- 1 | # Pybind11 bindings for Google's Protocol Buffers 2 | 3 | load("@pybind11_bazel//:build_defs.bzl", "pybind_library") 4 | 5 | licenses(["notice"]) 6 | 7 | pybind_library( 8 | name = "enum_type_caster", 9 | hdrs = ["enum_type_caster.h"], 10 | visibility = [ 11 | "//visibility:public", 12 | ], 13 | deps = [ 14 | "@com_google_protobuf//:protobuf", 15 | ], 16 | ) 17 | 18 | pybind_library( 19 | name = "native_proto_caster", 20 | hdrs = ["native_proto_caster.h"], 21 | visibility = [ 22 | "//visibility:public", 23 | ], 24 | deps = [ 25 | ":check_unknown_fields", 26 | ":enum_type_caster", 27 | ":proto_cast_util", 28 | "@com_google_absl//absl/strings:string_view", 29 | "@com_google_protobuf//:protobuf", 30 | ], 31 | ) 32 | 33 | pybind_library( 34 | name = "proto_cast_util", 35 | srcs = ["proto_cast_util.cc"], 36 | hdrs = [ 37 | "proto_cast_util.h", 38 | "proto_caster_impl.h", 39 | ], 40 | deps = [ 41 | ":check_unknown_fields", 42 | "@com_google_absl//absl/container:flat_hash_map", 43 | "@com_google_absl//absl/log", 44 | "@com_google_absl//absl/log:check", 45 | "@com_google_absl//absl/memory", 46 | "@com_google_absl//absl/strings", 47 | "@com_google_absl//absl/types:optional", 48 | "@com_google_protobuf//:protobuf", 49 | "@com_google_protobuf//python:proto_api", 50 | ], 51 | ) 52 | 53 | pybind_library( 54 | name = "wrapped_proto_caster", 55 | hdrs = ["wrapped_proto_caster.h"], 56 | visibility = [ 57 | "//visibility:public", 58 | ], 59 | deps = [ 60 | ":proto_cast_util", 61 | "@com_google_absl//absl/status:statusor", 62 | "@com_google_absl//absl/types:optional", 63 | "@com_google_protobuf//:protobuf", 64 | ], 65 | ) 66 | 67 | cc_library( 68 | name = "check_unknown_fields", 69 | srcs = ["check_unknown_fields.cc"], 70 | hdrs = ["check_unknown_fields.h"], 71 | deps = [ 72 | "@com_google_absl//absl/container:flat_hash_map", 73 | "@com_google_absl//absl/container:flat_hash_set", 74 | "@com_google_absl//absl/strings", 75 | "@com_google_absl//absl/synchronization", 76 | "@com_google_absl//absl/types:optional", 77 | "@com_google_protobuf//:protobuf", 78 | "@com_google_protobuf//python:proto_api", 79 | ], 80 | ) 81 | 82 | cc_library( 83 | name = "disallow_extensions_with_unknown_fields", 84 | srcs = ["disallow_extensions_with_unknown_fields.cc"], 85 | visibility = [ 86 | "//visibility:public", 87 | ], 88 | deps = [ 89 | ":check_unknown_fields", 90 | ], 91 | alwayslink = 1, 92 | ) 93 | -------------------------------------------------------------------------------- /pybind11_protobuf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # pybind11_proto_utils pybind11 extension module 3 | pybind11_add_module(pybind11_proto_utils MODULE proto_utils.cc proto_utils.h) 4 | add_library(pybind11_protobuf::pybind11_proto_utils ALIAS pybind11_proto_utils) 5 | 6 | # note: macOS is APPLE and also UNIX ! 7 | if(APPLE) 8 | set_target_properties(pybind11_proto_utils PROPERTIES SUFFIX ".so") 9 | endif() 10 | 11 | target_include_directories(pybind11_proto_utils 12 | INTERFACE $) 13 | 14 | target_link_libraries(pybind11_proto_utils PUBLIC absl::strings 15 | protobuf::libprotobuf) 16 | 17 | # ============================================================================ 18 | # pybind11_native_proto_caster shared library 19 | add_library( 20 | pybind11_native_proto_caster STATIC 21 | # bazel: pybind_library: native_proto_caster 22 | native_proto_caster.h 23 | # bazel: pybind_library: enum_type_caster 24 | enum_type_caster.h 25 | # bazel: pybind_library: proto_cast_util 26 | proto_cast_util.cc 27 | proto_cast_util.h 28 | proto_caster_impl.h) 29 | add_library(pybind11_protobuf::pybind11_native_proto_caster ALIAS 30 | pybind11_native_proto_caster) 31 | 32 | target_link_libraries( 33 | pybind11_native_proto_caster 34 | PUBLIC absl::flat_hash_map 35 | absl::flat_hash_set 36 | absl::hash 37 | absl::strings 38 | absl::optional 39 | protobuf::libprotobuf 40 | pybind11::pybind11) 41 | 42 | target_include_directories(pybind11_native_proto_caster 43 | PUBLIC $) 44 | 45 | # ============================================================================ 46 | # pybind11_wrapped_proto_caster shared library 47 | add_library( 48 | pybind11_wrapped_proto_caster STATIC 49 | # bazel: pybind_library: wrapped_proto_caster 50 | wrapped_proto_caster.h 51 | # bazel: pybind_library: proto_cast_util 52 | proto_cast_util.cc proto_cast_util.h proto_caster_impl.h) 53 | add_library(pybind11_protobuf::pybind11_wrapped_proto_caster ALIAS 54 | pybind11_wrapped_proto_caster) 55 | 56 | target_link_libraries( 57 | pybind11_wrapped_proto_caster 58 | PUBLIC absl::flat_hash_map 59 | absl::flat_hash_set 60 | absl::hash 61 | absl::strings 62 | absl::optional 63 | protobuf::libprotobuf 64 | pybind11::pybind11) 65 | 66 | target_include_directories(pybind11_wrapped_proto_caster 67 | PUBLIC $) 68 | 69 | if(BUILD_TESTING) 70 | add_subdirectory(tests) 71 | endif() 72 | 73 | if(CMAKE_INSTALL_PYDIR) 74 | # Copying to two target directories for simplicity. It is currently unknown 75 | # how to determine here which copy is actually being used. 76 | install( 77 | TARGETS status_py_extension_stub ok_status_singleton 78 | EXPORT pybind11_abseilTargets 79 | LIBRARY DESTINATION ${CMAKE_INSTALL_PYDIR}/pybind11_abseil 80 | ARCHIVE DESTINATION ${CMAKE_INSTALL_PYDIR}/pybind11_abseil 81 | RUNTIME DESTINATION ${CMAKE_INSTALL_PYDIR}/pybind11_abseil) 82 | 83 | install( 84 | TARGETS status_py_extension_stub ok_status_singleton 85 | EXPORT pybind11_abseil_cppTargets 86 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 87 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 88 | RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}) 89 | endif() 90 | -------------------------------------------------------------------------------- /pybind11_protobuf/check_unknown_fields.cc: -------------------------------------------------------------------------------- 1 | #include "pybind11_protobuf/check_unknown_fields.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "absl/container/flat_hash_map.h" 9 | #include "absl/container/flat_hash_set.h" 10 | #include "absl/strings/str_cat.h" 11 | #include "absl/strings/str_join.h" 12 | #include "absl/strings/string_view.h" 13 | #include "absl/synchronization/mutex.h" 14 | #include "absl/types/optional.h" 15 | #include "google/protobuf/descriptor.h" 16 | #include "google/protobuf/message.h" 17 | #include "google/protobuf/unknown_field_set.h" 18 | #include "python/google/protobuf/proto_api.h" 19 | 20 | namespace pybind11_protobuf::check_unknown_fields { 21 | namespace { 22 | 23 | using AllowListSet = absl::flat_hash_set; 24 | using MayContainExtensionsMap = 25 | absl::flat_hash_map; 26 | 27 | AllowListSet* GetAllowList() { 28 | static auto* allow_list = new AllowListSet(); 29 | return allow_list; 30 | } 31 | 32 | std::string MakeAllowListKey( 33 | absl::string_view top_message_descriptor_full_name, 34 | absl::string_view unknown_field_parent_message_fqn) { 35 | return absl::StrCat(top_message_descriptor_full_name, ":", 36 | unknown_field_parent_message_fqn); 37 | } 38 | 39 | /// Recurses through the message Descriptor class looking for valid extensions. 40 | /// Stores the result to `memoized`. 41 | bool MessageMayContainExtensionsRecursive(const ::google::protobuf::Descriptor* descriptor, 42 | MayContainExtensionsMap* memoized) { 43 | if (descriptor->extension_range_count() > 0) return true; 44 | 45 | auto [it, inserted] = memoized->try_emplace(descriptor, false); 46 | if (!inserted) { 47 | return it->second; 48 | } 49 | 50 | for (int i = 0; i < descriptor->field_count(); i++) { 51 | auto* fd = descriptor->field(i); 52 | if (fd->cpp_type() != ::google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) continue; 53 | if (MessageMayContainExtensionsRecursive(fd->message_type(), memoized)) { 54 | (*memoized)[descriptor] = true; 55 | return true; 56 | } 57 | } 58 | 59 | return false; 60 | } 61 | 62 | bool MessageMayContainExtensionsMemoized(const ::google::protobuf::Descriptor* descriptor) { 63 | static auto* memoized = new MayContainExtensionsMap(); 64 | static absl::Mutex lock; 65 | absl::MutexLock l(&lock); 66 | return MessageMayContainExtensionsRecursive(descriptor, memoized); 67 | } 68 | 69 | struct HasUnknownFields { 70 | HasUnknownFields(const ::google::protobuf::python::PyProto_API* py_proto_api, 71 | const ::google::protobuf::Descriptor* root_descriptor) 72 | : py_proto_api(py_proto_api), root_descriptor(root_descriptor) {} 73 | 74 | std::string FieldFQN() const { return absl::StrJoin(field_fqn_parts, "."); } 75 | std::string FieldFQNWithFieldNumber() const { 76 | return field_fqn_parts.empty() 77 | ? absl::StrCat(unknown_field_number) 78 | : absl::StrCat(FieldFQN(), ".", unknown_field_number); 79 | } 80 | 81 | bool FindUnknownFieldsRecursive(const ::google::protobuf::Message* sub_message, 82 | uint32_t depth); 83 | 84 | std::string BuildErrorMessage() const; 85 | 86 | const ::google::protobuf::python::PyProto_API* py_proto_api; 87 | const ::google::protobuf::Descriptor* root_descriptor = nullptr; 88 | const ::google::protobuf::Descriptor* unknown_field_parent_descriptor = nullptr; 89 | std::vector field_fqn_parts; 90 | int unknown_field_number; 91 | }; 92 | 93 | /// Recurses through the message fields class looking for UnknownFields. 94 | bool HasUnknownFields::FindUnknownFieldsRecursive( 95 | const ::google::protobuf::Message* sub_message, uint32_t depth) { 96 | const ::google::protobuf::Reflection& reflection = *sub_message->GetReflection(); 97 | 98 | // If there are unknown fields, stop searching. 99 | const ::google::protobuf::UnknownFieldSet& unknown_field_set = 100 | reflection.GetUnknownFields(*sub_message); 101 | if (!unknown_field_set.empty()) { 102 | unknown_field_parent_descriptor = sub_message->GetDescriptor(); 103 | unknown_field_number = unknown_field_set.field(0).number(); 104 | 105 | // Stop only if the extension is known by Python. 106 | if (py_proto_api->GetDefaultDescriptorPool()->FindExtensionByNumber( 107 | unknown_field_parent_descriptor, unknown_field_number)) { 108 | field_fqn_parts.resize(depth); 109 | return true; 110 | } 111 | } 112 | 113 | // If this message does not include submessages which allow extensions, 114 | // then it cannot include unknown fields. 115 | if (!MessageMayContainExtensionsMemoized(sub_message->GetDescriptor())) { 116 | return false; 117 | } 118 | 119 | // Otherwise the method has to check all present fields, including 120 | // extensions to determine if they include unknown fields. 121 | std::vector present_fields; 122 | reflection.ListFields(*sub_message, &present_fields); 123 | 124 | for (const auto* field : present_fields) { 125 | if (field->cpp_type() != ::google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { 126 | continue; 127 | } 128 | if (field->is_repeated()) { 129 | int field_size = reflection.FieldSize(*sub_message, field); 130 | for (int i = 0; i != field_size; ++i) { 131 | if (FindUnknownFieldsRecursive( 132 | &reflection.GetRepeatedMessage(*sub_message, field, i), 133 | depth + 1U)) { 134 | field_fqn_parts[depth] = field->name(); 135 | return true; 136 | } 137 | } 138 | } else if (FindUnknownFieldsRecursive( 139 | &reflection.GetMessage(*sub_message, field), depth + 1U)) { 140 | field_fqn_parts[depth] = field->name(); 141 | return true; 142 | } 143 | } 144 | 145 | return false; 146 | } 147 | 148 | std::string HasUnknownFields::BuildErrorMessage() const { 149 | assert(unknown_field_parent_descriptor != nullptr); 150 | assert(root_descriptor != nullptr); 151 | 152 | std::string emsg = absl::StrCat( // 153 | "Proto Message of type ", root_descriptor->full_name(), 154 | " has an Unknown Field"); 155 | if (root_descriptor != unknown_field_parent_descriptor) { 156 | absl::StrAppend(&emsg, " with parent of type ", 157 | unknown_field_parent_descriptor->full_name()); 158 | } 159 | absl::StrAppend(&emsg, ": ", FieldFQNWithFieldNumber(), " (", 160 | root_descriptor->file()->name()); 161 | if (root_descriptor->file() != unknown_field_parent_descriptor->file()) { 162 | absl::StrAppend(&emsg, ", ", 163 | unknown_field_parent_descriptor->file()->name()); 164 | } 165 | absl::StrAppend( 166 | &emsg, 167 | "). Please add the required `cc_proto_library` `deps`. " 168 | "Only if there is no alternative to suppressing this error, use " 169 | "`pybind11_protobuf::AllowUnknownFieldsFor(\"", 170 | root_descriptor->full_name(), "\", \"", FieldFQN(), 171 | "\");` (Warning: suppressions may mask critical bugs.)"); 172 | 173 | return emsg; 174 | } 175 | 176 | } // namespace 177 | 178 | void AllowUnknownFieldsFor(absl::string_view top_message_descriptor_full_name, 179 | absl::string_view unknown_field_parent_message_fqn) { 180 | GetAllowList()->insert(MakeAllowListKey(top_message_descriptor_full_name, 181 | unknown_field_parent_message_fqn)); 182 | } 183 | 184 | absl::optional CheckRecursively( 185 | const ::google::protobuf::python::PyProto_API* py_proto_api, 186 | const ::google::protobuf::Message* message) { 187 | const auto* root_descriptor = message->GetDescriptor(); 188 | HasUnknownFields search{py_proto_api, root_descriptor}; 189 | if (!search.FindUnknownFieldsRecursive(message, 0u)) { 190 | return absl::nullopt; 191 | } 192 | if (GetAllowList()->count(MakeAllowListKey(root_descriptor->full_name(), 193 | search.FieldFQN())) != 0) { 194 | return absl::nullopt; 195 | } 196 | return search.BuildErrorMessage(); 197 | } 198 | 199 | } // namespace pybind11_protobuf::check_unknown_fields 200 | -------------------------------------------------------------------------------- /pybind11_protobuf/check_unknown_fields.h: -------------------------------------------------------------------------------- 1 | #ifndef PYBIND11_PROTOBUF_CHECK_UNKNOWN_FIELDS_H_ 2 | #define PYBIND11_PROTOBUF_CHECK_UNKNOWN_FIELDS_H_ 3 | 4 | #include 5 | 6 | #include "absl/strings/string_view.h" 7 | #include "absl/types/optional.h" 8 | #include "google/protobuf/message.h" 9 | #include "python/google/protobuf/proto_api.h" 10 | 11 | namespace pybind11_protobuf::check_unknown_fields { 12 | 13 | class ExtensionsWithUnknownFieldsPolicy { 14 | enum State { 15 | // Initial state. 16 | kWeakDisallow, 17 | 18 | // Primary use case: PyCLIF extensions might set this when being imported. 19 | kWeakEnableFallbackToSerializeParse, 20 | 21 | // Primary use case: `:disallow_extensions_with_unknown_fields` in `deps` 22 | // of a binary (or test). 23 | kStrongDisallow 24 | }; 25 | 26 | static State& GetStateSingleton() { 27 | static State singleton = kWeakDisallow; 28 | return singleton; 29 | } 30 | 31 | public: 32 | static void WeakEnableFallbackToSerializeParse() { 33 | State& policy = GetStateSingleton(); 34 | if (policy == kWeakDisallow) { 35 | policy = kWeakEnableFallbackToSerializeParse; 36 | } 37 | } 38 | 39 | static void StrongSetDisallow() { GetStateSingleton() = kStrongDisallow; } 40 | 41 | static bool UnknownFieldsAreDisallowed() { 42 | return GetStateSingleton() != kWeakEnableFallbackToSerializeParse; 43 | } 44 | }; 45 | 46 | void AllowUnknownFieldsFor(absl::string_view top_message_descriptor_full_name, 47 | absl::string_view unknown_field_parent_message_fqn); 48 | 49 | absl::optional CheckRecursively( 50 | const ::google::protobuf::python::PyProto_API* py_proto_api, 51 | const ::google::protobuf::Message* top_message); 52 | 53 | } // namespace pybind11_protobuf::check_unknown_fields 54 | 55 | #endif // PYBIND11_PROTOBUF_CHECK_UNKNOWN_FIELDS_H_ 56 | -------------------------------------------------------------------------------- /pybind11_protobuf/disallow_extensions_with_unknown_fields.cc: -------------------------------------------------------------------------------- 1 | #include "pybind11_protobuf/check_unknown_fields.h" 2 | 3 | namespace pybind11_protobuf::check_unknown_fields { 4 | namespace { 5 | 6 | static int kSetConfigDone = []() { 7 | ExtensionsWithUnknownFieldsPolicy::StrongSetDisallow(); 8 | return 0; 9 | }(); 10 | 11 | } // namespace 12 | } // namespace pybind11_protobuf::check_unknown_fields 13 | -------------------------------------------------------------------------------- /pybind11_protobuf/enum_type_caster.h: -------------------------------------------------------------------------------- 1 | // IWYU pragma: always_keep // Appears to be the best we can do at the moment. 2 | // What we really want (but does not work with Include Cleaner @ 2023-10-27): 3 | // * If native_proto_caster.h is included: suggest removing enum_type_caster.h 4 | // * If only enum_type_caster.h is included: always_keep 5 | 6 | #ifndef PYBIND11_PROTOBUF_ENUM_TYPE_CASTER_H_ 7 | #define PYBIND11_PROTOBUF_ENUM_TYPE_CASTER_H_ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include "google/protobuf/generated_enum_util.h" 17 | 18 | // pybind11 type_caster specialization which translates Proto::Enum types 19 | // to/from ints. This will have ODR conflicts when users specify wrappers for 20 | // enums using py::enum_. 21 | // 22 | // ::google::protobuf::is_proto_enum and ::google::protobuf::GetEnumDescriptor are required. 23 | // 24 | // NOTE: The protobuf compiler does not generate ::google::protobuf::is_proto_enum 25 | // traits for enumerations of oneof fields. 26 | // 27 | // Example: 28 | // #include 29 | // #include "pybind11_protobuf/proto_enum_casters.h" 30 | // 31 | // MyMessage::EnumType GetMessageEnum() { ... } 32 | // PYBIND11_MODULE(my_module, m) { 33 | // m.def("get_message_enum", &GetMessageEnum); 34 | // } 35 | // 36 | // Users of enum_type_caster based extensions need dependencies on: 37 | // deps = [ "@com_google_protobuf//:protobuf_python" ] 38 | // 39 | 40 | namespace pybind11_protobuf { 41 | 42 | // Implementation details for pybind11 enum casting. 43 | template 44 | struct enum_type_caster { 45 | private: 46 | using T = std::underlying_type_t; 47 | using base_caster = pybind11::detail::make_caster; 48 | 49 | public: 50 | static constexpr auto name = pybind11::detail::const_name(); 51 | 52 | // cast converts from C++ -> Python 53 | static pybind11::handle cast(EnumType src, 54 | pybind11::return_value_policy policy, 55 | pybind11::handle p) { 56 | return base_caster::cast(static_cast(src), policy, p); 57 | } 58 | 59 | // load converts from Python -> C++ 60 | bool load(pybind11::handle src, bool convert) { 61 | base_caster base; 62 | if (base.load(src, convert)) { 63 | T v = static_cast(base); 64 | // Behavior change 2023-07-19: Previously we only accept integers that 65 | // are valid values of the enum. 66 | value = static_cast(v); 67 | return true; 68 | } 69 | // When convert is true, then the enum could be resolved via 70 | // FindValueByName. 71 | return false; 72 | } 73 | 74 | explicit operator EnumType() { return value; } 75 | 76 | template 77 | using cast_op_type = EnumType; 78 | 79 | private: 80 | EnumType value; 81 | }; 82 | 83 | } // namespace pybind11_protobuf 84 | namespace pybind11 { 85 | namespace detail { 86 | 87 | // ADL function to enable/disable specializations of proto enum type_caster<> 88 | // provided by pybind11_protobuf. Defaults to enabled. To disable the 89 | // pybind11_protobuf enum_type_caster for a specific enum type, define a 90 | // constexpr function in the same namespace, like: 91 | // 92 | // constexpr bool pybind11_protobuf_enable_enum_type_caster(tensorflow::DType*) 93 | // { return false; } 94 | // 95 | constexpr bool pybind11_protobuf_enable_enum_type_caster(...) { return true; } 96 | 97 | #if defined(PYBIND11_HAS_NATIVE_ENUM) 98 | template 99 | struct type_caster_enum_type_enabled< 100 | EnumType, std::enable_if_t<(::google::protobuf::is_proto_enum::value && 101 | pybind11_protobuf_enable_enum_type_caster( 102 | static_cast(nullptr)))>> 103 | : std::false_type {}; 104 | #endif 105 | 106 | // Specialization of pybind11::detail::type_caster for types satisfying 107 | // ::google::protobuf::is_proto_enum. 108 | template 109 | struct type_caster::value && 111 | pybind11_protobuf_enable_enum_type_caster( 112 | static_cast(nullptr)))>> 113 | : public pybind11_protobuf::enum_type_caster {}; 114 | 115 | } // namespace detail 116 | } // namespace pybind11 117 | 118 | #endif // PYBIND11_PROTOBUF_ENUM_TYPE_CASTER_H_ 119 | -------------------------------------------------------------------------------- /pybind11_protobuf/native_proto_caster.h: -------------------------------------------------------------------------------- 1 | // IWYU pragma: always_keep // See pybind11/docs/type_caster_iwyu.rst 2 | 3 | #ifndef PYBIND11_PROTOBUF_NATIVE_PROTO_CASTERS_H_ 4 | #define PYBIND11_PROTOBUF_NATIVE_PROTO_CASTERS_H_ 5 | 6 | // pybind11 includes have to be at the very top, even before Python.h 7 | #include 8 | #include 9 | #include 10 | 11 | // IWYU 12 | #include 13 | 14 | #include 15 | 16 | #include "absl/strings/string_view.h" 17 | #include "google/protobuf/message.h" 18 | #include "pybind11_protobuf/enum_type_caster.h" 19 | #include "pybind11_protobuf/proto_caster_impl.h" 20 | 21 | // pybind11::type_caster<> specialization for ::google::protobuf::Message types 22 | // that that converts protocol buffer objects between C++ and python 23 | // representations. This binder supports binaries linked with both native python 24 | // protos and fast cpp python protos. 25 | // 26 | // When passing protos between python and C++, if possible, an underlying C++ 27 | // object may have ownership transferred, or may be copied if both instances 28 | // use the same underlying protocol buffer Reflection instance. If not, the 29 | // object is serialized and deserialized. 30 | // 31 | // Dynamically generated protos are only partially supported at present. 32 | // 33 | // To use native_proto_caster, include this file in the binding definition 34 | // file. 35 | // 36 | // Example: 37 | // 38 | // #include 39 | // #include "pybind11_protobuf/native_proto_caster.h" 40 | // 41 | // MyMessage GetMessage() { ... } 42 | // PYBIND11_MODULE(my_module, m) { 43 | // pybind11_protobuf::ImportNativeProtoCasters(); 44 | // 45 | // m.def("get_message", &GetMessage); 46 | // } 47 | // 48 | // Users of native_proto_caster based extensions need dependencies on: 49 | // deps = [ "@com_google_protobuf//:protobuf_python" ] 50 | // 51 | 52 | namespace pybind11_protobuf { 53 | 54 | // Imports modules for protobuf conversion. This not thread safe and 55 | // is required to be called from a PYBIND11_MODULE definition before use. 56 | inline void ImportNativeProtoCasters() { InitializePybindProtoCastUtil(); } 57 | 58 | inline void AllowUnknownFieldsFor( 59 | absl::string_view top_message_descriptor_full_name, 60 | absl::string_view unknown_field_parent_message_fqn) { 61 | } 62 | 63 | } // namespace pybind11_protobuf 64 | 65 | namespace pybind11 { 66 | namespace detail { 67 | 68 | // ADL function to enable/disable specializations of type_caster<> provided by 69 | // pybind11_protobuf. Defaults to enabled. To disable the pybind11_protobuf 70 | // proto_caster for a specific proto type, define a constexpr function in the 71 | // same namespace, like: 72 | // 73 | // constexpr bool pybind11_protobuf_enable_type_caster(MyProto*) 74 | // { return false; } 75 | // 76 | constexpr bool pybind11_protobuf_enable_type_caster(...) { return true; } 77 | 78 | // pybind11 type_caster<> specialization for c++ protocol buffer types using 79 | // inheritance from google::proto_caster<>. 80 | template 81 | struct type_caster< 82 | ProtoType, 83 | std::enable_if_t<(std::is_base_of<::google::protobuf::Message, ProtoType>::value && 84 | pybind11_protobuf_enable_type_caster( 85 | static_cast(nullptr)))>> 86 | : public pybind11_protobuf::proto_caster< 87 | ProtoType, pybind11_protobuf::native_cast_impl> {}; 88 | 89 | #if defined(PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT) 90 | 91 | template 92 | struct copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled< 93 | ProtoType, enable_if_t::value>> 94 | : std::false_type {}; 95 | 96 | template 97 | struct move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled< 98 | ProtoType, enable_if_t::value>> 99 | : std::false_type {}; 100 | 101 | #endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT 102 | 103 | // NOTE: If smart_holders becomes the default we will need to change this to 104 | // type_caster, ... 105 | // Until then using that form is ambiguous due to the existing specialization 106 | // that does *not* forward a sfinae clause. Or we could add an sfinae clause 107 | // to the existing specialization, but that's a *much* larger change. 108 | // Anyway, the existing specializations fully forward to these. 109 | 110 | // move_only_holder_caster enables using move-only holder types such as 111 | // std::unique_ptr. It uses type_caster to manage the conversion 112 | // and construct a holder type. 113 | template 114 | struct move_only_holder_caster< 115 | ProtoType, HolderType, 116 | std::enable_if_t<(std::is_base_of<::google::protobuf::Message, ProtoType>::value && 117 | pybind11_protobuf_enable_type_caster( 118 | static_cast(nullptr)))>> 119 | : public pybind11_protobuf::move_only_holder_caster_impl {}; 121 | 122 | // copyable_holder_caster enables using copyable holder types such as 123 | // std::shared_ptr. It uses type_caster to manage the conversion 124 | // and construct a copy of the proto, then returns the shared_ptr. 125 | // 126 | // NOTE: When using pybind11 bindings, std::shared_ptr is almost 127 | // never correct, as it always makes a copy. It's mostly useful for handling 128 | // methods that return a shared_ptr, which the caller never intends 129 | // to mutate and where copy semantics will work just as well. 130 | // 131 | template 132 | struct copyable_holder_caster< 133 | ProtoType, HolderType, 134 | std::enable_if_t<(std::is_base_of<::google::protobuf::Message, ProtoType>::value && 135 | pybind11_protobuf_enable_type_caster( 136 | static_cast(nullptr)))>> 137 | : public pybind11_protobuf::copyable_holder_caster_impl {}; 139 | 140 | // NOTE: We also need to add support and/or test classes: 141 | // 142 | // ::google::protobuf::Descriptor 143 | // ::google::protobuf::EnumDescriptor 144 | // ::google::protobuf::EnumValueDescriptor 145 | // ::google::protobuf::FieldDescriptor 146 | // 147 | 148 | } // namespace detail 149 | } // namespace pybind11 150 | 151 | #endif // PYBIND11_PROTOBUF_FAST_CPP_PROTO_CASTERS_H_ 152 | -------------------------------------------------------------------------------- /pybind11_protobuf/proto_cast_util.h: -------------------------------------------------------------------------------- 1 | // IWYU pragma: private, include "pybind11_protobuf/native_proto_caster.h" 2 | 3 | #ifndef PYBIND11_PROTOBUF_PROTO_CAST_UTIL_H_ 4 | #define PYBIND11_PROTOBUF_PROTO_CAST_UTIL_H_ 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "absl/strings/string_view.h" 13 | #include "absl/types/optional.h" 14 | #include "google/protobuf/descriptor.h" 15 | #include "google/protobuf/message.h" 16 | 17 | // PYBIND11_PROTOBUF_ASSUME_FULL_ABI_COMPATIBILITY can be defined by users 18 | // certain about ABI compatibility between all Python extensions in their 19 | // environment using protobufs. If defined, passing protos from Python to C++ 20 | // may skip serialization/deserialization. 21 | // Assuming full ABI compatibility means (roughly) that the following are 22 | // compatible between all involved Python extensions: 23 | // * Protobuf library versions. 24 | // * Compiler/linker & compiler/linker options. 25 | // #define PYBIND11_PROTOBUF_ASSUME_FULL_ABI_COMPATIBILITY 26 | 27 | namespace pybind11_protobuf { 28 | 29 | // Strips ".proto" or ".protodevel" from the end of a filename. 30 | // Similar to 31 | // https://github.com/protocolbuffers/protobuf/blob/b375d010bf57a6d673125330ec47f6e6a7e03f5c/src/google/protobuf/compiler/code_generator.cc#L129-L136 32 | // which is not public, unfortunately. Providing a public function here until 33 | // that situation changes. 34 | std::string StripProtoSuffixFromDescriptorFileName(absl::string_view filename); 35 | 36 | // Returns the Python module name expected for a given .proto filename. 37 | // Similar to 38 | // https://github.com/protocolbuffers/protobuf/blob/b375d010bf57a6d673125330ec47f6e6a7e03f5c/src/google/protobuf/compiler/python/helpers.cc#L31-L35 39 | // which is not public, unfortunately. Providing a public function here until 40 | // that situation changes. 41 | std::string InferPythonModuleNameFromDescriptorFileName( 42 | absl::string_view filename); 43 | 44 | // Simple helper. Caller has to ensure that the py_bytes argument outlives the 45 | // returned string_view. 46 | absl::string_view PyBytesAsStringView(pybind11::bytes py_bytes); 47 | 48 | // Initialize internal proto cast dependencies, which includes importing 49 | // various protobuf-related modules. 50 | void InitializePybindProtoCastUtil(); 51 | 52 | // Imports a module pertaining to a given ::google::protobuf::Descriptor, if possible. 53 | void ImportProtoDescriptorModule(const ::google::protobuf::Descriptor *); 54 | 55 | // Returns a ::google::protobuf::Message* from a cpp_fast_proto, if backed by C++. 56 | const ::google::protobuf::Message *PyProtoGetCppMessagePointer(pybind11::handle src); 57 | 58 | // Returns the protocol buffer's py_proto.DESCRIPTOR.full_name attribute. 59 | absl::optional PyProtoDescriptorFullName( 60 | pybind11::handle py_proto); 61 | 62 | // Returns true if py_proto full name matches descriptor full name. 63 | bool PyProtoHasMatchingFullName(pybind11::handle py_proto, 64 | const ::google::protobuf::Descriptor *descriptor); 65 | 66 | // Caller should enforce any type identity that is required. 67 | pybind11::bytes PyProtoSerializePartialToString(pybind11::handle py_proto, 68 | bool raise_if_error); 69 | 70 | // Allocates a C++ protocol buffer for a given name. 71 | std::unique_ptr<::google::protobuf::Message> AllocateCProtoFromPythonSymbolDatabase( 72 | pybind11::handle src, const std::string &full_name); 73 | 74 | // Serialize the py_proto and deserialize it into the provided message. 75 | // Caller should enforce any type identity that is required. 76 | void CProtoCopyToPyProto(::google::protobuf::Message *message, pybind11::handle py_proto); 77 | 78 | // Returns a handle to a python protobuf suitably 79 | pybind11::handle GenericFastCppProtoCast(::google::protobuf::Message *src, 80 | pybind11::return_value_policy policy, 81 | pybind11::handle parent, 82 | bool is_const); 83 | 84 | pybind11::handle GenericPyProtoCast(::google::protobuf::Message *src, 85 | pybind11::return_value_policy policy, 86 | pybind11::handle parent, bool is_const); 87 | 88 | pybind11::handle GenericProtoCast(::google::protobuf::Message *src, 89 | pybind11::return_value_policy policy, 90 | pybind11::handle parent, bool is_const); 91 | 92 | } // namespace pybind11_protobuf 93 | 94 | #endif // PYBIND11_PROTOBUF_PROTO_CAST_UTIL_H_ 95 | -------------------------------------------------------------------------------- /pybind11_protobuf/proto_caster_impl.h: -------------------------------------------------------------------------------- 1 | // IWYU pragma: private, include "pybind11_protobuf/native_proto_caster.h" 2 | 3 | #ifndef PYBIND11_PROTOBUF_PROTO_CASTER_IMPL_H_ 4 | #define PYBIND11_PROTOBUF_PROTO_CASTER_IMPL_H_ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "google/protobuf/descriptor.h" 17 | #include "google/protobuf/descriptor.pb.h" 18 | #include "google/protobuf/message.h" 19 | #include "pybind11_protobuf/proto_cast_util.h" 20 | 21 | // Enables unsafe conversions; currently these are a work in progress. 22 | #if !defined(PYBIND11_PROTOBUF_UNSAFE) 23 | #define PYBIND11_PROTOBUF_UNSAFE 0 24 | #endif 25 | 26 | namespace pybind11_protobuf { 27 | 28 | // pybind11 constructs c++ references using the following mechanism, for 29 | // example: 30 | // 31 | // type_caster caster; 32 | // caster.load(handle, /*convert=*/ false); 33 | // call(pybind11::detail::cast_op(caster)); 34 | // 35 | template 36 | struct proto_caster_load_impl { 37 | static_assert( 38 | std::is_same>::value, 39 | ""); 40 | 41 | // load converts from Python -> C++ 42 | bool load(pybind11::handle src, bool convert) { 43 | // When given a none, treat it as a nullptr. 44 | if (src.is_none()) { 45 | value = nullptr; 46 | return true; 47 | } 48 | // NOTE: We might need to know whether the proto has extensions that 49 | // are python-only. 50 | 51 | // Attempt to use the PyProto_API to get an underlying C++ message pointer 52 | // from the object. 53 | const ::google::protobuf::Message *message = 54 | pybind11_protobuf::PyProtoGetCppMessagePointer(src); 55 | if (message) { 56 | value = ::google::protobuf::DynamicCastToGenerated(message); 57 | if (value) { 58 | // If the capability were available, then we could probe PyProto_API and 59 | // allow c++ mutability based on the python reference count. 60 | return true; 61 | } 62 | } 63 | 64 | if (!PyProtoHasMatchingFullName(src, ProtoType::GetDescriptor())) { 65 | return false; 66 | } 67 | pybind11::bytes serialized_bytes = 68 | PyProtoSerializePartialToString(src, convert); 69 | if (!serialized_bytes) { 70 | return false; 71 | } 72 | 73 | owned = std::unique_ptr(new ProtoType()); 74 | value = owned.get(); 75 | return owned->ParsePartialFromString(PyBytesAsStringView(serialized_bytes)); 76 | } 77 | 78 | // ensure_owned ensures that the owned member contains a copy of the 79 | // ::google::protobuf::Message. 80 | void ensure_owned() { 81 | if (value && !owned) { 82 | owned = std::unique_ptr(value->New()); 83 | *owned = *value; 84 | value = owned.get(); 85 | } 86 | } 87 | 88 | const ProtoType *value; 89 | std::unique_ptr owned; 90 | }; 91 | 92 | template <> 93 | struct proto_caster_load_impl<::google::protobuf::Message> { 94 | using ProtoType = ::google::protobuf::Message; 95 | 96 | bool load(pybind11::handle src, bool convert) { 97 | if (src.is_none()) { 98 | value = nullptr; 99 | return true; 100 | } 101 | 102 | // Attempt to use the PyProto_API to get an underlying C++ message pointer 103 | // from the object. 104 | value = pybind11_protobuf::PyProtoGetCppMessagePointer(src); 105 | if (value) { 106 | return true; 107 | } 108 | 109 | // `src` is not a C++ proto instance from the generated_pool, 110 | // so create a compatible native C++ proto. 111 | auto descriptor_name = pybind11_protobuf::PyProtoDescriptorFullName(src); 112 | if (!descriptor_name) { 113 | return false; 114 | } 115 | pybind11::bytes serialized_bytes = 116 | PyProtoSerializePartialToString(src, convert); 117 | if (!serialized_bytes) { 118 | return false; 119 | } 120 | 121 | owned.reset(static_cast( 122 | pybind11_protobuf::AllocateCProtoFromPythonSymbolDatabase( 123 | src, *descriptor_name) 124 | .release())); 125 | value = owned.get(); 126 | return owned->ParsePartialFromString(PyBytesAsStringView(serialized_bytes)); 127 | } 128 | 129 | // ensure_owned ensures that the owned member contains a copy of the 130 | // ::google::protobuf::Message. 131 | void ensure_owned() { 132 | if (value && !owned) { 133 | owned = std::unique_ptr(value->New()); 134 | owned->CopyFrom(*value); 135 | value = owned.get(); 136 | } 137 | } 138 | 139 | const ::google::protobuf::Message *value; 140 | std::unique_ptr<::google::protobuf::Message> owned; 141 | }; 142 | 143 | struct fast_cpp_cast_impl { 144 | inline static pybind11::handle cast_impl(::google::protobuf::Message *src, 145 | pybind11::return_value_policy policy, 146 | pybind11::handle parent, 147 | bool is_const) { 148 | if (src == nullptr) return pybind11::none().release(); 149 | 150 | #if PYBIND11_PROTOBUF_UNSAFE 151 | if (is_const && 152 | (policy == pybind11::return_value_policy::reference || 153 | policy == pybind11::return_value_policy::reference_internal)) { 154 | throw pybind11::type_error( 155 | "Cannot return a const reference to a ::google::protobuf::Message " 156 | "derived " 157 | "type. Consider setting return_value_policy::copy in the " 158 | "pybind11 def()."); 159 | } 160 | #else 161 | // references are inherently unsafe, so convert them to copies. 162 | if (policy == pybind11::return_value_policy::reference || 163 | policy == pybind11::return_value_policy::reference_internal) { 164 | policy = pybind11::return_value_policy::copy; 165 | } 166 | #endif 167 | 168 | return pybind11_protobuf::GenericFastCppProtoCast(src, policy, parent, 169 | is_const); 170 | } 171 | }; 172 | 173 | struct native_cast_impl { 174 | inline static pybind11::handle cast_impl(::google::protobuf::Message *src, 175 | pybind11::return_value_policy policy, 176 | pybind11::handle parent, 177 | bool is_const) { 178 | if (src == nullptr) return pybind11::none().release(); 179 | 180 | // references are inherently unsafe, so convert them to copies. 181 | if (policy == pybind11::return_value_policy::reference || 182 | policy == pybind11::return_value_policy::reference_internal) { 183 | policy = pybind11::return_value_policy::copy; 184 | } 185 | 186 | return pybind11_protobuf::GenericProtoCast(src, policy, parent, false); 187 | } 188 | }; 189 | 190 | // pybind11 type_caster specialization for c++ protocol buffer types. 191 | template 192 | struct proto_caster : public proto_caster_load_impl, 193 | protected CastBase { 194 | private: 195 | using Loader = proto_caster_load_impl; 196 | using CastBase::cast_impl; 197 | using Loader::ensure_owned; 198 | using Loader::owned; 199 | using Loader::value; 200 | 201 | public: 202 | static constexpr auto name = pybind11::detail::const_name(); 203 | 204 | // cast converts from C++ -> Python 205 | // 206 | // return_value_policy handling differs from the behavior for 207 | // py::class_-wrapped objects because because protocol buffer objects usually 208 | // need to be copied across the C++/python boundary as they contain internal 209 | // pointers which are unsafe to modify. See: 210 | // https://pybind11.readthedocs.io/en/stable/advanced/functions.html#return-value-policies 211 | static pybind11::handle cast(ProtoType &&src, 212 | pybind11::return_value_policy policy, 213 | pybind11::handle parent) { 214 | return cast_impl(&src, pybind11::return_value_policy::move, parent, false); 215 | } 216 | 217 | static pybind11::handle cast(const ProtoType *src, 218 | pybind11::return_value_policy policy, 219 | pybind11::handle parent) { 220 | std::unique_ptr wrapper; 221 | if (policy == pybind11::return_value_policy::automatic || 222 | policy == pybind11::return_value_policy::automatic_reference) { 223 | policy = pybind11::return_value_policy::copy; 224 | } else if (policy == pybind11::return_value_policy::take_ownership) { 225 | wrapper.reset(src); 226 | } 227 | return cast_impl(const_cast(src), policy, parent, true); 228 | } 229 | 230 | static pybind11::handle cast(ProtoType *src, 231 | pybind11::return_value_policy policy, 232 | pybind11::handle parent) { 233 | std::unique_ptr wrapper; 234 | if (policy == pybind11::return_value_policy::automatic_reference) { 235 | policy = pybind11::return_value_policy::copy; 236 | } else if (policy == pybind11::return_value_policy::automatic || 237 | policy == pybind11::return_value_policy::take_ownership) { 238 | policy = pybind11::return_value_policy::take_ownership; 239 | wrapper.reset(src); 240 | } 241 | return cast_impl(src, policy, parent, false); 242 | } 243 | 244 | static pybind11::handle cast(ProtoType const &src, 245 | pybind11::return_value_policy policy, 246 | pybind11::handle parent) { 247 | if (policy == pybind11::return_value_policy::automatic || 248 | policy == pybind11::return_value_policy::automatic_reference) { 249 | policy = pybind11::return_value_policy::copy; 250 | } 251 | return cast_impl(const_cast(&src), policy, parent, true); 252 | } 253 | 254 | static pybind11::handle cast(ProtoType &src, 255 | pybind11::return_value_policy policy, 256 | pybind11::handle parent) { 257 | if (policy == pybind11::return_value_policy::automatic || 258 | policy == pybind11::return_value_policy::automatic_reference) { 259 | policy = pybind11::return_value_policy::copy; 260 | } 261 | return cast_impl(&src, policy, parent, false); 262 | } 263 | 264 | std::unique_ptr as_unique_ptr() { 265 | ensure_owned(); 266 | return std::move(owned); 267 | } 268 | 269 | // PYBIND11_TYPE_CASTER 270 | explicit operator const ProtoType *() { return value; } 271 | explicit operator const ProtoType &() { 272 | if (!value) throw pybind11::reference_cast_error(); 273 | return *value; 274 | } 275 | explicit operator ProtoType &&() && { 276 | if (!value) throw pybind11::reference_cast_error(); 277 | ensure_owned(); 278 | return std::move(*owned); 279 | } 280 | explicit operator const ProtoType &&() { 281 | if (!value) throw pybind11::reference_cast_error(); 282 | ensure_owned(); 283 | return std::move(*owned); 284 | } 285 | #if PYBIND11_PROTOBUF_UNSAFE 286 | // The following unsafe conversions are not enabled: 287 | explicit operator ProtoType *() { return const_cast(value); } 288 | explicit operator ProtoType &() { 289 | if (!value) throw pybind11::reference_cast_error(); 290 | return *const_cast(value); 291 | } 292 | #endif 293 | 294 | // cast_op_type determines which operator overload to call for a given c++ 295 | // input parameter type. 296 | // clang-format off 297 | template 298 | using cast_op_type = 299 | std::conditional_t< 300 | std::is_same, const ProtoType *>::value, 301 | const ProtoType *, 302 | std::conditional_t< 303 | std::is_same, ProtoType *>::value, 304 | ProtoType *, 305 | std::conditional_t< 306 | std::is_same::value, 307 | const ProtoType &, 308 | std::conditional_t< 309 | std::is_same::value, 310 | ProtoType &, 311 | T_>>>>; // Fall back to `T_` (assumed `T&&`). 312 | // clang-format on 313 | }; 314 | 315 | // move_only_holder_caster enables using move-only holder types such as 316 | // std::unique_ptr. It uses type_caster to manage the conversion 317 | // and construct a holder type. 318 | template 319 | struct move_only_holder_caster_impl { 320 | private: 321 | using Base = 322 | pybind11::detail::type_caster>; 323 | static constexpr bool const_element = 324 | std::is_const::value; 325 | 326 | public: 327 | static constexpr auto name = Base::name; 328 | 329 | // C++->Python. 330 | static pybind11::handle cast(HolderType &&src, pybind11::return_value_policy, 331 | pybind11::handle p) { 332 | auto *ptr = pybind11::detail::holder_helper::get(src); 333 | if (!ptr) return pybind11::none().release(); 334 | return Base::cast(std::move(*ptr), pybind11::return_value_policy::move, p); 335 | } 336 | 337 | // Convert Python->C++. 338 | bool load(pybind11::handle src, bool convert) { 339 | Base base; 340 | if (!base.load(src, convert)) { 341 | return false; 342 | } 343 | holder = base.as_unique_ptr(); 344 | return true; 345 | } 346 | 347 | // PYBIND11_TYPE_CASTER 348 | explicit operator HolderType *() { return &holder; } 349 | explicit operator HolderType &() { return holder; } 350 | explicit operator HolderType &&() && { return std::move(holder); } 351 | 352 | template 353 | using cast_op_type = pybind11::detail::movable_cast_op_type; 354 | 355 | protected: 356 | HolderType holder; 357 | }; 358 | 359 | // copyable_holder_caster enables using copyable holder types such 360 | // as std::shared_ptr. It uses type_caster to manage the 361 | // conversion and construct a copy of the proto, then returns the 362 | // shared_ptr. 363 | // 364 | // NOTE: When using pybind11 bindings, std::shared_ptr is 365 | // almost never correct, as it always makes a copy. It's mostly 366 | // useful for handling methods that return a shared_ptr, 367 | // which the caller never intends to mutate and where copy semantics 368 | // will work just as well. 369 | // 370 | template 371 | struct copyable_holder_caster_impl { 372 | private: 373 | using Base = 374 | pybind11::detail::type_caster>; 375 | static constexpr bool const_element = 376 | std::is_const::value; 377 | 378 | public: 379 | static constexpr auto name = Base::name; 380 | 381 | // C++->Python. 382 | static pybind11::handle cast(const HolderType &src, 383 | pybind11::return_value_policy, 384 | pybind11::handle p) { 385 | // The default path calls into cast_holder so that the 386 | // holder/deleter gets added to the proto. Here we just make a 387 | // copy 388 | const auto *ptr = pybind11::detail::holder_helper::get(src); 389 | if (!ptr) return pybind11::none().release(); 390 | return Base::cast(*ptr, pybind11::return_value_policy::copy, p); 391 | } 392 | 393 | // Convert Python->C++. 394 | bool load(pybind11::handle src, bool convert) { 395 | Base base; 396 | if (!base.load(src, convert)) { 397 | return false; 398 | } 399 | // This always makes a copy, but it could, in some cases, grab a 400 | // reference and construct a shared_ptr, since the intention is 401 | // clearly to mutate the existing object... 402 | holder = base.as_unique_ptr(); 403 | return true; 404 | } 405 | 406 | explicit operator HolderType &() { return holder; } 407 | 408 | template 409 | using cast_op_type = HolderType &; 410 | 411 | protected: 412 | HolderType holder; 413 | }; 414 | } // namespace pybind11_protobuf 415 | 416 | #endif // PYBIND11_PROTOBUF_PROTO_CASTER_IMPL_H_ 417 | -------------------------------------------------------------------------------- /pybind11_protobuf/proto_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Pybind Development Team. All rights reserved. 2 | // 3 | // All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | #ifndef PYBIND11_PROTOBUF_PROTO_UTILS_H_ 7 | #define PYBIND11_PROTOBUF_PROTO_UTILS_H_ 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include "google/protobuf/message.h" 14 | 15 | namespace pybind11 { 16 | namespace google { 17 | 18 | // Initializes the fields in the given message from the the keyword args. 19 | // Unlike ProtoSetField, this allows setting message, map and repeated fields. 20 | void ProtoInitFields(::google::protobuf::Message* message, kwargs kwargs_in); 21 | 22 | // Wrapper around ::google::protobuf::Message::CopyFrom which can efficiently 23 | // copy from either a wrapped C++ or native python proto. Throws an error if 24 | // `other` is not a proto of the correct type. 25 | void ProtoCopyFrom(::google::protobuf::Message* msg, handle other); 26 | 27 | // Allocate and return the ProtoType given by the template argument. 28 | // py_proto is not used in this version, but is used by a specialization below. 29 | template 30 | std::unique_ptr PyProtoAllocateMessage(handle py_proto = handle(), 31 | kwargs kwargs_in = kwargs()) { 32 | auto message = std::make_unique(); 33 | ProtoInitFields(message.get(), kwargs_in); 34 | return message; 35 | } 36 | 37 | // Specialization for the case that the template argument is a generic message. 38 | // The type is pulled from the py_proto, which can be a native python proto, 39 | // a wrapped C proto, or a string with the full type name. 40 | template <> 41 | std::unique_ptr<::google::protobuf::Message> PyProtoAllocateMessage(handle py_proto, 42 | kwargs kwargs_in); 43 | 44 | // Allocate a C++ proto of the same type as py_proto and copy the contents 45 | // from py_proto. This works whether py_proto is a native or wrapped proto. 46 | // If expected_type is given and the type in py_proto does not match it, an 47 | // invalid argument error will be thrown. 48 | template 49 | std::unique_ptr PyProtoAllocateAndCopyMessage(handle py_proto) { 50 | auto new_msg = PyProtoAllocateMessage(py_proto); 51 | ProtoCopyFrom(new_msg.get(), py_proto); 52 | return new_msg; 53 | } 54 | 55 | } // namespace google 56 | } // namespace pybind11 57 | 58 | #endif // PYBIND11_PROTOBUF_PROTO_UTILS_H_ 59 | -------------------------------------------------------------------------------- /pybind11_protobuf/requirements/BUILD: -------------------------------------------------------------------------------- 1 | package( 2 | default_visibility = ["//visibility:private"], 3 | ) 4 | 5 | load("@python//3.12:defs.bzl", compile_pip_requirements_3_12 = "compile_pip_requirements") 6 | load("@python//3.11:defs.bzl", compile_pip_requirements_3_11 = "compile_pip_requirements") 7 | load("@python//3.10:defs.bzl", compile_pip_requirements_3_10 = "compile_pip_requirements") 8 | load("@python//3.9:defs.bzl", compile_pip_requirements_3_9 = "compile_pip_requirements") 9 | load("@python//3.8:defs.bzl", compile_pip_requirements_3_8 = "compile_pip_requirements") 10 | compile_pip_requirements_3_12( 11 | name = "requirements_3_12", 12 | src = "requirements.in", 13 | requirements_txt = "requirements_lock_3_12.txt", 14 | ) 15 | compile_pip_requirements_3_11( 16 | name = "requirements_3_11", 17 | src = "requirements.in", 18 | requirements_txt = "requirements_lock_3_11.txt", 19 | ) 20 | compile_pip_requirements_3_10( 21 | name = "requirements_3_10", 22 | src = "requirements.in", 23 | requirements_txt = "requirements_lock_3_10.txt", 24 | ) 25 | compile_pip_requirements_3_9( 26 | name = "requirements_3_9", 27 | src = "requirements.in", 28 | requirements_txt = "requirements_lock_3_9.txt", 29 | ) 30 | compile_pip_requirements_3_8( 31 | name = "requirements_3_8", 32 | src = "requirements.in", 33 | requirements_txt = "requirements_lock_3_8.txt", 34 | ) 35 | -------------------------------------------------------------------------------- /pybind11_protobuf/requirements/requirements.in: -------------------------------------------------------------------------------- 1 | absl-py==2.1.0 2 | protobuf==5.29.2 3 | -------------------------------------------------------------------------------- /pybind11_protobuf/requirements/requirements_lock_3_10.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.10 3 | # by the following command: 4 | # 5 | # bazel run //pybind11_protobuf/requirements:requirements_3_10.update 6 | # 7 | absl-py==2.1.0 \ 8 | --hash=sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308 \ 9 | --hash=sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff 10 | # via -r pybind11_protobuf/requirements/requirements.in 11 | protobuf==5.29.2 \ 12 | --hash=sha256:13d6d617a2a9e0e82a88113d7191a1baa1e42c2cc6f5f1398d3b054c8e7e714a \ 13 | --hash=sha256:2d2e674c58a06311c8e99e74be43e7f3a8d1e2b2fdf845eaa347fbd866f23355 \ 14 | --hash=sha256:36000f97ea1e76e8398a3f02936aac2a5d2b111aae9920ec1b769fc4a222c4d9 \ 15 | --hash=sha256:494229ecd8c9009dd71eda5fd57528395d1eacdf307dbece6c12ad0dd09e912e \ 16 | --hash=sha256:842de6d9241134a973aab719ab42b008a18a90f9f07f06ba480df268f86432f9 \ 17 | --hash=sha256:a0c53d78383c851bfa97eb42e3703aefdc96d2036a41482ffd55dc5f529466eb \ 18 | --hash=sha256:b2cc8e8bb7c9326996f0e160137b0861f1a82162502658df2951209d0cb0309e \ 19 | --hash=sha256:b6b0d416bbbb9d4fbf9d0561dbfc4e324fd522f61f7af0fe0f282ab67b22477e \ 20 | --hash=sha256:c12ba8249f5624300cf51c3d0bfe5be71a60c63e4dcf51ffe9a68771d958c851 \ 21 | --hash=sha256:e621a98c0201a7c8afe89d9646859859be97cb22b8bf1d8eacfd90d5bda2eb19 \ 22 | --hash=sha256:fde4554c0e578a5a0bcc9a276339594848d1e89f9ea47b4427c80e5d72f90181 23 | # via -r pybind11_protobuf/requirements/requirements.in 24 | -------------------------------------------------------------------------------- /pybind11_protobuf/requirements/requirements_lock_3_11.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # bazel run //pybind11_protobuf/requirements:requirements_3_11.update 6 | # 7 | absl-py==2.1.0 \ 8 | --hash=sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308 \ 9 | --hash=sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff 10 | # via -r pybind11_protobuf/requirements/requirements.in 11 | protobuf==5.29.2 \ 12 | --hash=sha256:13d6d617a2a9e0e82a88113d7191a1baa1e42c2cc6f5f1398d3b054c8e7e714a \ 13 | --hash=sha256:2d2e674c58a06311c8e99e74be43e7f3a8d1e2b2fdf845eaa347fbd866f23355 \ 14 | --hash=sha256:36000f97ea1e76e8398a3f02936aac2a5d2b111aae9920ec1b769fc4a222c4d9 \ 15 | --hash=sha256:494229ecd8c9009dd71eda5fd57528395d1eacdf307dbece6c12ad0dd09e912e \ 16 | --hash=sha256:842de6d9241134a973aab719ab42b008a18a90f9f07f06ba480df268f86432f9 \ 17 | --hash=sha256:a0c53d78383c851bfa97eb42e3703aefdc96d2036a41482ffd55dc5f529466eb \ 18 | --hash=sha256:b2cc8e8bb7c9326996f0e160137b0861f1a82162502658df2951209d0cb0309e \ 19 | --hash=sha256:b6b0d416bbbb9d4fbf9d0561dbfc4e324fd522f61f7af0fe0f282ab67b22477e \ 20 | --hash=sha256:c12ba8249f5624300cf51c3d0bfe5be71a60c63e4dcf51ffe9a68771d958c851 \ 21 | --hash=sha256:e621a98c0201a7c8afe89d9646859859be97cb22b8bf1d8eacfd90d5bda2eb19 \ 22 | --hash=sha256:fde4554c0e578a5a0bcc9a276339594848d1e89f9ea47b4427c80e5d72f90181 23 | # via -r pybind11_protobuf/requirements/requirements.in 24 | -------------------------------------------------------------------------------- /pybind11_protobuf/requirements/requirements_lock_3_12.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.12 3 | # by the following command: 4 | # 5 | # bazel run //pybind11_protobuf/requirements:requirements_3_12.update 6 | # 7 | absl-py==2.1.0 \ 8 | --hash=sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308 \ 9 | --hash=sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff 10 | # via -r pybind11_protobuf/requirements/requirements.in 11 | protobuf==5.29.2 \ 12 | --hash=sha256:13d6d617a2a9e0e82a88113d7191a1baa1e42c2cc6f5f1398d3b054c8e7e714a \ 13 | --hash=sha256:2d2e674c58a06311c8e99e74be43e7f3a8d1e2b2fdf845eaa347fbd866f23355 \ 14 | --hash=sha256:36000f97ea1e76e8398a3f02936aac2a5d2b111aae9920ec1b769fc4a222c4d9 \ 15 | --hash=sha256:494229ecd8c9009dd71eda5fd57528395d1eacdf307dbece6c12ad0dd09e912e \ 16 | --hash=sha256:842de6d9241134a973aab719ab42b008a18a90f9f07f06ba480df268f86432f9 \ 17 | --hash=sha256:a0c53d78383c851bfa97eb42e3703aefdc96d2036a41482ffd55dc5f529466eb \ 18 | --hash=sha256:b2cc8e8bb7c9326996f0e160137b0861f1a82162502658df2951209d0cb0309e \ 19 | --hash=sha256:b6b0d416bbbb9d4fbf9d0561dbfc4e324fd522f61f7af0fe0f282ab67b22477e \ 20 | --hash=sha256:c12ba8249f5624300cf51c3d0bfe5be71a60c63e4dcf51ffe9a68771d958c851 \ 21 | --hash=sha256:e621a98c0201a7c8afe89d9646859859be97cb22b8bf1d8eacfd90d5bda2eb19 \ 22 | --hash=sha256:fde4554c0e578a5a0bcc9a276339594848d1e89f9ea47b4427c80e5d72f90181 23 | # via -r pybind11_protobuf/requirements/requirements.in 24 | -------------------------------------------------------------------------------- /pybind11_protobuf/requirements/requirements_lock_3_8.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.8 3 | # by the following command: 4 | # 5 | # bazel run //pybind11_protobuf/requirements:requirements_3_8.update 6 | # 7 | absl-py==2.1.0 \ 8 | --hash=sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308 \ 9 | --hash=sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff 10 | # via -r pybind11_protobuf/requirements/requirements.in 11 | protobuf==5.29.2 \ 12 | --hash=sha256:13d6d617a2a9e0e82a88113d7191a1baa1e42c2cc6f5f1398d3b054c8e7e714a \ 13 | --hash=sha256:2d2e674c58a06311c8e99e74be43e7f3a8d1e2b2fdf845eaa347fbd866f23355 \ 14 | --hash=sha256:36000f97ea1e76e8398a3f02936aac2a5d2b111aae9920ec1b769fc4a222c4d9 \ 15 | --hash=sha256:494229ecd8c9009dd71eda5fd57528395d1eacdf307dbece6c12ad0dd09e912e \ 16 | --hash=sha256:842de6d9241134a973aab719ab42b008a18a90f9f07f06ba480df268f86432f9 \ 17 | --hash=sha256:a0c53d78383c851bfa97eb42e3703aefdc96d2036a41482ffd55dc5f529466eb \ 18 | --hash=sha256:b2cc8e8bb7c9326996f0e160137b0861f1a82162502658df2951209d0cb0309e \ 19 | --hash=sha256:b6b0d416bbbb9d4fbf9d0561dbfc4e324fd522f61f7af0fe0f282ab67b22477e \ 20 | --hash=sha256:c12ba8249f5624300cf51c3d0bfe5be71a60c63e4dcf51ffe9a68771d958c851 \ 21 | --hash=sha256:e621a98c0201a7c8afe89d9646859859be97cb22b8bf1d8eacfd90d5bda2eb19 \ 22 | --hash=sha256:fde4554c0e578a5a0bcc9a276339594848d1e89f9ea47b4427c80e5d72f90181 23 | # via -r pybind11_protobuf/requirements/requirements.in 24 | -------------------------------------------------------------------------------- /pybind11_protobuf/requirements/requirements_lock_3_9.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.9 3 | # by the following command: 4 | # 5 | # bazel run //pybind11_protobuf/requirements:requirements_3_9.update 6 | # 7 | absl-py==2.1.0 \ 8 | --hash=sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308 \ 9 | --hash=sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff 10 | # via -r pybind11_protobuf/requirements/requirements.in 11 | protobuf==5.29.2 \ 12 | --hash=sha256:13d6d617a2a9e0e82a88113d7191a1baa1e42c2cc6f5f1398d3b054c8e7e714a \ 13 | --hash=sha256:2d2e674c58a06311c8e99e74be43e7f3a8d1e2b2fdf845eaa347fbd866f23355 \ 14 | --hash=sha256:36000f97ea1e76e8398a3f02936aac2a5d2b111aae9920ec1b769fc4a222c4d9 \ 15 | --hash=sha256:494229ecd8c9009dd71eda5fd57528395d1eacdf307dbece6c12ad0dd09e912e \ 16 | --hash=sha256:842de6d9241134a973aab719ab42b008a18a90f9f07f06ba480df268f86432f9 \ 17 | --hash=sha256:a0c53d78383c851bfa97eb42e3703aefdc96d2036a41482ffd55dc5f529466eb \ 18 | --hash=sha256:b2cc8e8bb7c9326996f0e160137b0861f1a82162502658df2951209d0cb0309e \ 19 | --hash=sha256:b6b0d416bbbb9d4fbf9d0561dbfc4e324fd522f61f7af0fe0f282ab67b22477e \ 20 | --hash=sha256:c12ba8249f5624300cf51c3d0bfe5be71a60c63e4dcf51ffe9a68771d958c851 \ 21 | --hash=sha256:e621a98c0201a7c8afe89d9646859859be97cb22b8bf1d8eacfd90d5bda2eb19 \ 22 | --hash=sha256:fde4554c0e578a5a0bcc9a276339594848d1e89f9ea47b4427c80e5d72f90181 23 | # via -r pybind11_protobuf/requirements/requirements.in 24 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/BUILD: -------------------------------------------------------------------------------- 1 | # Tests and examples for the pybind11_protobuf package. 2 | 3 | load("@com_github_grpc_grpc//bazel:python_rules.bzl", "py_proto_library") 4 | 5 | load("@pybind11_bazel//:build_defs.bzl", "pybind_extension") 6 | 7 | # [internal] load cc_proto_library.bzl 8 | load("@pypi//:requirements.bzl", "requirement") 9 | 10 | licenses(["notice"]) 11 | 12 | py_library( 13 | name = "compare", 14 | srcs = ["compare.py"], 15 | visibility = ["//visibility:public"], 16 | deps = ["@com_google_protobuf//:protobuf_python"], 17 | ) 18 | 19 | proto_library( 20 | name = "test_proto", 21 | srcs = ["test.proto"], 22 | ) 23 | 24 | cc_proto_library( 25 | name = "test_cc_proto", 26 | deps = [":test_proto"], 27 | ) 28 | 29 | py_proto_library( 30 | name = "test_py_pb2", 31 | deps = [":test_proto"], 32 | ) 33 | 34 | proto_library( 35 | name = "extension_proto", 36 | srcs = ["extension.proto"], 37 | deps = [":test_proto"], 38 | ) 39 | 40 | cc_proto_library( 41 | name = "extension_cc_proto", 42 | deps = [":extension_proto"], 43 | ) 44 | 45 | py_proto_library( 46 | name = "extension_py_pb2", 47 | deps = [":extension_proto"], 48 | ) 49 | 50 | proto_library( 51 | name = "extension_nest_repeated_proto", 52 | srcs = ["extension_nest_repeated.proto"], 53 | deps = [":extension_proto"], 54 | ) 55 | 56 | cc_proto_library( 57 | name = "extension_nest_repeated_cc_proto", 58 | deps = [":extension_nest_repeated_proto"], 59 | ) 60 | 61 | py_proto_library( 62 | name = "extension_nest_repeated_py_pb2", 63 | deps = [":extension_nest_repeated_proto"], 64 | ) 65 | 66 | proto_library( 67 | name = "extension_in_other_file_in_deps_proto", 68 | srcs = ["extension_in_other_file_in_deps.proto"], 69 | deps = [":extension_proto"], 70 | ) 71 | 72 | cc_proto_library( 73 | name = "extension_in_other_file_in_deps_cc_proto", 74 | deps = [":extension_in_other_file_in_deps_proto"], 75 | ) 76 | 77 | py_proto_library( 78 | name = "extension_in_other_file_in_deps_py_pb2", 79 | deps = [":extension_in_other_file_in_deps_proto"], 80 | ) 81 | 82 | proto_library( 83 | name = "extension_in_other_file_proto", 84 | srcs = ["extension_in_other_file.proto"], 85 | deps = [":extension_proto"], 86 | ) 87 | 88 | # Intentionally omitted (but here as a comment for easy manual validation of expected behavior): 89 | # cc_proto_library( 90 | # name = "extension_in_other_file_cc_proto", 91 | # deps = [":extension_in_other_file_proto"], 92 | # ) 93 | 94 | py_proto_library( 95 | name = "extension_in_other_file_py_pb2", 96 | deps = [":extension_in_other_file_proto"], 97 | ) 98 | 99 | proto_library( 100 | name = "we-love-dashes_proto", 101 | srcs = ["we-love-dashes.proto"], 102 | ) 103 | 104 | cc_proto_library( 105 | name = "we_love_dashes_cc_proto", 106 | deps = [":we-love-dashes_proto"], 107 | ) 108 | 109 | # Tests for enum_type_caster 110 | 111 | pybind_extension( 112 | name = "proto_enum_module", 113 | srcs = ["proto_enum_module.cc"], 114 | deps = [ 115 | ":test_cc_proto", 116 | "//pybind11_protobuf:enum_type_caster", 117 | ], 118 | ) 119 | 120 | py_test( 121 | name = "proto_enum_test", 122 | srcs = ["proto_enum_test.py"], 123 | data = [":proto_enum_module.so"], 124 | deps = [ 125 | ":test_py_pb2", 126 | "@com_google_absl_py//absl/testing:absltest", 127 | "@com_google_protobuf//:protobuf_python", 128 | requirement("absl_py"), 129 | ], 130 | ) 131 | 132 | # Tests for native_proto_caster 133 | 134 | pybind_extension( 135 | name = "dynamic_message_module", 136 | srcs = ["dynamic_message_module.cc"], 137 | deps = [ 138 | "//pybind11_protobuf:native_proto_caster", 139 | "@com_google_protobuf//:protobuf", 140 | ], 141 | ) 142 | 143 | py_test( 144 | name = "dynamic_message_test", 145 | srcs = ["dynamic_message_test.py"], 146 | data = [":dynamic_message_module.so"], 147 | deps = [ 148 | ":compare", 149 | ":test_py_pb2", 150 | "@com_google_protobuf//:protobuf_python", 151 | "@com_google_absl_py//absl/testing:absltest", 152 | "@com_google_absl_py//absl/testing:parameterized", 153 | requirement("absl_py"), 154 | ], 155 | ) 156 | 157 | pybind_extension( 158 | name = "extension_module", 159 | srcs = ["extension_module.cc"], 160 | deps = [ 161 | ":extension_cc_proto", 162 | # Intentionally omitted: ":extension_in_other_file_cc_proto", 163 | ":extension_in_other_file_in_deps_cc_proto", 164 | ":extension_nest_repeated_cc_proto", 165 | ":test_cc_proto", 166 | "@com_google_protobuf//:protobuf", 167 | "//pybind11_protobuf:native_proto_caster", 168 | ], 169 | ) 170 | 171 | EXTENSION_TEST_DEPS_COMMON = [ 172 | ":extension_in_other_file_in_deps_py_pb2", 173 | ":extension_in_other_file_py_pb2", 174 | ":extension_nest_repeated_py_pb2", 175 | ":extension_py_pb2", 176 | ":test_py_pb2", # fixdeps: keep - Direct dependency needed in open-source version, see https://github.com/grpc/grpc/issues/22811 177 | "@com_google_absl_py//absl/testing:absltest", 178 | "@com_google_absl_py//absl/testing:parameterized", 179 | ] 180 | 181 | py_test( 182 | name = "extension_test", 183 | srcs = ["extension_test.py"], 184 | data = [ 185 | ":extension_module.so", 186 | ":proto_enum_module.so", 187 | ], 188 | deps = EXTENSION_TEST_DEPS_COMMON + ["@com_google_protobuf//:protobuf_python"], 189 | ) 190 | 191 | pybind_extension( 192 | name = "message_module", 193 | srcs = ["message_module.cc"], 194 | deps = [ 195 | ":test_cc_proto", 196 | "//pybind11_protobuf:native_proto_caster", 197 | "@com_google_protobuf//:protobuf", 198 | ], 199 | ) 200 | 201 | py_test( 202 | name = "message_test", 203 | srcs = ["message_test.py"], 204 | data = [ 205 | ":extension_module.so", 206 | ":message_module.so", 207 | ":proto_enum_module.so", 208 | ], 209 | deps = [ 210 | ":compare", 211 | ":test_py_pb2", 212 | "@com_google_protobuf//:protobuf_python", 213 | "@com_google_absl_py//absl/testing:absltest", 214 | "@com_google_absl_py//absl/testing:parameterized", 215 | requirement("absl_py"), 216 | ], 217 | ) 218 | 219 | pybind_extension( 220 | name = "pass_by_module", 221 | srcs = ["pass_by_module.cc"], 222 | deps = [ 223 | ":test_cc_proto", 224 | "//pybind11_protobuf:native_proto_caster", 225 | "@com_google_absl//absl/types:optional", 226 | "@com_google_absl//absl/types:variant", 227 | "@com_google_protobuf//:protobuf", 228 | "@pybind11_abseil//pybind11_abseil:absl_casters", 229 | ], 230 | ) 231 | 232 | py_test( 233 | name = "pass_by_test", 234 | srcs = ["pass_by_test.py"], 235 | data = [":pass_by_module.so"], 236 | deps = [ 237 | ":test_py_pb2", 238 | "@com_google_absl_py//absl/testing:absltest", 239 | "@com_google_absl_py//absl/testing:parameterized", 240 | "@com_google_protobuf//:protobuf_python", 241 | requirement("absl_py"), 242 | ], 243 | ) 244 | 245 | # Externally this is currently only built but not used. 246 | pybind_extension( 247 | name = "pass_proto2_message_module", 248 | srcs = ["pass_proto2_message_module.cc"], 249 | deps = [ 250 | "//pybind11_protobuf:native_proto_caster", 251 | "@com_google_protobuf//:protobuf", 252 | ], 253 | ) 254 | 255 | # tests for wrapped_proto_caster 256 | 257 | pybind_extension( 258 | name = "wrapped_proto_module", 259 | srcs = ["wrapped_proto_module.cc"], 260 | deps = [ 261 | ":test_cc_proto", 262 | "//pybind11_protobuf:wrapped_proto_caster", 263 | "@com_google_absl//absl/functional:function_ref", 264 | "@com_google_absl//absl/status:statusor", 265 | "@com_google_absl//absl/types:optional", 266 | "@com_google_protobuf//:protobuf", 267 | ], 268 | ) 269 | 270 | py_test( 271 | name = "wrapped_proto_module_test", 272 | srcs = ["wrapped_proto_module_test.py"], 273 | data = [":wrapped_proto_module.so"], 274 | deps = [ 275 | ":compare", 276 | ":test_py_pb2", 277 | "@com_google_absl_py//absl/testing:absltest", 278 | "@com_google_absl_py//absl/testing:parameterized", 279 | "@com_google_protobuf//:protobuf_python", 280 | requirement("absl_py"), 281 | ], 282 | ) 283 | 284 | pybind_extension( 285 | name = "thread_module", 286 | srcs = ["thread_module.cc"], 287 | deps = [ 288 | ":test_cc_proto", 289 | "//pybind11_protobuf:native_proto_caster", 290 | "@com_google_absl//absl/strings", 291 | "@com_google_absl//absl/time", 292 | "@pybind11_abseil//pybind11_abseil:absl_casters", 293 | ], 294 | ) 295 | 296 | py_test( 297 | name = "thread_module_test", 298 | srcs = ["thread_module_test.py"], 299 | data = [":thread_module.so"], 300 | deps = [ 301 | ":test_py_pb2", 302 | "@com_google_absl_py//absl/testing:absltest", 303 | "@com_google_absl_py//absl/testing:parameterized", 304 | "@com_google_protobuf//:protobuf_python", 305 | requirement("absl_py"), 306 | ], 307 | ) 308 | 309 | pybind_extension( 310 | name = "regression_wrappers_module", 311 | srcs = ["regression_wrappers_module.cc"], 312 | deps = [ 313 | "//pybind11_protobuf:native_proto_caster", 314 | "@com_google_protobuf//:protobuf", 315 | ], 316 | ) 317 | 318 | py_test( 319 | name = "regression_wrappers_test", 320 | srcs = ["regression_wrappers_test.py"], 321 | data = [":regression_wrappers_module.so"], 322 | deps = [ 323 | "@com_google_protobuf//:protobuf_python", 324 | "@com_google_absl_py//absl/testing:absltest", 325 | "@com_google_absl_py//absl/testing:parameterized", 326 | requirement("absl_py"), 327 | ], 328 | ) 329 | 330 | pybind_extension( 331 | name = "we_love_dashes_cc_only_module", 332 | srcs = ["we_love_dashes_cc_only_module.cc"], 333 | deps = [ 334 | ":we_love_dashes_cc_proto", 335 | "//pybind11_protobuf:native_proto_caster", 336 | ], 337 | ) 338 | 339 | py_test( 340 | name = "we_love_dashes_cc_only_test", 341 | srcs = ["we_love_dashes_cc_only_test.py"], 342 | data = [":we_love_dashes_cc_only_module.so"], 343 | deps = [ 344 | "@com_google_absl_py//absl/testing:absltest", 345 | "@com_google_protobuf//:protobuf_python", 346 | requirement("absl_py"), 347 | ], 348 | ) 349 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Evaluate if Protobuf uses the system package, otherwise explicitly include the 3 | # required macro 4 | # 5 | FetchContent_GetProperties(Protobuf SOURCE_DIR Protobuf_SOURCE_DIR) 6 | if(Protobuf_SOURCE_DIR) 7 | # Use macros from content made available by FetchContent 8 | include(${Protobuf_SOURCE_DIR}/cmake/protobuf-generate.cmake) 9 | endif() 10 | 11 | # cmake-format: off 12 | function(generate_cc_proto protoname) 13 | # Generate C++ files (.pb.h, .pb.cc) 14 | # 15 | add_library(${protoname}_cc_proto OBJECT) 16 | target_include_directories(${protoname}_cc_proto 17 | PRIVATE $ 18 | $) 19 | protobuf_generate( 20 | TARGET ${protoname}_cc_proto 21 | PROTOS ${CMAKE_SOURCE_DIR}/pybind11_protobuf/tests/${protoname}.proto 22 | IMPORT_DIRS ${CMAKE_SOURCE_DIR} 23 | PROTOC_OUT_DIR ${CMAKE_BINARY_DIR}) 24 | endfunction() 25 | 26 | function(generate_py_proto protoname) 27 | # Generate Python files (_pb2.py) 28 | # 29 | add_custom_target(${protoname}_py_pb2 ALL) 30 | protobuf_generate( 31 | TARGET ${protoname}_py_pb2 32 | LANGUAGE PYTHON 33 | PROTOS ${CMAKE_SOURCE_DIR}/pybind11_protobuf/tests/${protoname}.proto 34 | IMPORT_DIRS ${CMAKE_SOURCE_DIR} 35 | PROTOC_OUT_DIR ${CMAKE_BINARY_DIR}) 36 | endfunction() 37 | # cmake-format: on 38 | 39 | generate_cc_proto("test") 40 | generate_cc_proto("extension") 41 | generate_cc_proto("extension_nest_repeated") 42 | generate_cc_proto("extension_in_other_file_in_deps") 43 | generate_cc_proto("extension_in_other_file") 44 | generate_cc_proto("we-love-dashes") 45 | 46 | generate_py_proto("test") 47 | generate_py_proto("extension") 48 | generate_py_proto("extension_nest_repeated") 49 | generate_py_proto("extension_in_other_file_in_deps") 50 | generate_py_proto("extension_in_other_file") 51 | 52 | function(generate_extension modulename deps) 53 | pybind11_add_module(${modulename}_module ${modulename}_module.cc) 54 | add_dependencies(${modulename}_module ${deps}) 55 | target_include_directories(${modulename}_module # 56 | PRIVATE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) 57 | target_link_libraries(${modulename}_module # 58 | PRIVATE protobuf::libprotobuf ${deps}) 59 | endfunction() 60 | 61 | generate_extension(proto_enum "test_cc_proto") 62 | generate_extension(dynamic_message "pybind11_native_proto_caster") 63 | generate_extension( 64 | extension # 65 | "extension_in_other_file_in_deps_cc_proto;extension_nest_repeated_cc_proto;test_cc_proto;extension_cc_proto;pybind11_native_proto_caster" 66 | ) 67 | generate_extension(message "test_cc_proto;pybind11_native_proto_caster") 68 | generate_extension( 69 | pass_by 70 | "test_cc_proto;pybind11_native_proto_caster;pybind11_abseil::absl_casters") 71 | generate_extension(pass_proto2_message "pybind11_native_proto_caster") 72 | generate_extension(wrapped_proto "test_cc_proto;pybind11_wrapped_proto_caster") 73 | generate_extension( 74 | thread 75 | "test_cc_proto;pybind11_native_proto_caster;pybind11_abseil::absl_casters") 76 | generate_extension(regression_wrappers "pybind11_native_proto_caster") 77 | generate_extension(we_love_dashes_cc_only # 78 | "we-love-dashes_cc_proto;pybind11_native_proto_caster") 79 | 80 | if(NOT DEFINED Python_EXECUTABLE) 81 | if(NOT DEFINED PYBIND11_PYTHON_EXECUTABLE_LAST) 82 | set(Python_EXECUTABLE ${PYTHON_EXECUTABLE}) 83 | else() 84 | set(Python_EXECUTABLE ${PYBIND11_PYTHON_EXECUTABLE_LAST}) 85 | endif() 86 | endif() 87 | 88 | function(add_py_test testname) 89 | add_test(NAME ${testname}_test 90 | COMMAND ${Python_EXECUTABLE} 91 | ${CMAKE_CURRENT_SOURCE_DIR}/${testname}_test.py) 92 | set_property(TEST ${testname}_test # 93 | PROPERTY ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}") 94 | endfunction() 95 | 96 | file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/compare.py 97 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 98 | 99 | add_py_test(proto_enum) 100 | add_py_test(dynamic_message) 101 | add_py_test(extension) 102 | add_py_test(message) 103 | add_py_test(pass_by) 104 | add_py_test(wrapped_proto_module) 105 | add_py_test(thread_module) 106 | add_py_test(regression_wrappers) 107 | add_py_test(we_love_dashes_cc_only) 108 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/compare.py: -------------------------------------------------------------------------------- 1 | """Utility functions for comparing proto2 messages in Python. 2 | 3 | Forked from tensorflow.python.util.protobuf 4 | 5 | ProtoEq() compares two proto2 messages for equality. 6 | 7 | ClearDefaultValuedFields() recursively clears the fields that are set to their 8 | default values. This is useful for comparing protocol buffers where the 9 | semantics of unset fields and default valued fields are the same. 10 | 11 | assertProtoEqual() is useful for unit tests. It produces much more helpful 12 | output than assertEqual() for proto2 messages, e.g. this: 13 | 14 | outer { 15 | inner { 16 | - strings: "x" 17 | ? ^ 18 | + strings: "y" 19 | ? ^ 20 | } 21 | } 22 | 23 | ...compared to the default output from assertEqual() that looks like this: 24 | 25 | AssertionError: != 26 | 27 | Call it inside your unit test's absltest.TestCase subclasses like this: 28 | 29 | from tensorflow.python.util.protobuf import compare 30 | 31 | class MyTest(absltest.TestCase): 32 | ... 33 | def testXXX(self): 34 | ... 35 | compare.assertProtoEqual(self, a, b) 36 | 37 | Alternatively: 38 | 39 | from tensorflow.python.util.protobuf import compare 40 | 41 | class MyTest(compare.ProtoAssertions, absltest.TestCase): 42 | ... 43 | def testXXX(self): 44 | ... 45 | self.assertProtoEqual(a, b) 46 | """ 47 | import collections.abc as collections_abc 48 | import difflib 49 | 50 | from google.protobuf import descriptor 51 | from google.protobuf import descriptor_pool 52 | from google.protobuf import message 53 | from google.protobuf import text_format 54 | 55 | 56 | def assertProtoEqual(self, a, b, check_initialized=True, # pylint: disable=invalid-name 57 | normalize_numbers=False, msg=None): 58 | """Fails with a useful error if a and b aren't equal. 59 | 60 | Comparison of repeated fields matches the semantics of 61 | unittest.TestCase.assertEqual(), ie order and extra duplicates fields matter. 62 | 63 | Args: 64 | self: absltest.TestCase 65 | a: proto2 PB instance, or text string representing one. 66 | b: proto2 PB instance -- message.Message or subclass thereof. 67 | check_initialized: boolean, whether to fail if either a or b isn't 68 | initialized. 69 | normalize_numbers: boolean, whether to normalize types and precision of 70 | numbers before comparison. 71 | msg: if specified, is used as the error message on failure. 72 | """ 73 | pool = descriptor_pool.Default() 74 | if isinstance(a, str): 75 | a = text_format.Parse(a, b.__class__(), descriptor_pool=pool) 76 | 77 | for pb in a, b: 78 | if check_initialized: 79 | errors = pb.FindInitializationErrors() 80 | if errors: 81 | self.fail('Initialization errors: %s\n%s' % (errors, pb)) 82 | if normalize_numbers: 83 | NormalizeNumberFields(pb) 84 | 85 | a_str = text_format.MessageToString(a, descriptor_pool=pool) 86 | b_str = text_format.MessageToString(b, descriptor_pool=pool) 87 | 88 | # Some Python versions would perform regular diff instead of multi-line 89 | # diff if string is longer than 2**16. We substitute this behavior 90 | # with a call to unified_diff instead to have easier-to-read diffs. 91 | # For context, see: https://bugs.python.org/issue11763. 92 | if len(a_str) < 2**16 and len(b_str) < 2**16: 93 | self.assertMultiLineEqual(a_str, b_str, msg=msg) 94 | else: 95 | diff = '\n' + ''.join(difflib.unified_diff(a_str.splitlines(True), 96 | b_str.splitlines(True))) 97 | self.fail('%s : %s' % (msg, diff)) 98 | 99 | 100 | def NormalizeNumberFields(pb): 101 | """Normalizes types and precisions of number fields in a protocol buffer. 102 | 103 | Due to subtleties in the python protocol buffer implementation, it is possible 104 | for values to have different types and precision depending on whether they 105 | were set and retrieved directly or deserialized from a protobuf. This function 106 | normalizes integer values to ints and longs based on width, 32-bit floats to 107 | five digits of precision to account for python always storing them as 64-bit, 108 | and ensures doubles are floating point for when they're set to integers. 109 | 110 | Modifies pb in place. Recurses into nested objects. 111 | 112 | Args: 113 | pb: proto2 message. 114 | 115 | Returns: 116 | the given pb, modified in place. 117 | """ 118 | for desc, values in pb.ListFields(): 119 | is_repeated = True 120 | if desc.label != descriptor.FieldDescriptor.LABEL_REPEATED: 121 | is_repeated = False 122 | values = [values] 123 | 124 | normalized_values = None 125 | 126 | # We force 32-bit values to int and 64-bit values to long to make 127 | # alternate implementations where the distinction is more significant 128 | # (e.g. the C++ implementation) simpler. 129 | if desc.type in (descriptor.FieldDescriptor.TYPE_INT64, 130 | descriptor.FieldDescriptor.TYPE_UINT64, 131 | descriptor.FieldDescriptor.TYPE_SINT64): 132 | normalized_values = [int(x) for x in values] 133 | elif desc.type in (descriptor.FieldDescriptor.TYPE_INT32, 134 | descriptor.FieldDescriptor.TYPE_UINT32, 135 | descriptor.FieldDescriptor.TYPE_SINT32, 136 | descriptor.FieldDescriptor.TYPE_ENUM): 137 | normalized_values = [int(x) for x in values] 138 | elif desc.type == descriptor.FieldDescriptor.TYPE_FLOAT: 139 | normalized_values = [round(x, 6) for x in values] 140 | elif desc.type == descriptor.FieldDescriptor.TYPE_DOUBLE: 141 | normalized_values = [round(float(x), 7) for x in values] 142 | 143 | if normalized_values is not None: 144 | if is_repeated: 145 | pb.ClearField(desc.name) 146 | getattr(pb, desc.name).extend(normalized_values) 147 | else: 148 | setattr(pb, desc.name, normalized_values[0]) 149 | 150 | if (desc.type == descriptor.FieldDescriptor.TYPE_MESSAGE or 151 | desc.type == descriptor.FieldDescriptor.TYPE_GROUP): 152 | if (desc.type == descriptor.FieldDescriptor.TYPE_MESSAGE and 153 | desc.message_type.has_options and 154 | desc.message_type.GetOptions().map_entry): 155 | # This is a map, only recurse if the values have a message type. 156 | if (desc.message_type.fields_by_number[2].type == 157 | descriptor.FieldDescriptor.TYPE_MESSAGE): 158 | for v in values.items(): 159 | NormalizeNumberFields(v) 160 | else: 161 | for v in values: 162 | # recursive step 163 | NormalizeNumberFields(v) 164 | 165 | return pb 166 | 167 | 168 | def _IsMap(value): 169 | return isinstance(value, collections_abc.Mapping) 170 | 171 | 172 | def _IsRepeatedContainer(value): 173 | if isinstance(value, str): 174 | return False 175 | try: 176 | iter(value) 177 | return True 178 | except TypeError: 179 | return False 180 | 181 | 182 | def ProtoEq(a, b): 183 | """Compares two proto2 objects for equality. 184 | 185 | Recurses into nested messages. Uses list (not set) semantics for comparing 186 | repeated fields, ie duplicates and order matter. 187 | 188 | Args: 189 | a: A proto2 message or a primitive. 190 | b: A proto2 message or a primitive. 191 | 192 | Returns: 193 | `True` if the messages are equal. 194 | """ 195 | def Format(pb): 196 | """Returns a dictionary or unchanged pb bases on its type. 197 | 198 | Specifically, this function returns a dictionary that maps tag 199 | number (for messages) or element index (for repeated fields) to 200 | value, or just pb unchanged if it's neither. 201 | 202 | Args: 203 | pb: A proto2 message or a primitive. 204 | Returns: 205 | A dict or unchanged pb. 206 | """ 207 | if isinstance(pb, message.Message): 208 | return dict((desc.number, value) for desc, value in pb.ListFields()) 209 | elif _IsMap(pb): 210 | return dict(pb.items()) 211 | elif _IsRepeatedContainer(pb): 212 | return dict(enumerate(list(pb))) 213 | else: 214 | return pb 215 | 216 | a, b = Format(a), Format(b) 217 | 218 | # Base case 219 | if not isinstance(a, dict) or not isinstance(b, dict): 220 | return a == b 221 | 222 | # This list performs double duty: it compares two messages by tag value *or* 223 | # two repeated fields by element, in order. the magic is in the format() 224 | # function, which converts them both to the same easily comparable format. 225 | for tag in sorted(set(a.keys()) | set(b.keys())): 226 | if tag not in a or tag not in b: 227 | return False 228 | else: 229 | # Recursive step 230 | if not ProtoEq(a[tag], b[tag]): 231 | return False 232 | 233 | # Didn't find any values that differed, so they're equal! 234 | return True 235 | 236 | 237 | class ProtoAssertions(object): 238 | """Mix this into a absltest.TestCase class to get proto2 assertions. 239 | 240 | Usage: 241 | 242 | class SomeTestCase(compare.ProtoAssertions, absltest.TestCase): 243 | ... 244 | def testSomething(self): 245 | ... 246 | self.assertProtoEqual(a, b) 247 | 248 | See module-level definitions for method documentation. 249 | """ 250 | 251 | # pylint: disable=invalid-name 252 | def assertProtoEqual(self, *args, **kwargs): 253 | return assertProtoEqual(self, *args, **kwargs) 254 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/dynamic_message_module.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 The Pybind Development Team. All rights reserved. 2 | // 3 | // All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "google/protobuf/descriptor.h" 15 | #include "google/protobuf/descriptor.pb.h" 16 | #include "google/protobuf/dynamic_message.h" 17 | #include "google/protobuf/message.h" 18 | #include "google/protobuf/text_format.h" 19 | #include "pybind11_protobuf/native_proto_caster.h" 20 | 21 | namespace py = ::pybind11; 22 | 23 | namespace { 24 | 25 | // GetDynamicPool contains a dynamic message that is wire-compatible with 26 | // with IntMessage; conversion using the fast_cpp_proto api will fail 27 | // as the PyProto_API will not be able to find the proto in the default 28 | // pool. 29 | ::google::protobuf::DescriptorPool* GetDynamicPool() { 30 | static ::google::protobuf::DescriptorPool* pool = [] { 31 | ::google::protobuf::FileDescriptorProto file_proto; 32 | if (!::google::protobuf::TextFormat::ParseFromString( 33 | R"pb( 34 | name: 'pybind11_protobuf/tests' 35 | package: 'pybind11.test' 36 | message_type: { 37 | name: 'DynamicMessage' 38 | field: { name: 'value' number: 1 type: TYPE_INT32 } 39 | } 40 | message_type: { 41 | name: 'IntMessage' 42 | field: { name: 'value' number: 1 type: TYPE_INT32 } 43 | } 44 | )pb", 45 | &file_proto)) { 46 | throw std::invalid_argument("Failed to parse textproto."); 47 | } 48 | 49 | ::google::protobuf::DescriptorPool* pool = new ::google::protobuf::DescriptorPool(); 50 | pool->BuildFile(file_proto); 51 | return pool; 52 | }(); 53 | 54 | return pool; 55 | } 56 | 57 | void UpdateMessage(::google::protobuf::Message* message, int32_t value) { 58 | auto* f = message->GetDescriptor()->FindFieldByName("value"); 59 | if (!f) f = message->GetDescriptor()->FindFieldByName("int_value"); 60 | if (!f) return; 61 | message->GetReflection()->SetInt32(message, f, value); 62 | } 63 | 64 | bool CheckMessage(const ::google::protobuf::Message& message, int32_t value) { 65 | auto* f = message.GetDescriptor()->FindFieldByName("value"); 66 | if (!f) f = message.GetDescriptor()->FindFieldByName("int_value"); 67 | if (!f) return false; 68 | return message.GetReflection()->GetInt32(message, f) == value; 69 | } 70 | 71 | std::unique_ptr<::google::protobuf::Message> GetDynamicMessage(const std::string& full_name, 72 | int32_t value) { 73 | static ::google::protobuf::DynamicMessageFactory factory(GetDynamicPool()); 74 | 75 | auto* descriptor = GetDynamicPool()->FindMessageTypeByName(full_name); 76 | if (!descriptor) return nullptr; 77 | 78 | auto* prototype = factory.GetPrototype(descriptor); 79 | if (!prototype) return nullptr; 80 | 81 | std::unique_ptr<::google::protobuf::Message> dynamic(prototype->New()); 82 | UpdateMessage(dynamic.get(), value); 83 | return dynamic; 84 | } 85 | 86 | PYBIND11_MODULE(dynamic_message_module, m) { 87 | pybind11_protobuf::ImportNativeProtoCasters(); 88 | 89 | // Message building methods. 90 | m.def( 91 | "dynamic_message_ptr", 92 | [](std::string name, int32_t value) -> ::google::protobuf::Message* { 93 | return GetDynamicMessage(name, value).release(); 94 | }, 95 | py::arg("name") = "pybind11.test.DynamicMessage", py::arg("value") = 123); 96 | 97 | m.def( 98 | "dynamic_message_unique_ptr", 99 | [](std::string name, int32_t value) -> std::unique_ptr<::google::protobuf::Message> { 100 | return GetDynamicMessage(name, value); 101 | }, 102 | py::arg("name") = "pybind11.test.DynamicMessage", py::arg("value") = 123); 103 | 104 | m.def( 105 | "dynamic_message_shared_ptr", 106 | [](std::string name, int32_t value) -> std::shared_ptr<::google::protobuf::Message> { 107 | return GetDynamicMessage(name, value); 108 | }, 109 | py::arg("name") = "pybind11.test.DynamicMessage", py::arg("value") = 123); 110 | 111 | // Test methods 112 | m.def("check_message", &CheckMessage, py::arg("message"), py::arg("value")); 113 | m.def( 114 | "check_message_const_ptr", 115 | [](const ::google::protobuf::Message* m, int value) { 116 | return (m == nullptr) ? false : CheckMessage(*m, value); 117 | }, 118 | py::arg("message"), py::arg("value")); 119 | 120 | #if PYBIND11_PROTOBUF_UNSAFE 121 | m.def( 122 | "mutate_message", 123 | [](::google::protobuf::Message* msg, int value) { UpdateMessage(msg, value); }, 124 | py::arg("message"), py::arg("value")); 125 | 126 | m.def( 127 | "mutate_message_ref", 128 | [](::google::protobuf::Message& msg, int value) { UpdateMessage(&msg, value); }, 129 | py::arg("message"), py::arg("value")); 130 | #endif // PYBIND11_PROTOBUF_UNSAFE 131 | 132 | // copies 133 | m.def( 134 | "roundtrip", 135 | [](const ::google::protobuf::Message& inout) -> const ::google::protobuf::Message& { 136 | return inout; 137 | }, 138 | py::arg("message"), py::return_value_policy::copy); 139 | 140 | // parse 141 | m.def( 142 | "parse_as", 143 | [](std::string data, std::unique_ptr<::google::protobuf::Message> msg) 144 | -> std::unique_ptr<::google::protobuf::Message> { 145 | assert(msg.get()); 146 | assert(msg->GetDescriptor()); 147 | ::google::protobuf::TextFormat::Parser parser; 148 | parser.ParseFromString(data, msg.get()); 149 | return msg; 150 | }, 151 | py::arg("data"), py::arg("message")); 152 | 153 | m.def( 154 | "print", 155 | [](std::unique_ptr<::google::protobuf::Message> msg) -> std::string { 156 | std::string message; 157 | if (msg) { 158 | ::google::protobuf::TextFormat::PrintToString(*msg, &message); 159 | } else { 160 | message = ""; 161 | } 162 | return message; 163 | }, 164 | py::arg("message")); 165 | 166 | m.def( 167 | "print_descriptor", 168 | [](std::unique_ptr<::google::protobuf::Message> msg) -> std::string { 169 | return (msg && msg->GetDescriptor()) 170 | ? msg->GetDescriptor()->DebugString() 171 | : ""; 172 | }, 173 | py::arg("message")); 174 | } 175 | 176 | } // namespace 177 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/dynamic_message_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 The Pybind Development Team. All rights reserved. 2 | # 3 | # All rights reserved. Use of this source code is governed by a 4 | # BSD-style license that can be found in the LICENSE file. 5 | """Tests for protobuf casters.""" 6 | 7 | from __future__ import absolute_import 8 | from __future__ import division 9 | from __future__ import print_function 10 | 11 | from absl.testing import absltest 12 | from absl.testing import parameterized 13 | from google.protobuf import descriptor_pb2 14 | from google.protobuf import descriptor_pool 15 | from google.protobuf import message_factory 16 | 17 | from pybind11_protobuf.tests import compare 18 | from pybind11_protobuf.tests import dynamic_message_module as m 19 | from pybind11_protobuf.tests import test_pb2 20 | 21 | POOL = descriptor_pool.DescriptorPool() 22 | POOL.Add( 23 | descriptor_pb2.FileDescriptorProto( 24 | name='pybind11_protobuf/tests', 25 | package='pybind11.test', 26 | message_type=[ 27 | descriptor_pb2.DescriptorProto( 28 | name='DynamicMessage', 29 | field=[ 30 | descriptor_pb2.FieldDescriptorProto( 31 | name='value', number=1, type=5) 32 | ]), 33 | descriptor_pb2.DescriptorProto( 34 | name='IntMessage', 35 | field=[ 36 | descriptor_pb2.FieldDescriptorProto( 37 | name='value', number=1, type=5) 38 | ]) 39 | ])) 40 | 41 | 42 | def get_py_dynamic_message(value=5): 43 | """Returns a dynamic message that is wire-compatible with IntMessage.""" 44 | prototype = message_factory.GetMessageClass( 45 | POOL.FindMessageTypeByName('pybind11.test.DynamicMessage') 46 | ) 47 | msg = prototype(value=value) 48 | return msg 49 | 50 | 51 | def get_py_dynamic_int_message(value=5): 52 | """Returns a dynamic message named pybind11.test.IntMessage.""" 53 | prototype = message_factory.GetMessageClass( 54 | POOL.FindMessageTypeByName('pybind11.test.IntMessage') 55 | ) 56 | msg = prototype(value=value) 57 | return msg 58 | 59 | 60 | def get_cpp_dynamic_int_message(value=5): 61 | """Returns a dynamic message named pybind11.test.IntMessage.""" 62 | return m.dynamic_message_ptr('pybind11.test.IntMessage', value) 63 | 64 | 65 | def get_cpp_dynamic_message(value=5): 66 | """Returns a dynamic message that is wire-compatible with IntMessage.""" 67 | return m.dynamic_message_ptr('pybind11.test.DynamicMessage', value) 68 | 69 | 70 | def get_cpp_dynamic_message_unique_ptr(value=5): 71 | """Returns a dynamic message that is wire-compatible with IntMessage.""" 72 | return m.dynamic_message_unique_ptr('pybind11.test.DynamicMessage', value) 73 | 74 | 75 | def get_cpp_dynamic_message_shared_ptr(value=5): 76 | """Returns a dynamic message that is wire-compatible with IntMessage.""" 77 | return m.dynamic_message_shared_ptr('pybind11.test.DynamicMessage', value) 78 | 79 | 80 | class DynamicMessageTest(parameterized.TestCase, compare.ProtoAssertions): 81 | 82 | @parameterized.named_parameters( 83 | ('native_proto', test_pb2.IntMessage), 84 | ('py_dynamic_int', get_py_dynamic_int_message), 85 | ('cpp_dynamic_int', get_cpp_dynamic_int_message)) 86 | def test_full_name(self, get_message_function): 87 | message = get_message_function() 88 | self.assertEqual( 89 | str(message.DESCRIPTOR.full_name), 'pybind11.test.IntMessage') 90 | 91 | @parameterized.named_parameters( 92 | ('native_proto', test_pb2.IntMessage), 93 | ('py_dynamic_int', get_py_dynamic_int_message), 94 | ('cpp_dynamic_int', get_cpp_dynamic_int_message), 95 | ('py_dynamic', get_py_dynamic_message), 96 | ) 97 | def test_check_message(self, get_message_function): 98 | message = get_message_function(value=5) 99 | self.assertTrue(m.check_message(message, 5)) 100 | self.assertTrue(m.check_message_const_ptr(message, 5)) 101 | 102 | @parameterized.named_parameters( 103 | ('native_proto', test_pb2.IntMessage), 104 | ('py_dynamic_int', get_py_dynamic_int_message), 105 | ('cpp_dynamic_int', get_cpp_dynamic_int_message), 106 | ) 107 | def test_roundtrip(self, get_message_function): 108 | a = get_message_function(value=6) 109 | b = m.roundtrip(a) 110 | self.assertTrue(m.check_message(b, 6)) 111 | 112 | @parameterized.named_parameters( 113 | ('native_proto', test_pb2.IntMessage), 114 | ('py_dynamic_int', get_py_dynamic_int_message), 115 | ('cpp_dynamic_int', get_cpp_dynamic_int_message), 116 | ) 117 | def test_parse_as(self, get_message_function): 118 | a = get_message_function(value=6) 119 | b = m.parse_as('value: 333', a) 120 | self.assertTrue(m.check_message(b, 333), b) 121 | 122 | @parameterized.named_parameters( 123 | ('native_proto', test_pb2.IntMessage), 124 | ('py_dynamic_int', get_py_dynamic_int_message), 125 | ('cpp_dynamic_int', get_cpp_dynamic_int_message), 126 | ('py_dynamic', get_py_dynamic_message), 127 | ) 128 | def test_print(self, get_message_function): 129 | a = get_message_function(value=6) 130 | b = m.print(a) 131 | self.assertEqual(b.strip(), 'value: 6', b) 132 | 133 | @parameterized.named_parameters( 134 | ('native_proto', test_pb2.IntMessage), 135 | ('py_dynamic_int', get_py_dynamic_int_message), 136 | ('cpp_dynamic_int', get_cpp_dynamic_int_message), 137 | ('py_dynamic', get_py_dynamic_message), 138 | ) 139 | def test_print_descriptor(self, get_message_function): 140 | a = get_message_function(value=6) 141 | b = m.print_descriptor(a) 142 | self.assertNotEqual(-1, b.find('value = 1'), b) 143 | 144 | 145 | if __name__ == '__main__': 146 | absltest.main() 147 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/extension.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 The Pybind Development Team. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | syntax = "proto2"; 10 | 11 | package pybind11.test; 12 | 13 | import "pybind11_protobuf/tests/test.proto"; 14 | 15 | message BaseMessage { 16 | extensions 1000 to max; 17 | } 18 | 19 | extend BaseMessage { 20 | optional pybind11.test.IntMessage int_message = 1001; 21 | } 22 | 23 | message NestLevel1 { 24 | optional BaseMessage base_msg = 1; 25 | } 26 | 27 | message NestLevel2 { 28 | optional NestLevel1 nest_lvl1 = 1; 29 | } 30 | 31 | message AllowUnknownInner { 32 | extensions 2000 to max; 33 | } 34 | 35 | message AllowUnknownOuter { 36 | optional AllowUnknownInner inner = 1; 37 | } 38 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/extension_in_other_file.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package pybind11.test; 4 | 5 | import "pybind11_protobuf/tests/extension.proto"; 6 | 7 | message MessageInOtherFile { 8 | extend pybind11.test.BaseMessage { 9 | optional MessageInOtherFile message_in_other_file_extension = 1003; 10 | } 11 | 12 | optional int32 value = 1; 13 | } 14 | 15 | message AllowUnknownInnerExtension { 16 | extend pybind11.test.AllowUnknownInner { 17 | optional AllowUnknownInnerExtension hook = 2001; 18 | } 19 | 20 | optional int32 value = 1; 21 | } 22 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/extension_in_other_file_in_deps.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package pybind11.test; 4 | 5 | import "pybind11_protobuf/tests/extension.proto"; 6 | 7 | message MessageInOtherFileInDeps { 8 | extend pybind11.test.BaseMessage { 9 | optional MessageInOtherFileInDeps message_in_other_file_in_deps_extension = 10 | 1002; 11 | } 12 | 13 | optional int32 value = 1; 14 | } 15 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/extension_module.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 The Pybind Development Team. All rights reserved. 2 | // 3 | // All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "google/protobuf/message.h" 13 | #include "pybind11_protobuf/native_proto_caster.h" 14 | #include "pybind11_protobuf/tests/extension.pb.h" 15 | #include "pybind11_protobuf/tests/extension_nest_repeated.pb.h" 16 | #include "pybind11_protobuf/tests/test.pb.h" 17 | 18 | namespace py = ::pybind11; 19 | 20 | namespace { 21 | 22 | using pybind11::test::BaseMessage; 23 | 24 | std::unique_ptr GetMessage(int32_t value) { 25 | std::unique_ptr msg(new BaseMessage()); 26 | msg->MutableExtension(pybind11::test::int_message)->set_value(value); 27 | return msg; 28 | } 29 | 30 | const pybind11::test::IntMessage& GetExtension(const BaseMessage& msg) { 31 | return msg.GetExtension(pybind11::test::int_message); 32 | } 33 | 34 | template 35 | void DefReserialize(py::module_& m, const char* py_name) { 36 | m.def( 37 | py_name, 38 | [](const ProtoType& message) -> ProtoType { 39 | std::string serialized; 40 | message.SerializeToString(&serialized); 41 | ProtoType deserialized; 42 | deserialized.ParseFromString(serialized); 43 | return deserialized; 44 | }, 45 | py::arg("message")); 46 | } 47 | 48 | PYBIND11_MODULE(extension_module, m) { 49 | pybind11_protobuf::ImportNativeProtoCasters(); 50 | 51 | m.def("extensions_with_unknown_fields_are_disallowed", []() { 52 | return false; 53 | }); 54 | 55 | m.def("get_base_message", []() -> BaseMessage { return {}; }); 56 | 57 | m.def( 58 | "get_message", 59 | [](int32_t value) -> BaseMessage { 60 | return std::move(*GetMessage(value)); 61 | }, 62 | py::arg("value") = 123); 63 | 64 | m.def("get_extension", &GetExtension, py::arg("message")); 65 | 66 | // copies 67 | m.def( 68 | "roundtrip", 69 | [](const BaseMessage& inout) -> const BaseMessage& { return inout; }, 70 | py::arg("message"), py::return_value_policy::copy); 71 | 72 | DefReserialize(m, "reserialize_base_message"); 73 | DefReserialize(m, "reserialize_nest_level2"); 74 | DefReserialize(m, "reserialize_nest_repeated"); 75 | 76 | pybind11_protobuf::AllowUnknownFieldsFor("pybind11.test.AllowUnknownInner", 77 | ""); 78 | DefReserialize( 79 | m, "reserialize_allow_unknown_inner"); 80 | 81 | pybind11_protobuf::AllowUnknownFieldsFor("pybind11.test.AllowUnknownOuter", 82 | "inner"); 83 | DefReserialize( 84 | m, "reserialize_allow_unknown_outer"); 85 | } 86 | 87 | } // namespace 88 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/extension_nest_repeated.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package pybind11.test; 4 | 5 | import "pybind11_protobuf/tests/extension.proto"; 6 | 7 | message NestRepeated { 8 | repeated BaseMessage base_msgs = 1; 9 | } 10 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/extension_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 The Pybind Development Team. All rights reserved. 2 | # 3 | # All rights reserved. Use of this source code is governed by a 4 | # BSD-style license that can be found in the LICENSE file. 5 | """Tests for protobuf casters.""" 6 | 7 | from __future__ import absolute_import 8 | from __future__ import division 9 | from __future__ import print_function 10 | 11 | from absl.testing import absltest 12 | from absl.testing import parameterized 13 | 14 | from google.protobuf import unknown_fields 15 | from google.protobuf.internal import api_implementation 16 | from pybind11_protobuf.tests import extension_in_other_file_in_deps_pb2 17 | from pybind11_protobuf.tests import extension_in_other_file_pb2 18 | from pybind11_protobuf.tests import extension_module as m 19 | from pybind11_protobuf.tests import extension_nest_repeated_pb2 20 | from pybind11_protobuf.tests import extension_pb2 21 | 22 | 23 | def unknown_field_exception_is_expected(): 24 | return ( 25 | api_implementation.Type() == 'cpp' 26 | and m.extensions_with_unknown_fields_are_disallowed() 27 | ) 28 | 29 | 30 | def get_py_message(value=5, 31 | in_other_file_in_deps_value=None, 32 | in_other_file_value=None): 33 | """Returns a dynamic message that is wire-compatible with IntMessage.""" 34 | msg = extension_pb2.BaseMessage() 35 | if in_other_file_in_deps_value is not None: 36 | msg.Extensions[ 37 | extension_in_other_file_in_deps_pb2.MessageInOtherFileInDeps. 38 | message_in_other_file_in_deps_extension].value = in_other_file_in_deps_value 39 | elif in_other_file_value is not None: 40 | msg.Extensions[extension_in_other_file_pb2.MessageInOtherFile 41 | .message_in_other_file_extension].value = in_other_file_value 42 | else: 43 | msg.Extensions[extension_pb2.int_message].value = value 44 | return msg 45 | 46 | 47 | def get_allow_unknown_inner(value): 48 | msg = extension_pb2.AllowUnknownInner() 49 | msg.Extensions[ 50 | extension_in_other_file_pb2.AllowUnknownInnerExtension.hook].value = value 51 | return msg 52 | 53 | 54 | class ExtensionTest(parameterized.TestCase): 55 | 56 | @parameterized.named_parameters(('python', get_py_message), 57 | ('cpp', m.get_message)) 58 | def test_full_name(self, get_message_function): 59 | a = get_message_function() 60 | self.assertEqual(str(a.DESCRIPTOR.full_name), 'pybind11.test.BaseMessage') 61 | 62 | @parameterized.named_parameters(('python', get_py_message), 63 | ('cpp', m.get_message)) 64 | def test_get_extension_py(self, get_message_function): 65 | a = get_message_function(value=8) 66 | self.assertEqual(8, a.Extensions[extension_pb2.int_message].value) 67 | 68 | @parameterized.named_parameters(('python', get_py_message), 69 | ('cpp', m.get_message)) 70 | def test_set_extension_cpp(self, get_message_function): 71 | a = get_message_function() 72 | a.Extensions[extension_pb2.int_message].value = 8 73 | self.assertEqual(8, a.Extensions[extension_pb2.int_message].value) 74 | 75 | @parameterized.named_parameters(('python', get_py_message), 76 | ('cpp', m.get_message)) 77 | def test_extension_fn(self, get_message_function): 78 | a = get_message_function(value=7) 79 | b = m.get_extension(a) 80 | self.assertEqual(7, b.value) 81 | 82 | @parameterized.named_parameters(('python', get_py_message), 83 | ('cpp', m.get_message)) 84 | def test_extension_fn_roundtrip(self, get_message_function): 85 | a = get_message_function(value=7) 86 | b = m.get_extension(m.roundtrip(a)) 87 | self.assertEqual(7, b.value) 88 | 89 | @parameterized.parameters('roundtrip', 'reserialize_base_message') 90 | def test_extension_in_same_file(self, roundtrip_fn): 91 | a = get_py_message(94) 92 | b = getattr(m, roundtrip_fn)(a) 93 | self.assertEqual(94, b.Extensions[extension_pb2.int_message].value) 94 | 95 | @parameterized.parameters('roundtrip', 'reserialize_base_message') 96 | def test_extension_in_other_file_in_deps(self, roundtrip_fn): 97 | a = get_py_message(in_other_file_in_deps_value=38) 98 | b = getattr(m, roundtrip_fn)(a) 99 | self.assertEqual( 100 | 38, b.Extensions[ 101 | extension_in_other_file_in_deps_pb2.MessageInOtherFileInDeps 102 | .message_in_other_file_in_deps_extension].value) 103 | 104 | def test_extension_in_other_file_roundtrip(self): 105 | a = get_py_message(in_other_file_value=57) 106 | b = m.roundtrip(a) 107 | self.assertEqual( 108 | 57, b.Extensions[extension_in_other_file_pb2.MessageInOtherFile 109 | .message_in_other_file_extension].value) 110 | 111 | def test_reserialize_base_message(self): 112 | a = get_py_message(in_other_file_value=63) 113 | if unknown_field_exception_is_expected(): 114 | with self.assertRaises(ValueError) as ctx: 115 | m.reserialize_base_message(a) 116 | self.assertStartsWith( 117 | str(ctx.exception), 118 | 'Proto Message of type pybind11.test.BaseMessage has an' 119 | ' Unknown Field: 1003 (') 120 | self.assertEndsWith( 121 | str(ctx.exception), 122 | 'extension.proto). Please add the required `cc_proto_library` `deps`.' 123 | ' Only if there is no alternative to suppressing this error, use' 124 | ' `pybind11_protobuf::AllowUnknownFieldsFor(' 125 | '"pybind11.test.BaseMessage", "");`' 126 | ' (Warning: suppressions may mask critical bugs.)') 127 | else: 128 | b = m.reserialize_base_message(a) 129 | b_value = b.Extensions[extension_in_other_file_pb2.MessageInOtherFile 130 | .message_in_other_file_extension].value 131 | self.assertEqual(63, b_value) 132 | 133 | def test_reserialize_nest_level2(self): 134 | a = extension_pb2.NestLevel2( 135 | nest_lvl1=extension_pb2.NestLevel1( 136 | base_msg=get_py_message(in_other_file_value=52))) 137 | if unknown_field_exception_is_expected(): 138 | with self.assertRaises(ValueError) as ctx: 139 | m.reserialize_nest_level2(a) 140 | self.assertStartsWith( 141 | str(ctx.exception), 142 | 'Proto Message of type pybind11.test.NestLevel2 has an Unknown Field' 143 | ' with parent of type pybind11.test.BaseMessage:' 144 | ' nest_lvl1.base_msg.1003 (') 145 | self.assertEndsWith( 146 | str(ctx.exception), 147 | 'extension.proto). Please add the required `cc_proto_library` `deps`.' 148 | ' Only if there is no alternative to suppressing this error, use' 149 | ' `pybind11_protobuf::AllowUnknownFieldsFor(' 150 | '"pybind11.test.NestLevel2", "nest_lvl1.base_msg");`' 151 | ' (Warning: suppressions may mask critical bugs.)') 152 | else: 153 | b = m.reserialize_nest_level2(a) 154 | b_value = b.nest_lvl1.base_msg.Extensions[ 155 | extension_in_other_file_pb2.MessageInOtherFile 156 | .message_in_other_file_extension].value 157 | self.assertEqual(52, b_value) 158 | 159 | def test_reserialize_nest_repeated(self): 160 | a = extension_nest_repeated_pb2.NestRepeated(base_msgs=[ 161 | get_py_message(in_other_file_value=74), 162 | get_py_message(in_other_file_value=85) 163 | ]) 164 | if unknown_field_exception_is_expected(): 165 | with self.assertRaises(ValueError) as ctx: 166 | m.reserialize_nest_repeated(a) 167 | self.assertStartsWith( 168 | str(ctx.exception), 'Proto Message of type pybind11.test.NestRepeated' 169 | ' has an Unknown Field' 170 | ' with parent of type pybind11.test.BaseMessage: base_msgs.1003 (') 171 | self.assertIn('extension_nest_repeated.proto, ', str(ctx.exception)) 172 | self.assertEndsWith( 173 | str(ctx.exception), 174 | 'extension.proto). Please add the required `cc_proto_library` `deps`.' 175 | ' Only if there is no alternative to suppressing this error, use' 176 | ' `pybind11_protobuf::AllowUnknownFieldsFor(' 177 | '"pybind11.test.NestRepeated", "base_msgs");`' 178 | ' (Warning: suppressions may mask critical bugs.)') 179 | else: 180 | b = m.reserialize_nest_repeated(a) 181 | for bm, expected_b_value in zip(b.base_msgs, (74, 85)): 182 | b_value = bm.Extensions[extension_in_other_file_pb2.MessageInOtherFile 183 | .message_in_other_file_extension].value 184 | self.assertEqual(expected_b_value, b_value) 185 | 186 | def test_reserialize_allow_unknown_inner(self): 187 | a = get_allow_unknown_inner(96) 188 | b = m.reserialize_allow_unknown_inner(a) 189 | if api_implementation.Type() == 'cpp': 190 | self.assertLen(unknown_fields.UnknownFieldSet(b), 1) 191 | self.assertEqual(2001, unknown_fields.UnknownFieldSet(b)[0].field_number) 192 | else: 193 | b_value = b.Extensions[ 194 | extension_in_other_file_pb2.AllowUnknownInnerExtension.hook].value 195 | self.assertEqual(96, b_value) 196 | 197 | def test_reserialize_allow_unknown_outer(self): 198 | a = extension_pb2.AllowUnknownOuter(inner=get_allow_unknown_inner(97)) 199 | b = m.reserialize_allow_unknown_outer(a) 200 | if api_implementation.Type() == 'cpp': 201 | self.assertLen(unknown_fields.UnknownFieldSet(b.inner), 1) 202 | self.assertEqual( 203 | 2001, unknown_fields.UnknownFieldSet(b.inner)[0].field_number) 204 | else: 205 | b_inner_value = b.inner.Extensions[ 206 | extension_in_other_file_pb2.AllowUnknownInnerExtension.hook].value 207 | self.assertEqual(97, b_inner_value) 208 | 209 | def test_reserialize_allow_python_unknown_fields(self): 210 | inner = get_allow_unknown_inner(63) 211 | # Creates a message with only unknown fields. 212 | a = extension_pb2.BaseMessage.FromString(inner.SerializeToString()) 213 | b = m.reserialize_base_message(a) 214 | self.assertEqual(a.SerializeToString(), b.SerializeToString()) 215 | 216 | if __name__ == '__main__': 217 | absltest.main() 218 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/message_module.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 The Pybind Development Team. All rights reserved. 2 | // 3 | // All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "google/protobuf/message.h" 11 | #include "google/protobuf/text_format.h" 12 | #include "pybind11_protobuf/native_proto_caster.h" 13 | #include "pybind11_protobuf/tests/test.pb.h" 14 | 15 | namespace py = ::pybind11; 16 | 17 | namespace { 18 | 19 | using ::pybind11::test::IntMessage; 20 | using ::pybind11::test::TestMessage; 21 | 22 | PYBIND11_MODULE(message_module, m) { 23 | pybind11_protobuf::ImportNativeProtoCasters(); 24 | 25 | m.attr("TEXT_FORMAT_MESSAGE") = R"(string_value: "test" 26 | int_value: 4 27 | int_message { 28 | value: 5 29 | } 30 | repeated_int_value: 6 31 | repeated_int_value: 7 32 | repeated_int_message { 33 | value: 8 34 | } 35 | string_int_map { 36 | key: "k" 37 | value: 5 38 | } 39 | int_message_map { 40 | key: 1 41 | value { 42 | value: 6 43 | } 44 | } 45 | enum_value: ONE 46 | repeated_enum_value: TWO 47 | double_value: 4.5 48 | nested { 49 | value: 5 50 | } 51 | )"; 52 | 53 | m.def( 54 | "make_test_message", 55 | [](std::string text) -> TestMessage { 56 | TestMessage msg; 57 | if (!text.empty() && !::google::protobuf::TextFormat::ParseFromString(text, &msg)) { 58 | throw py::value_error("Failed to parse text format TestMessage"); 59 | } 60 | return msg; 61 | }, 62 | py::arg("text") = ""); 63 | 64 | m.def( 65 | "make_int_message", 66 | [](int value) -> IntMessage { 67 | IntMessage msg; 68 | msg.set_value(value); 69 | return msg; 70 | }, 71 | py::arg("value") = 123); 72 | 73 | m.def( 74 | "make_nested_message", 75 | [](int value) -> TestMessage::Nested { 76 | TestMessage::Nested msg; 77 | msg.set_value(value); 78 | return msg; 79 | }, 80 | py::arg("value") = 123); 81 | } 82 | 83 | } // namespace 84 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/pass_by_module.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 The Pybind Development Team. All rights reserved. 2 | // 3 | // All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "absl/types/optional.h" 14 | #include "absl/types/variant.h" 15 | #include "google/protobuf/descriptor.h" 16 | #include "google/protobuf/descriptor.pb.h" 17 | #include "google/protobuf/dynamic_message.h" 18 | #include "google/protobuf/message.h" 19 | #include "pybind11_abseil/absl_casters.h" 20 | #include "pybind11_protobuf/native_proto_caster.h" 21 | #include "pybind11_protobuf/tests/test.pb.h" 22 | 23 | namespace py = ::pybind11; 24 | 25 | // @TODO(mizux) To remove once BCR provides a pybind11_abseil version with it. 26 | namespace pybind11 { 27 | namespace detail { 28 | #ifndef ABSL_HAVE_STD_VARIANT 29 | template <> 30 | struct type_caster 31 | : void_caster {}; 32 | #endif 33 | 34 | } // namespace detail 35 | } // namespace pybind11 36 | 37 | 38 | namespace { 39 | 40 | using ::pybind11::test::IntMessage; 41 | 42 | bool CheckIntMessage(const IntMessage* message, int32_t value) { 43 | return message ? message->value() == value : false; 44 | } 45 | 46 | bool CheckMessage(const ::google::protobuf::Message* message, int32_t value) { 47 | if (!message) return false; 48 | auto* f = message->GetDescriptor()->FindFieldByName("value"); 49 | if (!f) f = message->GetDescriptor()->FindFieldByName("int_value"); 50 | if (!f) return false; 51 | return message->GetReflection()->GetInt32(*message, f) == value; 52 | } 53 | 54 | IntMessage* GetStatic() { 55 | static IntMessage* msg = new IntMessage(); 56 | msg->set_value(4); 57 | return msg; 58 | } 59 | 60 | PYBIND11_MODULE(pass_by_module, m) { 61 | pybind11_protobuf::ImportNativeProtoCasters(); 62 | 63 | m.attr("PYBIND11_PROTOBUF_UNSAFE") = pybind11::int_(PYBIND11_PROTOBUF_UNSAFE); 64 | 65 | m.def( 66 | "make_int_message", 67 | [](int value) -> IntMessage { 68 | IntMessage msg; 69 | msg.set_value(value); 70 | return msg; 71 | }, 72 | py::arg("value") = 123); 73 | 74 | // constructors 75 | m.def( 76 | "make_ptr", []() -> IntMessage* { return new IntMessage(); }, 77 | py::return_value_policy::take_ownership); 78 | m.def("make_uptr", []() { return std::make_shared(); }); 79 | m.def("make_sptr", 80 | []() { return std::unique_ptr(new IntMessage()); }); 81 | m.def("static_cptr", []() -> const IntMessage* { return GetStatic(); }); 82 | m.def("static_cref", []() -> const IntMessage& { return *GetStatic(); }); 83 | m.def( 84 | "static_ptr", []() -> IntMessage* { return GetStatic(); }, 85 | py::return_value_policy::copy); 86 | m.def("static_ref", []() -> IntMessage& { return *GetStatic(); }); 87 | m.def( 88 | "static_ref_auto", []() -> IntMessage& { return *GetStatic(); }, 89 | py::return_value_policy::automatic_reference); 90 | 91 | // concrete. 92 | m.def( 93 | "concrete", 94 | [](IntMessage message, int value) { 95 | return CheckIntMessage(&message, value); 96 | }, 97 | py::arg("message"), py::arg("value")); 98 | m.def( 99 | "concrete_rval", 100 | [](IntMessage&& message, int value) { 101 | return CheckIntMessage(&message, value); 102 | }, 103 | py::arg("message"), py::arg("value")); 104 | m.def( 105 | "concrete_crval", 106 | [](const IntMessage&& message, int value) { 107 | return CheckIntMessage(&message, value); 108 | }, 109 | py::arg("message"), py::arg("value")); 110 | m.def( 111 | "concrete_cref", 112 | [](const IntMessage& message, int value) { 113 | return CheckIntMessage(&message, value); 114 | }, 115 | py::arg("message"), py::arg("value")); 116 | m.def( 117 | "concrete_cptr", 118 | [](const IntMessage* message, int value) { 119 | return CheckIntMessage(message, value); 120 | }, 121 | py::arg("message"), py::arg("value")); 122 | m.def( 123 | "concrete_cptr_notnone", 124 | [](const IntMessage* message, int value) { 125 | return CheckIntMessage(message, value); 126 | }, 127 | py::arg("message").none(false), py::arg("value")); 128 | m.def( 129 | "concrete_uptr", 130 | [](std::unique_ptr message, int value) { 131 | return CheckIntMessage(message.get(), value); 132 | }, 133 | py::arg("message"), py::arg("value")); 134 | m.def( 135 | "concrete_uptr_ref", 136 | [](std::unique_ptr& message, int value) { 137 | return CheckIntMessage(message.get(), value); 138 | }, 139 | py::arg("message"), py::arg("value")); 140 | m.def( 141 | "concrete_uptr_ptr", 142 | [](std::unique_ptr* message, int value) { 143 | return CheckIntMessage(message->get(), value); 144 | }, 145 | py::arg("message"), py::arg("value")); 146 | 147 | m.def( 148 | "concrete_sptr", 149 | [](std::shared_ptr message, int value) { 150 | return CheckIntMessage(message.get(), value); 151 | }, 152 | py::arg("message"), py::arg("value")); 153 | m.def( 154 | "concrete_csptr", 155 | [](std::shared_ptr message, int value) { 156 | return CheckIntMessage(message.get(), value); 157 | }, 158 | py::arg("message"), py::arg("value")); 159 | m.def( 160 | "concrete_csptr_ref", 161 | [](std::shared_ptr& message, int value) { 162 | return CheckIntMessage(message.get(), value); 163 | }, 164 | py::arg("message"), py::arg("value")); 165 | 166 | m.def( 167 | "concrete_cwref", 168 | [](std::reference_wrapper message, int value) { 169 | return CheckMessage(&message.get(), value); 170 | }, 171 | py::arg("message"), py::arg("value")); 172 | 173 | m.def( 174 | "std_variant", 175 | [](absl::variant message, int value) { 176 | if (absl::holds_alternative(message)) { 177 | return CheckIntMessage(&absl::get(message), value); 178 | } 179 | return false; 180 | }, 181 | py::arg("message"), py::arg("value")); 182 | 183 | m.def( 184 | "std_optional", 185 | [](absl::optional message, int value) { 186 | if (message.has_value()) { 187 | return CheckIntMessage(&message.value(), value); 188 | } 189 | return false; 190 | }, 191 | py::arg("message"), py::arg("value")); 192 | 193 | #if PYBIND11_PROTOBUF_UNSAFE 194 | m.def( 195 | "concrete_ref", 196 | [](IntMessage& message, int value) { 197 | return CheckIntMessage(&message, value); 198 | }, 199 | py::arg("message"), py::arg("value")); 200 | m.def( 201 | "concrete_ptr", 202 | [](IntMessage* message, int value) { 203 | return CheckIntMessage(message, value); 204 | }, 205 | py::arg("message"), py::arg("value")); 206 | m.def( 207 | "concrete_ptr_notnone", 208 | [](IntMessage* message, int value) { 209 | return CheckIntMessage(message, value); 210 | }, 211 | py::arg("message").none(false), py::arg("value")); 212 | m.def( 213 | "concrete_wref", 214 | [](std::reference_wrapper message, int value) { 215 | return CheckMessage(&message.get(), value); 216 | }, 217 | py::arg("message"), py::arg("value")); 218 | #endif 219 | 220 | // abstract. 221 | m.def( 222 | "abstract_rval", 223 | [](::google::protobuf::Message&& message, int value) { 224 | return CheckMessage(&message, value); 225 | }, 226 | py::arg("message"), py::arg("value")); 227 | m.def( 228 | "abstract_cref", 229 | [](const ::google::protobuf::Message& message, int value) { 230 | return CheckMessage(&message, value); 231 | }, 232 | py::arg("message"), py::arg("value")); 233 | m.def( 234 | "abstract_cptr", 235 | [](const ::google::protobuf::Message* message, int value) { 236 | return CheckMessage(message, value); 237 | }, 238 | py::arg("message"), py::arg("value")); 239 | m.def( 240 | "abstract_cptr_notnone", 241 | [](const ::google::protobuf::Message* message, int value) { 242 | return CheckMessage(message, value); 243 | }, 244 | py::arg("message").none(false), py::arg("value")); 245 | m.def( 246 | "abstract_uptr", 247 | [](std::unique_ptr<::google::protobuf::Message> message, int value) { 248 | return CheckMessage(message.get(), value); 249 | }, 250 | py::arg("message"), py::arg("value")); 251 | m.def( 252 | "abstract_sptr", 253 | [](std::shared_ptr<::google::protobuf::Message> message, int value) { 254 | return CheckMessage(message.get(), value); 255 | }, 256 | py::arg("message"), py::arg("value")); 257 | m.def( 258 | "abstract_csptr", 259 | [](std::shared_ptr message, int value) { 260 | return CheckMessage(message.get(), value); 261 | }, 262 | py::arg("message"), py::arg("value")); 263 | m.def( 264 | "abstract_cwref", 265 | [](std::reference_wrapper message, int value) { 266 | return CheckMessage(&message.get(), value); 267 | }, 268 | py::arg("message"), py::arg("value")); 269 | 270 | #if PYBIND11_PROTOBUF_UNSAFE 271 | m.def( 272 | "abstract_ref", 273 | [](::google::protobuf::Message& message, int value) { 274 | return CheckMessage(&message, value); 275 | }, 276 | py::arg("message"), py::arg("value")); 277 | m.def( 278 | "abstract_ptr", 279 | [](::google::protobuf::Message* message, int value) { 280 | return CheckMessage(message, value); 281 | }, 282 | py::arg("message"), py::arg("value")); 283 | m.def( 284 | "abstract_ptr_notnone", 285 | [](::google::protobuf::Message* message, int value) { 286 | return CheckMessage(message, value); 287 | }, 288 | py::arg("message").none(false), py::arg("value")); 289 | m.def( 290 | "abstract_wref", 291 | [](std::reference_wrapper<::google::protobuf::Message> message, int value) { 292 | return CheckMessage(&message.get(), value); 293 | }, 294 | py::arg("message"), py::arg("value")); 295 | #endif 296 | 297 | // overloaded functions 298 | m.def( 299 | "fn_overload", [](const IntMessage&) -> int { return 2; }, 300 | py::arg("message")); 301 | m.def( 302 | "fn_overload", [](const ::google::protobuf::Message&) -> int { return 1; }, 303 | py::arg("message")); 304 | } 305 | 306 | } // namespace 307 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/pass_by_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 The Pybind Development Team. All rights reserved. 2 | # 3 | # All rights reserved. Use of this source code is governed by a 4 | # BSD-style license that can be found in the LICENSE file. 5 | 6 | from __future__ import absolute_import 7 | from __future__ import division 8 | from __future__ import print_function 9 | 10 | from absl.testing import absltest 11 | from absl.testing import parameterized 12 | from google.protobuf import descriptor_pool 13 | from google.protobuf import message_factory 14 | 15 | from pybind11_protobuf.tests import pass_by_module as m 16 | from pybind11_protobuf.tests import test_pb2 17 | 18 | 19 | def get_abstract_pass_by_params(): 20 | p = [] 21 | for x in [ 22 | 'abstract_cptr', 23 | 'abstract_cptr_notnone', 24 | 'abstract_cref', 25 | 'abstract_csptr', 26 | 'abstract_cwref', 27 | 'abstract_ptr', 28 | 'abstract_ptr_notnone', 29 | 'abstract_ref', 30 | 'abstract_rval', 31 | 'abstract_sptr', 32 | 'abstract_uptr', 33 | 'abstract_wref', 34 | ]: 35 | try: 36 | p.append((x, getattr(m, x))) 37 | except AttributeError: 38 | pass 39 | return p 40 | 41 | 42 | def get_pass_by_params(): 43 | p = get_abstract_pass_by_params() 44 | for x in [ 45 | 'concrete', 46 | 'concrete_cptr', 47 | 'concrete_cptr_notnone', 48 | 'concrete_cref', 49 | 'concrete_csptr', 50 | 'concrete_csptr_ref', 51 | 'concrete_cwref', 52 | 'concrete_ptr', 53 | 'concrete_ptr_notnone', 54 | 'concrete_ref', 55 | 'concrete_rval', 56 | 'concrete_crval', 57 | 'concrete_sptr', 58 | 'concrete_uptr', 59 | 'concrete_uptr_ptr', 60 | 'concrete_uptr_ref', 61 | 'concrete_wref', 62 | 'std_variant', 63 | 'std_optional,', 64 | ]: 65 | try: 66 | p.append((x, getattr(m, x))) 67 | except AttributeError: 68 | pass 69 | return p 70 | 71 | 72 | class FakeDescriptor: 73 | 74 | def __init__(self): 75 | self.full_name = 'not.a.Message' 76 | 77 | 78 | class FakeMessage: 79 | 80 | def __init__(self, value=1): 81 | self.DESCRIPTOR = FakeDescriptor() # pylint: disable=invalid-name 82 | self.value = value 83 | 84 | 85 | class PassByTest(parameterized.TestCase): 86 | 87 | @parameterized.named_parameters( 88 | ('make', m.make_int_message), 89 | ('make_ptr', m.make_ptr), 90 | ('make_sptr', m.make_sptr), 91 | ('make_uptr', m.make_uptr), 92 | ('static_cptr', m.static_cptr), 93 | ('static_cref', m.static_cref), 94 | ('static_ptr', m.static_ptr), 95 | ('static_ref', m.static_ref), 96 | ('static_ref_auto', m.static_ref_auto), 97 | ) 98 | def test_construct(self, constructor): 99 | message = constructor() 100 | message.value = 5 101 | self.assertEqual(message.value, 5) 102 | 103 | @parameterized.named_parameters(get_pass_by_params()) 104 | def test_cpp_proto_check(self, check_method): 105 | message = m.make_int_message(7) 106 | self.assertTrue(check_method(message, 7)) 107 | 108 | @parameterized.named_parameters(get_pass_by_params()) 109 | def test_py_proto_check(self, check_method): 110 | message = test_pb2.IntMessage(value=8) 111 | self.assertTrue(check_method(message, 8)) 112 | 113 | @parameterized.named_parameters(get_pass_by_params()) 114 | def test_pool_proto_check(self, check_method): 115 | pool = descriptor_pool.Default() 116 | prototype = message_factory.GetMessageClass( 117 | pool.FindMessageTypeByName('pybind11.test.IntMessage') 118 | ) 119 | message = prototype(value=9) 120 | self.assertTrue(check_method(message, 9)) 121 | 122 | def test_pass_none(self): 123 | self.assertFalse(m.concrete_cptr(None, 1)) 124 | self.assertFalse(m.abstract_cptr(None, 2)) 125 | with self.assertRaises(TypeError): 126 | m.concrete_cptr_notnone(None, 3) 127 | with self.assertRaises(TypeError): 128 | m.abstract_cptr_notnone(None, 4) 129 | self.assertFalse(m.std_variant(None, 0)) 130 | self.assertFalse(m.std_optional(None, 0)) 131 | 132 | @parameterized.named_parameters( 133 | ('concrete', m.concrete_cref), 134 | ('abstract', m.abstract_cref), 135 | ('shared_ptr', m.concrete_sptr), 136 | ('unique_ptr', m.abstract_uptr), 137 | ) 138 | def test_pass_string(self, check_method): 139 | with self.assertRaises(TypeError): 140 | check_method('string', 4) 141 | 142 | @parameterized.named_parameters( 143 | ('concrete', m.concrete_cref), 144 | ('abstract', m.abstract_cref), 145 | ('shared_ptr', m.concrete_sptr), 146 | ('unique_ptr', m.abstract_uptr), 147 | ) 148 | def test_pass_fake1(self, check_method): 149 | fake = FakeMessage() 150 | with self.assertRaises(TypeError): 151 | check_method(fake, 4) 152 | 153 | @parameterized.named_parameters( 154 | ('concrete', m.concrete_cref), 155 | ('abstract', m.abstract_cref), 156 | ('shared_ptr', m.concrete_sptr), 157 | ('unique_ptr', m.abstract_uptr), 158 | ) 159 | def test_pass_fake2(self, check_method): 160 | fake = FakeMessage() 161 | del fake.DESCRIPTOR.full_name 162 | with self.assertRaises(TypeError): 163 | check_method(fake, 4) 164 | 165 | @parameterized.named_parameters( 166 | ('make', m.make_int_message, 2), 167 | ('int_message', test_pb2.IntMessage, 2), 168 | ('test_message', test_pb2.TestMessage, 1), 169 | ) 170 | def test_overload_fn(self, message_fn, expected): 171 | self.assertEqual(expected, m.fn_overload(message_fn())) 172 | 173 | def test_bad_serialize_partial_function_calls(self): 174 | class FakeDescr: 175 | full_name = 'fake_full_name' 176 | 177 | class FakeProto: 178 | DESCRIPTOR = FakeDescr() 179 | 180 | def __init__(self, serialize_fn_return_value=None): 181 | self.serialize_fn_return_value = serialize_fn_return_value 182 | 183 | def SerializePartialToString(self): # pylint: disable=invalid-name 184 | if self.serialize_fn_return_value is None: 185 | raise RuntimeError('Broken serialize_fn.') 186 | return self.serialize_fn_return_value 187 | 188 | with self.assertRaisesRegex( 189 | TypeError, r'\.SerializePartialToString\(\) function call FAILED$' 190 | ): 191 | m.fn_overload(FakeProto()) 192 | with self.assertRaisesRegex( 193 | TypeError, 194 | r'\.SerializePartialToString\(\) function call is expected to return' 195 | r' bytes, but the returned value is \[\]$', 196 | ): 197 | m.fn_overload(FakeProto([])) 198 | with self.assertRaisesRegex(TypeError, r' object is not a valid protobuf$'): 199 | m.fn_overload(FakeProto(b'')) 200 | 201 | 202 | if __name__ == '__main__': 203 | absltest.main() 204 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/pass_proto2_message_module.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 The Pybind Development Team. All rights reserved. 2 | // 3 | // All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "google/protobuf/message.h" 11 | #include "pybind11_protobuf/native_proto_caster.h" 12 | 13 | PYBIND11_MODULE(pass_proto2_message_module, m) { 14 | pybind11_protobuf::ImportNativeProtoCasters(); 15 | 16 | m.def("get_space_used_estimate", 17 | [](const ::google::protobuf::Message& msg) -> std::size_t { 18 | const ::google::protobuf::Reflection* refl = msg.GetReflection(); 19 | return refl != nullptr ? refl->SpaceUsedLong(msg) : 0; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/proto_enum_module.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Pybind Development Team. All rights reserved. 2 | // 3 | // All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "pybind11_protobuf/enum_type_caster.h" 13 | #include "pybind11_protobuf/tests/test.pb.h" 14 | 15 | namespace pybind11 { 16 | namespace test { 17 | // Disabled to use py::enum_ 18 | constexpr bool pybind11_protobuf_enable_enum_type_caster( 19 | AnotherMessage::AnotherEnum*) { 20 | return false; 21 | } 22 | } // namespace test 23 | } // namespace pybind11 24 | 25 | namespace py = ::pybind11; 26 | 27 | namespace { 28 | 29 | using pybind11::test::AnotherMessage; 30 | using pybind11::test::TestMessage; 31 | 32 | PYBIND11_MODULE(proto_enum_module, m) { 33 | // TestMessage::TestEnum does use the pybind11_protobuf type_caster, so 34 | // registering py::enum_ is not allowed. But creating similar enum constants 35 | // is allowed. 36 | m.attr("ZERO") = TestMessage::ZERO; 37 | m.attr("ONE") = TestMessage::ONE; 38 | m.attr("TWO") = TestMessage::TWO; 39 | 40 | // enum functions 41 | m.def( 42 | "adjust_enum", 43 | [](TestMessage::TestEnum e) -> TestMessage::TestEnum { 44 | switch (e) { 45 | case TestMessage::ZERO: 46 | return TestMessage::ONE; 47 | case TestMessage::ONE: 48 | return TestMessage::TWO; 49 | case TestMessage::TWO: 50 | return TestMessage::ZERO; 51 | default: 52 | /// likely an error 53 | return TestMessage::ZERO; 54 | } 55 | }, 56 | py::arg("enum")); 57 | 58 | /// AnotherProto::AnotherEnum does not use pybind11_protobuf::type_caster, 59 | /// so it's fair game to define it here. 60 | py::enum_(m, "AnotherEnum") 61 | .value("ZERO", AnotherMessage::ZERO) 62 | .value("ONE", AnotherMessage::ONE) 63 | .value("TWO", AnotherMessage::TWO); 64 | 65 | m.def( 66 | "adjust_another_enum", 67 | [](AnotherMessage::AnotherEnum e) -> AnotherMessage::AnotherEnum { 68 | switch (e) { 69 | case AnotherMessage::ZERO: 70 | return AnotherMessage::ONE; 71 | case AnotherMessage::ONE: 72 | return AnotherMessage::TWO; 73 | case AnotherMessage::TWO: 74 | return AnotherMessage::ZERO; 75 | default: 76 | /// likely an error 77 | return AnotherMessage::ZERO; 78 | } 79 | }, 80 | py::arg("enum")); 81 | } 82 | 83 | } // namespace 84 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/proto_enum_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 The Pybind Development Team. All rights reserved. 2 | # 3 | # All rights reserved. Use of this source code is governed by a 4 | # BSD-style license that can be found in the LICENSE file. 5 | """Tests for protobuf casters.""" 6 | 7 | from __future__ import absolute_import 8 | 9 | from absl.testing import absltest 10 | 11 | from pybind11_protobuf.tests import proto_enum_module as m 12 | from pybind11_protobuf.tests import test_pb2 13 | 14 | 15 | class ProtoEnumTest(absltest.TestCase): 16 | 17 | def test_enum(self): 18 | self.assertEqual(m.adjust_enum(0), 1) 19 | self.assertEqual( 20 | m.adjust_enum(test_pb2.TestMessage.ONE), test_pb2.TestMessage.TWO) 21 | self.assertEqual(m.adjust_enum(m.ZERO), m.ONE) 22 | self.assertEqual(m.adjust_enum(7), m.ZERO) 23 | with self.assertRaises(TypeError): 24 | m.adjust_enum('ZERO') 25 | 26 | def test_another_enum(self): 27 | self.assertEqual(m.adjust_another_enum(m.AnotherEnum.ZERO), 11) 28 | self.assertEqual( 29 | m.adjust_another_enum(m.AnotherEnum.ONE), test_pb2.AnotherMessage.TWO) 30 | self.assertEqual( 31 | m.adjust_another_enum(m.AnotherEnum.TWO), test_pb2.AnotherMessage.ZERO) 32 | # py::enum_ conversion of proto::Enum is more strict than the type_caster<> 33 | # conversion. 34 | with self.assertRaises(TypeError): 35 | m.adjust_another_enum(test_pb2.AnotherMessage.ZERO) 36 | with self.assertRaises(TypeError): 37 | m.adjust_another_enum(0) 38 | with self.assertRaises(TypeError): 39 | m.adjust_enum('ZERO') 40 | 41 | 42 | if __name__ == '__main__': 43 | absltest.main() 44 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/regression_wrappers_module.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 The Pybind Development Team. All rights reserved. 2 | // 3 | // All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "google/protobuf/message.h" 12 | #include "pybind11_protobuf/native_proto_caster.h" 13 | 14 | namespace { 15 | 16 | PYBIND11_MODULE(regression_wrappers_module, m) { 17 | pybind11_protobuf::ImportNativeProtoCasters(); 18 | 19 | m.def("print_descriptor_unique_ptr", 20 | [](std::unique_ptr<::google::protobuf::Message> message) -> std::string { 21 | return message->GetDescriptor()->DebugString(); 22 | }); 23 | } 24 | 25 | } // namespace 26 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/regression_wrappers_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 The Pybind Development Team. All rights reserved. 2 | # 3 | # All rights reserved. Use of this source code is governed by a 4 | # BSD-style license that can be found in the LICENSE file. 5 | """Tests for protobuf casters.""" 6 | 7 | from __future__ import absolute_import 8 | 9 | from absl.testing import absltest 10 | from absl.testing import parameterized 11 | 12 | from google.protobuf import any_pb2 13 | from google.protobuf import wrappers_pb2 14 | from pybind11_protobuf.tests import regression_wrappers_module as m 15 | 16 | 17 | class RegressionWrappersTest(parameterized.TestCase): 18 | 19 | @parameterized.named_parameters(('bool_value', wrappers_pb2.BoolValue), 20 | ('string_value', wrappers_pb2.StringValue), 21 | ('any', any_pb2.Any)) 22 | def test_print_descriptor_unique_ptr(self, get_message_function): 23 | message = get_message_function() 24 | self.assertIn(message.DESCRIPTOR.name, 25 | m.print_descriptor_unique_ptr(message)) 26 | 27 | 28 | if __name__ == '__main__': 29 | absltest.main() 30 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/test.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Pybind Development Team. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | syntax = "proto3"; 10 | 11 | package pybind11.test; 12 | 13 | message IntMessage { 14 | int32 value = 1; 15 | } 16 | 17 | message TestMessage { 18 | enum TestEnum { 19 | ZERO = 0; 20 | ONE = 1; 21 | TWO = 2; 22 | } 23 | 24 | string string_value = 1; 25 | int32 int_value = 2; 26 | double double_value = 10; 27 | IntMessage int_message = 3; 28 | repeated int32 repeated_int_value = 4; 29 | repeated IntMessage repeated_int_message = 5; 30 | map string_int_map = 6; 31 | map int_message_map = 7; 32 | TestEnum enum_value = 8; 33 | repeated TestEnum repeated_enum_value = 9; 34 | 35 | oneof test_oneof { 36 | int32 oneof_a = 11; 37 | double oneof_b = 12; 38 | } 39 | 40 | message Nested { 41 | int32 value = 1; 42 | } 43 | Nested nested = 13; 44 | } 45 | 46 | message AnotherMessage { 47 | enum AnotherEnum { 48 | ZERO = 0; 49 | ONE = 11; 50 | TWO = 22; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/thread_module.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 The Pybind Development Team. All rights reserved. 2 | // 3 | // All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "absl/strings/string_view.h" 13 | #include "absl/time/clock.h" 14 | #include "absl/time/time.h" 15 | #include "pybind11_abseil/absl_casters.h" 16 | #include "pybind11_protobuf/native_proto_caster.h" 17 | #include "pybind11_protobuf/tests/test.pb.h" 18 | 19 | namespace py = ::pybind11; 20 | 21 | using pybind11::test::TestMessage; 22 | 23 | namespace { 24 | 25 | PYBIND11_MODULE(thread_module, m) { 26 | pybind11_protobuf::ImportNativeProtoCasters(); 27 | 28 | m.def( 29 | "make_message", 30 | [](std::string text) -> TestMessage { 31 | TestMessage msg; 32 | msg.set_string_value(std::move(text)); 33 | return msg; 34 | }, 35 | py::arg("text") = ""); 36 | 37 | m.def( 38 | "make_message_string_view", 39 | [](absl::string_view text) -> TestMessage { 40 | TestMessage msg; 41 | msg.set_string_value(std::string(text)); 42 | return msg; 43 | }, 44 | py::arg("text") = ""); 45 | 46 | m.def( 47 | "make_message_no_gil", 48 | [](std::string text) -> TestMessage { 49 | TestMessage msg; 50 | msg.set_string_value(std::move(text)); 51 | return msg; 52 | }, 53 | py::arg("text") = "", py::call_guard()); 54 | 55 | m.def( 56 | "make_message_string_view_no_gil", 57 | [](absl::string_view text) -> TestMessage { 58 | TestMessage msg; 59 | msg.set_string_value(std::string(text)); 60 | return msg; 61 | }, 62 | py::arg("text") = "", py::call_guard()); 63 | } 64 | 65 | } // namespace 66 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/thread_module_test.py: -------------------------------------------------------------------------------- 1 | """Test pybind11_protobuf with multiple threads. 2 | 3 | Run with `blaze test :thread_test --config=tsan`. 4 | """ 5 | 6 | import concurrent.futures 7 | 8 | from absl.testing import absltest 9 | from absl.testing import parameterized 10 | from pybind11_protobuf.tests import thread_module 11 | 12 | 13 | def make_message(x): 14 | return thread_module.make_message(x) 15 | 16 | 17 | def make_message_string_view(x): 18 | return thread_module.make_message_string_view(x) 19 | 20 | 21 | def make_message_no_gil(x): 22 | return thread_module.make_message_no_gil(x) 23 | 24 | 25 | def make_message_string_view_no_gil(x): 26 | return thread_module.make_message_string_view_no_gil(x) 27 | 28 | 29 | class ThreadTest(parameterized.TestCase): 30 | 31 | @parameterized.named_parameters( 32 | ('make_message', make_message), 33 | ('make_message_string_view', make_message_string_view), 34 | ('make_message_no_gil', make_message_no_gil), 35 | ('make_message_string_view_no_gil', make_message_string_view_no_gil), 36 | ) 37 | def test_parallel(self, fn): 38 | fn('a') 39 | 40 | with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor: 41 | results = list(executor.map(fn, ['abc'] * 10)) 42 | 43 | for x in results: 44 | self.assertEqual('abc', x.string_value) 45 | 46 | 47 | if __name__ == '__main__': 48 | absltest.main() 49 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/very_large_proto_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 The Pybind Development Team. All rights reserved. 2 | # 3 | # All rights reserved. Use of this source code is governed by a 4 | # BSD-style license that can be found in the LICENSE file. 5 | 6 | from absl.testing import absltest 7 | 8 | from pybind11_protobuf.tests import pass_proto2_message_module 9 | from pybind11_protobuf.tests import test_pb2 10 | 11 | 12 | class MessageTest(absltest.TestCase): 13 | 14 | def test_greater_than_2gb_limit(self): 15 | # This test is expected to fail if the Python-to-C++ conversion involves 16 | # serialization/deserialization. 17 | # Code exercised: IsCProtoSuitableForCopyFromCall() 18 | kb = 1024 19 | msg_size = 2 * kb**3 + kb # A little over 2 GB. 20 | msg = test_pb2.TestMessage(string_value='x' * msg_size) 21 | space_used_estimate = pass_proto2_message_module.get_space_used_estimate( 22 | msg 23 | ) 24 | self.assertGreater(space_used_estimate, msg_size) 25 | 26 | 27 | if __name__ == '__main__': 28 | absltest.main() 29 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/we-love-dashes.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package pybind11.test; 4 | 5 | message TokenEffort { 6 | optional int32 score = 1; 7 | } 8 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/we_love_dashes_cc_and_py_in_deps_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 The Pybind Development Team. All rights reserved. 2 | # 3 | # All rights reserved. Use of this source code is governed by a 4 | # BSD-style license that can be found in the LICENSE file. 5 | 6 | from absl.testing import absltest 7 | from pybind11_protobuf.tests import we_love_dashes_cc_only_module 8 | 9 | # NOTE: ":we-love-dashes_py_pb2" is in deps but intentionally not imported here. 10 | 11 | 12 | class MessageTest(absltest.TestCase): 13 | 14 | def test_return_then_pass(self): 15 | msg = we_love_dashes_cc_only_module.return_token_effort(234) 16 | score = we_love_dashes_cc_only_module.pass_token_effort(msg) 17 | self.assertEqual(score, 234) 18 | 19 | 20 | if __name__ == '__main__': 21 | absltest.main() 22 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/we_love_dashes_cc_only_module.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 The Pybind Development Team. All rights reserved. 2 | // 3 | // All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | #include 7 | 8 | #include "pybind11_protobuf/native_proto_caster.h" 9 | #include "pybind11_protobuf/tests/we-love-dashes.pb.h" 10 | 11 | namespace { 12 | 13 | PYBIND11_MODULE(we_love_dashes_cc_only_module, m) { 14 | pybind11_protobuf::ImportNativeProtoCasters(); 15 | 16 | m.def("return_token_effort", [](int score) { 17 | pybind11::test::TokenEffort msg; 18 | msg.set_score(score); 19 | return msg; 20 | }); 21 | 22 | m.def("pass_token_effort", 23 | [](const pybind11::test::TokenEffort& msg) { return msg.score(); }); 24 | } 25 | 26 | } // namespace 27 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/we_love_dashes_cc_only_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 The Pybind Development Team. All rights reserved. 2 | # 3 | # All rights reserved. Use of this source code is governed by a 4 | # BSD-style license that can be found in the LICENSE file. 5 | 6 | from absl.testing import absltest 7 | from google.protobuf.internal import api_implementation 8 | from pybind11_protobuf.tests import we_love_dashes_cc_only_module 9 | 10 | 11 | class MessageTest(absltest.TestCase): 12 | 13 | def test_return_then_pass(self): 14 | if api_implementation.Type() == 'cpp': 15 | msg = we_love_dashes_cc_only_module.return_token_effort(234) 16 | score = we_love_dashes_cc_only_module.pass_token_effort(msg) 17 | self.assertEqual(score, 234) 18 | else: 19 | with self.assertRaisesRegex( 20 | TypeError, 21 | r'^Cannot construct a protocol buffer message type' 22 | r' pybind11\.test\.TokenEffort in python\.' 23 | r' .*pybind11_protobuf\.tests\.we_love_dashes_pb2\?$', 24 | ): 25 | we_love_dashes_cc_only_module.return_token_effort(0) 26 | 27 | 28 | if __name__ == '__main__': 29 | absltest.main() 30 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/we_love_dashes_py_only_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 The Pybind Development Team. All rights reserved. 2 | # 3 | # All rights reserved. Use of this source code is governed by a 4 | # BSD-style license that can be found in the LICENSE file. 5 | 6 | from absl.testing import absltest 7 | 8 | from pybind11_protobuf.tests import pass_proto2_message_module 9 | from pybind11_protobuf.tests import we_love_dashes_pb2 10 | 11 | 12 | class MessageTest(absltest.TestCase): 13 | 14 | def test_pass_proto2_message(self): 15 | msg = we_love_dashes_pb2.TokenEffort(score=345) 16 | space_used_estimate = pass_proto2_message_module.get_space_used_estimate( 17 | msg 18 | ) 19 | self.assertGreater(space_used_estimate, 0) 20 | 21 | 22 | if __name__ == '__main__': 23 | absltest.main() 24 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/wrapped_proto_module.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 The Pybind Development Team. All rights reserved. 2 | // 3 | // All rights reserved. Use of this source code is governed by a 4 | // BSD-style license that can be found in the LICENSE file. 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "absl/functional/function_ref.h" 15 | #include "absl/status/statusor.h" 16 | #include "absl/types/optional.h" 17 | #include "google/protobuf/dynamic_message.h" 18 | #include "pybind11_protobuf/tests/test.pb.h" 19 | #include "pybind11_protobuf/wrapped_proto_caster.h" 20 | 21 | namespace py = ::pybind11; 22 | 23 | namespace { 24 | 25 | using ::pybind11::test::IntMessage; 26 | using ::pybind11::test::TestMessage; 27 | using ::pybind11_protobuf::WithWrappedProtos; 28 | using ::pybind11_protobuf::WrappedProto; 29 | using ::pybind11_protobuf::WrappedProtoKind; 30 | 31 | const TestMessage& GetStatic() { 32 | static TestMessage test_message = [] { 33 | TestMessage msg; 34 | msg.set_int_value(123); 35 | return msg; 36 | }(); 37 | 38 | return test_message; 39 | } 40 | 41 | bool CheckMessage(const ::google::protobuf::Message* message, int32_t value) { 42 | if (!message) return false; 43 | auto* f = message->GetDescriptor()->FindFieldByName("value"); 44 | if (!f) f = message->GetDescriptor()->FindFieldByName("int_value"); 45 | if (!f) return false; 46 | return message->GetReflection()->GetInt32(*message, f) == value; 47 | } 48 | 49 | bool CheckIntMessage(const IntMessage* message, int32_t value) { 50 | return CheckMessage(message, value); 51 | } 52 | 53 | class A { 54 | public: 55 | A(const IntMessage& message) : value_(message.value()) {} 56 | int64_t value() { return value_; } 57 | 58 | private: 59 | int64_t value_; 60 | }; 61 | 62 | PYBIND11_MODULE(wrapped_proto_module, m) { 63 | pybind11_protobuf::ImportWrappedProtoCasters(); 64 | 65 | m.def("get_test_message", WithWrappedProtos(GetStatic)); 66 | 67 | m.def("make_int_message", WithWrappedProtos([](int value) -> IntMessage { 68 | IntMessage msg; 69 | msg.set_value(value); 70 | return msg; 71 | }), 72 | py::arg("value") = 123); 73 | 74 | m.def("fn_overload", 75 | [](WrappedProto proto) { 76 | return 1; 77 | }); 78 | m.def("fn_overload", [](const IntMessage& proto) { return 2; }); 79 | 80 | m.def("check_int", WithWrappedProtos(&CheckIntMessage), py::arg("message"), 81 | py::arg("value")); 82 | 83 | // Check calls. 84 | m.def("check", WithWrappedProtos(&CheckMessage), py::arg("message"), 85 | py::arg("value")); 86 | 87 | m.def("check_cref", 88 | WithWrappedProtos([](const TestMessage& msg, int32_t value) { 89 | return CheckMessage(&msg, value); 90 | }), 91 | py::arg("proto"), py::arg("value")); 92 | m.def("check_cptr", 93 | WithWrappedProtos([](const TestMessage* msg, int32_t value) { 94 | return CheckMessage(msg, value); 95 | }), 96 | py::arg("proto"), py::arg("value")); 97 | m.def("check_val", WithWrappedProtos([](TestMessage msg, int32_t value) { 98 | return CheckMessage(&msg, value); 99 | }), 100 | py::arg("proto"), py::arg("value")); 101 | m.def("check_rval", WithWrappedProtos([](TestMessage&& msg, int32_t value) { 102 | return CheckMessage(&msg, value); 103 | }), 104 | py::arg("proto"), py::arg("value")); 105 | 106 | // WithWrappedProto does not auto-wrap mutable protos, but constructing a 107 | // wrapper manually will still work. Note, however, that the proto will be 108 | // copied. 109 | m.def( 110 | "check_mutable", 111 | [](WrappedProto msg, 112 | int32_t value) { 113 | return CheckMessage(static_cast(msg), value); 114 | }, 115 | py::arg("proto"), py::arg("value")); 116 | 117 | // Use WithWrappedProto to define an A.__init__ method 118 | py::class_(m, "A") 119 | .def(py::init(WithWrappedProtos( 120 | [](const IntMessage& message) { return A(message); }))) 121 | .def("value", &A::value); 122 | 123 | // And wrap std::vector 124 | m.def("check_int_message_list", 125 | WithWrappedProtos([](const std::vector& v, int32_t value) { 126 | int i = 0; 127 | for (const auto& x : v) { 128 | i += CheckMessage(&x, value); 129 | } 130 | return i; 131 | }), 132 | py::arg("protos"), py::arg("value")); 133 | m.def("take_int_message_list", 134 | WithWrappedProtos([](std::vector v, int32_t value) { 135 | int i = 0; 136 | for (const auto& x : v) { 137 | i += CheckMessage(&x, value); 138 | } 139 | return i; 140 | }), 141 | py::arg("protos"), py::arg("value")); 142 | 143 | m.def("make_int_message_list", WithWrappedProtos([](int value) { 144 | std::vector result; 145 | for (int i = 0; i < 3; i++) { 146 | result.emplace_back(); 147 | result.back().set_value(value); 148 | } 149 | return result; 150 | }), 151 | py::arg("value") = 123); 152 | } 153 | 154 | /// Below here are compile tests for fast_cpp_proto_casters 155 | int GetInt() { return 0; } 156 | static TestMessage kMessage; 157 | const TestMessage& GetConstReference() { return kMessage; } 158 | const TestMessage* GetConstPtr() { return &kMessage; } 159 | TestMessage GetValue() { return TestMessage(); } 160 | // Note that this should never actually run. 161 | TestMessage&& GetRValue() { return std::move(kMessage); } 162 | absl::StatusOr GetStatusOr() { return TestMessage(); } 163 | absl::optional GetOptional() { return TestMessage(); } 164 | std::vector GetVector() { return {}; } 165 | 166 | void PassInt(int) {} 167 | void PassConstReference(const TestMessage&) {} 168 | void PassConstPtr(const TestMessage*) {} 169 | void PassValue(TestMessage) {} 170 | void PassRValue(TestMessage&&) {} 171 | void PassOptional(absl::optional) {} 172 | void PassVector(std::vector) {} 173 | 174 | struct Struct { 175 | TestMessage MemberFn() { return kMessage; } 176 | TestMessage ConstMemberFn() const { return kMessage; } 177 | }; 178 | 179 | void test_static_asserts() { 180 | using pybind11::test::IntMessage; 181 | using pybind11::test::TestMessage; 182 | using pybind11_protobuf::WithWrappedProtos; 183 | using pybind11_protobuf::WrappedProto; 184 | using pybind11_protobuf::impl::WrapHelper; 185 | 186 | static_assert(std::is_same, 187 | WrapHelper::type>::value, 188 | ""); 189 | 190 | static_assert(std::is_same, 191 | WrapHelper::type>::value, 192 | ""); 193 | 194 | static_assert(std::is_same, 195 | WrapHelper::type>::value, 196 | ""); 197 | 198 | static_assert(std::is_same, 199 | WrapHelper::type>::value, 200 | ""); 201 | 202 | // These function refs ensure that the generated wrappers have the expected 203 | // type signatures. 204 | // Return types 205 | absl::FunctionRef(WithWrappedProtos(GetInt)); 206 | absl::FunctionRef(WithWrappedProtos(GetConstReference)); 207 | absl::FunctionRef(WithWrappedProtos(GetConstPtr)); 208 | absl::FunctionRef(WithWrappedProtos(GetValue)); 209 | absl::FunctionRef(WithWrappedProtos(GetRValue)); 210 | absl::FunctionRef(WithWrappedProtos(&Struct::MemberFn)); 211 | absl::FunctionRef( 212 | WithWrappedProtos(&Struct::ConstMemberFn)); 213 | absl::FunctionRef()>( 214 | WithWrappedProtos(GetStatusOr)); 215 | absl::FunctionRef()>( 216 | WithWrappedProtos(GetOptional)); 217 | absl::FunctionRef()>(WithWrappedProtos(GetVector)); 218 | 219 | // Passing types 220 | absl::FunctionRef(WithWrappedProtos(PassInt)); 221 | absl::FunctionRef( 222 | WithWrappedProtos(PassConstReference)); 223 | absl::FunctionRef(WithWrappedProtos(PassConstPtr)); 224 | absl::FunctionRef(WithWrappedProtos(PassValue)); 225 | absl::FunctionRef(WithWrappedProtos(PassRValue)); 226 | absl::FunctionRef)>( 227 | WithWrappedProtos(PassOptional)); 228 | absl::FunctionRef)>( 229 | WithWrappedProtos(PassVector)); 230 | } 231 | 232 | #if defined(WRAPPED_PROTO_CASTER_NONCOMPILE_TEST) 233 | // This code could be added as a non-compile test. 234 | // 235 | // It exercises the WithWrappedProtos(...) codepaths when called with mutable 236 | // protos, and is expected to fail with a static_assert. 237 | // 238 | TestMessage& GetReference(); 239 | TestMessage* GetPtr(); 240 | void PassPtr(TestMessage*); 241 | void PassReference(TestMessage&); 242 | 243 | void test_wrapping_disabled() { 244 | absl::FunctionRef(WithWrappedProtos(GetReference)); 245 | absl::FunctionRef(WithWrappedProtos(GetPtr)); 246 | absl::FunctionRef(WithWrappedProtos(PassPtr)); 247 | absl::FunctionRef(WithWrappedProtos(PassReference)); 248 | } 249 | #endif // WRAPPED_PROTO_CASTER_NONCOMPILE_TEST 250 | 251 | } // namespace 252 | -------------------------------------------------------------------------------- /pybind11_protobuf/tests/wrapped_proto_module_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 The Pybind Development Team. All rights reserved. 2 | # 3 | # All rights reserved. Use of this source code is governed by a 4 | # BSD-style license that can be found in the LICENSE file. 5 | """Tests for protobuf casters.""" 6 | 7 | from __future__ import absolute_import 8 | from __future__ import division 9 | from __future__ import print_function 10 | 11 | from absl.testing import absltest 12 | from absl.testing import parameterized 13 | 14 | from pybind11_protobuf.tests import compare 15 | from pybind11_protobuf.tests import test_pb2 16 | from pybind11_protobuf.tests import wrapped_proto_module as m 17 | 18 | 19 | class WrappedProtoTest(parameterized.TestCase, compare.ProtoAssertions): 20 | 21 | def test_type(self): 22 | # These are both seen as the concrete type. 23 | self.assertIn( 24 | str(type(m.get_test_message())), [ 25 | "", 26 | ]) 27 | 28 | def test_full_name(self): 29 | self.assertEqual( 30 | str(m.make_int_message().DESCRIPTOR.full_name), 31 | 'pybind11.test.IntMessage') 32 | 33 | def test_str(self): 34 | a = m.get_test_message() 35 | b = m.get_test_message() 36 | self.assertEqual(str(a), str(b)) 37 | 38 | def test_readable(self): 39 | a = m.get_test_message() 40 | self.assertEqual(123, a.int_value) 41 | 42 | @parameterized.named_parameters( 43 | ('check', m.check), 44 | ('check_cref', m.check_cref), 45 | ('check_cptr', m.check_cptr), 46 | ('check_val', m.check_val), 47 | ('check_rval', m.check_rval), 48 | ('check_mutable', m.check_mutable), 49 | ) 50 | def test_native(self, check): 51 | a = test_pb2.TestMessage() 52 | a.int_value = 33 53 | self.assertTrue(check(a, 33)) 54 | 55 | @parameterized.named_parameters( 56 | ('check', m.check), 57 | ('check_cref', m.check_cref), 58 | ('check_cptr', m.check_cptr), 59 | ('check_val', m.check_val), 60 | ('check_rval', m.check_rval), 61 | ('check_mutable', m.check_mutable), 62 | ) 63 | def test_wrapped(self, check): 64 | a = m.get_test_message() 65 | a.int_value = 34 66 | self.assertTrue(check(a, 34)) 67 | 68 | def test_check_list(self): 69 | a = [ 70 | m.make_int_message(value=33), 71 | m.make_int_message(value=34), 72 | test_pb2.IntMessage(value=34), 73 | test_pb2.IntMessage(value=33), 74 | ] 75 | self.assertEqual(2, m.check_int_message_list(a, 34)) 76 | self.assertEqual(2, m.check_int_message_list(a, 33)) 77 | 78 | def test_make_list(self): 79 | a = m.make_int_message_list(44) 80 | self.assertEqual(3, m.take_int_message_list(a, 44)) 81 | 82 | def test_call_with_str(self): 83 | with self.assertRaises(TypeError): 84 | m.check('any string', 32) 85 | 86 | def test_call_with_none(self): 87 | self.assertFalse(m.check(None, 32)) 88 | 89 | def test_equality(self): 90 | a = m.get_test_message() 91 | a.int_value = 44 92 | self.assertProtoEqual(test_pb2.TestMessage(int_value=44), a) 93 | 94 | def test_fn_overload(self): 95 | self.assertEqual(1, m.fn_overload(test_pb2.IntMessage())) 96 | self.assertEqual(1, m.fn_overload(m.make_int_message())) 97 | with self.assertRaises(TypeError): 98 | m.fn_overload('any string') 99 | 100 | def test_constructor(self): 101 | a = m.A(m.make_int_message()) 102 | self.assertEqual(m.make_int_message().value, a.value()) 103 | 104 | 105 | if __name__ == '__main__': 106 | absltest.main() 107 | -------------------------------------------------------------------------------- /scripts/build_and_run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The following script builds and runs tests 4 | 5 | set -e # exit when any command fails 6 | set -x # Prints all executed command 7 | 8 | MYDIR="$(dirname "$(realpath "$0")")" 9 | 10 | BAZEL=$(which bazel || true) 11 | if [[ -z $BAZEL || ! -x $BAZEL ]] 12 | then 13 | echo -e -n '\e[1m\e[93m' 14 | echo -n 'Bazel not found (bazel (https://bazel.build/) is needed to ' 15 | echo -n 'compile & test). ' 16 | echo -e 'Exiting...\e[0m' 17 | exit 1 18 | fi 19 | 20 | echo "Building and testing in $PWD using 'python' (version $PYVERSION)." 21 | 22 | bazel clean --expunge # Force a dep update 23 | 24 | BAZEL_CXXOPTS="-std=c++17" bazel test ... --test_output=errors "$@" --enable_bzlmod 25 | BAZEL_CXXOPTS="-std=c++20" bazel test ... --test_output=errors "$@" --enable_bzlmod 26 | 27 | BAZEL_CXXOPTS="-std=c++17" bazel test ... --test_output=errors "$@" --noenable_bzlmod 28 | BAZEL_CXXOPTS="-std=c++20" bazel test ... --test_output=errors "$@" --noenable_bzlmod 29 | --------------------------------------------------------------------------------