stinger-ipc 0.11.0.dev1__tar.gz → 0.11.0.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. {stinger_ipc-0.11.0.dev1/stinger_ipc.egg-info → stinger_ipc-0.11.0.dev2}/PKG-INFO +1 -1
  2. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/pyproject.toml +1 -1
  3. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2/stinger_ipc.egg-info}/PKG-INFO +1 -1
  4. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/components.py +8 -0
  5. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/cpp_symb.py +11 -0
  6. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/payload.py +11 -0
  7. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/protobuf_compiler.py +70 -8
  8. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/python_symb.py +10 -0
  9. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/rust_symb.py +34 -1
  10. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/schema/0.3/schema.yaml +5 -0
  11. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/partials/codec.jinja2 +5 -4
  12. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/partials/codec.jinja2 +2 -2
  13. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/Cargo.toml.jinja2 +3 -0
  14. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/examples/server_demo.rs.jinja2 +1 -1
  15. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/partials/codec.jinja2 +4 -4
  16. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/src/server.rs.jinja2 +3 -3
  17. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/tools/generic_generator.py +24 -8
  18. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_protobuf_payloads.py +91 -1
  19. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/LICENSE +0 -0
  20. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/README.md +0 -0
  21. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/setup.cfg +0 -0
  22. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stinger_ipc.egg-info/SOURCES.txt +0 -0
  23. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stinger_ipc.egg-info/dependency_links.txt +0 -0
  24. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stinger_ipc.egg-info/entry_points.txt +0 -0
  25. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stinger_ipc.egg-info/requires.txt +0 -0
  26. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stinger_ipc.egg-info/top_level.txt +0 -0
  27. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/__init__.py +0 -0
  28. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/arg_datatypes.py +0 -0
  29. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/arg_models.py +0 -0
  30. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/args.py +0 -0
  31. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/asyncapi.py +0 -0
  32. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/config.py +0 -0
  33. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/connection.py +0 -0
  34. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/exceptions.py +0 -0
  35. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/filtering.py +0 -0
  36. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/interface.py +0 -0
  37. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/ipc_command.py +0 -0
  38. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/ipc_method.py +0 -0
  39. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/ipc_property.py +0 -0
  40. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/ipc_signal.py +0 -0
  41. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/lang_symb.py +0 -0
  42. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/loading.py +0 -0
  43. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/markdown_symb.py +0 -0
  44. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/models.py +0 -0
  45. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/schema/0.1/schema.yaml +0 -0
  46. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/schema/0.2/schema.yaml +0 -0
  47. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/symbols/__init__.py +0 -0
  48. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/symbols/protobuf.py +0 -0
  49. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/CMakeLists.txt.jinja2 +0 -0
  50. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/cmake/config.cmake.in.jinja2 +0 -0
  51. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/examples/client_main.cpp.jinja2 +0 -0
  52. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/examples/discovery_example.cpp.jinja2 +0 -0
  53. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/examples/discovery_publisher.cpp.jinja2 +0 -0
  54. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/examples/server_main.cpp.jinja2 +0 -0
  55. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/include/client.hpp.jinja2 +0 -0
  56. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/include/command_payloads.hpp.jinja2 +0 -0
  57. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/include/constants.hpp.jinja2 +0 -0
  58. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/include/discovery.hpp.jinja2 +0 -0
  59. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/include/enums.hpp.jinja2 +0 -0
  60. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/include/method_payloads.hpp.jinja2 +0 -0
  61. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/include/property_structs.hpp.jinja2 +0 -0
  62. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/include/server.hpp.jinja2 +0 -0
  63. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/include/signal_payloads.hpp.jinja2 +0 -0
  64. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/include/structs.hpp.jinja2 +0 -0
  65. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/include/wire.hpp.jinja2 +0 -0
  66. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/partials/args.jinja2 +0 -0
  67. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/partials/deserialize.jinja2 +0 -0
  68. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/partials/properties.jinja2 +0 -0
  69. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/partials/serialize.jinja2 +0 -0
  70. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/partials/validate.jinja2 +0 -0
  71. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/src/client.cpp.jinja2 +0 -0
  72. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/src/command_payloads.cpp.jinja2 +0 -0
  73. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/src/discovery.cpp.jinja2 +0 -0
  74. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/src/method_payloads.cpp.jinja2 +0 -0
  75. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/src/property_structs.cpp.jinja2 +0 -0
  76. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/src/server.cpp.jinja2 +0 -0
  77. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/src/signal_payloads.cpp.jinja2 +0 -0
  78. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/src/structs.cpp.jinja2 +0 -0
  79. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/tests/test_client.cpp.jinja2 +0 -0
  80. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/tests/test_serialization.cpp.jinja2 +0 -0
  81. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/cpp/tests/test_server.cpp.jinja2 +0 -0
  82. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/markdown/index.md.jinja2 +0 -0
  83. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/protobuf/{{stinger.name|snake_case}}.proto.jinja2 +0 -0
  84. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/Taskfile.yml.jinja2 +0 -0
  85. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/docs/api.rst.jinja2 +0 -0
  86. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/docs/conf.py.jinja2 +0 -0
  87. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/docs/examples.rst.jinja2 +0 -0
  88. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/docs/index.rst.jinja2 +0 -0
  89. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/examples/client_demo.py.jinja2 +0 -0
  90. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/examples/client_demo_classes.py.jinja2 +0 -0
  91. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/examples/server_demo.py.jinja2 +0 -0
  92. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/mypy.ini.jinja2 +0 -0
  93. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/partials/docstrings.jinja2 +0 -0
  94. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/partials/imports.jinja2 +0 -0
  95. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/partials/payloads.jinja2 +0 -0
  96. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/pyproject.toml.jinja2 +0 -0
  97. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/src/{{stinger.python.package_directory}}/__init__.py.jinja2 +0 -0
  98. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/src/{{stinger.python.package_directory}}/client.py.jinja2 +0 -0
  99. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/src/{{stinger.python.package_directory}}/constants.py.jinja2 +0 -0
  100. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/src/{{stinger.python.package_directory}}/discovery.py.jinja2 +0 -0
  101. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/src/{{stinger.python.package_directory}}/interface_types.py.jinja2 +0 -0
  102. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/src/{{stinger.python.package_directory}}/lwt.py.jinja2 +0 -0
  103. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/src/{{stinger.python.package_directory}}/method_codes.py.jinja2 +0 -0
  104. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/src/{{stinger.python.package_directory}}/property.py.jinja2 +0 -0
  105. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/src/{{stinger.python.package_directory}}/semver.py.jinja2 +0 -0
  106. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/src/{{stinger.python.package_directory}}/server.py.jinja2 +0 -0
  107. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/tests/test_client.py.jinja2 +0 -0
  108. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/python/tests/test_server.py.jinja2 +0 -0
  109. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/Cargo.lock.jinja2 +0 -0
  110. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/README.md.jinja2 +0 -0
  111. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/docs/client.md.jinja2 +0 -0
  112. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/docs/overview.md.jinja2 +0 -0
  113. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/docs/server.md.jinja2 +0 -0
  114. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/examples/client_demo.rs.jinja2 +0 -0
  115. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/partials/payloads.jinja2 +0 -0
  116. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/src/client.rs.jinja2 +0 -0
  117. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/src/constants.rs.jinja2 +0 -0
  118. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/src/discovery.rs.jinja2 +0 -0
  119. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/src/interface.rs.jinja2 +0 -0
  120. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/src/lib.rs.jinja2 +0 -0
  121. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/src/lwt.rs.jinja2 +0 -0
  122. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/src/message.rs.jinja2 +0 -0
  123. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/src/payloads.rs.jinja2 +0 -0
  124. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/src/property.rs.jinja2 +0 -0
  125. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/rust/src/serde_formats.rs.jinja2 +0 -0
  126. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/web/app.js.jinja2 +0 -0
  127. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/web/index.html.jinja2 +0 -0
  128. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/templates/web/styles.css.jinja2 +0 -0
  129. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/tools/__init__.py +0 -0
  130. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/tools/cli.py +0 -0
  131. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/topic_template.py +0 -0
  132. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/stingeripc/topic_util.py +0 -0
  133. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_args.py +0 -0
  134. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_enum.py +0 -0
  135. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_example_values.py +0 -0
  136. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_examples.py +0 -0
  137. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_interface.py +0 -0
  138. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_ipc_command.py +0 -0
  139. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_ipc_method.py +0 -0
  140. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_ipc_property.py +0 -0
  141. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_ipc_signal.py +0 -0
  142. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_struct.py +0 -0
  143. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_topic_template.py +0 -0
  144. {stinger_ipc-0.11.0.dev1 → stinger_ipc-0.11.0.dev2}/tests/test_topic_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stinger-ipc
