nv-ingest-api 2025.4.17.dev20250417__py3-none-any.whl → 2025.4.19.dev20250419__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.

Potentially problematic release.


This version of nv-ingest-api might be problematic. Click here for more details.

Files changed (153) hide show
  1. nv_ingest_api/__init__.py +0 -3
  2. nv_ingest_api/{internal/primitives → primitives}/control_message_task.py +0 -4
  3. nv_ingest_api/{internal/primitives → primitives}/ingest_control_message.py +2 -5
  4. {nv_ingest_api-2025.4.17.dev20250417.dist-info → nv_ingest_api-2025.4.19.dev20250419.dist-info}/METADATA +1 -1
  5. nv_ingest_api-2025.4.19.dev20250419.dist-info/RECORD +9 -0
  6. {nv_ingest_api-2025.4.17.dev20250417.dist-info → nv_ingest_api-2025.4.19.dev20250419.dist-info}/WHEEL +1 -1
  7. nv_ingest_api/interface/__init__.py +0 -215
  8. nv_ingest_api/interface/extract.py +0 -972
  9. nv_ingest_api/interface/mutate.py +0 -154
  10. nv_ingest_api/interface/store.py +0 -218
  11. nv_ingest_api/interface/transform.py +0 -382
  12. nv_ingest_api/interface/utility.py +0 -200
  13. nv_ingest_api/internal/enums/__init__.py +0 -3
  14. nv_ingest_api/internal/enums/common.py +0 -494
  15. nv_ingest_api/internal/extract/__init__.py +0 -3
  16. nv_ingest_api/internal/extract/audio/__init__.py +0 -3
  17. nv_ingest_api/internal/extract/audio/audio_extraction.py +0 -149
  18. nv_ingest_api/internal/extract/docx/__init__.py +0 -5
  19. nv_ingest_api/internal/extract/docx/docx_extractor.py +0 -205
  20. nv_ingest_api/internal/extract/docx/engines/__init__.py +0 -0
  21. nv_ingest_api/internal/extract/docx/engines/docxreader_helpers/__init__.py +0 -3
  22. nv_ingest_api/internal/extract/docx/engines/docxreader_helpers/docx_helper.py +0 -122
  23. nv_ingest_api/internal/extract/docx/engines/docxreader_helpers/docxreader.py +0 -895
  24. nv_ingest_api/internal/extract/image/__init__.py +0 -3
  25. nv_ingest_api/internal/extract/image/chart_extractor.py +0 -353
  26. nv_ingest_api/internal/extract/image/image_extractor.py +0 -204
  27. nv_ingest_api/internal/extract/image/image_helpers/__init__.py +0 -3
  28. nv_ingest_api/internal/extract/image/image_helpers/common.py +0 -403
  29. nv_ingest_api/internal/extract/image/infographic_extractor.py +0 -253
  30. nv_ingest_api/internal/extract/image/table_extractor.py +0 -344
  31. nv_ingest_api/internal/extract/pdf/__init__.py +0 -3
  32. nv_ingest_api/internal/extract/pdf/engines/__init__.py +0 -19
  33. nv_ingest_api/internal/extract/pdf/engines/adobe.py +0 -484
  34. nv_ingest_api/internal/extract/pdf/engines/llama.py +0 -243
  35. nv_ingest_api/internal/extract/pdf/engines/nemoretriever.py +0 -597
  36. nv_ingest_api/internal/extract/pdf/engines/pdf_helpers/__init__.py +0 -146
  37. nv_ingest_api/internal/extract/pdf/engines/pdfium.py +0 -603
  38. nv_ingest_api/internal/extract/pdf/engines/tika.py +0 -96
  39. nv_ingest_api/internal/extract/pdf/engines/unstructured_io.py +0 -426
  40. nv_ingest_api/internal/extract/pdf/pdf_extractor.py +0 -74
  41. nv_ingest_api/internal/extract/pptx/__init__.py +0 -5
  42. nv_ingest_api/internal/extract/pptx/engines/__init__.py +0 -0
  43. nv_ingest_api/internal/extract/pptx/engines/pptx_helper.py +0 -799
  44. nv_ingest_api/internal/extract/pptx/pptx_extractor.py +0 -187
  45. nv_ingest_api/internal/mutate/__init__.py +0 -3
  46. nv_ingest_api/internal/mutate/deduplicate.py +0 -110
  47. nv_ingest_api/internal/mutate/filter.py +0 -133
  48. nv_ingest_api/internal/primitives/__init__.py +0 -0
  49. nv_ingest_api/internal/primitives/nim/__init__.py +0 -8
  50. nv_ingest_api/internal/primitives/nim/default_values.py +0 -15
  51. nv_ingest_api/internal/primitives/nim/model_interface/__init__.py +0 -3
  52. nv_ingest_api/internal/primitives/nim/model_interface/cached.py +0 -274
  53. nv_ingest_api/internal/primitives/nim/model_interface/decorators.py +0 -56
  54. nv_ingest_api/internal/primitives/nim/model_interface/deplot.py +0 -270
  55. nv_ingest_api/internal/primitives/nim/model_interface/helpers.py +0 -275
  56. nv_ingest_api/internal/primitives/nim/model_interface/nemoretriever_parse.py +0 -238
  57. nv_ingest_api/internal/primitives/nim/model_interface/paddle.py +0 -462
  58. nv_ingest_api/internal/primitives/nim/model_interface/parakeet.py +0 -367
  59. nv_ingest_api/internal/primitives/nim/model_interface/text_embedding.py +0 -132
  60. nv_ingest_api/internal/primitives/nim/model_interface/vlm.py +0 -152
  61. nv_ingest_api/internal/primitives/nim/model_interface/yolox.py +0 -1400
  62. nv_ingest_api/internal/primitives/nim/nim_client.py +0 -344
  63. nv_ingest_api/internal/primitives/nim/nim_model_interface.py +0 -81
  64. nv_ingest_api/internal/primitives/tracing/__init__.py +0 -0
  65. nv_ingest_api/internal/primitives/tracing/latency.py +0 -69
  66. nv_ingest_api/internal/primitives/tracing/logging.py +0 -96
  67. nv_ingest_api/internal/primitives/tracing/tagging.py +0 -197
  68. nv_ingest_api/internal/schemas/__init__.py +0 -3
  69. nv_ingest_api/internal/schemas/extract/__init__.py +0 -3
  70. nv_ingest_api/internal/schemas/extract/extract_audio_schema.py +0 -130
  71. nv_ingest_api/internal/schemas/extract/extract_chart_schema.py +0 -135
  72. nv_ingest_api/internal/schemas/extract/extract_docx_schema.py +0 -124
  73. nv_ingest_api/internal/schemas/extract/extract_image_schema.py +0 -124
  74. nv_ingest_api/internal/schemas/extract/extract_infographic_schema.py +0 -128
  75. nv_ingest_api/internal/schemas/extract/extract_pdf_schema.py +0 -218
  76. nv_ingest_api/internal/schemas/extract/extract_pptx_schema.py +0 -124
  77. nv_ingest_api/internal/schemas/extract/extract_table_schema.py +0 -129
  78. nv_ingest_api/internal/schemas/message_brokers/__init__.py +0 -3
  79. nv_ingest_api/internal/schemas/message_brokers/message_broker_client_schema.py +0 -23
  80. nv_ingest_api/internal/schemas/message_brokers/request_schema.py +0 -34
  81. nv_ingest_api/internal/schemas/message_brokers/response_schema.py +0 -19
  82. nv_ingest_api/internal/schemas/meta/__init__.py +0 -3
  83. nv_ingest_api/internal/schemas/meta/base_model_noext.py +0 -11
  84. nv_ingest_api/internal/schemas/meta/ingest_job_schema.py +0 -237
  85. nv_ingest_api/internal/schemas/meta/metadata_schema.py +0 -221
  86. nv_ingest_api/internal/schemas/mutate/__init__.py +0 -3
  87. nv_ingest_api/internal/schemas/mutate/mutate_image_dedup_schema.py +0 -16
  88. nv_ingest_api/internal/schemas/store/__init__.py +0 -3
  89. nv_ingest_api/internal/schemas/store/store_embedding_schema.py +0 -28
  90. nv_ingest_api/internal/schemas/store/store_image_schema.py +0 -30
  91. nv_ingest_api/internal/schemas/transform/__init__.py +0 -3
  92. nv_ingest_api/internal/schemas/transform/transform_image_caption_schema.py +0 -15
  93. nv_ingest_api/internal/schemas/transform/transform_image_filter_schema.py +0 -17
  94. nv_ingest_api/internal/schemas/transform/transform_text_embedding_schema.py +0 -25
  95. nv_ingest_api/internal/schemas/transform/transform_text_splitter_schema.py +0 -22
  96. nv_ingest_api/internal/store/__init__.py +0 -3
  97. nv_ingest_api/internal/store/embed_text_upload.py +0 -236
  98. nv_ingest_api/internal/store/image_upload.py +0 -232
  99. nv_ingest_api/internal/transform/__init__.py +0 -3
  100. nv_ingest_api/internal/transform/caption_image.py +0 -205
  101. nv_ingest_api/internal/transform/embed_text.py +0 -496
  102. nv_ingest_api/internal/transform/split_text.py +0 -157
  103. nv_ingest_api/util/__init__.py +0 -0
  104. nv_ingest_api/util/control_message/__init__.py +0 -0
  105. nv_ingest_api/util/control_message/validators.py +0 -47
  106. nv_ingest_api/util/converters/__init__.py +0 -0
  107. nv_ingest_api/util/converters/bytetools.py +0 -78
  108. nv_ingest_api/util/converters/containers.py +0 -65
  109. nv_ingest_api/util/converters/datetools.py +0 -90
  110. nv_ingest_api/util/converters/dftools.py +0 -127
  111. nv_ingest_api/util/converters/formats.py +0 -64
  112. nv_ingest_api/util/converters/type_mappings.py +0 -27
  113. nv_ingest_api/util/detectors/__init__.py +0 -5
  114. nv_ingest_api/util/detectors/language.py +0 -38
  115. nv_ingest_api/util/exception_handlers/__init__.py +0 -0
  116. nv_ingest_api/util/exception_handlers/converters.py +0 -72
  117. nv_ingest_api/util/exception_handlers/decorators.py +0 -223
  118. nv_ingest_api/util/exception_handlers/detectors.py +0 -74
  119. nv_ingest_api/util/exception_handlers/pdf.py +0 -116
  120. nv_ingest_api/util/exception_handlers/schemas.py +0 -68
  121. nv_ingest_api/util/image_processing/__init__.py +0 -5
  122. nv_ingest_api/util/image_processing/clustering.py +0 -260
  123. nv_ingest_api/util/image_processing/processing.py +0 -179
  124. nv_ingest_api/util/image_processing/table_and_chart.py +0 -449
  125. nv_ingest_api/util/image_processing/transforms.py +0 -407
  126. nv_ingest_api/util/logging/__init__.py +0 -0
  127. nv_ingest_api/util/logging/configuration.py +0 -31
  128. nv_ingest_api/util/message_brokers/__init__.py +0 -3
  129. nv_ingest_api/util/message_brokers/simple_message_broker/__init__.py +0 -9
  130. nv_ingest_api/util/message_brokers/simple_message_broker/broker.py +0 -465
  131. nv_ingest_api/util/message_brokers/simple_message_broker/ordered_message_queue.py +0 -71
  132. nv_ingest_api/util/message_brokers/simple_message_broker/simple_client.py +0 -435
  133. nv_ingest_api/util/metadata/__init__.py +0 -5
  134. nv_ingest_api/util/metadata/aggregators.py +0 -469
  135. nv_ingest_api/util/multi_processing/__init__.py +0 -8
  136. nv_ingest_api/util/multi_processing/mp_pool_singleton.py +0 -194
  137. nv_ingest_api/util/nim/__init__.py +0 -56
  138. nv_ingest_api/util/pdf/__init__.py +0 -3
  139. nv_ingest_api/util/pdf/pdfium.py +0 -427
  140. nv_ingest_api/util/schema/__init__.py +0 -0
  141. nv_ingest_api/util/schema/schema_validator.py +0 -10
  142. nv_ingest_api/util/service_clients/__init__.py +0 -3
  143. nv_ingest_api/util/service_clients/client_base.py +0 -72
  144. nv_ingest_api/util/service_clients/kafka/__init__.py +0 -3
  145. nv_ingest_api/util/service_clients/redis/__init__.py +0 -0
  146. nv_ingest_api/util/service_clients/redis/redis_client.py +0 -334
  147. nv_ingest_api/util/service_clients/rest/__init__.py +0 -0
  148. nv_ingest_api/util/service_clients/rest/rest_client.py +0 -398
  149. nv_ingest_api/util/string_processing/__init__.py +0 -51
  150. nv_ingest_api-2025.4.17.dev20250417.dist-info/RECORD +0 -152
  151. /nv_ingest_api/{internal → primitives}/__init__.py +0 -0
  152. {nv_ingest_api-2025.4.17.dev20250417.dist-info → nv_ingest_api-2025.4.19.dev20250419.dist-info}/licenses/LICENSE +0 -0
  153. {nv_ingest_api-2025.4.17.dev20250417.dist-info → nv_ingest_api-2025.4.19.dev20250419.dist-info}/top_level.txt +0 -0
