adc-streaming 2.5.0__tar.gz → 2.6.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.
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/PKG-INFO +30 -3
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc/auth.py +1 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc/consumer.py +0 -2
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc/producer.py +21 -8
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc_streaming.egg-info/PKG-INFO +30 -3
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc_streaming.egg-info/SOURCES.txt +0 -1
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc_streaming.egg-info/requires.txt +1 -3
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/setup.py +1 -3
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/tests/test_kafka_integration.py +40 -2
- adc-streaming-2.5.0/adc/oidc.py +0 -27
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/.github/workflows/build.yml +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/.gitignore +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/LICENSE +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/Makefile +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/README.md +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc/__init__.py +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc/errors.py +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc/io.py +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc/kafka.py +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc_streaming.egg-info/dependency_links.txt +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc_streaming.egg-info/not-zip-safe +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/adc_streaming.egg-info/top_level.txt +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/doc/Makefile +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/doc/_static/css/my_theme.css +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/doc/_templates/layout.html +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/doc/api/api.rst +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/doc/api/streaming.rst +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/doc/conf.py +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/doc/index.rst +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/doc/user/installation.rst +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/doc/user/quickstart.rst +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/pyproject.toml +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/recipe/meta.yaml +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/setup.cfg +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/tests/test_auth.py +0 -0
- {adc-streaming-2.5.0 → adc_streaming-2.6.0}/tests/test_kafka.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: adc-streaming
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: Astronomy Data Commons streaming client libraries
|
|
5
5
|
Home-page: https://github.com/astronomy-commons/adc-streaming
|
|
6
6
|
Author: Astronomy Data Commons Team
|
|
@@ -12,8 +12,35 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
12
12
|
Classifier: Operating System :: POSIX :: Linux
|
|
13
13
|
Classifier: Operating System :: MacOS :: MacOS X
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
|
-
Provides-Extra: dev
|
|
16
15
|
License-File: LICENSE
|
|
16
|
+
Requires-Dist: confluent-kafka>=2.11.0
|
|
17
|
+
Requires-Dist: dataclasses; python_version < "3.7"
|
|
18
|
+
Requires-Dist: importlib-metadata; python_version < "3.8"
|
|
19
|
+
Requires-Dist: tqdm
|
|
20
|
+
Requires-Dist: certifi>=2020.04.05.1
|
|
21
|
+
Requires-Dist: typing-extensions; python_version < "3.8"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: autopep8; extra == "dev"
|
|
24
|
+
Requires-Dist: docker; extra == "dev"
|
|
25
|
+
Requires-Dist: flake8; extra == "dev"
|
|
26
|
+
Requires-Dist: isort; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-timeout; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-integration; extra == "dev"
|
|
30
|
+
Requires-Dist: sphinx; extra == "dev"
|
|
31
|
+
Requires-Dist: sphinx_rtd_theme; extra == "dev"
|
|
32
|
+
Requires-Dist: twine; extra == "dev"
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: author-email
|
|
35
|
+
Dynamic: classifier
|
|
36
|
+
Dynamic: description
|
|
37
|
+
Dynamic: description-content-type
|
|
38
|
+
Dynamic: home-page
|
|
39
|
+
Dynamic: license
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
Dynamic: provides-extra
|
|
42
|
+
Dynamic: requires-dist
|
|
43
|
+
Dynamic: summary
|
|
17
44
|
|
|
18
45
|
# Astronomy Data Commons Streaming Client Libraries
|
|
19
46
|
|
|
@@ -64,6 +64,7 @@ class SASLAuth(object):
|
|
|
64
64
|
self._config = {
|
|
65
65
|
"security.protocol": "SASL_SSL",
|
|
66
66
|
"ssl.ca.location": ssl_cert,
|
|
67
|
+
"https.ca.location": ssl_cert,
|
|
67
68
|
}
|
|
68
69
|
if "ssl_endpoint_identification_algorithm" in kwargs:
|
|
69
70
|
self._config["ssl.endpoint.identification.algorithm"] = \
|
|
@@ -14,7 +14,6 @@ import confluent_kafka.admin # type: ignore
|
|
|
14
14
|
|
|
15
15
|
from .auth import SASLAuth
|
|
16
16
|
from .errors import ErrorCallback, log_client_errors
|
|
17
|
-
from .oidc import set_oauth_cb
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
class LogicalOffset(enum.IntEnum):
|
|
@@ -360,5 +359,4 @@ class ConsumerConfig:
|
|
|
360
359
|
|
|
361
360
|
if self.auth is not None:
|
|
362
361
|
config.update(self.auth())
|
|
363
|
-
set_oauth_cb(config)
|
|
364
362
|
return config
|
|
@@ -14,7 +14,6 @@ import confluent_kafka # type: ignore
|
|
|
14
14
|
from .auth import SASLAuth
|
|
15
15
|
from .errors import (DeliveryCallback, ErrorCallback, log_client_errors,
|
|
16
16
|
log_delivery_errors)
|
|
17
|
-
from .oidc import set_oauth_cb
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
class Producer:
|
|
@@ -48,11 +47,26 @@ class Producer:
|
|
|
48
47
|
"Either configure a topic when constructing the Producer, "
|
|
49
48
|
"or specify the topic argument to write()")
|
|
50
49
|
self.logger.debug("writing message to %s", topic)
|
|
50
|
+
produce_kwargs = {"headers": headers, "key": key}
|
|
51
51
|
if delivery_callback is not None:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
produce_kwargs["on_delivery"] = delivery_callback
|
|
53
|
+
while True:
|
|
54
|
+
try:
|
|
55
|
+
self._producer.produce(topic, msg, **produce_kwargs)
|
|
56
|
+
break
|
|
57
|
+
except BufferError:
|
|
58
|
+
# It's hard to know what the size limit on the buffer is, so we will try to
|
|
59
|
+
# wait until the number of items in it decreases (or it is empty, in case all
|
|
60
|
+
# messages were successfully sent in the time it took to handle the error).
|
|
61
|
+
buffer_len = len(self._producer)
|
|
62
|
+
self.logger.debug(f"Blocking due to BufferError, buffer size: {buffer_len}")
|
|
63
|
+
while buffer_len > 0 and len(self._producer) == buffer_len:
|
|
64
|
+
self._producer.poll(0.01)
|
|
65
|
+
|
|
66
|
+
def queued_message_count(self):
|
|
67
|
+
"""Get the number of messages waiting to be sent to the broker.
|
|
68
|
+
"""
|
|
69
|
+
return len(self._producer)
|
|
56
70
|
|
|
57
71
|
def flush(self, timeout: timedelta = timedelta(seconds=10)) -> int:
|
|
58
72
|
"""Attempt to flush enqueued messages. Return the number of messages still
|
|
@@ -66,9 +80,9 @@ class Producer:
|
|
|
66
80
|
self.logger.debug("flushed all messages")
|
|
67
81
|
return n
|
|
68
82
|
|
|
69
|
-
def close(self) -> int:
|
|
83
|
+
def close(self, timeout: timedelta = timedelta(seconds=10)) -> int:
|
|
70
84
|
self.logger.debug("shutting down producer")
|
|
71
|
-
return self.flush()
|
|
85
|
+
return self.flush(timeout)
|
|
72
86
|
|
|
73
87
|
def __enter__(self) -> 'Producer':
|
|
74
88
|
return self
|
|
@@ -142,7 +156,6 @@ class ProducerConfig:
|
|
|
142
156
|
config["error_cb"] = self.error_callback
|
|
143
157
|
if self.auth is not None:
|
|
144
158
|
config.update(self.auth())
|
|
145
|
-
set_oauth_cb(config)
|
|
146
159
|
return config
|
|
147
160
|
|
|
148
161
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: adc-streaming
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: Astronomy Data Commons streaming client libraries
|
|
5
5
|
Home-page: https://github.com/astronomy-commons/adc-streaming
|
|
6
6
|
Author: Astronomy Data Commons Team
|
|
@@ -12,8 +12,35 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
12
12
|
Classifier: Operating System :: POSIX :: Linux
|
|
13
13
|
Classifier: Operating System :: MacOS :: MacOS X
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
|
-
Provides-Extra: dev
|
|
16
15
|
License-File: LICENSE
|
|
16
|
+
Requires-Dist: confluent-kafka>=2.11.0
|
|
17
|
+
Requires-Dist: dataclasses; python_version < "3.7"
|
|
18
|
+
Requires-Dist: importlib-metadata; python_version < "3.8"
|
|
19
|
+
Requires-Dist: tqdm
|
|
20
|
+
Requires-Dist: certifi>=2020.04.05.1
|
|
21
|
+
Requires-Dist: typing-extensions; python_version < "3.8"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: autopep8; extra == "dev"
|
|
24
|
+
Requires-Dist: docker; extra == "dev"
|
|
25
|
+
Requires-Dist: flake8; extra == "dev"
|
|
26
|
+
Requires-Dist: isort; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-timeout; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-integration; extra == "dev"
|
|
30
|
+
Requires-Dist: sphinx; extra == "dev"
|
|
31
|
+
Requires-Dist: sphinx_rtd_theme; extra == "dev"
|
|
32
|
+
Requires-Dist: twine; extra == "dev"
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: author-email
|
|
35
|
+
Dynamic: classifier
|
|
36
|
+
Dynamic: description
|
|
37
|
+
Dynamic: description-content-type
|
|
38
|
+
Dynamic: home-page
|
|
39
|
+
Dynamic: license
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
Dynamic: provides-extra
|
|
42
|
+
Dynamic: requires-dist
|
|
43
|
+
Dynamic: summary
|
|
17
44
|
|
|
18
45
|
# Astronomy Data Commons Streaming Client Libraries
|
|
19
46
|
|
|
@@ -3,11 +3,9 @@ from setuptools import setup
|
|
|
3
3
|
|
|
4
4
|
# requirements
|
|
5
5
|
install_requires = [
|
|
6
|
-
"
|
|
7
|
-
"confluent-kafka >= 1.6.1, != 2.1.0, != 2.1.1",
|
|
6
|
+
"confluent-kafka >= 2.11.0",
|
|
8
7
|
"dataclasses ; python_version < '3.7'",
|
|
9
8
|
"importlib-metadata ; python_version < '3.8'",
|
|
10
|
-
"requests", # FIXME: drop after next release of confluent-kafka with OIDC support
|
|
11
9
|
"tqdm",
|
|
12
10
|
"certifi>=2020.04.05.1",
|
|
13
11
|
"typing-extensions ; python_version < '3.8'",
|
|
@@ -84,6 +84,44 @@ class KafkaIntegrationTestCase(unittest.TestCase):
|
|
|
84
84
|
self.assertEqual(msg.value(), b"can you hear me?")
|
|
85
85
|
self.assertEqual(msg.key(), b"test_msg")
|
|
86
86
|
|
|
87
|
+
def test_message_with_callback(self):
|
|
88
|
+
"""Try writing a message into the Kafka broker, with a delivery callback.
|
|
89
|
+
"""
|
|
90
|
+
topic = "test_message_with_callback"
|
|
91
|
+
callback_called = False
|
|
92
|
+
callback_error = False
|
|
93
|
+
|
|
94
|
+
def callback(err, msg):
|
|
95
|
+
nonlocal callback_called
|
|
96
|
+
nonlocal callback_error
|
|
97
|
+
callback_called = True
|
|
98
|
+
callback_error = err is not None
|
|
99
|
+
|
|
100
|
+
ap = adc.producer
|
|
101
|
+
with ap.Producer(ap.ProducerConfig(broker_urls=[self.kafka.address],
|
|
102
|
+
topic=topic, auth=self.kafka.auth)) as producer:
|
|
103
|
+
producer.write("message data", delivery_callback=callback)
|
|
104
|
+
producer.flush()
|
|
105
|
+
consumer = adc.consumer.Consumer(adc.consumer.ConsumerConfig(
|
|
106
|
+
broker_urls=[self.kafka.address],
|
|
107
|
+
group_id="test_consumer",
|
|
108
|
+
auth=self.kafka.auth,
|
|
109
|
+
))
|
|
110
|
+
consumer.subscribe(topic)
|
|
111
|
+
stream = consumer.stream()
|
|
112
|
+
|
|
113
|
+
msg = next(stream)
|
|
114
|
+
if msg.error() is not None:
|
|
115
|
+
raise Exception(msg.error())
|
|
116
|
+
# give the producer's background thread time to notice the ack from the broker and
|
|
117
|
+
# fire the callback
|
|
118
|
+
producer._producer.poll(0.1)
|
|
119
|
+
|
|
120
|
+
self.assertEqual(msg.topic(), topic)
|
|
121
|
+
self.assertEqual(msg.value(), b"message data")
|
|
122
|
+
self.assertEqual(callback_called, True)
|
|
123
|
+
self.assertEqual(callback_error, False)
|
|
124
|
+
|
|
87
125
|
def test_reset_to_end(self):
|
|
88
126
|
# Write a few messages.
|
|
89
127
|
topic = "test_reset_to_end"
|
|
@@ -486,7 +524,7 @@ class KafkaDockerConnection:
|
|
|
486
524
|
"""Block until the Docker daemon tells us the IP and Port of the Kafa broker.
|
|
487
525
|
|
|
488
526
|
Returns the ip and port as a string in the form "ip:port."
|
|
489
|
-
"""
|
|
527
|
+
"""
|
|
490
528
|
i = 0
|
|
491
529
|
while (not self.query_kafka_broker_address()) and i < maxiter:
|
|
492
530
|
logger.info("polling to wait for container to acquire port...")
|
|
@@ -508,7 +546,7 @@ class KafkaDockerConnection:
|
|
|
508
546
|
port = addrs[0]['HostPort']
|
|
509
547
|
return f"{ip}:{port}"
|
|
510
548
|
|
|
511
|
-
def poll_for_kafka_active(self, maxiter=
|
|
549
|
+
def poll_for_kafka_active(self, maxiter=40, sleep=timedelta(milliseconds=500)):
|
|
512
550
|
"""Block until Kafka's network listener is accepting connections."""
|
|
513
551
|
i = 0
|
|
514
552
|
while (not self.query_kafka_active()) and i < maxiter:
|
adc-streaming-2.5.0/adc/oidc.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
def set_oauth_cb(config):
|
|
2
|
-
"""Implement client support for KIP-768 OpenID Connect.
|
|
3
|
-
|
|
4
|
-
Apache Kafka 3.1.0 supports authentication using OpenID Client Credentials.
|
|
5
|
-
Native support for Python is still incomplete due to this issue:
|
|
6
|
-
https://github.com/confluentinc/librdkafka/issues/3751
|
|
7
|
-
|
|
8
|
-
Meanwhile, this is a pure Python implementation of the refresh token
|
|
9
|
-
callback.
|
|
10
|
-
"""
|
|
11
|
-
if config.pop('sasl.oauthbearer.method', None) != 'oidc':
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
client_id = config.pop('sasl.oauthbearer.client.id')
|
|
15
|
-
client_secret = config.pop('sasl.oauthbearer.client.secret')
|
|
16
|
-
scope = config.pop('sasl.oauthbearer.scope', None)
|
|
17
|
-
token_endpoint = config.pop('sasl.oauthbearer.token.endpoint.url')
|
|
18
|
-
|
|
19
|
-
from authlib.integrations.requests_client import OAuth2Session
|
|
20
|
-
session = OAuth2Session(client_id, client_secret, scope=scope)
|
|
21
|
-
|
|
22
|
-
def oauth_cb(*_, **__):
|
|
23
|
-
token = session.fetch_token(
|
|
24
|
-
token_endpoint, grant_type='client_credentials')
|
|
25
|
-
return token['access_token'], token['expires_at']
|
|
26
|
-
|
|
27
|
-
config['oauth_cb'] = oauth_cb
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|