3
- Version: 0.11.0.dev1
3
+ Version: 0.11.0.dev2
4
4
  Summary: Tools to create code to do IPC over MQTT
5
5
  License: MIT License
6
6
  Copyright © 2018-2025 Jacob Brunson.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stinger-ipc"
3
- version = "0.11.0.dev1"
3
+ version = "0.11.0.dev2"
4
4
  description = "Tools to create code to do IPC over MQTT"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stinger-ipc
3
- Version: 0.11.0.dev1
3
+ Version: 0.11.0.dev2
4
4
  Summary: Tools to create code to do IPC over MQTT
5
5
  License: MIT License
6
6
  Copyright © 2018-2025 Jacob Brunson.
@@ -257,6 +257,14 @@ class StingerSpec:
257
257
  """True if any payload in the interface is a protobuf message."""
258
258
  return any(payload.is_protobuf for payload in self.all_payloads())
259
259
 
260
+ def uses_well_known_protobuf(self) -> bool:
261
+ """True if any payload is one of protobuf's well-known types.
262
+
263
+ Those come from the protobuf runtime rather than from this interface's own
264
+ .proto files, and some languages need an extra dependency to reach them.
265
+ """
266
+ return any(ref.is_well_known for ref in self.all_protobuf_refs())
267
+
260
268
  def uses_json(self) -> bool:
