hgraph-kafka 0.8.0__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.
- hgraph_kafka-0.8.0/.gitignore +7 -0
- hgraph_kafka-0.8.0/CHANGELOG.md +6 -0
- hgraph_kafka-0.8.0/CMakeLists.txt +204 -0
- hgraph_kafka-0.8.0/LICENSE +21 -0
- hgraph_kafka-0.8.0/PKG-INFO +169 -0
- hgraph_kafka-0.8.0/README.md +155 -0
- hgraph_kafka-0.8.0/cmake/hgraph-kafkaConfig.cmake.in +15 -0
- hgraph_kafka-0.8.0/include/hgraph/kafka/export.h +16 -0
- hgraph_kafka-0.8.0/include/hgraph/kafka/service.h +59 -0
- hgraph_kafka-0.8.0/include/hgraph/kafka/testing/fake_broker.h +67 -0
- hgraph_kafka-0.8.0/include/hgraph/kafka/testing/mock_cluster.h +51 -0
- hgraph_kafka-0.8.0/include/hgraph/kafka/types.h +434 -0
- hgraph_kafka-0.8.0/include/hgraph/kafka/value_builders.h +191 -0
- hgraph_kafka-0.8.0/pyproject.toml +48 -0
- hgraph_kafka-0.8.0/python/hgraph/adaptors/kafka/__init__.py +12 -0
- hgraph_kafka-0.8.0/python/hgraph/adaptors/kafka/_api.py +18 -0
- hgraph_kafka-0.8.0/python/hgraph/adaptors/kafka/_impl.py +5 -0
- hgraph_kafka-0.8.0/python/hgraph_kafka/__init__.py +505 -0
- hgraph_kafka-0.8.0/python/hgraph_kafka/compat.py +525 -0
- hgraph_kafka-0.8.0/python/hgraph_kafka/testing.py +5 -0
- hgraph_kafka-0.8.0/python/tests/test_api.py +157 -0
- hgraph_kafka-0.8.0/python/tests/test_compat.py +191 -0
- hgraph_kafka-0.8.0/python/tests/test_packaging.py +139 -0
- hgraph_kafka-0.8.0/python/tests/test_runtime.py +524 -0
- hgraph_kafka-0.8.0/src/detail/service_bridge.h +618 -0
- hgraph_kafka-0.8.0/src/librdkafka_service.cpp +2634 -0
- hgraph_kafka-0.8.0/src/python_module.cpp +220 -0
- hgraph_kafka-0.8.0/src/service.cpp +33 -0
- hgraph_kafka-0.8.0/src/testing/fake_broker.cpp +513 -0
- hgraph_kafka-0.8.0/src/testing/mock_cluster.cpp +214 -0
- hgraph_kafka-0.8.0/src/value_builders.cpp +676 -0
- hgraph_kafka-0.8.0/test_package/CMakeLists.txt +13 -0
- hgraph_kafka-0.8.0/test_package/main.cpp +44 -0
- hgraph_kafka-0.8.0/tests/CMakeLists.txt +17 -0
- hgraph_kafka-0.8.0/tests/test_service.cpp +2228 -0
- hgraph_kafka-0.8.0/tools/audit_distribution.py +170 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.25)
|
|
2
|
+
|
|
3
|
+
project(hgraph_kafka VERSION 0.8.0 LANGUAGES C CXX)
|
|
4
|
+
|
|
5
|
+
include(GNUInstallDirs)
|
|
6
|
+
include(CMakePackageConfigHelpers)
|
|
7
|
+
include(CTest)
|
|
8
|
+
include(FetchContent)
|
|
9
|
+
|
|
10
|
+
option(HGRAPH_KAFKA_FETCH_LIBRDKAFKA
|
|
11
|
+
"Fetch the pinned librdkafka release when a CMake package is unavailable"
|
|
12
|
+
ON)
|
|
13
|
+
option(HGRAPH_KAFKA_FORCE_FETCH_LIBRDKAFKA
|
|
14
|
+
"Use the pinned librdkafka release even when a system package is available"
|
|
15
|
+
OFF)
|
|
16
|
+
option(HGRAPH_KAFKA_BUILD_PYTHON "Build the optional Python authoring bridge" OFF)
|
|
17
|
+
|
|
18
|
+
# Load only the limited-API Python targets before the installed hgraph SDK.
|
|
19
|
+
# Linking an embedding target such as Python::Python would pin this ABI3
|
|
20
|
+
# extension to the interpreter used for the build.
|
|
21
|
+
if(HGRAPH_KAFKA_BUILD_PYTHON)
|
|
22
|
+
find_package(Python 3.12 COMPONENTS
|
|
23
|
+
Interpreter Development.Module Development.SABIModule REQUIRED)
|
|
24
|
+
endif()
|
|
25
|
+
|
|
26
|
+
if(NOT TARGET hgraph::core)
|
|
27
|
+
find_package(hgraph CONFIG REQUIRED)
|
|
28
|
+
endif()
|
|
29
|
+
|
|
30
|
+
if(HGRAPH_KAFKA_BUILD_PYTHON)
|
|
31
|
+
get_target_property(_hgraph_kafka_core_links hgraph::options INTERFACE_LINK_LIBRARIES)
|
|
32
|
+
if("Python::Python" IN_LIST _hgraph_kafka_core_links)
|
|
33
|
+
message(FATAL_ERROR
|
|
34
|
+
"The selected hgraph SDK embeds a specific Python interpreter and "
|
|
35
|
+
"cannot produce an ABI3 hgraph-kafka wheel; use the SDK installed "
|
|
36
|
+
"by an hgraph stable-ABI wheel")
|
|
37
|
+
endif()
|
|
38
|
+
endif()
|
|
39
|
+
|
|
40
|
+
if(NOT HGRAPH_KAFKA_FORCE_FETCH_LIBRDKAFKA)
|
|
41
|
+
find_package(RdKafka 2.15 CONFIG QUIET)
|
|
42
|
+
endif()
|
|
43
|
+
if(NOT TARGET RdKafka::rdkafka AND NOT TARGET rdkafka)
|
|
44
|
+
if(NOT HGRAPH_KAFKA_FETCH_LIBRDKAFKA)
|
|
45
|
+
message(FATAL_ERROR
|
|
46
|
+
"librdkafka >= 2.15 was not found; install RdKafka or enable "
|
|
47
|
+
"HGRAPH_KAFKA_FETCH_LIBRDKAFKA")
|
|
48
|
+
endif()
|
|
49
|
+
|
|
50
|
+
set(RDKAFKA_BUILD_STATIC ON CACHE BOOL "" FORCE)
|
|
51
|
+
set(RDKAFKA_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
|
52
|
+
set(RDKAFKA_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
|
53
|
+
|
|
54
|
+
# A Python extension statically embeds librdkafka. Keep OpenSSL inside the
|
|
55
|
+
# extension on wheel platforms where a package-local dynamic dependency
|
|
56
|
+
# would otherwise require loader-path repair. In particular, Python 3.8+
|
|
57
|
+
# does not search the Windows process PATH for dependent DLLs.
|
|
58
|
+
if(HGRAPH_KAFKA_BUILD_PYTHON AND (APPLE OR WIN32))
|
|
59
|
+
set(OPENSSL_USE_STATIC_LIBS TRUE)
|
|
60
|
+
endif()
|
|
61
|
+
|
|
62
|
+
# On macOS, prefer the static codec library as well so the resulting wheel
|
|
63
|
+
# does not retain an absolute Homebrew dylib path. System libraries remain
|
|
64
|
+
# normal platform dependencies.
|
|
65
|
+
if(APPLE AND HGRAPH_KAFKA_BUILD_PYTHON)
|
|
66
|
+
set(ENABLE_LZ4_EXT OFF CACHE BOOL "" FORCE)
|
|
67
|
+
find_library(HGRAPH_KAFKA_ZSTD_STATIC_LIBRARY NAMES libzstd.a)
|
|
68
|
+
if(NOT HGRAPH_KAFKA_ZSTD_STATIC_LIBRARY)
|
|
69
|
+
message(FATAL_ERROR
|
|
70
|
+
"A static zstd library is required to build a portable macOS wheel")
|
|
71
|
+
endif()
|
|
72
|
+
set(ZSTD_LIBRARY_DEBUG "${HGRAPH_KAFKA_ZSTD_STATIC_LIBRARY}"
|
|
73
|
+
CACHE FILEPATH "" FORCE)
|
|
74
|
+
set(ZSTD_LIBRARY_RELEASE "${HGRAPH_KAFKA_ZSTD_STATIC_LIBRARY}"
|
|
75
|
+
CACHE FILEPATH "" FORCE)
|
|
76
|
+
endif()
|
|
77
|
+
|
|
78
|
+
FetchContent_Declare(librdkafka
|
|
79
|
+
GIT_REPOSITORY https://github.com/confluentinc/librdkafka.git
|
|
80
|
+
GIT_TAG v2.15.0
|
|
81
|
+
GIT_SHALLOW TRUE
|
|
82
|
+
)
|
|
83
|
+
FetchContent_MakeAvailable(librdkafka)
|
|
84
|
+
|
|
85
|
+
if(MSVC AND OPENSSL_USE_STATIC_LIBS)
|
|
86
|
+
# librdkafka's Windows TLS source embeds default-library directives for
|
|
87
|
+
# the OpenSSL import libraries. Ignore those names when CMake selected
|
|
88
|
+
# the explicitly linked *_static.lib archives for a self-contained
|
|
89
|
+
# consumer; otherwise MSVC still searches for the absent import libs.
|
|
90
|
+
target_link_options(rdkafka INTERFACE
|
|
91
|
+
/NODEFAULTLIB:libcrypto.lib
|
|
92
|
+
/NODEFAULTLIB:libssl.lib
|
|
93
|
+
)
|
|
94
|
+
endif()
|
|
95
|
+
endif()
|
|
96
|
+
|
|
97
|
+
if(TARGET RdKafka::rdkafka)
|
|
98
|
+
set(HGRAPH_KAFKA_RDKAFKA_TARGET RdKafka::rdkafka)
|
|
99
|
+
elseif(TARGET rdkafka)
|
|
100
|
+
add_library(RdKafka::rdkafka ALIAS rdkafka)
|
|
101
|
+
set(HGRAPH_KAFKA_RDKAFKA_TARGET RdKafka::rdkafka)
|
|
102
|
+
else()
|
|
103
|
+
message(FATAL_ERROR "librdkafka did not provide its C target")
|
|
104
|
+
endif()
|
|
105
|
+
|
|
106
|
+
add_library(hgraph_kafka
|
|
107
|
+
src/librdkafka_service.cpp
|
|
108
|
+
src/service.cpp
|
|
109
|
+
src/value_builders.cpp
|
|
110
|
+
src/testing/fake_broker.cpp
|
|
111
|
+
src/testing/mock_cluster.cpp
|
|
112
|
+
)
|
|
113
|
+
add_library(hgraph::kafka ALIAS hgraph_kafka)
|
|
114
|
+
|
|
115
|
+
get_target_property(HGRAPH_KAFKA_LIBRARY_TYPE hgraph_kafka TYPE)
|
|
116
|
+
if(HGRAPH_KAFKA_LIBRARY_TYPE STREQUAL "STATIC_LIBRARY")
|
|
117
|
+
target_compile_definitions(hgraph_kafka PUBLIC HGRAPH_KAFKA_STATIC_DEFINE)
|
|
118
|
+
endif()
|
|
119
|
+
|
|
120
|
+
target_compile_features(hgraph_kafka PUBLIC cxx_std_23)
|
|
121
|
+
target_link_libraries(hgraph_kafka
|
|
122
|
+
PUBLIC hgraph::core
|
|
123
|
+
PRIVATE ${HGRAPH_KAFKA_RDKAFKA_TARGET}
|
|
124
|
+
)
|
|
125
|
+
target_include_directories(hgraph_kafka
|
|
126
|
+
PUBLIC
|
|
127
|
+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
128
|
+
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
|
129
|
+
PRIVATE
|
|
130
|
+
${CMAKE_CURRENT_SOURCE_DIR}/src
|
|
131
|
+
)
|
|
132
|
+
set_target_properties(hgraph_kafka PROPERTIES
|
|
133
|
+
EXPORT_NAME kafka
|
|
134
|
+
POSITION_INDEPENDENT_CODE ON
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if(MSVC)
|
|
138
|
+
# librdkafka includes Windows headers in the implementation. Keep their
|
|
139
|
+
# min/max macros from rewriting standard-library calls in this target.
|
|
140
|
+
target_compile_definitions(hgraph_kafka PRIVATE NOMINMAX)
|
|
141
|
+
target_compile_options(hgraph_kafka PRIVATE /W4 /permissive-)
|
|
142
|
+
else()
|
|
143
|
+
target_compile_options(hgraph_kafka PRIVATE -Wall -Wextra -Wpedantic)
|
|
144
|
+
endif()
|
|
145
|
+
|
|
146
|
+
if(BUILD_TESTING)
|
|
147
|
+
add_subdirectory(tests)
|
|
148
|
+
endif()
|
|
149
|
+
|
|
150
|
+
if(HGRAPH_KAFKA_BUILD_PYTHON)
|
|
151
|
+
find_package(Python 3.12 COMPONENTS Interpreter Development.Module Development.SABIModule REQUIRED)
|
|
152
|
+
if(NOT COMMAND hgraph_add_python_module OR NOT TARGET hgraph::nanobind)
|
|
153
|
+
message(FATAL_ERROR
|
|
154
|
+
"HGRAPH_KAFKA_BUILD_PYTHON requires a Python-enabled installed hgraph SDK")
|
|
155
|
+
endif()
|
|
156
|
+
hgraph_add_python_module(_hgraph_kafka STABLE_ABI NOMINSIZE src/python_module.cpp)
|
|
157
|
+
target_link_libraries(_hgraph_kafka PRIVATE hgraph::kafka)
|
|
158
|
+
if(APPLE)
|
|
159
|
+
set_target_properties(_hgraph_kafka PROPERTIES
|
|
160
|
+
INSTALL_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR}")
|
|
161
|
+
elseif(UNIX)
|
|
162
|
+
set_target_properties(_hgraph_kafka PROPERTIES
|
|
163
|
+
INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}")
|
|
164
|
+
endif()
|
|
165
|
+
install(TARGETS _hgraph_kafka
|
|
166
|
+
COMPONENT Python
|
|
167
|
+
LIBRARY DESTINATION hgraph_kafka
|
|
168
|
+
RUNTIME DESTINATION hgraph_kafka
|
|
169
|
+
)
|
|
170
|
+
endif()
|
|
171
|
+
|
|
172
|
+
install(TARGETS hgraph_kafka
|
|
173
|
+
EXPORT hgraphKafkaTargets
|
|
174
|
+
COMPONENT Development
|
|
175
|
+
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
176
|
+
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
177
|
+
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
178
|
+
)
|
|
179
|
+
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
|
180
|
+
COMPONENT Development FILES_MATCHING PATTERN "*.h")
|
|
181
|
+
|
|
182
|
+
write_basic_package_version_file(
|
|
183
|
+
"${PROJECT_BINARY_DIR}/hgraph-kafkaConfigVersion.cmake"
|
|
184
|
+
VERSION ${PROJECT_VERSION}
|
|
185
|
+
COMPATIBILITY SameMajorVersion
|
|
186
|
+
)
|
|
187
|
+
configure_package_config_file(
|
|
188
|
+
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/hgraph-kafkaConfig.cmake.in"
|
|
189
|
+
"${PROJECT_BINARY_DIR}/hgraph-kafkaConfig.cmake"
|
|
190
|
+
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/hgraph-kafka
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
install(EXPORT hgraphKafkaTargets
|
|
194
|
+
FILE hgraphKafkaTargets.cmake
|
|
195
|
+
NAMESPACE hgraph::
|
|
196
|
+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/hgraph-kafka
|
|
197
|
+
COMPONENT Development
|
|
198
|
+
)
|
|
199
|
+
install(FILES
|
|
200
|
+
"${PROJECT_BINARY_DIR}/hgraph-kafkaConfig.cmake"
|
|
201
|
+
"${PROJECT_BINARY_DIR}/hgraph-kafkaConfigVersion.cmake"
|
|
202
|
+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/hgraph-kafka
|
|
203
|
+
COMPONENT Development
|
|
204
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Howard Henson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: hgraph-kafka
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: C++-first Kafka services for hgraph
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/hhenson/hgraph
|
|
7
|
+
Project-URL: Repository, https://github.com/hhenson/hgraph.git
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Requires-Dist: hgraph>=0.8.0
|
|
10
|
+
Requires-Dist: frozendict>=2
|
|
11
|
+
Provides-Extra: test
|
|
12
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# hgraph-kafka
|
|
16
|
+
|
|
17
|
+
C++-first Kafka services for hgraph, implementing the contract in hgraph RFC
|
|
18
|
+
0015. The extension uses librdkafka's C API and exposes the same service model
|
|
19
|
+
to native C++ and Python graphs.
|
|
20
|
+
|
|
21
|
+
One path-bound, multi-interface `service_impl` owns the Kafka clients for a
|
|
22
|
+
configuration. Subscriptions, publish requests, explicit commits, and events
|
|
23
|
+
all bind to that service instance. Graph output reaches Kafka through the
|
|
24
|
+
service's sink inputs; records, delivery reports, and events re-enter the root
|
|
25
|
+
graph through bounded push sources. Kafka clients and worker threads are
|
|
26
|
+
created on graph start and stopped with the graph.
|
|
27
|
+
|
|
28
|
+
The public record and configuration shapes are hgraph compound scalars. Kafka
|
|
29
|
+
headers preserve order, duplicates, null values, and empty byte strings.
|
|
30
|
+
|
|
31
|
+
## Native C++
|
|
32
|
+
|
|
33
|
+
The installed package exports `hgraph::kafka`:
|
|
34
|
+
|
|
35
|
+
```cpp
|
|
36
|
+
#include <hgraph/kafka/service.h>
|
|
37
|
+
#include <hgraph/kafka/value_builders.h>
|
|
38
|
+
#include <hgraph/lib/std/operators/conversion.h>
|
|
39
|
+
#include <hgraph/lib/std/operators/registration.h>
|
|
40
|
+
|
|
41
|
+
using namespace hgraph;
|
|
42
|
+
using namespace hgraph::kafka;
|
|
43
|
+
|
|
44
|
+
struct KafkaGraph {
|
|
45
|
+
static constexpr auto name = "kafka_graph";
|
|
46
|
+
|
|
47
|
+
static void compose(Wiring &w) {
|
|
48
|
+
const auto path = service::path("primary");
|
|
49
|
+
register_service(
|
|
50
|
+
w, path,
|
|
51
|
+
service_config().bootstrap_servers({Str{"localhost:9092"}}).build());
|
|
52
|
+
|
|
53
|
+
auto key = wire<stdlib::const_, TS<KafkaSubscriptionKey>>(
|
|
54
|
+
w, subscription_key()
|
|
55
|
+
.topics({Str{"orders"}})
|
|
56
|
+
.group_id(Str{"orders-worker"})
|
|
57
|
+
.build());
|
|
58
|
+
auto subscription = subscribe(w, path, key);
|
|
59
|
+
|
|
60
|
+
auto record = wire<stdlib::const_, TS<KafkaProduceRecord>>(
|
|
61
|
+
w, make_produce_record(Bytes{"ready"}));
|
|
62
|
+
auto delivery = publish(
|
|
63
|
+
w, path, publish_request(w, Str{"status"}, record));
|
|
64
|
+
|
|
65
|
+
auto cursor = wire<stdlib::getattr_, TS<KafkaCursor>>(
|
|
66
|
+
w, subscription, Str{"cursor"});
|
|
67
|
+
commit(w, path, cursor);
|
|
68
|
+
auto event = events(w, path);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`KafkaSubscriptionOutput` provides the record and its matching next-offset
|
|
74
|
+
cursor on the same graph tick, plus subscription state. A cursor is accepted
|
|
75
|
+
only while its subscription identity, assignment generation, and partition
|
|
76
|
+
remain live. Commits are monotonic per assigned partition.
|
|
77
|
+
|
|
78
|
+
## Python
|
|
79
|
+
|
|
80
|
+
The Python authoring surface lowers to the same native service:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import hgraph as hg
|
|
84
|
+
import hgraph_kafka as kafka
|
|
85
|
+
|
|
86
|
+
@hg.graph
|
|
87
|
+
def app():
|
|
88
|
+
kafka.register_kafka_service(
|
|
89
|
+
kafka.KafkaServiceConfig.from_bootstrap_servers(
|
|
90
|
+
["localhost:9092"], client_id="orders-worker"
|
|
91
|
+
),
|
|
92
|
+
path="primary",
|
|
93
|
+
)
|
|
94
|
+
key = kafka.KafkaSubscriptionKey(
|
|
95
|
+
topics=("orders",),
|
|
96
|
+
group_id="orders-worker",
|
|
97
|
+
start_position=kafka.KafkaStartPosition.committed(),
|
|
98
|
+
)
|
|
99
|
+
subscription = kafka.kafka_subscribe(
|
|
100
|
+
hg.const(key, tp=hg.TS[kafka.KafkaSubscriptionKey]),
|
|
101
|
+
path="primary",
|
|
102
|
+
)
|
|
103
|
+
kafka.kafka_commit(subscription["cursor"], path="primary")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The extension wheel also owns the released import path
|
|
107
|
+
`hgraph.adaptors.kafka`. Existing `message_publisher`, `message_subscriber`,
|
|
108
|
+
`KafkaMessage`, and `register_kafka_adaptor` imports therefore continue to
|
|
109
|
+
work without making the core `hgraph` package depend on this extension.
|
|
110
|
+
|
|
111
|
+
## Recovery and simulation
|
|
112
|
+
|
|
113
|
+
Subscriptions support explicit topic, pattern, or partition selection; group
|
|
114
|
+
or independent assignment; earliest, latest, committed, timestamp, explicit,
|
|
115
|
+
and graph-start positions; snapshot, timestamp, and explicit stop boundaries;
|
|
116
|
+
key filters; deterministic timestamp/topic/partition/offset replay; and
|
|
117
|
+
explicit or graph-delivery commits.
|
|
118
|
+
|
|
119
|
+
Simulation is intentionally limited to bounded, record-time recovery. The
|
|
120
|
+
consumer preloads the finite replay and schedules records at deterministic
|
|
121
|
+
graph times. Publish, commit, unbounded asynchronous input, and
|
|
122
|
+
`OnGraphDelivery` commit mode are rejected in simulation rather than silently
|
|
123
|
+
changing their semantics.
|
|
124
|
+
|
|
125
|
+
## Build and test
|
|
126
|
+
|
|
127
|
+
This is a first-party extension in the hgraph monorepo. It remains a separate
|
|
128
|
+
CMake package and Python distribution: the top-level core package does not
|
|
129
|
+
link librdkafka or install these modules.
|
|
130
|
+
|
|
131
|
+
For an in-tree native development build from the repository root:
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
cmake -S . -B build-kafka \
|
|
135
|
+
-DHGRAPH_BUILD_KAFKA_EXTENSION=ON \
|
|
136
|
+
-DBUILD_TESTING=ON
|
|
137
|
+
cmake --build build-kafka --parallel
|
|
138
|
+
ctest --test-dir build-kafka --output-on-failure
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The extension can still be configured independently against an installed
|
|
142
|
+
hgraph SDK:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/hgraph/install
|
|
146
|
+
cmake --build build --parallel
|
|
147
|
+
ctest --test-dir build --output-on-failure
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Build its separately deployable ABI3 wheel from the repository root after
|
|
151
|
+
making the matching hgraph SDK discoverable through `CMAKE_PREFIX_PATH`:
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
CMAKE_PREFIX_PATH=/path/to/hgraph/sdk \
|
|
155
|
+
uv build --wheel --package hgraph-kafka --python 3.12
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The deterministic suite uses librdkafka's mock cluster and the extension fake
|
|
159
|
+
transport. To include a real broker round trip, provide a clean topic:
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
HGRAPH_KAFKA_INTEGRATION_BOOTSTRAP=localhost:9092 \
|
|
163
|
+
HGRAPH_KAFKA_INTEGRATION_TOPIC=hgraph-kafka-integration \
|
|
164
|
+
ctest --test-dir build --output-on-failure
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Wheel builds require the SDK installed by a stable-ABI hgraph wheel. The
|
|
168
|
+
extension rejects an SDK that links `Python::Python`, because that would pin
|
|
169
|
+
the nominal ABI3 module to the build interpreter.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# hgraph-kafka
|
|
2
|
+
|
|
3
|
+
C++-first Kafka services for hgraph, implementing the contract in hgraph RFC
|
|
4
|
+
0015. The extension uses librdkafka's C API and exposes the same service model
|
|
5
|
+
to native C++ and Python graphs.
|
|
6
|
+
|
|
7
|
+
One path-bound, multi-interface `service_impl` owns the Kafka clients for a
|
|
8
|
+
configuration. Subscriptions, publish requests, explicit commits, and events
|
|
9
|
+
all bind to that service instance. Graph output reaches Kafka through the
|
|
10
|
+
service's sink inputs; records, delivery reports, and events re-enter the root
|
|
11
|
+
graph through bounded push sources. Kafka clients and worker threads are
|
|
12
|
+
created on graph start and stopped with the graph.
|
|
13
|
+
|
|
14
|
+
The public record and configuration shapes are hgraph compound scalars. Kafka
|
|
15
|
+
headers preserve order, duplicates, null values, and empty byte strings.
|
|
16
|
+
|
|
17
|
+
## Native C++
|
|
18
|
+
|
|
19
|
+
The installed package exports `hgraph::kafka`:
|
|
20
|
+
|
|
21
|
+
```cpp
|
|
22
|
+
#include <hgraph/kafka/service.h>
|
|
23
|
+
#include <hgraph/kafka/value_builders.h>
|
|
24
|
+
#include <hgraph/lib/std/operators/conversion.h>
|
|
25
|
+
#include <hgraph/lib/std/operators/registration.h>
|
|
26
|
+
|
|
27
|
+
using namespace hgraph;
|
|
28
|
+
using namespace hgraph::kafka;
|
|
29
|
+
|
|
30
|
+
struct KafkaGraph {
|
|
31
|
+
static constexpr auto name = "kafka_graph";
|
|
32
|
+
|
|
33
|
+
static void compose(Wiring &w) {
|
|
34
|
+
const auto path = service::path("primary");
|
|
35
|
+
register_service(
|
|
36
|
+
w, path,
|
|
37
|
+
service_config().bootstrap_servers({Str{"localhost:9092"}}).build());
|
|
38
|
+
|
|
39
|
+
auto key = wire<stdlib::const_, TS<KafkaSubscriptionKey>>(
|
|
40
|
+
w, subscription_key()
|
|
41
|
+
.topics({Str{"orders"}})
|
|
42
|
+
.group_id(Str{"orders-worker"})
|
|
43
|
+
.build());
|
|
44
|
+
auto subscription = subscribe(w, path, key);
|
|
45
|
+
|
|
46
|
+
auto record = wire<stdlib::const_, TS<KafkaProduceRecord>>(
|
|
47
|
+
w, make_produce_record(Bytes{"ready"}));
|
|
48
|
+
auto delivery = publish(
|
|
49
|
+
w, path, publish_request(w, Str{"status"}, record));
|
|
50
|
+
|
|
51
|
+
auto cursor = wire<stdlib::getattr_, TS<KafkaCursor>>(
|
|
52
|
+
w, subscription, Str{"cursor"});
|
|
53
|
+
commit(w, path, cursor);
|
|
54
|
+
auto event = events(w, path);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`KafkaSubscriptionOutput` provides the record and its matching next-offset
|
|
60
|
+
cursor on the same graph tick, plus subscription state. A cursor is accepted
|
|
61
|
+
only while its subscription identity, assignment generation, and partition
|
|
62
|
+
remain live. Commits are monotonic per assigned partition.
|
|
63
|
+
|
|
64
|
+
## Python
|
|
65
|
+
|
|
66
|
+
The Python authoring surface lowers to the same native service:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import hgraph as hg
|
|
70
|
+
import hgraph_kafka as kafka
|
|
71
|
+
|
|
72
|
+
@hg.graph
|
|
73
|
+
def app():
|
|
74
|
+
kafka.register_kafka_service(
|
|
75
|
+
kafka.KafkaServiceConfig.from_bootstrap_servers(
|
|
76
|
+
["localhost:9092"], client_id="orders-worker"
|
|
77
|
+
),
|
|
78
|
+
path="primary",
|
|
79
|
+
)
|
|
80
|
+
key = kafka.KafkaSubscriptionKey(
|
|
81
|
+
topics=("orders",),
|
|
82
|
+
group_id="orders-worker",
|
|
83
|
+
start_position=kafka.KafkaStartPosition.committed(),
|
|
84
|
+
)
|
|
85
|
+
subscription = kafka.kafka_subscribe(
|
|
86
|
+
hg.const(key, tp=hg.TS[kafka.KafkaSubscriptionKey]),
|
|
87
|
+
path="primary",
|
|
88
|
+
)
|
|
89
|
+
kafka.kafka_commit(subscription["cursor"], path="primary")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The extension wheel also owns the released import path
|
|
93
|
+
`hgraph.adaptors.kafka`. Existing `message_publisher`, `message_subscriber`,
|
|
94
|
+
`KafkaMessage`, and `register_kafka_adaptor` imports therefore continue to
|
|
95
|
+
work without making the core `hgraph` package depend on this extension.
|
|
96
|
+
|
|
97
|
+
## Recovery and simulation
|
|
98
|
+
|
|
99
|
+
Subscriptions support explicit topic, pattern, or partition selection; group
|
|
100
|
+
or independent assignment; earliest, latest, committed, timestamp, explicit,
|
|
101
|
+
and graph-start positions; snapshot, timestamp, and explicit stop boundaries;
|
|
102
|
+
key filters; deterministic timestamp/topic/partition/offset replay; and
|
|
103
|
+
explicit or graph-delivery commits.
|
|
104
|
+
|
|
105
|
+
Simulation is intentionally limited to bounded, record-time recovery. The
|
|
106
|
+
consumer preloads the finite replay and schedules records at deterministic
|
|
107
|
+
graph times. Publish, commit, unbounded asynchronous input, and
|
|
108
|
+
`OnGraphDelivery` commit mode are rejected in simulation rather than silently
|
|
109
|
+
changing their semantics.
|
|
110
|
+
|
|
111
|
+
## Build and test
|
|
112
|
+
|
|
113
|
+
This is a first-party extension in the hgraph monorepo. It remains a separate
|
|
114
|
+
CMake package and Python distribution: the top-level core package does not
|
|
115
|
+
link librdkafka or install these modules.
|
|
116
|
+
|
|
117
|
+
For an in-tree native development build from the repository root:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
cmake -S . -B build-kafka \
|
|
121
|
+
-DHGRAPH_BUILD_KAFKA_EXTENSION=ON \
|
|
122
|
+
-DBUILD_TESTING=ON
|
|
123
|
+
cmake --build build-kafka --parallel
|
|
124
|
+
ctest --test-dir build-kafka --output-on-failure
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The extension can still be configured independently against an installed
|
|
128
|
+
hgraph SDK:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/hgraph/install
|
|
132
|
+
cmake --build build --parallel
|
|
133
|
+
ctest --test-dir build --output-on-failure
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Build its separately deployable ABI3 wheel from the repository root after
|
|
137
|
+
making the matching hgraph SDK discoverable through `CMAKE_PREFIX_PATH`:
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
CMAKE_PREFIX_PATH=/path/to/hgraph/sdk \
|
|
141
|
+
uv build --wheel --package hgraph-kafka --python 3.12
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The deterministic suite uses librdkafka's mock cluster and the extension fake
|
|
145
|
+
transport. To include a real broker round trip, provide a clean topic:
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
HGRAPH_KAFKA_INTEGRATION_BOOTSTRAP=localhost:9092 \
|
|
149
|
+
HGRAPH_KAFKA_INTEGRATION_TOPIC=hgraph-kafka-integration \
|
|
150
|
+
ctest --test-dir build --output-on-failure
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Wheel builds require the SDK installed by a stable-ABI hgraph wheel. The
|
|
154
|
+
extension rejects an SDK that links `Python::Python`, because that would pin
|
|
155
|
+
the nominal ABI3 module to the build interpreter.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@PACKAGE_INIT@
|
|
2
|
+
|
|
3
|
+
include(CMakeFindDependencyMacro)
|
|
4
|
+
find_dependency(hgraph CONFIG)
|
|
5
|
+
set(_hgraph_kafka_saved_module_path "${CMAKE_MODULE_PATH}")
|
|
6
|
+
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../RdKafka/FindLZ4.cmake")
|
|
7
|
+
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../RdKafka")
|
|
8
|
+
endif()
|
|
9
|
+
find_dependency(RdKafka 2.15 CONFIG)
|
|
10
|
+
set(CMAKE_MODULE_PATH "${_hgraph_kafka_saved_module_path}")
|
|
11
|
+
unset(_hgraph_kafka_saved_module_path)
|
|
12
|
+
|
|
13
|
+
include("${CMAKE_CURRENT_LIST_DIR}/hgraphKafkaTargets.cmake")
|
|
14
|
+
|
|
15
|
+
check_required_components(hgraph-kafka)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#ifndef HGRAPH_KAFKA_EXPORT_H
|
|
2
|
+
#define HGRAPH_KAFKA_EXPORT_H
|
|
3
|
+
|
|
4
|
+
#if defined(HGRAPH_KAFKA_STATIC_DEFINE)
|
|
5
|
+
#define HGRAPH_KAFKA_EXPORT
|
|
6
|
+
#elif defined(_WIN32)
|
|
7
|
+
#if defined(hgraph_kafka_EXPORTS)
|
|
8
|
+
#define HGRAPH_KAFKA_EXPORT __declspec(dllexport)
|
|
9
|
+
#else
|
|
10
|
+
#define HGRAPH_KAFKA_EXPORT __declspec(dllimport)
|
|
11
|
+
#endif
|
|
12
|
+
#else
|
|
13
|
+
#define HGRAPH_KAFKA_EXPORT __attribute__((visibility("default")))
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
#endif // HGRAPH_KAFKA_EXPORT_H
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#ifndef HGRAPH_KAFKA_SERVICE_H
|
|
2
|
+
#define HGRAPH_KAFKA_SERVICE_H
|
|
3
|
+
|
|
4
|
+
#include <hgraph/kafka/export.h>
|
|
5
|
+
#include <hgraph/kafka/types.h>
|
|
6
|
+
|
|
7
|
+
#include <hgraph/types/graph_wiring.h>
|
|
8
|
+
#include <hgraph/types/service_wiring.h>
|
|
9
|
+
|
|
10
|
+
namespace hgraph::kafka
|
|
11
|
+
{
|
|
12
|
+
struct KafkaSubscriptionService
|
|
13
|
+
{
|
|
14
|
+
static constexpr std::string_view name{"kafka_subscription"};
|
|
15
|
+
using key_type = KafkaSubscriptionKey;
|
|
16
|
+
using value_schema = KafkaSubscriptionOutput;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
struct KafkaPublishService
|
|
20
|
+
{
|
|
21
|
+
static constexpr std::string_view name{"kafka_publish"};
|
|
22
|
+
using request_schema = KafkaPublishRequest;
|
|
23
|
+
using response_schema = TS<KafkaDeliveryReport>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
struct KafkaCommitService
|
|
27
|
+
{
|
|
28
|
+
static constexpr std::string_view name{"kafka_commit"};
|
|
29
|
+
using request_schema = TS<KafkaCursor>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
struct KafkaEventService
|
|
33
|
+
{
|
|
34
|
+
static constexpr std::string_view name{"kafka_events"};
|
|
35
|
+
using output_schema = TS<KafkaEvent>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Register one lazy, path-bound production Kafka service implementation.
|
|
39
|
+
* No Kafka client or worker thread is created until the graph is started. */
|
|
40
|
+
HGRAPH_KAFKA_EXPORT void register_service(Wiring &w, service::ServicePath path, Value service_config);
|
|
41
|
+
|
|
42
|
+
[[nodiscard]] HGRAPH_KAFKA_EXPORT Port<KafkaSubscriptionOutput> subscribe(Wiring &w, service::ServicePath path,
|
|
43
|
+
Port<TS<KafkaSubscriptionKey>> key);
|
|
44
|
+
|
|
45
|
+
[[nodiscard]] HGRAPH_KAFKA_EXPORT Port<KafkaPublishRequest> publish_request(Wiring &w, Port<TS<Str>> topic,
|
|
46
|
+
Port<TS<KafkaProduceRecord>> record);
|
|
47
|
+
|
|
48
|
+
[[nodiscard]] HGRAPH_KAFKA_EXPORT Port<KafkaPublishRequest> publish_request(Wiring &w, Str topic,
|
|
49
|
+
Port<TS<KafkaProduceRecord>> record);
|
|
50
|
+
|
|
51
|
+
[[nodiscard]] HGRAPH_KAFKA_EXPORT Port<TS<KafkaDeliveryReport>> publish(Wiring &w, service::ServicePath path,
|
|
52
|
+
Port<KafkaPublishRequest> request);
|
|
53
|
+
|
|
54
|
+
HGRAPH_KAFKA_EXPORT void commit(Wiring &w, service::ServicePath path, Port<TS<KafkaCursor>> cursor);
|
|
55
|
+
|
|
56
|
+
[[nodiscard]] HGRAPH_KAFKA_EXPORT Port<TS<KafkaEvent>> events(Wiring &w, service::ServicePath path);
|
|
57
|
+
} // namespace hgraph::kafka
|
|
58
|
+
|
|
59
|
+
#endif // HGRAPH_KAFKA_SERVICE_H
|