cledar-sdk 2.0.2__py3-none-any.whl → 2.1.0__py3-none-any.whl
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.
- cledar/__init__.py +1 -0
- cledar/kafka/README.md +239 -0
- cledar/kafka/__init__.py +42 -0
- cledar/kafka/clients/base.py +117 -0
- cledar/kafka/clients/consumer.py +138 -0
- cledar/kafka/clients/producer.py +97 -0
- cledar/kafka/config/schemas.py +262 -0
- cledar/kafka/exceptions.py +17 -0
- cledar/kafka/handlers/dead_letter.py +88 -0
- cledar/kafka/handlers/parser.py +83 -0
- cledar/kafka/logger.py +5 -0
- cledar/kafka/models/input.py +17 -0
- cledar/kafka/models/message.py +14 -0
- cledar/kafka/models/output.py +12 -0
- cledar/kafka/tests/.env.test.kafka +3 -0
- cledar/kafka/tests/README.md +216 -0
- cledar/kafka/tests/conftest.py +104 -0
- cledar/kafka/tests/integration/__init__.py +1 -0
- cledar/kafka/tests/integration/conftest.py +78 -0
- cledar/kafka/tests/integration/helpers.py +47 -0
- cledar/kafka/tests/integration/test_consumer_integration.py +375 -0
- cledar/kafka/tests/integration/test_integration.py +394 -0
- cledar/kafka/tests/integration/test_producer_consumer_interaction.py +388 -0
- cledar/kafka/tests/integration/test_producer_integration.py +217 -0
- cledar/kafka/tests/unit/__init__.py +1 -0
- cledar/kafka/tests/unit/test_base_kafka_client.py +391 -0
- cledar/kafka/tests/unit/test_config_validation.py +609 -0
- cledar/kafka/tests/unit/test_dead_letter_handler.py +443 -0
- cledar/kafka/tests/unit/test_error_handling.py +674 -0
- cledar/kafka/tests/unit/test_input_parser.py +310 -0
- cledar/kafka/tests/unit/test_input_parser_comprehensive.py +489 -0
- cledar/kafka/tests/unit/test_utils.py +25 -0
- cledar/kafka/tests/unit/test_utils_comprehensive.py +408 -0
- cledar/kafka/utils/callbacks.py +28 -0
- cledar/kafka/utils/messages.py +39 -0
- cledar/kafka/utils/topics.py +15 -0
- cledar/kserve/README.md +352 -0
- cledar/kserve/__init__.py +5 -0
- cledar/kserve/tests/__init__.py +0 -0
- cledar/kserve/tests/test_utils.py +64 -0
- cledar/kserve/utils.py +30 -0
- cledar/logging/README.md +53 -0
- cledar/logging/__init__.py +5 -0
- cledar/logging/tests/test_universal_plaintext_formatter.py +249 -0
- cledar/logging/universal_plaintext_formatter.py +99 -0
- cledar/monitoring/README.md +71 -0
- cledar/monitoring/__init__.py +5 -0
- cledar/monitoring/monitoring_server.py +156 -0
- cledar/monitoring/tests/integration/test_monitoring_server_int.py +162 -0
- cledar/monitoring/tests/test_monitoring_server.py +59 -0
- cledar/nonce/README.md +99 -0
- cledar/nonce/__init__.py +5 -0
- cledar/nonce/nonce_service.py +62 -0
- cledar/nonce/tests/__init__.py +0 -0
- cledar/nonce/tests/test_nonce_service.py +136 -0
- cledar/redis/README.md +536 -0
- cledar/redis/__init__.py +17 -0
- cledar/redis/async_example.py +112 -0
- cledar/redis/example.py +67 -0
- cledar/redis/exceptions.py +25 -0
- cledar/redis/logger.py +5 -0
- cledar/redis/model.py +14 -0
- cledar/redis/redis.py +764 -0
- cledar/redis/redis_config_store.py +333 -0
- cledar/redis/tests/test_async_integration_redis.py +158 -0
- cledar/redis/tests/test_async_redis_service.py +380 -0
- cledar/redis/tests/test_integration_redis.py +119 -0
- cledar/redis/tests/test_redis_service.py +319 -0
- cledar/storage/README.md +529 -0
- cledar/storage/__init__.py +6 -0
- cledar/storage/constants.py +5 -0
- cledar/storage/exceptions.py +79 -0
- cledar/storage/models.py +41 -0
- cledar/storage/object_storage.py +1274 -0
- cledar/storage/tests/conftest.py +18 -0
- cledar/storage/tests/test_abfs.py +164 -0
- cledar/storage/tests/test_integration_filesystem.py +359 -0
- cledar/storage/tests/test_integration_s3.py +453 -0
- cledar/storage/tests/test_local.py +384 -0
- cledar/storage/tests/test_s3.py +521 -0
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.1.0.dist-info}/METADATA +1 -1
- cledar_sdk-2.1.0.dist-info/RECORD +84 -0
- cledar_sdk-2.0.2.dist-info/RECORD +0 -4
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.1.0.dist-info}/WHEEL +0 -0
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import Any, TypeVar
|
|
3
|
+
|
|
4
|
+
import pydantic
|
|
5
|
+
import pytest
|
|
6
|
+
from faker import Faker
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from cledar.kafka.handlers.parser import (
|
|
10
|
+
IncorrectMessageValueError,
|
|
11
|
+
InputParser,
|
|
12
|
+
)
|
|
13
|
+
from cledar.kafka.models.message import KafkaMessage
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pydantic.dataclasses.dataclass
|
|
17
|
+
class S3Metadata:
|
|
18
|
+
url: str
|
|
19
|
+
type: str | None
|
|
20
|
+
container: str | None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pydantic.dataclasses.dataclass
|
|
24
|
+
class ContentNetwork:
|
|
25
|
+
id: str | None
|
|
26
|
+
name: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pydantic.dataclasses.dataclass
|
|
30
|
+
class ContentDistributor:
|
|
31
|
+
id: str | None
|
|
32
|
+
name: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pydantic.dataclasses.dataclass
|
|
36
|
+
class ContentMetadata:
|
|
37
|
+
id: str | None
|
|
38
|
+
medium: str
|
|
39
|
+
type: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pydantic.dataclasses.dataclass
|
|
43
|
+
class ContentStream:
|
|
44
|
+
id: str
|
|
45
|
+
type: str | None
|
|
46
|
+
index: int | None
|
|
47
|
+
codec_name: str | None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pydantic.dataclasses.dataclass
|
|
51
|
+
class AudioStreamInfo(ContentStream):
|
|
52
|
+
language: str | None
|
|
53
|
+
format: str | None
|
|
54
|
+
channels: int | None
|
|
55
|
+
channels_layout: str | None
|
|
56
|
+
sample_rate: int | None
|
|
57
|
+
bit_rate: int | None
|
|
58
|
+
frame_size: int | None
|
|
59
|
+
delay: int | None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pydantic.dataclasses.dataclass
|
|
63
|
+
class VideoStreamInfo(ContentStream):
|
|
64
|
+
format: str | None
|
|
65
|
+
width: int | None
|
|
66
|
+
height: int | None
|
|
67
|
+
display_aspect_ratio: str | None
|
|
68
|
+
pixel_aspect_ratio: str | None
|
|
69
|
+
pixel_format: str | None
|
|
70
|
+
framerate: str | None
|
|
71
|
+
delay: int | None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pydantic.dataclasses.dataclass
|
|
75
|
+
class SubtitleStreamInfo(ContentStream):
|
|
76
|
+
language: str | None
|
|
77
|
+
delay: int | None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@pydantic.dataclasses.dataclass
|
|
81
|
+
class ContentOrigin:
|
|
82
|
+
id: str
|
|
83
|
+
name: str
|
|
84
|
+
url: str
|
|
85
|
+
mode: str
|
|
86
|
+
target: str
|
|
87
|
+
|
|
88
|
+
network: ContentNetwork
|
|
89
|
+
distributor: ContentDistributor
|
|
90
|
+
metadata: ContentMetadata
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pydantic.dataclasses.dataclass
|
|
94
|
+
class PipelineMessageReference:
|
|
95
|
+
id: str
|
|
96
|
+
created_at: str
|
|
97
|
+
created_by: str
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
StreamInfo = AudioStreamInfo | VideoStreamInfo | SubtitleStreamInfo
|
|
101
|
+
PipelineMessageData = TypeVar("PipelineMessageData", bound=BaseModel)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pydantic.dataclasses.dataclass
|
|
105
|
+
class PipelineMessageDataContainer[PipelineMessageData: BaseModel]:
|
|
106
|
+
data: dict[str, Any] | PipelineMessageData
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pydantic.dataclasses.dataclass
|
|
110
|
+
class PipelineStageReference[PipelineMessageData: BaseModel](
|
|
111
|
+
PipelineMessageReference,
|
|
112
|
+
PipelineMessageDataContainer[PipelineMessageData],
|
|
113
|
+
):
|
|
114
|
+
output_topic_name: str
|
|
115
|
+
parent_ids: list[str]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pydantic.dataclasses.dataclass
|
|
119
|
+
class PipelineMessageMetadata[PipelineMessageData: BaseModel]:
|
|
120
|
+
origin: ContentOrigin
|
|
121
|
+
streams: list[StreamInfo]
|
|
122
|
+
pipeline_stages: list[PipelineStageReference[PipelineMessageData]]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@pydantic.dataclasses.dataclass
|
|
126
|
+
class PipelineMessage[PipelineMessageData: BaseModel](
|
|
127
|
+
PipelineMessageReference,
|
|
128
|
+
PipelineMessageDataContainer[PipelineMessageData],
|
|
129
|
+
):
|
|
130
|
+
metadata: PipelineMessageMetadata[PipelineMessageData]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class InputMessageData(BaseModel):
|
|
134
|
+
chunks: Sequence[S3Metadata]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class InputMessagePayload(BaseModel, PipelineMessage[InputMessageData]):
|
|
138
|
+
data: InputMessageData
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
fake = Faker()
|
|
142
|
+
|
|
143
|
+
input_json_value = '{"data":{"chunks":[{"url":"s3://reference-transformed-chunks/video/0285ee22c95f43f9a98792d210aa6813_stream_0.mp4","type":"video","container":"mpegts"},{"url":"s3://reference-transformed-chunks/audio/0285ee22c95f43f9a98792d210aa6813_stream_1.mp4","type":"audio","container":"mpegts"},{"url":"s3://reference-transformed-chunks/audio/0285ee22c95f43f9a98792d210aa6813_stream_2.mp4","type":"audio","container":"mpegts"},{"url":"s3://reference-transformed-chunks/audio/0285ee22c95f43f9a98792d210aa6813_stream_6.mp4","type":"audio","container":"mpegts"}]},"id":"78c7392659984c2d89057bd628275855","created_at":"2024-08-30T11:09:56Z","created_by":"chunk-transformer","metadata":{"origin":{"id":"dbd5f857572e49bfb8dd678dd14a4b85","name":"TVP_1","url":"https://example.com","mode":"broadcast","target":"television","network":{"id":null,"name":"korbank"},"distributor":{"id":null,"name":"TVP_1"},"metadata":{"id":null,"medium":"video","type":"channel"}},"streams":[{"id":"2cad60eac39f4cb4a676eb001350c3c3","type":"video","index":0,"codec_name":"h264","format":"yuv420p","width":1920,"height":1080,"display_aspect_ratio":"16:9","pixel_aspect_ratio":"1:1","pixel_format":"yuv420p","framerate":"25:1","delay":0},{"id":"9a8c7216c1e24f9eb2152c52ac3feb50","type":"audio","index":1,"codec_name":"mp2","language":"pol","format":"s16p","channels":2,"channels_layout":"stereo","sample_rate":48000,"bit_rate":192000,"frame_size":1152,"delay":0},{"id":"50bc1f29f4b84b28a92aa263567dd9ca","type":"audio","index":2,"codec_name":"ac3","language":"qaa","format":"fltp","channels":6,"channels_layout":"5.1(side)","sample_rate":48000,"bit_rate":384000,"frame_size":0,"delay":0},{"id":"eb2f0753258b4d0fbf77af82ac7cff59","type":"subtitle","index":4,"codec_name":"dvbsub","language":"pol","delay":0},{"id":"dc57b3f8fdb04420bc820fe1d45985b6","type":"audio","index":6,"codec_name":"mp2","language":"aux","format":"s16p","channels":2,"channels_layout":"stereo","sample_rate":48000,"bit_rate":128000,"frame_size":1152,"delay":0}],"pipeline_stages":[{"data":{},"id":"c577dd6d2be743d5a155ef75d6d1ec79","created_at":"2024-08-30T11:09:55Z","created_by":"stream-chunker","output_topic_name":"reference-chunks","parent_ids":[]},{"data":{"id":"f11a8990bfff4288b9b284cb9908c788","created_at":"2024-08-30T11:09:55Z","created_by":"stream-chunker","s3_key":"0285ee22c95f43f9a98792d210aa6813.mpegts","s3_bucket":"reference-chunks","chunk_duration":5,"chunk_overlap_sec":0,"chunk_size":5179024,"ignored_streams":[3,5,7]},"id":"78c7392659984c2d89057bd628275855","created_at":"2024-08-30T11:09:56Z","created_by":"chunk-transformer","output_topic_name":"reference-transformed-chunks","parent_ids":[]}]}}'
|
|
144
|
+
|
|
145
|
+
expected = InputMessagePayload( # type: ignore
|
|
146
|
+
id="78c7392659984c2d89057bd628275855",
|
|
147
|
+
created_at="2024-08-30T11:09:56Z",
|
|
148
|
+
created_by="chunk-transformer",
|
|
149
|
+
data=InputMessageData(
|
|
150
|
+
chunks=[
|
|
151
|
+
S3Metadata(
|
|
152
|
+
url="s3://reference-transformed-chunks/video/0285ee22c95f43f9a98792d210aa6813_stream_0.mp4",
|
|
153
|
+
type="video",
|
|
154
|
+
container="mpegts",
|
|
155
|
+
),
|
|
156
|
+
S3Metadata(
|
|
157
|
+
url="s3://reference-transformed-chunks/audio/0285ee22c95f43f9a98792d210aa6813_stream_1.mp4",
|
|
158
|
+
type="audio",
|
|
159
|
+
container="mpegts",
|
|
160
|
+
),
|
|
161
|
+
S3Metadata(
|
|
162
|
+
url="s3://reference-transformed-chunks/audio/0285ee22c95f43f9a98792d210aa6813_stream_2.mp4",
|
|
163
|
+
type="audio",
|
|
164
|
+
container="mpegts",
|
|
165
|
+
),
|
|
166
|
+
S3Metadata(
|
|
167
|
+
url="s3://reference-transformed-chunks/audio/0285ee22c95f43f9a98792d210aa6813_stream_6.mp4",
|
|
168
|
+
type="audio",
|
|
169
|
+
container="mpegts",
|
|
170
|
+
),
|
|
171
|
+
],
|
|
172
|
+
),
|
|
173
|
+
metadata=PipelineMessageMetadata(
|
|
174
|
+
origin=ContentOrigin(
|
|
175
|
+
id="dbd5f857572e49bfb8dd678dd14a4b85",
|
|
176
|
+
name="TVP_1",
|
|
177
|
+
url="https://example.com",
|
|
178
|
+
mode="broadcast",
|
|
179
|
+
target="television",
|
|
180
|
+
network=ContentNetwork(id=None, name="korbank"),
|
|
181
|
+
distributor=ContentDistributor(id=None, name="TVP_1"),
|
|
182
|
+
metadata=ContentMetadata(id=None, medium="video", type="channel"),
|
|
183
|
+
),
|
|
184
|
+
streams=[
|
|
185
|
+
VideoStreamInfo(
|
|
186
|
+
id="2cad60eac39f4cb4a676eb001350c3c3",
|
|
187
|
+
type="video",
|
|
188
|
+
index=0,
|
|
189
|
+
codec_name="h264",
|
|
190
|
+
format="yuv420p",
|
|
191
|
+
width=1920,
|
|
192
|
+
height=1080,
|
|
193
|
+
display_aspect_ratio="16:9",
|
|
194
|
+
pixel_aspect_ratio="1:1",
|
|
195
|
+
pixel_format="yuv420p",
|
|
196
|
+
framerate="25:1",
|
|
197
|
+
delay=0,
|
|
198
|
+
),
|
|
199
|
+
AudioStreamInfo(
|
|
200
|
+
id="9a8c7216c1e24f9eb2152c52ac3feb50",
|
|
201
|
+
type="audio",
|
|
202
|
+
index=1,
|
|
203
|
+
codec_name="mp2",
|
|
204
|
+
language="pol",
|
|
205
|
+
format="s16p",
|
|
206
|
+
channels=2,
|
|
207
|
+
channels_layout="stereo",
|
|
208
|
+
sample_rate=48000,
|
|
209
|
+
bit_rate=192000,
|
|
210
|
+
frame_size=1152,
|
|
211
|
+
delay=0,
|
|
212
|
+
),
|
|
213
|
+
AudioStreamInfo(
|
|
214
|
+
id="50bc1f29f4b84b28a92aa263567dd9ca",
|
|
215
|
+
type="audio",
|
|
216
|
+
index=2,
|
|
217
|
+
codec_name="ac3",
|
|
218
|
+
language="qaa",
|
|
219
|
+
format="fltp",
|
|
220
|
+
channels=6,
|
|
221
|
+
channels_layout="5.1(side)",
|
|
222
|
+
sample_rate=48000,
|
|
223
|
+
bit_rate=384000,
|
|
224
|
+
frame_size=0,
|
|
225
|
+
delay=0,
|
|
226
|
+
),
|
|
227
|
+
SubtitleStreamInfo(
|
|
228
|
+
id="eb2f0753258b4d0fbf77af82ac7cff59",
|
|
229
|
+
type="subtitle",
|
|
230
|
+
index=4,
|
|
231
|
+
codec_name="dvbsub",
|
|
232
|
+
language="pol",
|
|
233
|
+
delay=0,
|
|
234
|
+
),
|
|
235
|
+
AudioStreamInfo(
|
|
236
|
+
id="dc57b3f8fdb04420bc820fe1d45985b6",
|
|
237
|
+
type="audio",
|
|
238
|
+
index=6,
|
|
239
|
+
codec_name="mp2",
|
|
240
|
+
language="aux",
|
|
241
|
+
format="s16p",
|
|
242
|
+
channels=2,
|
|
243
|
+
channels_layout="stereo",
|
|
244
|
+
sample_rate=48000,
|
|
245
|
+
bit_rate=128000,
|
|
246
|
+
frame_size=1152,
|
|
247
|
+
delay=0,
|
|
248
|
+
),
|
|
249
|
+
],
|
|
250
|
+
pipeline_stages=[
|
|
251
|
+
PipelineStageReference(
|
|
252
|
+
id="c577dd6d2be743d5a155ef75d6d1ec79",
|
|
253
|
+
created_at="2024-08-30T11:09:55Z",
|
|
254
|
+
created_by="stream-chunker",
|
|
255
|
+
output_topic_name="reference-chunks",
|
|
256
|
+
parent_ids=[],
|
|
257
|
+
data={},
|
|
258
|
+
),
|
|
259
|
+
PipelineStageReference(
|
|
260
|
+
id="78c7392659984c2d89057bd628275855",
|
|
261
|
+
created_at="2024-08-30T11:09:56Z",
|
|
262
|
+
created_by="chunk-transformer",
|
|
263
|
+
parent_ids=[],
|
|
264
|
+
output_topic_name="reference-transformed-chunks",
|
|
265
|
+
data={
|
|
266
|
+
"id": "f11a8990bfff4288b9b284cb9908c788",
|
|
267
|
+
"created_at": "2024-08-30T11:09:55Z",
|
|
268
|
+
"created_by": "stream-chunker",
|
|
269
|
+
"s3_key": "0285ee22c95f43f9a98792d210aa6813.mpegts",
|
|
270
|
+
"s3_bucket": "reference-chunks",
|
|
271
|
+
"chunk_duration": 5,
|
|
272
|
+
"chunk_overlap_sec": 0,
|
|
273
|
+
"chunk_size": 5179024,
|
|
274
|
+
"ignored_streams": [3, 5, 7],
|
|
275
|
+
},
|
|
276
|
+
),
|
|
277
|
+
],
|
|
278
|
+
),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def test_parse_1() -> None:
|
|
283
|
+
parser = InputParser(InputMessagePayload)
|
|
284
|
+
obj = parser.parse_json(input_json_value)
|
|
285
|
+
|
|
286
|
+
assert obj.data == expected.data
|
|
287
|
+
assert obj.data.chunks == expected.data.chunks
|
|
288
|
+
assert obj.id == expected.id
|
|
289
|
+
assert obj.created_at == expected.created_at
|
|
290
|
+
assert obj.created_by == expected.created_by
|
|
291
|
+
assert obj.metadata.origin == expected.metadata.origin
|
|
292
|
+
assert obj.metadata.pipeline_stages == expected.metadata.pipeline_stages
|
|
293
|
+
assert obj.metadata.streams == expected.metadata.streams
|
|
294
|
+
assert obj == expected
|
|
295
|
+
assert obj.model_dump_json() == input_json_value
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def test_parse_mesage_raise() -> None:
|
|
299
|
+
parser = InputParser(InputMessagePayload)
|
|
300
|
+
|
|
301
|
+
with pytest.raises(IncorrectMessageValueError):
|
|
302
|
+
parser.parse_message(
|
|
303
|
+
KafkaMessage(
|
|
304
|
+
value=None,
|
|
305
|
+
key=fake.text(),
|
|
306
|
+
topic=fake.text(),
|
|
307
|
+
offset=fake.random_int(),
|
|
308
|
+
partition=fake.random_int(),
|
|
309
|
+
)
|
|
310
|
+
)
|