261
269
  """True if any payload in the interface is a JSON argument list."""
262
270
  return any(not payload.is_protobuf for payload in self.all_payloads())
@@ -444,6 +444,17 @@ class CppProtobufRefSymbols(CppSymbols):
444
444
  """The C++ namespace protoc puts this message in, from its protobuf package."""
445
445
  return "::".join(p for p in self._ref.package.split(".") if p)
446
446
 
447
+ @property
448
+ def include(self) -> str:
449
+ """The ``#include`` argument, brackets and all, for this message's header.
450
+
451
+ A well-known type's header ships with libprotobuf and is found on the
452
+ include path; the interface's own headers protoc wrote into the generated
453
+ library are quoted and relative to it.
454
+ """
455
+ header = self._ref.proto_file.replace(".proto", ".pb.h")
456
+ return f"<{header}>" if self._ref.is_well_known else f'"proto/{header}"'
457
+
447
458
  @property
448
459
  def qualified_name(self) -> str:
449
460
  """How generated C++ code names this message, e.g. ``::weather::v1::CurrentConditions``."""
@@ -67,6 +67,17 @@ class ProtobufMessageRef(BaseModel):
67
67
  """True once the reference has been matched to a message in the .proto sources."""
68
68
  return self._descriptor is not None
69
69
 
70
+ @property
71
+ def is_well_known(self) -> bool:
72
+ """True for one of protobuf's own well-known types, e.g. ``google.protobuf.Empty``.
73
+
74
+ These are declared by the protobuf project rather than by this interface,
75
+ so they are neither found in the interface's .proto directory nor compiled
76
+ alongside it: every language's protobuf runtime already ships them, and
77
+ generated code reaches for that copy instead.
78
+ """
79
+ return self.package == "google.protobuf"
80
+
70
81
  @property
71
82
  def proto_file(self) -> str:
72
83
  """The .proto file that declares this message, relative to the source directory."""
@@ -9,11 +9,16 @@ is what the generated code needs in order to import it.
9
9
  Matching is done from a descriptor set produced by ``protoc`` rather than by
10
10
  parsing ``.proto`` text, so imports, packages and nested messages resolve exactly
11
11
  the way the protobuf compiler resolves them.
12
+
13
+ Protobuf's own well-known types -- ``google.protobuf.Empty`` and its siblings --
14
+ are resolvable without appearing in the interface's sources at all, since they
15
+ are declared by the protobuf project and shipped with every language's runtime.
12
16
  """
13
17
 
14
18
  from __future__ import annotations
15
19
 
16
20
  import difflib
21
+ import importlib
17
22
  import shutil
18
23
  import subprocess
19
24
  import tempfile
@@ -87,6 +92,49 @@ def _message_names(proto_file: FileDescriptorProto) -> list[tuple[str, Descripto
87
92
  return found
88
93
 
89
94
 
95
+ # The .proto files the protobuf project ships as its "well-known types". Every
96
+ # protobuf runtime carries them, so a message from one of these resolves without
97
+ # the interface declaring anything, and nothing here is ever copied or compiled
98
+ # into the generated output. Listed by module rather than discovered, because the
99
+ # set is fixed by the protobuf specification and reading it from the runtime's
100
+ # descriptor pool would only report whichever modules happened to be imported.
101
+ _WELL_KNOWN_MODULES = (
102
+ "any_pb2",
103
+ "api_pb2",
104
+ "duration_pb2",
105
+ "empty_pb2",
106
+ "field_mask_pb2",
107
+ "source_context_pb2",
108
+ "struct_pb2",
109
+ "timestamp_pb2",
110
+ "type_pb2",
111
+ "wrappers_pb2",
112
+ )
113
+
114
+ _well_known_index: Optional[dict[str, tuple[str, DescriptorProto]]] = None
115
+
116
+
117
+ def well_known_message_index() -> dict[str, tuple[str, DescriptorProto]]:
118
+ """Fully-qualified name -> (declaring file, descriptor) for the well-known types.
119
+
120
+ Read out of the protobuf runtime that stinger itself imports, so the messages
121
+ match the ones the generated code will use, and no protoc invocation is needed
122
+ to know they exist.
123
+ """
124
+ global _well_known_index
125
+ if _well_known_index is None:
126
+ index: dict[str, tuple[str, DescriptorProto]] = {}
127
+ for module_name in _WELL_KNOWN_MODULES:
128
+ module = importlib.import_module(f"google.protobuf.{module_name}")
129
+ file_descriptor = module.DESCRIPTOR
130
+ file_proto = FileDescriptorProto()
131
+ file_descriptor.CopyToProto(file_proto)
132
+ for full_name, descriptor in _message_names(file_proto):
133
+ index[full_name] = (file_descriptor.name, descriptor)
134
+ _well_known_index = index
135
+ return _well_known_index
136
+
137
+
90
138
  class ProtobufSources:
91
139
  """The ``.proto`` files an interface draws its messages from."""
92
140
 
@@ -102,6 +150,16 @@ class ProtobufSources:
102
150
  raise ProtobufError(f"Protobuf source directory not found: {self.proto_dir}")
103
151
  return sorted(self.proto_dir.rglob("*.proto"))
104
152
 
153
+ @property
154
+ def has_sources(self) -> bool:
155
+ """True when the directory exists and holds at least one .proto file.
156
+
157
+ An interface whose only protobuf payloads are well-known types declares no
158
+ .proto files of its own, so the absence of sources is not an error until
159
+ something actually needs to be compiled.
160
+ """
161
+ return self.proto_dir.is_dir() and any(self.proto_dir.rglob("*.proto"))
162
+
105
163
  def run(self, *flags: str) -> None:
106
164
  """Run protoc over every source file with the given flags."""
107
165
  files = self.proto_files
@@ -125,9 +183,10 @@ class ProtobufSources:
125
183
  """Fully-qualified message name -> (declaring file, descriptor), compiled once."""
126
184
  if self._index is None:
127
185
  index: dict[str, tuple[str, DescriptorProto]] = {}
128
- for proto_file in self.descriptor_set().file:
129
- for full_name, descriptor in _message_names(proto_file):
130
- index[full_name] = (proto_file.name, descriptor)
186
+ if self.has_sources:
187
+ for proto_file in self.descriptor_set().file:
188
+ for full_name, descriptor in _message_names(proto_file):
189
+ index[full_name] = (proto_file.name, descriptor)
131
190
  self._index = index
132
191
  return self._index
133
192
 
@@ -139,16 +198,19 @@ class ProtobufSources:
139
198
  def resolve(self, ref: ProtobufMessageRef) -> None:
140
199
  """Match a reference to a message in the sources, filling in its descriptor.
