nv-ingest-api 2025.4.20.dev20250420__py3-none-any.whl → 2025.4.22.dev20250422__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 +3 -0
  2. nv_ingest_api/interface/__init__.py +215 -0
  3. nv_ingest_api/interface/extract.py +972 -0
  4. nv_ingest_api/interface/mutate.py +154 -0
  5. nv_ingest_api/interface/store.py +218 -0
  6. nv_ingest_api/interface/transform.py +382 -0
  7. nv_ingest_api/interface/utility.py +200 -0
  8. nv_ingest_api/internal/enums/__init__.py +3 -0
  9. nv_ingest_api/internal/enums/common.py +494 -0
  10. nv_ingest_api/internal/extract/__init__.py +3 -0
  11. nv_ingest_api/internal/extract/audio/__init__.py +3 -0
  12. nv_ingest_api/internal/extract/audio/audio_extraction.py +149 -0
  13. nv_ingest_api/internal/extract/docx/__init__.py +5 -0
  14. nv_ingest_api/internal/extract/docx/docx_extractor.py +205 -0
  15. nv_ingest_api/internal/extract/docx/engines/__init__.py +0 -0
  16. nv_ingest_api/internal/extract/docx/engines/docxreader_helpers/__init__.py +3 -0
  17. nv_ingest_api/internal/extract/docx/engines/docxreader_helpers/docx_helper.py +122 -0
  18. nv_ingest_api/internal/extract/docx/engines/docxreader_helpers/docxreader.py +895 -0
  19. nv_ingest_api/internal/extract/image/__init__.py +3 -0
  20. nv_ingest_api/internal/extract/image/chart_extractor.py +353 -0
  21. nv_ingest_api/internal/extract/image/image_extractor.py +204 -0
  22. nv_ingest_api/internal/extract/image/image_helpers/__init__.py +3 -0
  23. nv_ingest_api/internal/extract/image/image_helpers/common.py +403 -0
  24. nv_ingest_api/internal/extract/image/infographic_extractor.py +253 -0
  25. nv_ingest_api/internal/extract/image/table_extractor.py +344 -0
  26. nv_ingest_api/internal/extract/pdf/__init__.py +3 -0
  27. nv_ingest_api/internal/extract/pdf/engines/__init__.py +19 -0
  28. nv_ingest_api/internal/extract/pdf/engines/adobe.py +484 -0
  29. nv_ingest_api/internal/extract/pdf/engines/llama.py +243 -0
  30. nv_ingest_api/internal/extract/pdf/engines/nemoretriever.py +597 -0
  31. nv_ingest_api/internal/extract/pdf/engines/pdf_helpers/__init__.py +146 -0
  32. nv_ingest_api/internal/extract/pdf/engines/pdfium.py +603 -0
  33. nv_ingest_api/internal/extract/pdf/engines/tika.py +96 -0
  34. nv_ingest_api/internal/extract/pdf/engines/unstructured_io.py +426 -0
  35. nv_ingest_api/internal/extract/pdf/pdf_extractor.py +74 -0
  36. nv_ingest_api/internal/extract/pptx/__init__.py +5 -0
  37. nv_ingest_api/internal/extract/pptx/engines/__init__.py +0 -0
  38. nv_ingest_api/internal/extract/pptx/engines/pptx_helper.py +799 -0
  39. nv_ingest_api/internal/extract/pptx/pptx_extractor.py +187 -0
  40. nv_ingest_api/internal/mutate/__init__.py +3 -0
  41. nv_ingest_api/internal/mutate/deduplicate.py +110 -0
  42. nv_ingest_api/internal/mutate/filter.py +133 -0
  43. nv_ingest_api/internal/primitives/__init__.py +0 -0
  44. nv_ingest_api/{primitives → internal/primitives}/control_message_task.py +4 -0
  45. nv_ingest_api/{primitives → internal/primitives}/ingest_control_message.py +5 -2
  46. nv_ingest_api/internal/primitives/nim/__init__.py +8 -0
  47. nv_ingest_api/internal/primitives/nim/default_values.py +15 -0
  48. nv_ingest_api/internal/primitives/nim/model_interface/__init__.py +3 -0
  49. nv_ingest_api/internal/primitives/nim/model_interface/cached.py +274 -0
  50. nv_ingest_api/internal/primitives/nim/model_interface/decorators.py +56 -0
  51. nv_ingest_api/internal/primitives/nim/model_interface/deplot.py +270 -0
  52. nv_ingest_api/internal/primitives/nim/model_interface/helpers.py +275 -0
  53. nv_ingest_api/internal/primitives/nim/model_interface/nemoretriever_parse.py +238 -0
  54. nv_ingest_api/internal/primitives/nim/model_interface/paddle.py +462 -0
  55. nv_ingest_api/internal/primitives/nim/model_interface/parakeet.py +367 -0
  56. nv_ingest_api/internal/primitives/nim/model_interface/text_embedding.py +132 -0
  57. nv_ingest_api/internal/primitives/nim/model_interface/vlm.py +152 -0
  58. nv_ingest_api/internal/primitives/nim/model_interface/yolox.py +1400 -0
  59. nv_ingest_api/internal/primitives/nim/nim_client.py +344 -0
  60. nv_ingest_api/internal/primitives/nim/nim_model_interface.py +81 -0
  61. nv_ingest_api/internal/primitives/tracing/__init__.py +0 -0
  62. nv_ingest_api/internal/primitives/tracing/latency.py +69 -0
  63. nv_ingest_api/internal/primitives/tracing/logging.py +96 -0
  64. nv_ingest_api/internal/primitives/tracing/tagging.py +197 -0
  65. nv_ingest_api/internal/schemas/__init__.py +3 -0
  66. nv_ingest_api/internal/schemas/extract/__init__.py +3 -0
  67. nv_ingest_api/internal/schemas/extract/extract_audio_schema.py +130 -0
  68. nv_ingest_api/internal/schemas/extract/extract_chart_schema.py +135 -0
  69. nv_ingest_api/internal/schemas/extract/extract_docx_schema.py +124 -0
  70. nv_ingest_api/internal/schemas/extract/extract_image_schema.py +124 -0
  71. nv_ingest_api/internal/schemas/extract/extract_infographic_schema.py +128 -0
  72. nv_ingest_api/internal/schemas/extract/extract_pdf_schema.py +218 -0
  73. nv_ingest_api/internal/schemas/extract/extract_pptx_schema.py +124 -0
  74. nv_ingest_api/internal/schemas/extract/extract_table_schema.py +129 -0
  75. nv_ingest_api/internal/schemas/message_brokers/__init__.py +3 -0
  76. nv_ingest_api/internal/schemas/message_brokers/message_broker_client_schema.py +23 -0
  77. nv_ingest_api/internal/schemas/message_brokers/request_schema.py +34 -0
  78. nv_ingest_api/internal/schemas/message_brokers/response_schema.py +19 -0
  79. nv_ingest_api/internal/schemas/meta/__init__.py +3 -0
  80. nv_ingest_api/internal/schemas/meta/base_model_noext.py +11 -0
  81. nv_ingest_api/internal/schemas/meta/ingest_job_schema.py +237 -0
  82. nv_ingest_api/internal/schemas/meta/metadata_schema.py +221 -0
  83. nv_ingest_api/internal/schemas/mutate/__init__.py +3 -0
  84. nv_ingest_api/internal/schemas/mutate/mutate_image_dedup_schema.py +16 -0
  85. nv_ingest_api/internal/schemas/store/__init__.py +3 -0
  86. nv_ingest_api/internal/schemas/store/store_embedding_schema.py +28 -0
  87. nv_ingest_api/internal/schemas/store/store_image_schema.py +30 -0
  88. nv_ingest_api/internal/schemas/transform/__init__.py +3 -0
  89. nv_ingest_api/internal/schemas/transform/transform_image_caption_schema.py +15 -0
  90. nv_ingest_api/internal/schemas/transform/transform_image_filter_schema.py +17 -0
  91. nv_ingest_api/internal/schemas/transform/transform_text_embedding_schema.py +25 -0
  92. nv_ingest_api/internal/schemas/transform/transform_text_splitter_schema.py +22 -0
  93. nv_ingest_api/internal/store/__init__.py +3 -0
  94. nv_ingest_api/internal/store/embed_text_upload.py +236 -0
  95. nv_ingest_api/internal/store/image_upload.py +232 -0
  96. nv_ingest_api/internal/transform/__init__.py +3 -0
  97. nv_ingest_api/internal/transform/caption_image.py +205 -0
  98. nv_ingest_api/internal/transform/embed_text.py +496 -0
  99. nv_ingest_api/internal/transform/split_text.py +157 -0
  100. nv_ingest_api/util/__init__.py +0 -0
  101. nv_ingest_api/util/control_message/__init__.py +0 -0
  102. nv_ingest_api/util/control_message/validators.py +47 -0
  103. nv_ingest_api/util/converters/__init__.py +0 -0
  104. nv_ingest_api/util/converters/bytetools.py +78 -0
  105. nv_ingest_api/util/converters/containers.py +65 -0
  106. nv_ingest_api/util/converters/datetools.py +90 -0
  107. nv_ingest_api/util/converters/dftools.py +127 -0
  108. nv_ingest_api/util/converters/formats.py +64 -0
  109. nv_ingest_api/util/converters/type_mappings.py +27 -0
  110. nv_ingest_api/util/detectors/__init__.py +5 -0
  111. nv_ingest_api/util/detectors/language.py +38 -0
  112. nv_ingest_api/util/exception_handlers/__init__.py +0 -0
  113. nv_ingest_api/util/exception_handlers/converters.py +72 -0
  114. nv_ingest_api/util/exception_handlers/decorators.py +223 -0
  115. nv_ingest_api/util/exception_handlers/detectors.py +74 -0
  116. nv_ingest_api/util/exception_handlers/pdf.py +116 -0
  117. nv_ingest_api/util/exception_handlers/schemas.py +68 -0
  118. nv_ingest_api/util/image_processing/__init__.py +5 -0
  119. nv_ingest_api/util/image_processing/clustering.py +260 -0
  120. nv_ingest_api/util/image_processing/processing.py +179 -0
  121. nv_ingest_api/util/image_processing/table_and_chart.py +449 -0
  122. nv_ingest_api/util/image_processing/transforms.py +407 -0
  123. nv_ingest_api/util/logging/__init__.py +0 -0
  124. nv_ingest_api/util/logging/configuration.py +31 -0
  125. nv_ingest_api/util/message_brokers/__init__.py +3 -0
  126. nv_ingest_api/util/message_brokers/simple_message_broker/__init__.py +9 -0
  127. nv_ingest_api/util/message_brokers/simple_message_broker/broker.py +465 -0
  128. nv_ingest_api/util/message_brokers/simple_message_broker/ordered_message_queue.py +71 -0
  129. nv_ingest_api/util/message_brokers/simple_message_broker/simple_client.py +451 -0
  130. nv_ingest_api/util/metadata/__init__.py +5 -0
  131. nv_ingest_api/util/metadata/aggregators.py +469 -0
  132. nv_ingest_api/util/multi_processing/__init__.py +8 -0
  133. nv_ingest_api/util/multi_processing/mp_pool_singleton.py +194 -0
  134. nv_ingest_api/util/nim/__init__.py +56 -0
  135. nv_ingest_api/util/pdf/__init__.py +3 -0
  136. nv_ingest_api/util/pdf/pdfium.py +427 -0
  137. nv_ingest_api/util/schema/__init__.py +0 -0
  138. nv_ingest_api/util/schema/schema_validator.py +10 -0
  139. nv_ingest_api/util/service_clients/__init__.py +3 -0
  140. nv_ingest_api/util/service_clients/client_base.py +86 -0
  141. nv_ingest_api/util/service_clients/kafka/__init__.py +3 -0
  142. nv_ingest_api/util/service_clients/redis/__init__.py +0 -0
  143. nv_ingest_api/util/service_clients/redis/redis_client.py +823 -0
  144. nv_ingest_api/util/service_clients/rest/__init__.py +0 -0
  145. nv_ingest_api/util/service_clients/rest/rest_client.py +531 -0
  146. nv_ingest_api/util/string_processing/__init__.py +51 -0
  147. {nv_ingest_api-2025.4.20.dev20250420.dist-info → nv_ingest_api-2025.4.22.dev20250422.dist-info}/METADATA +1 -1
  148. nv_ingest_api-2025.4.22.dev20250422.dist-info/RECORD +152 -0
  149. nv_ingest_api-2025.4.20.dev20250420.dist-info/RECORD +0 -9
  150. /nv_ingest_api/{primitives → internal}/__init__.py +0 -0
  151. {nv_ingest_api-2025.4.20.dev20250420.dist-info → nv_ingest_api-2025.4.22.dev20250422.dist-info}/WHEEL +0 -0
  152. {nv_ingest_api-2025.4.20.dev20250420.dist-info → nv_ingest_api-2025.4.22.dev20250422.dist-info}/licenses/LICENSE +0 -0
  153. {nv_ingest_api-2025.4.20.dev20250420.dist-info → nv_ingest_api-2025.4.22.dev20250422.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,451 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES.
2
+ # All rights reserved.
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ # NOTE: This code is duplicated from the ingest service:
6
+ # src/nv_ingest/util/message_brokers/simple_message_broker/simple_client.py
7
+ # Eventually we should move all client wrappers for the message broker into a shared library that both the ingest
8
+ # service and the client can use.
9
+
10
+ import socket
11
+ import json
12
+ import time
13
+ import logging
14
+ from typing import Optional, Tuple, Union
15
+
16
+ from nv_ingest_api.internal.schemas.message_brokers.response_schema import ResponseSchema
17
+ from nv_ingest_api.util.service_clients.client_base import MessageBrokerClientBase, FetchMode
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class SimpleClient(MessageBrokerClientBase):
23
+ """
24
+ A client for interfacing with SimpleMessageBroker, creating a new socket connection per request
25
+ to ensure thread safety and robustness. Respects timeouts for all operations.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ host: str,
31
+ port: int,
32
+ db: int = 0,
33
+ max_retries: int = 3,
34
+ max_backoff: int = 32,
35
+ connection_timeout: int = 300,
36
+ max_pool_size: int = 128,
37
+ use_ssl: bool = False,
38
+ ):
39
+ """
40
+ Initialize the SimpleClient with configuration parameters.
41
+
42
+ Parameters
43
+ ----------
44
+ host : str
45
+ The hostname or IP address of the broker.
46
+ port : int
47
+ The port number of the broker.
48
+ db : int, optional
49
+ The database index (default: 0).
50
+ max_retries : int, optional
51
+ Maximum number of retries for operations (default: 3).
52
+ max_backoff : int, optional
53
+ Maximum backoff time in seconds for retries (default: 32).
54
+ connection_timeout : int, optional
55
+ Timeout in seconds for establishing a connection (default: 300).
56
+ max_pool_size : int, optional
57
+ Maximum pool size for connections (default: 128).
58
+ use_ssl : bool, optional
59
+ Whether to use SSL for connections (default: False).
60
+ """
61
+
62
+ self._host = host
63
+ self._port = port
64
+ self._db = db
65
+ self._max_retries = max_retries
66
+ self._max_backoff = max_backoff
67
+ self._max_pool_size = max_pool_size
68
+ self._connection_timeout = connection_timeout
69
+ self._use_ssl = use_ssl
70
+
71
+ def get_client(self):
72
+ """
73
+ Retrieve the current client instance.
74
+
75
+ Returns
76
+ -------
77
+ SimpleClient
78
+ The current client instance.
79
+ """
80
+ return self
81
+
82
+ def submit_message(
83
+ self,
84
+ queue_name: str,
85
+ message: str,
86
+ timeout: Optional[Tuple[int, Union[float]]] = (100, None),
87
+ for_nv_ingest: bool = False,
88
+ ) -> ResponseSchema:
89
+ """
90
+ Submit a message to the specified queue.
91
+
92
+ Parameters
93
+ ----------
94
+ queue_name : str
95
+ The name of the queue.
96
+ message : str
97
+ The message to be submitted.
98
+ timeout : float, optional
99
+ Timeout in seconds for the operation.
100
+ for_nv_ingest : bool, optional
101
+ Indicates whether the message is for NV ingest operations.
102
+
103
+ Returns
104
+ -------
105
+ ResponseSchema
106
+ The response from the broker.
107
+ """
108
+ return self._handle_push(queue_name, message, timeout, for_nv_ingest)
109
+
110
+ def fetch_message(
111
+ self,
112
+ queue_name: str,
113
+ timeout: Optional[Tuple[int, Union[float]]] = (100, None),
114
+ override_fetch_mode: FetchMode = None,
115
+ ) -> ResponseSchema:
116
+ """
117
+ Fetch a message from the specified queue.
118
+
119
+ Parameters
120
+ ----------
121
+ queue_name : str
122
+ The name of the queue.
123
+ timeout : float, optional
124
+ Timeout in seconds for the operation.
125
+
126
+ Returns
127
+ -------
128
+ ResponseSchema
129
+ The response containing the fetched message.
130
+ """
131
+ if isinstance(timeout, int):
132
+ timeout = (timeout, None)
133
+
134
+ return self._handle_pop(queue_name, timeout)
135
+
136
+ def ping(self) -> ResponseSchema:
137
+ """
138
+ Ping the broker to check connectivity.
139
+
140
+ Returns
141
+ -------
142
+ ResponseSchema
143
+ The response indicating the success of the ping operation.
144
+ """
145
+ command = {"command": "PING"}
146
+ return self._execute_simple_command(command)
147
+
148
+ def size(self, queue_name: str) -> ResponseSchema:
149
+ """
150
+ Fetch the size of the specified queue.
151
+
152
+ Parameters
153
+ ----------
154
+ queue_name : str
155
+ The name of the queue.
156
+
157
+ Returns
158
+ -------
159
+ ResponseSchema
160
+ The response containing the queue size.
161
+ """
162
+ command = {"command": "SIZE", "queue_name": queue_name}
163
+ return self._execute_simple_command(command)
164
+
165
+ def _handle_push(
166
+ self, queue_name: str, message: str, timeout: Optional[Tuple[int, Union[float, None]]], for_nv_ingest: bool
167
+ ) -> ResponseSchema:
168
+ """
169
+ Push a message to the queue with optional timeout.
170
+
171
+ Parameters
172
+ ----------
173
+ queue_name : str
174
+ The name of the queue.
175
+ message : str
176
+ The message to push.
177
+ timeout : float, optional
178
+ Timeout in seconds for the operation.
179
+ for_nv_ingest : bool
180
+ Indicates whether the message is for NV ingest operations.
181
+
182
+ Returns
183
+ -------
184
+ ResponseSchema
185
+ The response from the broker.
186
+ """
187
+
188
+ if not queue_name or not isinstance(queue_name, str):
189
+ return ResponseSchema(response_code=1, response_reason="Invalid queue name.")
190
+ if not message or not isinstance(message, str):
191
+ return ResponseSchema(response_code=1, response_reason="Invalid message.")
192
+
193
+ if for_nv_ingest:
194
+ command = {"command": "PUSH_FOR_NV_INGEST", "queue_name": queue_name, "message": message}
195
+ else:
196
+ command = {"command": "PUSH", "queue_name": queue_name, "message": message}
197
+
198
+ timeout = int(timeout[0])
199
+ if timeout is not None:
200
+ command["timeout"] = timeout
201
+
202
+ start_time = time.time()
203
+ while True:
204
+ elapsed = time.time() - start_time
205
+ remaining_timeout = (timeout - elapsed) if (timeout is not None) else None
206
+ if (remaining_timeout is not None) and (remaining_timeout <= 0):
207
+ return ResponseSchema(response_code=1, response_reason="PUSH operation timed out.")
208
+
209
+ try:
210
+ with socket.create_connection((self._host, self._port), timeout=self._connection_timeout) as sock:
211
+ self._send(sock, json.dumps(command).encode("utf-8"))
212
+ # Receive initial response with transaction ID
213
+ response_data = self._recv(sock)
214
+ response = json.loads(response_data)
215
+
216
+ if response.get("response_code") != 0:
217
+ if (
218
+ response.get("response_reason") == "Queue is full"
219
+ or response.get("response_reason") == "Queue is not available"
220
+ ):
221
+ time.sleep(0.5)
222
+ continue
223
+ else:
224
+ return ResponseSchema(**response)
225
+
226
+ if "transaction_id" not in response:
227
+ error_msg = "No transaction_id in response."
228
+ logger.error(error_msg)
229
+
230
+ return ResponseSchema(response_code=1, response_reason=error_msg)
231
+
232
+ transaction_id = response["transaction_id"]
233
+
234
+ # Send ACK
235
+ ack_data = json.dumps({"transaction_id": transaction_id, "ack": True}).encode("utf-8")
236
+ self._send(sock, ack_data)
237
+
238
+ # Receive final response
239
+ final_response_data = self._recv(sock)
240
+ final_response = json.loads(final_response_data)
241
+
242
+ return ResponseSchema(**final_response)
243
+
244
+ except (ConnectionError, socket.error, BrokenPipeError):
245
+ pass
246
+ except json.JSONDecodeError:
247
+ return ResponseSchema(response_code=1, response_reason="Invalid JSON response from server.")
248
+ except Exception as e:
249
+ return ResponseSchema(response_code=1, response_reason=str(e))
250
+
251
+ time.sleep(0.5) # Backoff delay before retry
252
+
253
+ def _handle_pop(self, queue_name: str, timeout: Optional[Tuple[int, Union[float, None]]]) -> ResponseSchema:
254
+ """
255
+ Pop a message from the queue with optional timeout.
256
+
257
+ Parameters
258
+ ----------
259
+ queue_name : str
260
+ The name of the queue.
261
+ timeout : float, optional
262
+ Timeout in seconds for the operation.
263
+
264
+ Returns
265
+ -------
266
+ ResponseSchema
267
+ The response containing the popped message.
268
+ """
269
+
270
+ if not queue_name or not isinstance(queue_name, str):
271
+ return ResponseSchema(response_code=1, response_reason="Invalid queue name.")
272
+
273
+ command = {"command": "POP", "queue_name": queue_name}
274
+
275
+ timeout = int(timeout[0])
276
+
277
+ if timeout is not None:
278
+ command["timeout"] = timeout
279
+
280
+ start_time = time.time()
281
+ while True:
282
+ elapsed = time.time() - start_time
283
+ remaining_timeout = timeout - elapsed if timeout else None
284
+ if remaining_timeout is not None and remaining_timeout <= 0:
285
+ return ResponseSchema(response_code=1, response_reason="POP operation timed out.")
286
+
287
+ try:
288
+ with socket.create_connection((self._host, self._port), timeout=self._connection_timeout) as sock:
289
+ self._send(sock, json.dumps(command).encode("utf-8"))
290
+ # Receive initial response with transaction ID and message
291
+ response_data = self._recv(sock)
292
+ response = json.loads(response_data)
293
+
294
+ if response.get("response_code") != 0:
295
+ if response.get("response_reason") == "Queue is empty":
296
+ time.sleep(0.1)
297
+ continue
298
+ else:
299
+ return ResponseSchema(**response)
300
+
301
+ if "transaction_id" not in response:
302
+ error_msg = "No transaction_id in response."
303
+
304
+ return ResponseSchema(response_code=1, response_reason=error_msg)
305
+
306
+ transaction_id = response["transaction_id"]
307
+ message = response.get("response")
308
+
309
+ # Send ACK
310
+ ack_data = json.dumps({"transaction_id": transaction_id, "ack": True}).encode("utf-8")
311
+ self._send(sock, ack_data)
312
+
313
+ # Receive final response
314
+ final_response_data = self._recv(sock)
315
+ final_response = json.loads(final_response_data)
316
+
317
+ if final_response.get("response_code") == 0:
318
+ return ResponseSchema(response_code=0, response=message, transaction_id=transaction_id)
319
+ else:
320
+ return ResponseSchema(**final_response)
321
+
322
+ except (ConnectionError, socket.error, BrokenPipeError):
323
+ pass
324
+ except json.JSONDecodeError:
325
+ return ResponseSchema(response_code=1, response_reason="Invalid JSON response from server.")
326
+ except Exception as e:
327
+ return ResponseSchema(response_code=1, response_reason=str(e))
328
+
329
+ time.sleep(0.1) # Backoff delay before retry
330
+
331
+ def _execute_simple_command(self, command: dict) -> ResponseSchema:
332
+ """
333
+ Execute a simple command on the broker and process the response.
334
+
335
+ Parameters
336
+ ----------
337
+ command : dict
338
+ The command to execute.
339
+
340
+ Returns
341
+ -------
342
+ ResponseSchema
343
+ The response from the broker.
344
+ """
345
+
346
+ if isinstance(command, dict):
347
+ data = json.dumps(command).encode("utf-8")
348
+ elif isinstance(command, str):
349
+ data = command.encode("utf-8")
350
+
351
+ try:
352
+ with socket.create_connection((self._host, self._port), timeout=self._connection_timeout) as sock:
353
+ self._send(sock, data)
354
+ response_data = self._recv(sock)
355
+ response = json.loads(response_data)
356
+ return ResponseSchema(**response)
357
+ except (ConnectionError, socket.error, BrokenPipeError) as e:
358
+ return ResponseSchema(response_code=1, response_reason=f"Connection error: {e}")
359
+ except json.JSONDecodeError:
360
+ return ResponseSchema(response_code=1, response_reason="Invalid JSON response from server.")
361
+ except Exception as e:
362
+ return ResponseSchema(response_code=1, response_reason=str(e))
363
+
364
+ def _send(self, sock: socket.socket, data: bytes) -> None:
365
+ """
366
+ Send data over a socket connection with a length header.
367
+
368
+ Parameters
369
+ ----------
370
+ sock : socket.socket
371
+ The socket connection.
372
+ data : bytes
373
+ The data to send.
374
+
375
+ Raises
376
+ ------
377
+ ConnectionError
378
+ If sending data fails.
379
+ """
380
+
381
+ total_length = len(data)
382
+ if total_length == 0:
383
+ raise ValueError("Cannot send an empty message.")
384
+
385
+ try:
386
+ sock.sendall(total_length.to_bytes(8, "big"))
387
+ sock.sendall(data)
388
+ except (socket.error, BrokenPipeError):
389
+ raise ConnectionError("Failed to send data.")
390
+
391
+ def _recv(self, sock: socket.socket) -> str:
392
+ """
393
+ Receive data from a socket connection based on a length header.
394
+
395
+ Parameters
396
+ ----------
397
+ sock : socket.socket
398
+ The socket connection.
399
+
400
+ Returns
401
+ -------
402
+ str
403
+ The received data as a string.
404
+
405
+ Raises
406
+ ------
407
+ ConnectionError
408
+ If receiving data fails.
409
+ """
410
+
411
+ try:
412
+ length_header = self._recv_exact(sock, 8)
413
+ if not length_header:
414
+ raise ConnectionError("Incomplete length header received.")
415
+ total_length = int.from_bytes(length_header, "big")
416
+ data_bytes = self._recv_exact(sock, total_length)
417
+ if not data_bytes:
418
+ raise ConnectionError("Incomplete message received.")
419
+ return data_bytes.decode("utf-8")
420
+ except (socket.error, BrokenPipeError, ConnectionError):
421
+ raise ConnectionError("Failed to receive data.")
422
+
423
+ def _recv_exact(self, sock: socket.socket, num_bytes: int) -> Optional[bytes]:
424
+ """
425
+ Receive an exact number of bytes from a socket connection.
426
+
427
+ Parameters
428
+ ----------
429
+ sock : socket.socket
430
+ The socket connection.
431
+ num_bytes : int
432
+ The number of bytes to receive.
433
+
434
+ Returns
435
+ -------
436
+ Optional[bytes]
437
+ The received bytes, or None if the connection is closed.
438
+ """
439
+
440
+ data = bytearray()
441
+ while len(data) < num_bytes:
442
+ try:
443
+ packet = sock.recv(num_bytes - len(data))
444
+ if not packet:
445
+ return None
446
+ data.extend(packet)
447
+ except socket.timeout:
448
+ return None
449
+ except Exception:
450
+ return None
451
+ return bytes(data)
@@ -0,0 +1,5 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES.
2
+ # All rights reserved.
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ # Copyright (c) 2024, NVIDIA CORPORATION.