@@ -1,334 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES.
2
- # All rights reserved.
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- import json
6
- import logging
7
- import time
8
- from typing import Any
9
- from typing import Dict
10
- from typing import List
11
- from typing import Optional
12
- from typing import Tuple
13
- from typing import Union
14
-
15
- import redis
16
- from redis.exceptions import RedisError
17
-
18
- from nv_ingest_api.util.service_clients.client_base import MessageBrokerClientBase
19
-
20
- # pylint: skip-file
21
-
22
- logger = logging.getLogger(__name__)
23
-
24
-
25
- class RedisClient(MessageBrokerClientBase):
26
- """
27
- A client for interfacing with Redis, providing mechanisms for sending and receiving messages
28
- with retry logic and connection management.
29
-
30
- Parameters
31
- ----------
32
- host : str
33
- The hostname of the Redis server.
34
- port : int
35
- The port number of the Redis server.
36
- db : int, optional
37
- The database number to connect to. Default is 0.
38
- max_retries : int, optional
39
- The maximum number of retry attempts for operations. Default is 0 (no retries).
40
- max_backoff : int, optional
41
- The maximum backoff delay between retries in seconds. Default is 32 seconds.
42
- connection_timeout : int, optional
43
- The timeout in seconds for connecting to the Redis server. Default is 300 seconds.
44
- max_pool_size : int, optional
45
- The maximum number of connections in the Redis connection pool. Default is 128.
46
- use_ssl : bool, optional
47
- Specifies if SSL should be used for the connection. Default is False.
48
- redis_allocator : Any, optional
49
- The Redis client allocator, allowing for custom Redis client instances. Default is redis.Redis.
50
-
51
- Attributes
52
- ----------
53
- client : Any
54
- The Redis client instance used for operations.
55
- """
56
-
57
- def __init__(
58
- self,
59
- host: str,
60
- port: int,
61
- db: int = 0,
62
- max_retries: int = 0,
63
- max_backoff: int = 32,
64
- connection_timeout: int = 300,
65
- max_pool_size: int = 128,
66
- use_ssl: bool = False,
67
- redis_allocator: Any = redis.Redis, # Type hint as 'Any' due to dynamic nature
68
- ):
69
- self._host = host
70
- self._port = port
71
- self._db = db
72
- self._max_retries = max_retries
73
- self._max_backoff = max_backoff
74
- self._connection_timeout = connection_timeout
75
- self._use_ssl = use_ssl
76
- self._pool = redis.ConnectionPool(
77
- host=self._host,
78
- port=self._port,
79
- db=self._db,
80
- socket_connect_timeout=self._connection_timeout,
81
- max_connections=max_pool_size,
82
- )
83
- self._redis_allocator = redis_allocator
84
- self._client = self._redis_allocator(connection_pool=self._pool)
85
- self._retries = 0
86
-
87
- def _connect(self) -> None:
88
- """
89
- Attempts to reconnect to the Redis server if the current connection is not responsive.
90
- """
91
- if not self.ping():
92
- logger.debug("Reconnecting to Redis")
93
- self._client = self._redis_allocator(connection_pool=self._pool)
94
-
95
- @property
96
- def max_retries(self) -> int:
97
- return self._max_retries
98
-
99
- @max_retries.setter
100
- def max_retries(self, value: int) -> None:
101
- self._max_retries = value
102
-
103
- def get_client(self) -> Any:
104
- """
105
- Returns a Redis client instance, reconnecting if necessary.
106
-
107
- Returns
108
- -------
109
- Any
110
- The Redis client instance.
111
- """
112
- if self._client is None or not self.ping():
113
- self._connect()
114
- return self._client
115
-
116
- def ping(self) -> bool:
117
- """
118
- Checks if the Redis server is responsive.
119
-
120
- Returns
121
- -------
122
- bool
123
- True if the server responds to a ping, False otherwise.
124
- """
125
- try:
126
- self._client.ping()
127
- return True
128
- except (RedisError, AttributeError):
129
- return False
130
-
131
- def _check_response(
132
- self, channel_name: str, timeout: float
133
- ) -> Tuple[Optional[Dict[str, Any]], Optional[int], Optional[int]]:
134
- """
135
- Checks for a response from the Redis queue and processes it into a message, fragment, and fragment count.
136
-
137
- Parameters
138
- ----------
139
- channel_name : str
140
- The name of the Redis channel from which to receive the response.
141
- timeout : float
142
- The time in seconds to wait for a response from the Redis queue before timing out.
143
-
144
- Returns
145
- -------
146
- Tuple[Optional[Dict[str, Any]], Optional[int], Optional[int]]
147
- A tuple containing:
148
- - message: A dictionary containing the decoded message if successful,
149
- or None if no message was retrieved.
150
- - fragment: An integer representing the fragment number of the message,
151
- or None if no fragment was found.
152
- - fragment_count: An integer representing the total number of message fragments,
153
- or None if no fragment count was found.
154
-
155
- Raises
156
- ------
157
- ValueError
158
- If the message retrieved from Redis cannot be decoded from JSON.
159
- """
160
-
161
- response = self.get_client().blpop([channel_name], timeout)
162
- if response is None:
163
- raise TimeoutError("No response was received in the specified timeout period")
164
-
165
- if len(response) > 1 and response[1]:
166
- try:
167
- message = json.loads(response[1])
168
- fragment = message.get("fragment", 0)
169
- fragment_count = message.get("fragment_count", 1)
170
-
171
- return message, fragment, fragment_count
172
- except json.JSONDecodeError as e:
173
- logger.error(f"Failed to decode message: {e}")
174
- raise ValueError(f"Failed to decode message from Redis: {e}")
175
-
176
- return None, None, None
177
-
178
- def fetch_message(self, channel_name: str, timeout: float = 10) -> Optional[Union[str, Dict]]:
179
- """
180
- Fetches a message from the specified queue with retries on failure. If the message is fragmented, it will
181
- continue fetching fragments until all parts have been collected.
182
-
183
- Parameters
184
- ----------
185
- channel_name: str
186
- Channel to fetch the message from.
187
- timeout : float
188
- The timeout in seconds for blocking until a message is available. If we receive a multi-part message,
189
- this value will be temporarily extended in order to collect all fragments.
190
-
191
- Returns
192
- -------
193
- Optional[str or Dict]
194
- The full fetched message, or None if no message could be fetched after retries.
195
-
196
- Raises
197
- ------
198
- ValueError
199
- If fetching the message fails after the specified number of retries or due to other critical errors.
200
- """
201
- accumulated_time = 0
202
- collected_fragments = []
203
- fragment_count = None
204
- retries = 0
205
-
206
- logger.debug(f"Starting fetch_message on channel '{channel_name}' with timeout {timeout}s.")
207
-
208
- while True:
209
- try:
210
- # Attempt to fetch a message from the Redis queue
211
- message, fragment, fragment_count = self._check_response(channel_name, timeout)
212
- logger.debug(f"Fetched fragment: {fragment} (fragment_count: {fragment_count}).")
213
-
214
- if message is not None:
215
- if fragment_count == 1:
216
- return message
217
-
218
- collected_fragments.append(message)
219
- logger.debug(f"Collected {len(collected_fragments)} of {fragment_count} fragments so far.")
220
-
221
- # If we have collected all fragments, combine and return
222
- if len(collected_fragments) == fragment_count:
223
- logger.debug("All fragments received. Sorting and combining fragments.")
224
- # Sort fragments by the 'fragment' field to ensure correct order
225
- collected_fragments.sort(key=lambda x: x["fragment"])
226
- reconstructed_message = self._combine_fragments(collected_fragments)
227
- logger.debug("Message reconstructed successfully. Returning combined message.")
228
- return reconstructed_message
229
- else:
230
- logger.debug("Received empty response; returning None.")
231
- return message
232
-
233
- except TimeoutError:
234
- # When fragments are expected but not all received before timeout
235
- if fragment_count and fragment_count > 1:
236
- accumulated_time += timeout
237
- logger.debug(
238
- f"Timeout occurred waiting for fragments. "
239
- f"Accumulated timeout: {accumulated_time}s (Threshold: {timeout * fragment_count}s)."
240
- )
241
- if accumulated_time >= (timeout * fragment_count):
242
- err_msg = f"Failed to reconstruct message from {channel_name} after {accumulated_time} sec."
243
- logger.error(err_msg)
244
- raise ValueError(err_msg)
245
- else:
246
- raise # This is expected in many cases, so re-raise it
247
-
248
- except RedisError as err:
249
- retries += 1
250
- logger.error(f"Redis error during fetch: {err}")
251
- backoff_delay = min(2**retries, self._max_backoff)
252
-
253
- if self.max_retries > 0 and retries <= self.max_retries:
254
- logger.error(f"Fetch attempt failed, retrying in {backoff_delay}s...")
255
- time.sleep(backoff_delay)
256
- else:
257
- logger.error(f"Failed to fetch message from {channel_name} after {retries} attempts.")
258
- raise ValueError(f"Failed to fetch message from Redis queue after {retries} attempts: {err}")
259
-
260
- # Invalidate client to force reconnection on the next try
261
- self._client = None
262
-
263
- except Exception as e:
264
- # Handle non-Redis specific exceptions
265
- logger.error(f"Unexpected error during fetch from {channel_name}: {e}")
266
- raise ValueError(f"Unexpected error during fetch: {e}")
267
-
268
- @staticmethod
269
- def _combine_fragments(fragments: List[Dict[str, Any]]) -> Dict:
270
- """
271
- Combines multiple message fragments into a single message by extending the 'data' elements,
272
- retaining the 'status' and 'description' of the first fragment, and removing 'fragment' and 'fragment_counts'.
273
-
274
- Parameters
275
- ----------
276
- fragments : List[Dict[str, Any]]
277
- A list of fragments to be combined.
278
-
279
- Returns
280
- -------
281
- str
282
- The combined message as a JSON string, containing 'status', 'description', and combined 'data'.
283
- """
284
- if not fragments:
285
- raise ValueError("Fragments list is empty")
286
-
287
- # Use 'status' and 'description' from the first fragment
288
- combined_message = {
289
- "status": fragments[0]["status"],
290
- "description": fragments[0]["description"],
291
- "data": [],
292
- "trace": fragments[0].get("trace", {}),
293
- }
294
-
295
- # Combine the 'data' elements from all fragments
296
- for fragment in fragments:
297
- combined_message["data"].extend(fragment["data"])
298
-
299
- return combined_message
300
-
301
- def submit_message(self, channel_name: str, message: str) -> None:
302
- """
303
- Submits a message to a specified Redis queue with retries on failure.
304
-
305
- Parameters
306
- ----------
307
- channel_name : str
308
- The name of the queue to submit the message to.
309
- message : str
310
- The message to submit.
311
-
312
- Raises
313
- ------
314
- RedisError
315
- If submitting the message fails after the specified number of retries.
316
- """
317
- retries = 0
318
- while True:
319
- try:
320
- self.get_client().rpush(channel_name, message)
321
- logger.debug(f"Message submitted to {channel_name}")
322
- break
323
- except RedisError as e:
324
- logger.error(f"Failed to submit message, retrying... Error: {e}")
325
- self._client = None # Invalidate client to force reconnection
326
- retries += 1
327
- backoff_delay = min(2**retries, self._max_backoff)
328
-
329
- if self.max_retries == 0 or retries < self.max_retries:
330
- logger.error(f"Submit attempt failed, retrying in {backoff_delay}s...")
331
- time.sleep(backoff_delay)
332
- else:
333
- logger.error(f"Failed to submit message to {channel_name} after {retries} attempts.")
334
- raise
File without changes