141
200
 
142
- Raises with the closest matching names when the message is not found, since
143
- a typo in a fully-qualified name is otherwise tedious to spot.
201
+ A well-known type is matched against the protobuf runtime instead of the
202
+ sources, since it is not the interface's to declare. Otherwise this raises
203
+ with the closest matching names, since a typo in a fully-qualified name is
204
+ tedious to spot.
144
205
  """
145
- index = self._message_index()
206
+ index = well_known_message_index() if ref.is_well_known else self._message_index()
146
207
  found = index.get(ref.full_name)
147
208
  if found is None:
148
- known = self.message_names
209
+ known = sorted(index)
149
210
  suggestions = difflib.get_close_matches(ref.full_name, known, n=3)
150
211
  hint = f" Did you mean: {', '.join(suggestions)}?" if suggestions else f" Available messages: {', '.join(known) or '(none)'}"
151
- raise ProtobufError(f"Protobuf message '{ref.full_name}' was not found in {self.proto_dir}.{hint}")
212
+ where = "protobuf's well-known types" if ref.is_well_known else str(self.proto_dir)
213
+ raise ProtobufError(f"Protobuf message '{ref.full_name}' was not found in {where}.{hint}")
152
214
  proto_file, descriptor = found
153
215
  ref._proto_file = proto_file
154
216
  ref._descriptor = descriptor
@@ -499,6 +499,16 @@ class PythonProtobufRefSymbols(PythonSymbols):
499
499
  stem = stem[: -len(".proto")]
500
500
  return f"{stem}_pb2"
501
501
 
502
+ @property
503
+ def import_statement(self) -> str:
504
+ """The import that brings this message's module into a generated module.
505
+
506
+ A well-known type comes from the installed protobuf runtime; everything
507
+ else from the bindings protoc wrote beside the generated package.
508
+ """
509
+ package = "google.protobuf" if self._ref.is_well_known else ".proto"
510
+ return f"from {package} import {self.module}"
511
+
502
512
  @property
503
513
  def qualified_name(self) -> str:
504
514
  """How generated Python code names this message, e.g. ``weather_pb2.CurrentConditions``."""
@@ -389,6 +389,26 @@ class RustPayloadSymbols(RustSymbols):
389
389
  return self.struct_name
390
390
 
391
391
 
392
+ # How prost names protobuf's well-known types. prost never generates code for
393
+ # them: it substitutes a ready-made type, which for most is the `prost-types`
394
+ # struct of the same name, but for `Empty` and the wrapper messages is a plain
395
+ # Rust type instead. Mirrored here because generated code has to spell the
396
+ # message the same way prost does, and these exceptions are not derivable from
397
+ # the name. Kept in step with prost-build's `ExternPaths::new`.
398
+ _RUST_WELL_KNOWN_EXCEPTIONS = {
399
+ "Empty": "()",
400
+ "BoolValue": "bool",
401
+ "BytesValue": "::prost::alloc::vec::Vec<u8>",
402
+ "DoubleValue": "f64",
403
+ "FloatValue": "f32",
404
+ "Int32Value": "i32",
405
+ "Int64Value": "i64",
406
+ "StringValue": "::prost::alloc::string::String",
407
+ "UInt32Value": "u32",
408
+ "UInt64Value": "u64",
409
+ }
410
+
411
+
392
412
  class RustProtobufRefSymbols(RustSymbols):
393
413
  """Rust symbols for a :class:`ProtobufMessageRef`."""
394
414
 
@@ -403,12 +423,21 @@ class RustProtobufRefSymbols(RustSymbols):
403
423
  prost writes one file per protobuf package with the messages at its top
404
424
  level, and those files are included into a single ``proto`` module, so the
405
425
  package does not reappear in the Rust path.
426
+
427
+ A well-known type is the exception: prost generates nothing for one,
428
+ substituting the ready-made definition in ``prost-types``.
406
429
  """
