cangling-broker 0.1.11__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.
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: cangling-broker
3
+ Version: 0.1.11
4
+ Summary: Python producer and consumer for cangling-broker
5
+ Author-email: zhangjianshe <zhangjianshe@gmail.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/zhangjianshe/cangling-broker
8
+ Project-URL: Repository, https://github.com/zhangjianshe/cangling-broker
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: grpcio>=1.83.0
14
+ Requires-Dist: protobuf>=5.28.0
15
+ Provides-Extra: dev
16
+ Requires-Dist: grpcio-tools>=1.83.0; extra == "dev"
17
+ Requires-Dist: build; extra == "dev"
18
+ Requires-Dist: twine; extra == "dev"
19
+
20
+ # cangling-broker (Python)
21
+
22
+ Producer and consumer for cangling-broker. Same contract as the Java client: `AcceptMessages` to publish, `Subscribe` to consume, optional `Register` metadata, `CL_BROKER_AUTH_TOKEN` on every RPC.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install cangling-broker
28
+ ```
29
+
30
+ From this tree:
31
+
32
+ ```bash
33
+ pip install -e python
34
+ ```
35
+
36
+ ## Use
37
+
38
+ ```python
39
+ from cangling_broker import SatwayClient, SubscribeOptions
40
+
41
+ with SatwayClient.connect("127.0.0.1:7500", "change-me") as client:
42
+ client.send("cangling-test", "hello")
43
+ with client.subscribe(
44
+ SubscribeOptions(topic="cangling-test", name="worker-1"),
45
+ lambda message: print(message.id, message.payload),
46
+ ):
47
+ ...
48
+ ```
49
+
50
+ `SatwayClient.connect(broker)` also reads `CL_BROKER_AUTH_TOKEN` from the environment.
51
+
52
+ Batch-set topic delivery (`single` = one consumer, `broadcast` = every live stream):
53
+
54
+ ```python
55
+ from cangling_broker import TopicConfig
56
+
57
+ client.configure_topics([
58
+ TopicConfig("jobs", "single"),
59
+ TopicConfig("alerts", "broadcast"),
60
+ ])
61
+ print(client.list_topics())
62
+ ```
63
+
64
+ Existing Kafka senders can keep ``send(topic, value)`` / ``flush()``:
65
+
66
+ ```python
67
+ # from kafka import KafkaProducer
68
+ from cangling_broker import KafkaProducer
69
+
70
+ producer = KafkaProducer(bootstrap_servers="127.0.0.1:7500")
71
+ producer.send(topic, msg)
72
+ producer.flush()
73
+ ```
74
+
75
+ ```bash
76
+ # consume
77
+ python python/examples/consumer.py --broker 127.0.0.1:7500 --topic cangling-test --name py-s0 --token change-me
78
+
79
+ # produce
80
+ python python/examples/producer.py --broker 127.0.0.1:7500 --topic cangling-test --text hello --count 1 --token change-me
81
+ ```
82
+
83
+ ## Publish to PyPI
84
+
85
+ CI on tag `v*` builds the package and uploads with `pypa/gh-action-pypi-publish`. Set repository secret `PYPI_API_TOKEN`.
86
+
87
+ ```bash
88
+ cd python
89
+ python generate_proto.py
90
+ python -m build
91
+ ```
@@ -0,0 +1,72 @@
1
+ # cangling-broker (Python)
2
+
3
+ Producer and consumer for cangling-broker. Same contract as the Java client: `AcceptMessages` to publish, `Subscribe` to consume, optional `Register` metadata, `CL_BROKER_AUTH_TOKEN` on every RPC.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install cangling-broker
9
+ ```
10
+
11
+ From this tree:
12
+
13
+ ```bash
14
+ pip install -e python
15
+ ```
16
+
17
+ ## Use
18
+
19
+ ```python
20
+ from cangling_broker import SatwayClient, SubscribeOptions
21
+
22
+ with SatwayClient.connect("127.0.0.1:7500", "change-me") as client:
23
+ client.send("cangling-test", "hello")
24
+ with client.subscribe(
25
+ SubscribeOptions(topic="cangling-test", name="worker-1"),
26
+ lambda message: print(message.id, message.payload),
27
+ ):
28
+ ...
29
+ ```
30
+
31
+ `SatwayClient.connect(broker)` also reads `CL_BROKER_AUTH_TOKEN` from the environment.
32
+
33
+ Batch-set topic delivery (`single` = one consumer, `broadcast` = every live stream):
34
+
35
+ ```python
36
+ from cangling_broker import TopicConfig
37
+
38
+ client.configure_topics([
39
+ TopicConfig("jobs", "single"),
40
+ TopicConfig("alerts", "broadcast"),
41
+ ])
42
+ print(client.list_topics())
43
+ ```
44
+
45
+ Existing Kafka senders can keep ``send(topic, value)`` / ``flush()``:
46
+
47
+ ```python
48
+ # from kafka import KafkaProducer
49
+ from cangling_broker import KafkaProducer
50
+
51
+ producer = KafkaProducer(bootstrap_servers="127.0.0.1:7500")
52
+ producer.send(topic, msg)
53
+ producer.flush()
54
+ ```
55
+
56
+ ```bash
57
+ # consume
58
+ python python/examples/consumer.py --broker 127.0.0.1:7500 --topic cangling-test --name py-s0 --token change-me
59
+
60
+ # produce
61
+ python python/examples/producer.py --broker 127.0.0.1:7500 --topic cangling-test --text hello --count 1 --token change-me
62
+ ```
63
+
64
+ ## Publish to PyPI
65
+
66
+ CI on tag `v*` builds the package and uploads with `pypa/gh-action-pypi-publish`. Set repository secret `PYPI_API_TOKEN`.
67
+
68
+ ```bash
69
+ cd python
70
+ python generate_proto.py
71
+ python -m build
72
+ ```
@@ -0,0 +1,12 @@
1
+ from .client import SatwayClient
2
+ from .compat import KafkaProducer
3
+ from .models import SatwayMessage, SendResult, SubscribeOptions, TopicConfig
4
+
5
+ __all__ = [
6
+ "KafkaProducer",
7
+ "SatwayClient",
8
+ "SatwayMessage",
9
+ "SendResult",
10
+ "SubscribeOptions",
11
+ "TopicConfig",
12
+ ]
@@ -0,0 +1,354 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ import logging
5
+ import threading
6
+ import time
7
+ import uuid
8
+
9
+ import grpc
10
+
11
+ from .models import SatwayMessage, SendResult, SubscribeOptions, TopicConfig, auth_token_from_env
12
+ from .proto import queue_pb2, queue_pb2_grpc
13
+
14
+ LOG = logging.getLogger("cangling_broker")
15
+
16
+ INITIAL_BACKOFF_SECS = 0.2
17
+ MAX_BACKOFF_SECS = 5.0
18
+ RPC_DEADLINE_SECS = 15
19
+ RETRYABLE = {
20
+ grpc.StatusCode.UNAVAILABLE,
21
+ grpc.StatusCode.DEADLINE_EXCEEDED,
22
+ grpc.StatusCode.ABORTED,
23
+ grpc.StatusCode.UNKNOWN,
24
+ }
25
+
26
+ Handler = Callable[[SatwayMessage], None]
27
+
28
+
29
+ def _broker_target(broker: str) -> str:
30
+ broker = broker.strip()
31
+ if broker.startswith("http://"):
32
+ return broker[len("http://") :]
33
+ if broker.startswith("https://"):
34
+ return broker[len("https://") :]
35
+ return broker
36
+
37
+
38
+ def _auth_metadata(token: str | None) -> list[tuple[str, str]] | None:
39
+ token = (token or "").strip()
40
+ if not token:
41
+ return None
42
+ if not token.lower().startswith("bearer "):
43
+ token = "Bearer " + token
44
+ return [("authorization", token)]
45
+
46
+
47
+ class SatwayClient:
48
+ """Broker client. Owns the gRPC channel and retries unary RPCs.
49
+
50
+ Each :meth:`subscribe` stream reopens on the same ``consumer_id`` after a drop.
51
+ """
52
+
53
+ def __init__(self, channel: grpc.Channel, metadata: list[tuple[str, str]] | None):
54
+ self._channel = channel
55
+ self._stub = queue_pb2_grpc.MessageQueueStub(channel)
56
+ self._metadata = metadata
57
+ self._open = True
58
+ self._consumers: list[Consumer] = []
59
+ self._lock = threading.Lock()
60
+
61
+ @classmethod
62
+ def connect(cls, broker: str, token: str | None = None) -> SatwayClient:
63
+ if not broker or not broker.strip():
64
+ raise ValueError("broker is required")
65
+ if token is None:
66
+ token = auth_token_from_env()
67
+ channel = grpc.insecure_channel(_broker_target(broker))
68
+ return cls(channel, _auth_metadata(token))
69
+
70
+ def send(
71
+ self,
72
+ topic: str,
73
+ payload: str | bytes,
74
+ idempotency_key: str = "",
75
+ attributes: dict[str, str] | None = None,
76
+ ) -> SendResult:
77
+ if not topic or not topic.strip():
78
+ raise ValueError("topic is required")
79
+ if payload is None or payload == "" or payload == b"":
80
+ raise ValueError("payload is required")
81
+ body = payload.encode("utf-8") if isinstance(payload, str) else payload
82
+ key = idempotency_key.strip() if idempotency_key else str(uuid.uuid4())
83
+ attrs = attributes or {}
84
+
85
+ def once() -> SendResult:
86
+ request = queue_pb2.AcceptMessageRequest(
87
+ idempotency_key=key,
88
+ topic=topic,
89
+ payload=body,
90
+ attributes=attrs,
91
+ )
92
+ for response in self._stub.AcceptMessages(
93
+ iter([request]),
94
+ timeout=RPC_DEADLINE_SECS,
95
+ metadata=self._metadata,
96
+ ):
97
+ return SendResult(response.message_id, response.duplicate)
98
+ raise RuntimeError("publish stream closed without a response")
99
+
100
+ return self._call_with_reconnect("publish", once)
101
+
102
+ def register(
103
+ self,
104
+ topic: str,
105
+ name: str = "",
106
+ attributes: dict[str, str] | None = None,
107
+ consumer_id: str = "",
108
+ ) -> str:
109
+ def once() -> str:
110
+ return self._stub.Register(
111
+ queue_pb2.RegisterRequest(
112
+ topic=topic,
113
+ consumer_id=consumer_id or "",
114
+ name=name or "",
115
+ attributes=attributes or {},
116
+ ),
117
+ timeout=RPC_DEADLINE_SECS,
118
+ metadata=self._metadata,
119
+ ).consumer_id
120
+
121
+ return self._call_with_reconnect("register", once)
122
+
123
+ def configure_topics(self, topics: list[TopicConfig]) -> list[TopicConfig]:
124
+ if not topics:
125
+ raise ValueError("topics is required")
126
+
127
+ def once() -> list[TopicConfig]:
128
+ response = self._stub.ConfigureTopics(
129
+ queue_pb2.ConfigureTopicsRequest(
130
+ topics=[
131
+ queue_pb2.TopicConfig(topic=item.topic, delivery=item.delivery)
132
+ for item in topics
133
+ ]
134
+ ),
135
+ timeout=RPC_DEADLINE_SECS,
136
+ metadata=self._metadata,
137
+ )
138
+ return [TopicConfig(topic=item.topic, delivery=item.delivery) for item in response.topics]
139
+
140
+ return self._call_with_reconnect("configure_topics", once)
141
+
142
+ def list_topics(self) -> list[TopicConfig]:
143
+ def once() -> list[TopicConfig]:
144
+ response = self._stub.ListTopics(
145
+ queue_pb2.ListTopicsRequest(),
146
+ timeout=RPC_DEADLINE_SECS,
147
+ metadata=self._metadata,
148
+ )
149
+ return [TopicConfig(topic=item.topic, delivery=item.delivery) for item in response.topics]
150
+
151
+ return self._call_with_reconnect("list_topics", once)
152
+
153
+ def unregister(self, consumer_id: str) -> None:
154
+ if not consumer_id:
155
+ return
156
+
157
+ def once() -> None:
158
+ self._stub.Unregister(
159
+ queue_pb2.UnregisterRequest(consumer_id=consumer_id),
160
+ timeout=RPC_DEADLINE_SECS,
161
+ metadata=self._metadata,
162
+ )
163
+
164
+ self._call_with_reconnect("unregister", once)
165
+
166
+ def subscribe(
167
+ self,
168
+ topic: str | SubscribeOptions,
169
+ handler: Handler,
170
+ *,
171
+ name: str = "",
172
+ consumer_id: str = "",
173
+ attributes: dict[str, str] | None = None,
174
+ ) -> Consumer:
175
+ if handler is None:
176
+ raise ValueError("handler is required")
177
+ if isinstance(topic, SubscribeOptions):
178
+ options = topic
179
+ else:
180
+ options = SubscribeOptions(
181
+ topic=topic,
182
+ name=name,
183
+ consumer_id=consumer_id,
184
+ attributes=attributes or {},
185
+ )
186
+ cid = options.consumer_id
187
+ if options.name or options.attributes or options.consumer_id:
188
+ cid = self.register(
189
+ options.topic,
190
+ name=options.name,
191
+ attributes=dict(options.attributes),
192
+ consumer_id=options.consumer_id,
193
+ )
194
+ consumer = Consumer(self, options, cid, handler)
195
+ with self._lock:
196
+ self._consumers.append(consumer)
197
+ return consumer
198
+
199
+ def close(self) -> None:
200
+ with self._lock:
201
+ if not self._open:
202
+ return
203
+ self._open = False
204
+ consumers = list(self._consumers)
205
+ for consumer in consumers:
206
+ consumer.close()
207
+ self._channel.close()
208
+
209
+ def __enter__(self) -> SatwayClient:
210
+ return self
211
+
212
+ def __exit__(self, exc_type, exc, tb) -> None:
213
+ self.close()
214
+
215
+ def _is_open(self) -> bool:
216
+ return self._open
217
+
218
+ def _ack(self, message_id: str, lease: str, success: bool, error: str = "") -> None:
219
+ def once() -> None:
220
+ self._stub.AckMessage(
221
+ queue_pb2.AckMessageRequest(
222
+ message_id=message_id,
223
+ lease=lease,
224
+ success=success,
225
+ error=error or "",
226
+ ),
227
+ timeout=RPC_DEADLINE_SECS,
228
+ metadata=self._metadata,
229
+ )
230
+
231
+ self._call_with_reconnect("ack", once)
232
+
233
+ def _ensure_registered(self, options: SubscribeOptions, consumer_id: str) -> None:
234
+ if not consumer_id:
235
+ return
236
+ self.register(
237
+ options.topic,
238
+ name=options.name,
239
+ attributes=dict(options.attributes),
240
+ consumer_id=consumer_id,
241
+ )
242
+
243
+ def _subscribe_stream(self, topic: str, consumer_id: str):
244
+ return self._stub.Subscribe(
245
+ queue_pb2.SubscribeRequest(topic=topic, consumer_id=consumer_id or ""),
246
+ metadata=self._metadata,
247
+ )
248
+
249
+ def _call_with_reconnect(self, op: str, call):
250
+ backoff = INITIAL_BACKOFF_SECS
251
+ while True:
252
+ if not self._open:
253
+ raise RuntimeError("client closed")
254
+ try:
255
+ return call()
256
+ except grpc.RpcError as error:
257
+ if not self._open:
258
+ raise RuntimeError("client closed") from error
259
+ if error.code() not in RETRYABLE:
260
+ raise RuntimeError(f"{op} failed: {error.code()}: {error.details()}") from error
261
+ LOG.warning("%s failed, reconnecting: %s", op, error.details() or error.code())
262
+ time.sleep(backoff)
263
+ backoff = min(backoff * 2, MAX_BACKOFF_SECS)
264
+
265
+
266
+ class Consumer:
267
+ def __init__(
268
+ self,
269
+ client: SatwayClient,
270
+ options: SubscribeOptions,
271
+ consumer_id: str,
272
+ handler: Handler,
273
+ ):
274
+ self._client = client
275
+ self._closed = False
276
+ self.consumer_id = consumer_id
277
+ self._thread = threading.Thread(
278
+ target=self._run,
279
+ args=(options, handler),
280
+ name="cangling-subscribe",
281
+ daemon=True,
282
+ )
283
+ self._thread.start()
284
+
285
+ def close(self) -> None:
286
+ self._closed = True
287
+
288
+ def __enter__(self) -> Consumer:
289
+ return self
290
+
291
+ def __exit__(self, exc_type, exc, tb) -> None:
292
+ self.close()
293
+
294
+ def _running(self) -> bool:
295
+ return not self._closed and self._client._is_open()
296
+
297
+ def _run(self, options: SubscribeOptions, handler: Handler) -> None:
298
+ backoff = INITIAL_BACKOFF_SECS
299
+ while self._running():
300
+ try:
301
+ self._client._ensure_registered(options, self.consumer_id)
302
+ stream = self._client._subscribe_stream(options.topic, self.consumer_id)
303
+ backoff = INITIAL_BACKOFF_SECS
304
+ for incoming in stream:
305
+ if not self._running():
306
+ return
307
+ message = _to_message(incoming)
308
+ try:
309
+ handler(message)
310
+ self._client._ack(incoming.message_id, incoming.lease, True, "")
311
+ except Exception as error:
312
+ LOG.warning("handler failed", exc_info=error)
313
+ self._client._ack(
314
+ incoming.message_id,
315
+ incoming.lease,
316
+ False,
317
+ str(error) or "handler failed",
318
+ )
319
+ if self._running():
320
+ LOG.info("subscribe stream ended, reconnecting")
321
+ except grpc.RpcError as error:
322
+ if self._running():
323
+ LOG.warning("subscribe stream closed, reconnecting: %s", error.details() or error.code())
324
+ else:
325
+ return
326
+ except Exception as error:
327
+ if self._running():
328
+ LOG.warning("subscribe failed, reconnecting: %s", error)
329
+ else:
330
+ return
331
+ if self._running():
332
+ time.sleep(backoff)
333
+ backoff = min(backoff * 2, MAX_BACKOFF_SECS)
334
+
335
+
336
+ def _to_message(incoming: queue_pb2.SatwayMessage) -> SatwayMessage:
337
+ raw = incoming.payload
338
+ try:
339
+ payload = raw.decode("utf-8")
340
+ encoding = "utf-8"
341
+ except UnicodeDecodeError:
342
+ import base64
343
+
344
+ payload = base64.b64encode(raw).decode("ascii")
345
+ encoding = "base64"
346
+ return SatwayMessage(
347
+ id=incoming.message_id,
348
+ topic=incoming.topic,
349
+ payload=payload,
350
+ payload_encoding=encoding,
351
+ attributes=dict(incoming.attributes),
352
+ created_at=incoming.created_at,
353
+ lease=incoming.lease,
354
+ )
@@ -0,0 +1,37 @@
1
+ """kafka-python shaped helpers so existing senders can target this broker."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .client import SatwayClient
6
+
7
+
8
+ class KafkaProducer:
9
+ """Stand-in for ``kafka.KafkaProducer``.
10
+
11
+ Keep calling ``send(topic, value)`` and ``flush()``. ``bootstrap_servers``
12
+ is the cangling-broker (``host:port``). Extra Kafka kwargs are ignored.
13
+ """
14
+
15
+ def __init__(
16
+ self,
17
+ bootstrap_servers="",
18
+ token=None,
19
+ value_serializer=None,
20
+ **_ignored,
21
+ ):
22
+ if isinstance(bootstrap_servers, (list, tuple)):
23
+ if not bootstrap_servers:
24
+ raise ValueError("bootstrap_servers is required")
25
+ bootstrap_servers = bootstrap_servers[0]
26
+ self._client = SatwayClient.connect(str(bootstrap_servers), token)
27
+ self._value_serializer = value_serializer
28
+
29
+ def send(self, topic, value=None, key=None, headers=None, partition=None, timestamp_ms=None):
30
+ payload = self._value_serializer(value) if self._value_serializer is not None else value
31
+ return self._client.send(topic, payload)
32
+
33
+ def flush(self, timeout=None):
34
+ return None
35
+
36
+ def close(self, timeout=None):
37
+ self._client.close()
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ import os
5
+ from typing import Mapping
6
+
7
+
8
+ def auth_token_from_env() -> str:
9
+ return (os.environ.get("CL_BROKER_AUTH_TOKEN") or "").strip()
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class SendResult:
14
+ message_id: str
15
+ duplicate: bool = False
16
+
17
+
18
+ @dataclass
19
+ class SatwayMessage:
20
+ id: str
21
+ topic: str
22
+ payload: str
23
+ payload_encoding: str = "utf-8"
24
+ attributes: Mapping[str, str] = field(default_factory=dict)
25
+ created_at: str = ""
26
+ lease: str = ""
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class TopicConfig:
31
+ topic: str
32
+ delivery: str = "single"
33
+
34
+ def __post_init__(self) -> None:
35
+ if not self.topic or not self.topic.strip():
36
+ raise ValueError("topic is required")
37
+ delivery = (self.delivery or "single").strip().lower()
38
+ if delivery not in {"single", "broadcast", "queue", "competing", "fanout", "pubsub"}:
39
+ raise ValueError("delivery must be single or broadcast")
40
+ if delivery in {"queue", "competing"}:
41
+ delivery = "single"
42
+ if delivery in {"fanout", "pubsub"}:
43
+ delivery = "broadcast"
44
+ object.__setattr__(self, "topic", self.topic.strip())
45
+ object.__setattr__(self, "delivery", delivery)
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class SubscribeOptions:
50
+ topic: str
51
+ consumer_id: str = ""
52
+ name: str = ""
53
+ attributes: Mapping[str, str] = field(default_factory=dict)
54
+
55
+ def __post_init__(self) -> None:
56
+ if not self.topic or not self.topic.strip():
57
+ raise ValueError("topic is required")
58
+ object.__setattr__(self, "topic", self.topic.strip())
59
+ object.__setattr__(self, "consumer_id", self.consumer_id or "")
60
+ object.__setattr__(self, "name", self.name or "")
61
+ object.__setattr__(self, "attributes", dict(self.attributes or {}))
@@ -0,0 +1 @@
1
+ # Generated gRPC stubs live in this package.
@@ -0,0 +1,79 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: queue.proto
5
+ # Protobuf Python Version: 7.35.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 7,
15
+ 35,
16
+ 1,
17
+ '',
18
+ 'queue.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0bqueue.proto\x12\rdispatcher.v1\"\xcb\x01\n\x14\x41\x63\x63\x65ptMessageRequest\x12\x17\n\x0fidempotency_key\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12\x0f\n\x07payload\x18\x03 \x01(\x0c\x12G\n\nattributes\x18\x04 \x03(\x0b\x32\x33.dispatcher.v1.AcceptMessageRequest.AttributesEntry\x1a\x31\n\x0f\x41ttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\">\n\x15\x41\x63\x63\x65ptMessageResponse\x12\x12\n\nmessage_id\x18\x01 \x01(\t\x12\x11\n\tduplicate\x18\x02 \x01(\x08\"\xba\x01\n\x0fRegisterRequest\x12\r\n\x05topic\x18\x01 \x01(\t\x12\x13\n\x0b\x63onsumer_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x42\n\nattributes\x18\x04 \x03(\x0b\x32..dispatcher.v1.RegisterRequest.AttributesEntry\x1a\x31\n\x0f\x41ttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\'\n\x10RegisterResponse\x12\x13\n\x0b\x63onsumer_id\x18\x01 \x01(\t\"(\n\x11UnregisterRequest\x12\x13\n\x0b\x63onsumer_id\x18\x01 \x01(\t\"\x14\n\x12UnregisterResponse\"6\n\x10SubscribeRequest\x12\r\n\x05topic\x18\x01 \x01(\t\x12\x13\n\x0b\x63onsumer_id\x18\x02 \x01(\t\"\xdb\x01\n\rSatwayMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12\x0f\n\x07payload\x18\x03 \x01(\x0c\x12@\n\nattributes\x18\x04 \x03(\x0b\x32,.dispatcher.v1.SatwayMessage.AttributesEntry\x12\x12\n\ncreated_at\x18\x05 \x01(\t\x12\r\n\x05lease\x18\x06 \x01(\t\x1a\x31\n\x0f\x41ttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"V\n\x11\x41\x63kMessageRequest\x12\x12\n\nmessage_id\x18\x01 \x01(\t\x12\r\n\x05lease\x18\x02 \x01(\t\x12\x0f\n\x07success\x18\x03 \x01(\x08\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"&\n\x12\x41\x63kMessageResponse\x12\x10\n\x08\x61\x63\x63\x65pted\x18\x01 \x01(\x08\".\n\x0bTopicConfig\x12\r\n\x05topic\x18\x01 \x01(\t\x12\x10\n\x08\x64\x65livery\x18\x02 \x01(\t\"D\n\x16\x43onfigureTopicsRequest\x12*\n\x06topics\x18\x01 \x03(\x0b\x32\x1a.dispatcher.v1.TopicConfig\"E\n\x17\x43onfigureTopicsResponse\x12*\n\x06topics\x18\x01 \x03(\x0b\x32\x1a.dispatcher.v1.TopicConfig\"\x13\n\x11ListTopicsRequest\"@\n\x12ListTopicsResponse\x12*\n\x06topics\x18\x01 \x03(\x0b\x32\x1a.dispatcher.v1.TopicConfig2\xe5\x04\n\x0cMessageQueue\x12_\n\x0e\x41\x63\x63\x65ptMessages\x12#.dispatcher.v1.AcceptMessageRequest\x1a$.dispatcher.v1.AcceptMessageResponse(\x01\x30\x01\x12K\n\x08Register\x12\x1e.dispatcher.v1.RegisterRequest\x1a\x1f.dispatcher.v1.RegisterResponse\x12Q\n\nUnregister\x12 .dispatcher.v1.UnregisterRequest\x1a!.dispatcher.v1.UnregisterResponse\x12L\n\tSubscribe\x12\x1f.dispatcher.v1.SubscribeRequest\x1a\x1c.dispatcher.v1.SatwayMessage0\x01\x12Q\n\nAckMessage\x12 .dispatcher.v1.AckMessageRequest\x1a!.dispatcher.v1.AckMessageResponse\x12`\n\x0f\x43onfigureTopics\x12%.dispatcher.v1.ConfigureTopicsRequest\x1a&.dispatcher.v1.ConfigureTopicsResponse\x12Q\n\nListTopics\x12 .dispatcher.v1.ListTopicsRequest\x1a!.dispatcher.v1.ListTopicsResponseB&\n\x16\x63n.mapway.broker.protoB\nQueueProtoP\x01\x62\x06proto3')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'queue_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ _globals['DESCRIPTOR']._loaded_options = None
34
+ _globals['DESCRIPTOR']._serialized_options = b'\n\026cn.mapway.broker.protoB\nQueueProtoP\001'
35
+ _globals['_ACCEPTMESSAGEREQUEST_ATTRIBUTESENTRY']._loaded_options = None
36
+ _globals['_ACCEPTMESSAGEREQUEST_ATTRIBUTESENTRY']._serialized_options = b'8\001'
37
+ _globals['_REGISTERREQUEST_ATTRIBUTESENTRY']._loaded_options = None
38
+ _globals['_REGISTERREQUEST_ATTRIBUTESENTRY']._serialized_options = b'8\001'
39
+ _globals['_SATWAYMESSAGE_ATTRIBUTESENTRY']._loaded_options = None
40
+ _globals['_SATWAYMESSAGE_ATTRIBUTESENTRY']._serialized_options = b'8\001'
41
+ _globals['_ACCEPTMESSAGEREQUEST']._serialized_start=31
42
+ _globals['_ACCEPTMESSAGEREQUEST']._serialized_end=234
43
+ _globals['_ACCEPTMESSAGEREQUEST_ATTRIBUTESENTRY']._serialized_start=185
44
+ _globals['_ACCEPTMESSAGEREQUEST_ATTRIBUTESENTRY']._serialized_end=234
45
+ _globals['_ACCEPTMESSAGERESPONSE']._serialized_start=236
46
+ _globals['_ACCEPTMESSAGERESPONSE']._serialized_end=298
47
+ _globals['_REGISTERREQUEST']._serialized_start=301
48
+ _globals['_REGISTERREQUEST']._serialized_end=487
49
+ _globals['_REGISTERREQUEST_ATTRIBUTESENTRY']._serialized_start=185
50
+ _globals['_REGISTERREQUEST_ATTRIBUTESENTRY']._serialized_end=234
51
+ _globals['_REGISTERRESPONSE']._serialized_start=489
52
+ _globals['_REGISTERRESPONSE']._serialized_end=528
53
+ _globals['_UNREGISTERREQUEST']._serialized_start=530
54
+ _globals['_UNREGISTERREQUEST']._serialized_end=570
55
+ _globals['_UNREGISTERRESPONSE']._serialized_start=572
56
+ _globals['_UNREGISTERRESPONSE']._serialized_end=592
57
+ _globals['_SUBSCRIBEREQUEST']._serialized_start=594
58
+ _globals['_SUBSCRIBEREQUEST']._serialized_end=648
59
+ _globals['_SATWAYMESSAGE']._serialized_start=651
60
+ _globals['_SATWAYMESSAGE']._serialized_end=870
61
+ _globals['_SATWAYMESSAGE_ATTRIBUTESENTRY']._serialized_start=185
62
+ _globals['_SATWAYMESSAGE_ATTRIBUTESENTRY']._serialized_end=234
63
+ _globals['_ACKMESSAGEREQUEST']._serialized_start=872
64
+ _globals['_ACKMESSAGEREQUEST']._serialized_end=958
65
+ _globals['_ACKMESSAGERESPONSE']._serialized_start=960
66
+ _globals['_ACKMESSAGERESPONSE']._serialized_end=998
67
+ _globals['_TOPICCONFIG']._serialized_start=1000
68
+ _globals['_TOPICCONFIG']._serialized_end=1046
69
+ _globals['_CONFIGURETOPICSREQUEST']._serialized_start=1048
70
+ _globals['_CONFIGURETOPICSREQUEST']._serialized_end=1116
71
+ _globals['_CONFIGURETOPICSRESPONSE']._serialized_start=1118
72
+ _globals['_CONFIGURETOPICSRESPONSE']._serialized_end=1187
73
+ _globals['_LISTTOPICSREQUEST']._serialized_start=1189
74
+ _globals['_LISTTOPICSREQUEST']._serialized_end=1208
75
+ _globals['_LISTTOPICSRESPONSE']._serialized_start=1210
76
+ _globals['_LISTTOPICSRESPONSE']._serialized_end=1274
77
+ _globals['_MESSAGEQUEUE']._serialized_start=1277
78
+ _globals['_MESSAGEQUEUE']._serialized_end=1890
79
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,364 @@
1
+ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2
+ """Client and server classes corresponding to protobuf-defined services."""
3
+ import grpc
4
+ import warnings
5
+
6
+ from . import queue_pb2 as queue__pb2
7
+
8
+ GRPC_GENERATED_VERSION = '1.83.0'
9
+ GRPC_VERSION = grpc.__version__
10
+ _version_not_supported = False
11
+
12
+ try:
13
+ from grpc._utilities import first_version_is_lower
14
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
15
+ except ImportError:
16
+ _version_not_supported = True
17
+
18
+ if _version_not_supported:
19
+ raise RuntimeError(
20
+ f'The grpc package installed is at version {GRPC_VERSION},'
21
+ + ' but the generated code in queue_pb2_grpc.py depends on'
22
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
23
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
24
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
25
+ )
26
+
27
+
28
+ class MessageQueueStub:
29
+ """Missing associated documentation comment in .proto file."""
30
+
31
+ def __init__(self, channel):
32
+ """Constructor.
33
+
34
+ Args:
35
+ channel: A grpc.Channel.
36
+ """
37
+ self.AcceptMessages = channel.stream_stream(
38
+ '/dispatcher.v1.MessageQueue/AcceptMessages',
39
+ request_serializer=queue__pb2.AcceptMessageRequest.SerializeToString,
40
+ response_deserializer=queue__pb2.AcceptMessageResponse.FromString,
41
+ _registered_method=True)
42
+ self.Register = channel.unary_unary(
43
+ '/dispatcher.v1.MessageQueue/Register',
44
+ request_serializer=queue__pb2.RegisterRequest.SerializeToString,
45
+ response_deserializer=queue__pb2.RegisterResponse.FromString,
46
+ _registered_method=True)
47
+ self.Unregister = channel.unary_unary(
48
+ '/dispatcher.v1.MessageQueue/Unregister',
49
+ request_serializer=queue__pb2.UnregisterRequest.SerializeToString,
50
+ response_deserializer=queue__pb2.UnregisterResponse.FromString,
51
+ _registered_method=True)
52
+ self.Subscribe = channel.unary_stream(
53
+ '/dispatcher.v1.MessageQueue/Subscribe',
54
+ request_serializer=queue__pb2.SubscribeRequest.SerializeToString,
55
+ response_deserializer=queue__pb2.SatwayMessage.FromString,
56
+ _registered_method=True)
57
+ self.AckMessage = channel.unary_unary(
58
+ '/dispatcher.v1.MessageQueue/AckMessage',
59
+ request_serializer=queue__pb2.AckMessageRequest.SerializeToString,
60
+ response_deserializer=queue__pb2.AckMessageResponse.FromString,
61
+ _registered_method=True)
62
+ self.ConfigureTopics = channel.unary_unary(
63
+ '/dispatcher.v1.MessageQueue/ConfigureTopics',
64
+ request_serializer=queue__pb2.ConfigureTopicsRequest.SerializeToString,
65
+ response_deserializer=queue__pb2.ConfigureTopicsResponse.FromString,
66
+ _registered_method=True)
67
+ self.ListTopics = channel.unary_unary(
68
+ '/dispatcher.v1.MessageQueue/ListTopics',
69
+ request_serializer=queue__pb2.ListTopicsRequest.SerializeToString,
70
+ response_deserializer=queue__pb2.ListTopicsResponse.FromString,
71
+ _registered_method=True)
72
+
73
+
74
+ class MessageQueueServicer:
75
+ """Missing associated documentation comment in .proto file."""
76
+
77
+ def AcceptMessages(self, request_iterator, context):
78
+ """Client streams publishes; server streams one response per request after SQLite commit.
79
+ """
80
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
81
+ context.set_details('Method not implemented!')
82
+ raise NotImplementedError('Method not implemented!')
83
+
84
+ def Register(self, request, context):
85
+ """Optional metadata about a consumer. Delivery is on Subscribe, not this call.
86
+ """
87
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
88
+ context.set_details('Method not implemented!')
89
+ raise NotImplementedError('Method not implemented!')
90
+
91
+ def Unregister(self, request, context):
92
+ """Unregister drops stored consumer metadata.
93
+ """
94
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
95
+ context.set_details('Method not implemented!')
96
+ raise NotImplementedError('Method not implemented!')
97
+
98
+ def Subscribe(self, request, context):
99
+ """Open a consume stream. Multiple subscribers on one topic compete:
100
+ each message is sent to exactly one of them.
101
+ """
102
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
103
+ context.set_details('Method not implemented!')
104
+ raise NotImplementedError('Method not implemented!')
105
+
106
+ def AckMessage(self, request, context):
107
+ """Confirm or reject a message received from Subscribe.
108
+ """
109
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
110
+ context.set_details('Method not implemented!')
111
+ raise NotImplementedError('Method not implemented!')
112
+
113
+ def ConfigureTopics(self, request, context):
114
+ """Create or replace delivery mode for many topics at once.
115
+ delivery is "single" (competing consumers, default) or "broadcast" (every live stream).
116
+ """
117
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
118
+ context.set_details('Method not implemented!')
119
+ raise NotImplementedError('Method not implemented!')
120
+
121
+ def ListTopics(self, request, context):
122
+ """List stored topic delivery configs.
123
+ """
124
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
125
+ context.set_details('Method not implemented!')
126
+ raise NotImplementedError('Method not implemented!')
127
+
128
+
129
+ def add_MessageQueueServicer_to_server(servicer, server):
130
+ rpc_method_handlers = {
131
+ 'AcceptMessages': grpc.stream_stream_rpc_method_handler(
132
+ servicer.AcceptMessages,
133
+ request_deserializer=queue__pb2.AcceptMessageRequest.FromString,
134
+ response_serializer=queue__pb2.AcceptMessageResponse.SerializeToString,
135
+ ),
136
+ 'Register': grpc.unary_unary_rpc_method_handler(
137
+ servicer.Register,
138
+ request_deserializer=queue__pb2.RegisterRequest.FromString,
139
+ response_serializer=queue__pb2.RegisterResponse.SerializeToString,
140
+ ),
141
+ 'Unregister': grpc.unary_unary_rpc_method_handler(
142
+ servicer.Unregister,
143
+ request_deserializer=queue__pb2.UnregisterRequest.FromString,
144
+ response_serializer=queue__pb2.UnregisterResponse.SerializeToString,
145
+ ),
146
+ 'Subscribe': grpc.unary_stream_rpc_method_handler(
147
+ servicer.Subscribe,
148
+ request_deserializer=queue__pb2.SubscribeRequest.FromString,
149
+ response_serializer=queue__pb2.SatwayMessage.SerializeToString,
150
+ ),
151
+ 'AckMessage': grpc.unary_unary_rpc_method_handler(
152
+ servicer.AckMessage,
153
+ request_deserializer=queue__pb2.AckMessageRequest.FromString,
154
+ response_serializer=queue__pb2.AckMessageResponse.SerializeToString,
155
+ ),
156
+ 'ConfigureTopics': grpc.unary_unary_rpc_method_handler(
157
+ servicer.ConfigureTopics,
158
+ request_deserializer=queue__pb2.ConfigureTopicsRequest.FromString,
159
+ response_serializer=queue__pb2.ConfigureTopicsResponse.SerializeToString,
160
+ ),
161
+ 'ListTopics': grpc.unary_unary_rpc_method_handler(
162
+ servicer.ListTopics,
163
+ request_deserializer=queue__pb2.ListTopicsRequest.FromString,
164
+ response_serializer=queue__pb2.ListTopicsResponse.SerializeToString,
165
+ ),
166
+ }
167
+ generic_handler = grpc.method_handlers_generic_handler(
168
+ 'dispatcher.v1.MessageQueue', rpc_method_handlers)
169
+ server.add_generic_rpc_handlers((generic_handler,))
170
+ server.add_registered_method_handlers('dispatcher.v1.MessageQueue', rpc_method_handlers)
171
+
172
+
173
+ # This class is part of an EXPERIMENTAL API.
174
+ class MessageQueue:
175
+ """Missing associated documentation comment in .proto file."""
176
+
177
+ @staticmethod
178
+ def AcceptMessages(request_iterator,
179
+ target,
180
+ options=(),
181
+ channel_credentials=None,
182
+ call_credentials=None,
183
+ insecure=False,
184
+ compression=None,
185
+ wait_for_ready=None,
186
+ timeout=None,
187
+ metadata=None):
188
+ return grpc.experimental.stream_stream(
189
+ request_iterator,
190
+ target,
191
+ '/dispatcher.v1.MessageQueue/AcceptMessages',
192
+ queue__pb2.AcceptMessageRequest.SerializeToString,
193
+ queue__pb2.AcceptMessageResponse.FromString,
194
+ options,
195
+ channel_credentials,
196
+ insecure,
197
+ call_credentials,
198
+ compression,
199
+ wait_for_ready,
200
+ timeout,
201
+ metadata,
202
+ _registered_method=True)
203
+
204
+ @staticmethod
205
+ def Register(request,
206
+ target,
207
+ options=(),
208
+ channel_credentials=None,
209
+ call_credentials=None,
210
+ insecure=False,
211
+ compression=None,
212
+ wait_for_ready=None,
213
+ timeout=None,
214
+ metadata=None):
215
+ return grpc.experimental.unary_unary(
216
+ request,
217
+ target,
218
+ '/dispatcher.v1.MessageQueue/Register',
219
+ queue__pb2.RegisterRequest.SerializeToString,
220
+ queue__pb2.RegisterResponse.FromString,
221
+ options,
222
+ channel_credentials,
223
+ insecure,
224
+ call_credentials,
225
+ compression,
226
+ wait_for_ready,
227
+ timeout,
228
+ metadata,
229
+ _registered_method=True)
230
+
231
+ @staticmethod
232
+ def Unregister(request,
233
+ target,
234
+ options=(),
235
+ channel_credentials=None,
236
+ call_credentials=None,
237
+ insecure=False,
238
+ compression=None,
239
+ wait_for_ready=None,
240
+ timeout=None,
241
+ metadata=None):
242
+ return grpc.experimental.unary_unary(
243
+ request,
244
+ target,
245
+ '/dispatcher.v1.MessageQueue/Unregister',
246
+ queue__pb2.UnregisterRequest.SerializeToString,
247
+ queue__pb2.UnregisterResponse.FromString,
248
+ options,
249
+ channel_credentials,
250
+ insecure,
251
+ call_credentials,
252
+ compression,
253
+ wait_for_ready,
254
+ timeout,
255
+ metadata,
256
+ _registered_method=True)
257
+
258
+ @staticmethod
259
+ def Subscribe(request,
260
+ target,
261
+ options=(),
262
+ channel_credentials=None,
263
+ call_credentials=None,
264
+ insecure=False,
265
+ compression=None,
266
+ wait_for_ready=None,
267
+ timeout=None,
268
+ metadata=None):
269
+ return grpc.experimental.unary_stream(
270
+ request,
271
+ target,
272
+ '/dispatcher.v1.MessageQueue/Subscribe',
273
+ queue__pb2.SubscribeRequest.SerializeToString,
274
+ queue__pb2.SatwayMessage.FromString,
275
+ options,
276
+ channel_credentials,
277
+ insecure,
278
+ call_credentials,
279
+ compression,
280
+ wait_for_ready,
281
+ timeout,
282
+ metadata,
283
+ _registered_method=True)
284
+
285
+ @staticmethod
286
+ def AckMessage(request,
287
+ target,
288
+ options=(),
289
+ channel_credentials=None,
290
+ call_credentials=None,
291
+ insecure=False,
292
+ compression=None,
293
+ wait_for_ready=None,
294
+ timeout=None,
295
+ metadata=None):
296
+ return grpc.experimental.unary_unary(
297
+ request,
298
+ target,
299
+ '/dispatcher.v1.MessageQueue/AckMessage',
300
+ queue__pb2.AckMessageRequest.SerializeToString,
301
+ queue__pb2.AckMessageResponse.FromString,
302
+ options,
303
+ channel_credentials,
304
+ insecure,
305
+ call_credentials,
306
+ compression,
307
+ wait_for_ready,
308
+ timeout,
309
+ metadata,
310
+ _registered_method=True)
311
+
312
+ @staticmethod
313
+ def ConfigureTopics(request,
314
+ target,
315
+ options=(),
316
+ channel_credentials=None,
317
+ call_credentials=None,
318
+ insecure=False,
319
+ compression=None,
320
+ wait_for_ready=None,
321
+ timeout=None,
322
+ metadata=None):
323
+ return grpc.experimental.unary_unary(
324
+ request,
325
+ target,
326
+ '/dispatcher.v1.MessageQueue/ConfigureTopics',
327
+ queue__pb2.ConfigureTopicsRequest.SerializeToString,
328
+ queue__pb2.ConfigureTopicsResponse.FromString,
329
+ options,
330
+ channel_credentials,
331
+ insecure,
332
+ call_credentials,
333
+ compression,
334
+ wait_for_ready,
335
+ timeout,
336
+ metadata,
337
+ _registered_method=True)
338
+
339
+ @staticmethod
340
+ def ListTopics(request,
341
+ target,
342
+ options=(),
343
+ channel_credentials=None,
344
+ call_credentials=None,
345
+ insecure=False,
346
+ compression=None,
347
+ wait_for_ready=None,
348
+ timeout=None,
349
+ metadata=None):
350
+ return grpc.experimental.unary_unary(
351
+ request,
352
+ target,
353
+ '/dispatcher.v1.MessageQueue/ListTopics',
354
+ queue__pb2.ListTopicsRequest.SerializeToString,
355
+ queue__pb2.ListTopicsResponse.FromString,
356
+ options,
357
+ channel_credentials,
358
+ insecure,
359
+ call_credentials,
360
+ compression,
361
+ wait_for_ready,
362
+ timeout,
363
+ metadata,
364
+ _registered_method=True)
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: cangling-broker
3
+ Version: 0.1.11
4
+ Summary: Python producer and consumer for cangling-broker
5
+ Author-email: zhangjianshe <zhangjianshe@gmail.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/zhangjianshe/cangling-broker
8
+ Project-URL: Repository, https://github.com/zhangjianshe/cangling-broker
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: grpcio>=1.83.0
14
+ Requires-Dist: protobuf>=5.28.0
15
+ Provides-Extra: dev
16
+ Requires-Dist: grpcio-tools>=1.83.0; extra == "dev"
17
+ Requires-Dist: build; extra == "dev"
18
+ Requires-Dist: twine; extra == "dev"
19
+
20
+ # cangling-broker (Python)
21
+
22
+ Producer and consumer for cangling-broker. Same contract as the Java client: `AcceptMessages` to publish, `Subscribe` to consume, optional `Register` metadata, `CL_BROKER_AUTH_TOKEN` on every RPC.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install cangling-broker
28
+ ```
29
+
30
+ From this tree:
31
+
32
+ ```bash
33
+ pip install -e python
34
+ ```
35
+
36
+ ## Use
37
+
38
+ ```python
39
+ from cangling_broker import SatwayClient, SubscribeOptions
40
+
41
+ with SatwayClient.connect("127.0.0.1:7500", "change-me") as client:
42
+ client.send("cangling-test", "hello")
43
+ with client.subscribe(
44
+ SubscribeOptions(topic="cangling-test", name="worker-1"),
45
+ lambda message: print(message.id, message.payload),
46
+ ):
47
+ ...
48
+ ```
49
+
50
+ `SatwayClient.connect(broker)` also reads `CL_BROKER_AUTH_TOKEN` from the environment.
51
+
52
+ Batch-set topic delivery (`single` = one consumer, `broadcast` = every live stream):
53
+
54
+ ```python
55
+ from cangling_broker import TopicConfig
56
+
57
+ client.configure_topics([
58
+ TopicConfig("jobs", "single"),
59
+ TopicConfig("alerts", "broadcast"),
60
+ ])
61
+ print(client.list_topics())
62
+ ```
63
+
64
+ Existing Kafka senders can keep ``send(topic, value)`` / ``flush()``:
65
+
66
+ ```python
67
+ # from kafka import KafkaProducer
68
+ from cangling_broker import KafkaProducer
69
+
70
+ producer = KafkaProducer(bootstrap_servers="127.0.0.1:7500")
71
+ producer.send(topic, msg)
72
+ producer.flush()
73
+ ```
74
+
75
+ ```bash
76
+ # consume
77
+ python python/examples/consumer.py --broker 127.0.0.1:7500 --topic cangling-test --name py-s0 --token change-me
78
+
79
+ # produce
80
+ python python/examples/producer.py --broker 127.0.0.1:7500 --topic cangling-test --text hello --count 1 --token change-me
81
+ ```
82
+
83
+ ## Publish to PyPI
84
+
85
+ CI on tag `v*` builds the package and uploads with `pypa/gh-action-pypi-publish`. Set repository secret `PYPI_API_TOKEN`.
86
+
87
+ ```bash
88
+ cd python
89
+ python generate_proto.py
90
+ python -m build
91
+ ```
@@ -0,0 +1,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ cangling_broker/__init__.py
4
+ cangling_broker/client.py
5
+ cangling_broker/compat.py
6
+ cangling_broker/models.py
7
+ cangling_broker.egg-info/PKG-INFO
8
+ cangling_broker.egg-info/SOURCES.txt
9
+ cangling_broker.egg-info/dependency_links.txt
10
+ cangling_broker.egg-info/requires.txt
11
+ cangling_broker.egg-info/top_level.txt
12
+ cangling_broker/proto/__init__.py
13
+ cangling_broker/proto/queue_pb2.py
14
+ cangling_broker/proto/queue_pb2_grpc.py
@@ -0,0 +1,7 @@
1
+ grpcio>=1.83.0
2
+ protobuf>=5.28.0
3
+
4
+ [dev]
5
+ grpcio-tools>=1.83.0
6
+ build
7
+ twine
@@ -0,0 +1 @@
1
+ cangling_broker
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "cangling-broker"
3
+ version = "0.1.11"
4
+ description = "Python producer and consumer for cangling-broker"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ authors = [{ name = "zhangjianshe", email = "zhangjianshe@gmail.com" }]
8
+ license = { text = "Apache-2.0" }
9
+ dependencies = [
10
+ "grpcio>=1.83.0",
11
+ "protobuf>=5.28.0",
12
+ ]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "License :: OSI Approved :: Apache Software License",
16
+ ]
17
+ urls.Homepage = "https://github.com/zhangjianshe/cangling-broker"
18
+ urls.Repository = "https://github.com/zhangjianshe/cangling-broker"
19
+
20
+ [project.optional-dependencies]
21
+ dev = ["grpcio-tools>=1.83.0", "build", "twine"]
22
+
23
+ [build-system]
24
+ requires = ["setuptools", "wheel"]
25
+ build-backend = "setuptools.build_meta"
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["."]
29
+ include = ["cangling_broker", "cangling_broker.*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+