430
+ if self._ref.is_well_known:
431
+ return "::prost_types"
407
432
  return "crate::proto"
408
433
 
409
434
  @property
410
435
  def qualified_name(self) -> str:
411
436
  """How generated Rust code names this message, fully qualified from the crate root."""
437
+ if self._ref.is_well_known:
438
+ substitute = _RUST_WELL_KNOWN_EXCEPTIONS.get(self._ref.message_name)
439
+ if substitute is not None:
440
+ return substitute
412
441
  return f"{self.module_path}::{self._ref.message_name}"
413
442
 
414
443
  @property
@@ -416,6 +445,10 @@ class RustProtobufRefSymbols(RustSymbols):
416
445
  """How code outside the library names this message.
417
446
 
418
447
  Examples and integration tests are separate crates, so they import the
419
- library's ``proto`` module rather than reaching through ``crate::``.
448
+ library's ``proto`` module rather than reaching through ``crate::``. A
449
+ well-known type is spelled the same way everywhere, since it comes from
450
+ neither.
420
451
  """
452
+ if self._ref.is_well_known:
453
+ return self.qualified_name
421
454
  return f"proto::{self._ref.message_name}"
@@ -259,6 +259,11 @@ $defs:
259
259
 
260
260
  protobufPayload:
261
261
  type: string
262
+ description: >-
263
+ The fully-qualified name of a protobuf message carrying this payload, e.g.
264
+ 'weather.v1.Forecast'. Resolved against the .proto sources configured for
265
+ the interface, or, for a name in the 'google.protobuf' package, against
266
+ protobuf's own well-known types, which every protobuf runtime ships.
262
267
 
263
268
  enumValueDescription:
264
269
  type: string
@@ -21,11 +21,12 @@
21
21
  {%- endmacro -%}
22
22
 
23
23
  {#
24
- The protoc-generated headers this interface's protobuf payloads come from.
25
- Emitted once per .proto file, and nothing at all when no payload is protobuf.
24
+ The headers this interface's protobuf payloads come from -- protoc-generated
25
+ for the interface's own messages, shipped with libprotobuf for the well-known
26
+ types. Emitted once per header, and nothing at all when no payload is protobuf.
26
27
  #}
27
28
  {%- macro imports(stinger) -%}
28
- {%- for proto_file in stinger.protobuf_files() %}
29
- #include "proto/{{ proto_file | replace(".proto", ".pb.h") }}"
29
+ {%- for include in stinger.protobuf_messages() | map(attribute='cpp.include') | unique | sort %}
30
+ #include {{ include }}
30
31
  {%- endfor -%}
31
32
  {%- endmacro -%}
@@ -26,8 +26,8 @@
26
26
  all for an interface that uses no protobuf messages.
27
27
  #}
28
28
  {%- macro imports(stinger) -%}
29
- {%- for module in stinger.protobuf_messages() | map(attribute='python.module') | unique | sort %}
30
- from .proto import {{ module }}
29
+ {%- for import_statement in stinger.protobuf_messages() | map(attribute='python.import_statement') | unique | sort %}
30
+ {{ import_statement }}
31
31
  {%- endfor -%}
32
32
  {%- endmacro -%}
33
33
 
@@ -39,6 +39,9 @@ jsonschema = "0.46" # Only needed because this interface declares JSON schema co
39
39
  {%- if stinger.uses_protobuf() %}
40
40
  prost = "0.13" # Only needed because this interface carries protobuf payloads.
41
41
  {%- endif %}
42
+ {%- if stinger.uses_well_known_protobuf() %}
43
+ prost-types = "0.13" # Only needed because this interface carries protobuf well-known types.
44
+ {%- endif %}
42
45
  {%- if stinger.uses_binary() %}
43
46
  base64 = "0.22.1" # Only needed because this interface has binary fields.
44
47
  {%- endif %}
@@ -56,7 +56,7 @@ impl {{stinger.name | UpperCamelCase }}MethodHandlers<MqttierClient> for {{sting
56
56
  async fn handle_{{method_name|snake_case}}(&self, {%if method.request_payload.is_protobuf%}_payload: {{method.request_payload.protobuf.rust.external_name}}{%else%}{%for arg in method.arg_list%}_{{arg.name|snake_case}}: {{arg.rust.type}}{%if not loop.last%}, {%endif%}{%endfor%}{%endif%}) -> Result<{%if method.response_payload.is_protobuf%}{{method.response_payload.protobuf.rust.external_name}}{%else%}{{method.rust.return_value_type}}{%endif%}, MethodReturnCode> {
57
57
  println!("Handling {{method_name}}");
58
58
  {%- if method.response_payload.is_protobuf %}
59
- Ok({{method.response_payload.protobuf.rust.external_name}}::default())
59
+ Ok(<{{method.response_payload.protobuf.rust.external_name}} as Default>::default())
60
60
  {%- elif method.return_value_type == 'struct'%}
61
61
  let rv = {{method.return_value.rust.temp_type}} {
62
62
  {%for arg in method.return_value.values-%}
@@ -217,7 +217,7 @@ let data = {{ payload.rust.struct_name }} {
217
217
  #}
218
218
  {%- macro example_payload(payload, arg_list) -%}
219
219
  {%- if payload.is_protobuf -%}
220
- {{ payload.protobuf.rust.external_name }}::default()
220
+ <{{ payload.protobuf.rust.external_name }} as Default>::default()
221
221
  {%- else -%}
222
222
  {%- for arg in arg_list %}{{ arg.get_random_example_value(lang="rust") }}{% if not loop.last %}, {% endif %}{% endfor -%}
223
223
  {%- endif -%}
@@ -229,7 +229,7 @@ let data = {{ payload.rust.struct_name }} {
229
229
  #}
230
230
  {%- macro example_payload_local(payload, arg_list) -%}
231
231
  {%- if payload.is_protobuf -%}
232
- {{ payload.rust.struct_name }}::default()
232
+ <{{ payload.rust.struct_name }} as Default>::default()
233
233
  {%- else -%}
234
234
  {%- for arg in arg_list %}{{ arg.get_random_example_value(lang="rust") }}{% if not loop.last %}, {% endif %}{% endfor -%}
235
235
  {%- endif -%}
@@ -267,7 +267,7 @@ let data = {{ payload.rust.struct_name }} {
267
267
  {# An example value for a property, for demos and generated tests. #}
268
268
  {%- macro property_example(prop) -%}
269
269
  {%- if prop.payload.is_protobuf -%}
270
- {{ prop.payload.rust.struct_name }}::default()
270
+ <{{ prop.payload.rust.struct_name }} as Default>::default()
271
271
  {%- else -%}
272
272
  {{ prop.value.get_random_example_value(lang="rust") }}
273
273
  {%- endif -%}
@@ -276,7 +276,7 @@ let data = {{ payload.rust.struct_name }} {
276
276
  {# Like `property_example`, but for code outside the library (demos, integration tests). #}
277
277
  {%- macro property_example_external(prop) -%}
278
278
  {%- if prop.payload.is_protobuf -%}
279
- {{ prop.payload.protobuf.rust.external_name }}::default()
279
+ <{{ prop.payload.protobuf.rust.external_name }} as Default>::default()
280
280
  {%- else -%}
281
281
  {{ prop.value.get_random_example_value(lang="rust") }}
282
282
  {%- endif -%}
@@ -906,7 +906,7 @@ mod tests {
906
906
  Ok(rv)
907
907
  {%endif%}
908
908
  {%elif method.response_payload.is_protobuf %}
909
- Ok({{method.response_payload.rust.struct_name}}::default())
909
+ Ok(<{{method.response_payload.rust.struct_name}} as Default>::default())
910
910
  {%elif method.return_value_type is not false %}
911
911
  Ok({{method.return_value.get_random_example_value(lang="rust")}})
912
912
  {%else%}
@@ -986,7 +986,7 @@ mod tests {
986
986
 
987
987
  // Just to get this test working faster, we're copy-pasting test code from payloads.rs to generate example property payloads.
988
988
  {%- if prop.payload.is_protobuf %}
989
- let payload = {{prop.payload.rust.struct_name}}::default();
989
+ let payload = <{{prop.payload.rust.struct_name}} as Default>::default();
990
990
  {%- else %}
991
991
  let json_str = r#"{ "{{prop.value.name}}": {{prop.value.get_random_example_value(lang="json")}} }"#;
992
992
  let payload: {{prop.payload.rust.class_name}} = serde_json::from_str(json_str).unwrap();
@@ -1021,7 +1021,7 @@ mod tests {
1021
1021
 
1022
1022
  // Example values always satisfy any schema constraint declared on the argument,
1023
1023
  // so this payload reaches the channel.
1024
- let payload = {%if cmd.payload.is_protobuf%}{{cmd.payload.rust.struct_name}}::default(){%else%}{{cmd.rust.payload_struct_name}} {
1024
+ let payload = {%if cmd.payload.is_protobuf%}<{{cmd.payload.rust.struct_name}} as Default>::default(){%else%}{{cmd.rust.payload_struct_name}} {
1025
1025
  {%-for arg in cmd.arg_list%}
1026
1026
  {{arg.name|snake_case}}: {{arg.get_random_example_value(lang="rust")}},
1027
1027
  {%-endfor%}
@@ -14,7 +14,7 @@ from stingeripc.loading import parse_yaml_file
14
14
  from stevedore import ExtensionManager
15
15
  from stingeripc import StingerInterface, __version__, topic_util
16
16
  from stingeripc.filtering import filter_by_consumer
17
- from stingeripc.config import load_config, StingerConfig
17
+ from stingeripc.config import load_config, ProtobufConfig, StingerConfig
18
18
  from stingeripc.exceptions import ProtobufError
19
19
  from stingeripc.protobuf_compiler import ProtobufSources
20
20
 
@@ -68,8 +68,13 @@ def emit_protobuf_bindings(template_dir: Path, outdir: Path, stinger, config_obj
68
68
  }
69
69
  recipe = tomllib.loads(recipe_path.read_text())
70
70
 
71
- proto_src = (inname.parent / config_obj.protobuf.path).resolve()
72
- sources = ProtobufSources(proto_src, config_obj.protobuf.protoc, python37=config_obj.python.python37)
71
+ sources = protobuf_sources(config_obj, inname)
72
+ # An interface whose protobuf payloads are all well-known types has no .proto
73
+ # files of its own; those messages come from the language's protobuf runtime,
74
+ # so there is nothing to copy and nothing to compile.
75
+ if not sources.has_sources:
76
+ print("🧬 PROTO: no .proto sources to compile (well-known types only)")
77
+ return
73
78
 
74
79
  # The .proto files travel with the generated code, so the output is
75
80
  # self-contained and can be regenerated for another language later.
@@ -109,6 +114,20 @@ def emit_protobuf_bindings(template_dir: Path, outdir: Path, stinger, config_obj
109
114
  target.write_text(content.format(**placeholders))
110
115
 
111
116
 
117
+ def protobuf_sources(config_obj, inname: Path) -> ProtobufSources:
118
+ """The .proto sources for an interface, relative to the interface file.
119
+
120
+ Resolved relative to the .stinger.yaml so a definition and its .proto files
121
+ travel together regardless of where the generator is invoked from. The
122
+ protobuf configuration is optional: an interface that names only well-known
123
+ types needs no .proto directory, and the default one simply turns out to be
124
+ empty.
125
+ """
126
+ protobuf_config = config_obj.protobuf or ProtobufConfig()
127
+ proto_dir = (inname.parent / protobuf_config.path).resolve()
128
+ return ProtobufSources(proto_dir, protobuf_config.protoc, python37=config_obj.python.python37)
129
+
130
+
112
131
  def resolve_protobuf_messages(stinger, config_obj, inname: Path) -> None:
113
132
  """Match every protobuf message the interface names against the .proto sources.
114
133
 
@@ -121,11 +140,8 @@ def resolve_protobuf_messages(stinger, config_obj, inname: Path) -> None:
121
140
  if not refs:
122
141
  return
123
142
 
124
- # Relative to the interface file, so a .stinger.yaml and its .proto files
125
- # travel together regardless of where the generator is invoked from.
126
- proto_dir = (inname.parent / config_obj.protobuf.path).resolve()
127
- print(f"🧬 [bold cyan]PROTOS:[/bold cyan] {proto_dir}")
128
- sources = ProtobufSources(proto_dir, config_obj.protobuf.protoc, python37=config_obj.python.python37)
143
+ sources = protobuf_sources(config_obj, inname)
144
+ print(f"🧬 [bold cyan]PROTOS:[/bold cyan] {sources.proto_dir}")
129
145
  sources.resolve_all(refs)
130
146
  for ref in stinger.protobuf_messages():
131
147
  print(f"🧬 MSG: {ref.full_name} ({ref.proto_file})")
@@ -15,8 +15,11 @@ from google.protobuf.descriptor_pb2 import FileDescriptorSet
15
15
  from stingeripc.components import StingerSpec
16
16
  from stingeripc.config import ProtobufConfig, StingerConfig
17
17
  from stingeripc.exceptions import InvalidStingerStructure, ProtobufError
18
+ from stingeripc.cpp_symb import CppProtobufRefSymbols
18
19
  from stingeripc.payload import PayloadRole, ProtobufMessageRef
19
- from stingeripc.protobuf_compiler import ProtobufSources, find_protoc
20
+ from stingeripc.protobuf_compiler import ProtobufSources, find_protoc, well_known_message_index
21
+ from stingeripc.python_symb import PythonProtobufRefSymbols
22
+ from stingeripc.rust_symb import RustProtobufRefSymbols
20
23
 
21
24
  FIXTURES = Path(__file__).parent / "fixtures"
22
25
 
@@ -90,6 +93,93 @@ class TestResolution(unittest.TestCase):
90
93
  )
91
94
 
92
95
 
96
+
97
+ class TestWellKnownTypes(unittest.TestCase):
98
+ """Protobuf's own types resolve without the interface declaring them."""
99
+
100
+ def _resolved(self, full_name: str) -> ProtobufMessageRef:
101
+ ref = ProtobufMessageRef(full_name=full_name)
102
+ _sources().resolve(ref)
103
+ return ref
104
+
105
+ def test_recognises_the_google_protobuf_package(self):
106
+ self.assertTrue(ProtobufMessageRef(full_name="google.protobuf.Empty").is_well_known)
107
+ self.assertFalse(ProtobufMessageRef(full_name="weather.v1.Forecast").is_well_known)
108
+ self.assertFalse(
109
+ ProtobufMessageRef(full_name="google.protobuf.nested.Thing").is_well_known,
110
+ "only the well-known package itself, not anything beneath it",
111
+ )
112
+
113
+ def test_resolves_without_any_local_sources(self):
114
+ ref = self._resolved("google.protobuf.Empty")
115
+ self.assertTrue(ref.is_resolved)
116
+ self.assertEqual(ref.proto_file, "google/protobuf/empty.proto")
117
+
118
+ def test_resolves_with_no_proto_directory_at_all(self):
119
+ """An interface using only well-known types needs no .proto sources."""
120
+ sources = ProtobufSources(FIXTURES / "no_such_directory")
121
+ self.assertFalse(sources.has_sources)
122
+ ref = ProtobufMessageRef(full_name="google.protobuf.Empty")
123
+ sources.resolve(ref)
124
+ self.assertTrue(ref.is_resolved)
125
+
126
+ def test_index_covers_the_whole_well_known_set(self):
127
+ index = well_known_message_index()
128
+ for name in ("Empty", "Timestamp", "Duration", "Any", "Struct", "FieldMask", "StringValue"):
129
+ self.assertIn(f"google.protobuf.{name}", index)
130
+
131
+ def test_unknown_well_known_type_is_reported_against_the_right_place(self):
132
+ ref = ProtobufMessageRef(full_name="google.protobuf.Emty")
133
+ with self.assertRaises(ProtobufError) as caught:
134
+ _sources().resolve(ref)
135
+ message = str(caught.exception)
136
+ self.assertIn("well-known types", message)
137
+ self.assertIn("google.protobuf.Empty", message, "a near miss is suggested")
138
+
139
+ def test_interface_reports_that_it_uses_them(self):
140
+ spec = _spec({"commands": {"c": {"protobuf": "google.protobuf.Empty"}}})
141
+ self.assertTrue(spec.uses_protobuf())
142
+ self.assertTrue(spec.uses_well_known_protobuf())
143
+
144
+ def test_interface_of_its_own_messages_only_does_not(self):
145
+ spec = _spec({"commands": {"c": {"protobuf": "weather.v1.Forecast"}}})
146
+ self.assertTrue(spec.uses_protobuf())
147
+ self.assertFalse(spec.uses_well_known_protobuf())
148
+
149
+ def test_python_imports_from_the_installed_runtime(self):
150
+ symbols = PythonProtobufRefSymbols(self._resolved("google.protobuf.Empty"))
151
+ self.assertEqual(symbols.import_statement, "from google.protobuf import empty_pb2")
152
+ self.assertEqual(symbols.qualified_name, "empty_pb2.Empty")
153
+
154
+ def test_python_imports_the_interfaces_own_messages_from_its_package(self):
155
+ symbols = PythonProtobufRefSymbols(self._resolved("weather.v1.Forecast"))
156
+ self.assertEqual(symbols.import_statement, "from .proto import weather_pb2")
157
+
158
+ def test_cpp_includes_the_libprotobuf_header(self):
159
+ symbols = CppProtobufRefSymbols(self._resolved("google.protobuf.Empty"))
160
+ self.assertEqual(symbols.include, "<google/protobuf/empty.pb.h>")
161
+ self.assertEqual(symbols.qualified_name, "::google::protobuf::Empty")
162
+
163
+ def test_cpp_includes_the_interfaces_own_headers_relatively(self):
164
+ symbols = CppProtobufRefSymbols(self._resolved("weather.v1.Forecast"))
165
+ self.assertEqual(symbols.include, '"proto/weather.pb.h"')
166
+
167
+ def test_rust_uses_the_substitute_prost_gives_empty(self):
168
+ symbols = RustProtobufRefSymbols(self._resolved("google.protobuf.Empty"))
169
+ self.assertEqual(symbols.qualified_name, "()", "prost maps Empty onto the unit type")
170
+ self.assertEqual(symbols.external_name, "()")
171
+
172
+ def test_rust_uses_prost_types_for_the_rest(self):
173
+ symbols = RustProtobufRefSymbols(self._resolved("google.protobuf.Timestamp"))
174
+ self.assertEqual(symbols.qualified_name, "::prost_types::Timestamp")
175
+ self.assertEqual(symbols.external_name, "::prost_types::Timestamp")
176
+
177
+ def test_rust_still_reaches_the_interfaces_own_messages_through_the_crate(self):
178
+ symbols = RustProtobufRefSymbols(self._resolved("weather.v1.Forecast"))
179
+ self.assertEqual(symbols.qualified_name, "crate::proto::Forecast")
180
+ self.assertEqual(symbols.external_name, "proto::Forecast")
181
+
182
+
93
183
  class TestFindProtoc(unittest.TestCase):
94
184
  def test_prefers_protoc_on_path(self):
95
185
  with mock.patch("shutil.which", side_effect=lambda name: "/usr/bin/protoc" if name == "protoc" else None):