nv-ingest-api 2025.4.15.dev20250415__py3-none-any.whl → 2025.4.17.dev20250417__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 +435 -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 +72 -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 +334 -0
  144. nv_ingest_api/util/service_clients/rest/__init__.py +0 -0
  145. nv_ingest_api/util/service_clients/rest/rest_client.py +398 -0
  146. nv_ingest_api/util/string_processing/__init__.py +51 -0
  147. {nv_ingest_api-2025.4.15.dev20250415.dist-info → nv_ingest_api-2025.4.17.dev20250417.dist-info}/METADATA +1 -1
  148. nv_ingest_api-2025.4.17.dev20250417.dist-info/RECORD +152 -0
  149. nv_ingest_api-2025.4.15.dev20250415.dist-info/RECORD +0 -9
  150. /nv_ingest_api/{primitives → internal}/__init__.py +0 -0
  151. {nv_ingest_api-2025.4.15.dev20250415.dist-info → nv_ingest_api-2025.4.17.dev20250417.dist-info}/WHEEL +0 -0
  152. {nv_ingest_api-2025.4.15.dev20250415.dist-info → nv_ingest_api-2025.4.17.dev20250417.dist-info}/licenses/LICENSE +0 -0
  153. {nv_ingest_api-2025.4.15.dev20250415.dist-info → nv_ingest_api-2025.4.17.dev20250417.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,435 @@
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
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
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, queue_name: str, message: str, timeout: Optional[float] = None, for_nv_ingest: bool = False
84
+ ) -> ResponseSchema:
85
+ """
86
+ Submit a message to the specified queue.
87
+
88
+ Parameters
89
+ ----------
90
+ queue_name : str
91
+ The name of the queue.
92
+ message : str
93
+ The message to be submitted.
94
+ timeout : float, optional
95
+ Timeout in seconds for the operation.
96
+ for_nv_ingest : bool, optional
97
+ Indicates whether the message is for NV ingest operations.
98
+
99
+ Returns
100
+ -------
101
+ ResponseSchema
102
+ The response from the broker.
103
+ """
104
+ return self._handle_push(queue_name, message, timeout, for_nv_ingest)
105
+
106
+ def fetch_message(self, queue_name: str, timeout: Optional[float] = None) -> ResponseSchema:
107
+ """
108
+ Fetch a message from the specified queue.
109
+
110
+ Parameters
111
+ ----------
112
+ queue_name : str
113
+ The name of the queue.
114
+ timeout : float, optional
115
+ Timeout in seconds for the operation.
116
+
117
+ Returns
118
+ -------
119
+ ResponseSchema
120
+ The response containing the fetched message.
121
+ """
122
+ return self._handle_pop(queue_name, timeout)
123
+
124
+ def ping(self) -> ResponseSchema:
125
+ """
126
+ Ping the broker to check connectivity.
127
+
128
+ Returns
129
+ -------
130
+ ResponseSchema
131
+ The response indicating the success of the ping operation.
132
+ """
133
+ command = {"command": "PING"}
134
+ return self._execute_simple_command(command)
135
+
136
+ def size(self, queue_name: str) -> ResponseSchema:
137
+ """
138
+ Fetch the size of the specified queue.
139
+
140
+ Parameters
141
+ ----------
142
+ queue_name : str
143
+ The name of the queue.
144
+
145
+ Returns
146
+ -------
147
+ ResponseSchema
148
+ The response containing the queue size.
149
+ """
150
+ command = {"command": "SIZE", "queue_name": queue_name}
151
+ return self._execute_simple_command(command)
152
+
153
+ def _handle_push(
154
+ self, queue_name: str, message: str, timeout: Optional[float], for_nv_ingest: bool
155
+ ) -> ResponseSchema:
156
+ """
157
+ Push a message to the queue with optional timeout.
158
+
159
+ Parameters
160
+ ----------
161
+ queue_name : str
162
+ The name of the queue.
163
+ message : str
164
+ The message to push.
165
+ timeout : float, optional
166
+ Timeout in seconds for the operation.
167
+ for_nv_ingest : bool
168
+ Indicates whether the message is for NV ingest operations.
169
+
170
+ Returns
171
+ -------
172
+ ResponseSchema
173
+ The response from the broker.
174
+ """
175
+
176
+ if not queue_name or not isinstance(queue_name, str):
177
+ return ResponseSchema(response_code=1, response_reason="Invalid queue name.")
178
+ if not message or not isinstance(message, str):
179
+ return ResponseSchema(response_code=1, response_reason="Invalid message.")
180
+
181
+ if for_nv_ingest:
182
+ command = {"command": "PUSH_FOR_NV_INGEST", "queue_name": queue_name, "message": message}
183
+ else:
184
+ command = {"command": "PUSH", "queue_name": queue_name, "message": message}
185
+
186
+ if timeout is not None:
187
+ command["timeout"] = timeout
188
+
189
+ start_time = time.time()
190
+ while True:
191
+ elapsed = time.time() - start_time
192
+ remaining_timeout = (timeout - elapsed) if (timeout is not None) else None
193
+ if (remaining_timeout is not None) and (remaining_timeout <= 0):
194
+ return ResponseSchema(response_code=1, response_reason="PUSH operation timed out.")
195
+
196
+ try:
197
+ with socket.create_connection((self._host, self._port), timeout=self._connection_timeout) as sock:
198
+ self._send(sock, json.dumps(command).encode("utf-8"))
199
+ # Receive initial response with transaction ID
200
+ response_data = self._recv(sock)
201
+ response = json.loads(response_data)
202
+
203
+ if response.get("response_code") != 0:
204
+ if (
205
+ response.get("response_reason") == "Queue is full"
206
+ or response.get("response_reason") == "Queue is not available"
207
+ ):
208
+ time.sleep(0.5)
209
+ continue
210
+ else:
211
+ return ResponseSchema(**response)
212
+
213
+ if "transaction_id" not in response:
214
+ error_msg = "No transaction_id in response."
215
+ logger.error(error_msg)
216
+
217
+ return ResponseSchema(response_code=1, response_reason=error_msg)
218
+
219
+ transaction_id = response["transaction_id"]
220
+
221
+ # Send ACK
222
+ ack_data = json.dumps({"transaction_id": transaction_id, "ack": True}).encode("utf-8")
223
+ self._send(sock, ack_data)
224
+
225
+ # Receive final response
226
+ final_response_data = self._recv(sock)
227
+ final_response = json.loads(final_response_data)
228
+
229
+ return ResponseSchema(**final_response)
230
+
231
+ except (ConnectionError, socket.error, BrokenPipeError):
232
+ pass
233
+ except json.JSONDecodeError:
234
+ return ResponseSchema(response_code=1, response_reason="Invalid JSON response from server.")
235
+ except Exception as e:
236
+ return ResponseSchema(response_code=1, response_reason=str(e))
237
+
238
+ time.sleep(0.5) # Backoff delay before retry
239
+
240
+ def _handle_pop(self, queue_name: str, timeout: Optional[float]) -> ResponseSchema:
241
+ """
242
+ Pop a message from the queue with optional timeout.
243
+
244
+ Parameters
245
+ ----------
246
+ queue_name : str
247
+ The name of the queue.
248
+ timeout : float, optional
249
+ Timeout in seconds for the operation.
250
+
251
+ Returns
252
+ -------
253
+ ResponseSchema
254
+ The response containing the popped message.
255
+ """
256
+
257
+ if not queue_name or not isinstance(queue_name, str):
258
+ return ResponseSchema(response_code=1, response_reason="Invalid queue name.")
259
+
260
+ command = {"command": "POP", "queue_name": queue_name}
261
+ if timeout is not None:
262
+ command["timeout"] = timeout
263
+
264
+ start_time = time.time()
265
+ while True:
266
+ elapsed = time.time() - start_time
267
+ remaining_timeout = timeout - elapsed if timeout else None
268
+ if remaining_timeout is not None and remaining_timeout <= 0:
269
+ return ResponseSchema(response_code=1, response_reason="POP operation timed out.")
270
+
271
+ try:
272
+ with socket.create_connection((self._host, self._port), timeout=self._connection_timeout) as sock:
273
+ self._send(sock, json.dumps(command).encode("utf-8"))
274
+ # Receive initial response with transaction ID and message
275
+ response_data = self._recv(sock)
276
+ response = json.loads(response_data)
277
+
278
+ if response.get("response_code") != 0:
279
+ if response.get("response_reason") == "Queue is empty":
280
+ time.sleep(0.1)
281
+ continue
282
+ else:
283
+ return ResponseSchema(**response)
284
+
285
+ if "transaction_id" not in response:
286
+ error_msg = "No transaction_id in response."
287
+
288
+ return ResponseSchema(response_code=1, response_reason=error_msg)
289
+
290
+ transaction_id = response["transaction_id"]
291
+ message = response.get("response")
292
+
293
+ # Send ACK
294
+ ack_data = json.dumps({"transaction_id": transaction_id, "ack": True}).encode("utf-8")
295
+ self._send(sock, ack_data)
296
+
297
+ # Receive final response
298
+ final_response_data = self._recv(sock)
299
+ final_response = json.loads(final_response_data)
300
+
301
+ if final_response.get("response_code") == 0:
302
+ return ResponseSchema(response_code=0, response=message, transaction_id=transaction_id)
303
+ else:
304
+ return ResponseSchema(**final_response)
305
+
306
+ except (ConnectionError, socket.error, BrokenPipeError):
307
+ pass
308
+ except json.JSONDecodeError:
309
+ return ResponseSchema(response_code=1, response_reason="Invalid JSON response from server.")
310
+ except Exception as e:
311
+ return ResponseSchema(response_code=1, response_reason=str(e))
312
+
313
+ time.sleep(0.1) # Backoff delay before retry
314
+
315
+ def _execute_simple_command(self, command: dict) -> ResponseSchema:
316
+ """
317
+ Execute a simple command on the broker and process the response.
318
+
319
+ Parameters
320
+ ----------
321
+ command : dict
322
+ The command to execute.
323
+
324
+ Returns
325
+ -------
326
+ ResponseSchema
327
+ The response from the broker.
328
+ """
329
+
330
+ if isinstance(command, dict):
331
+ data = json.dumps(command).encode("utf-8")
332
+ elif isinstance(command, str):
333
+ data = command.encode("utf-8")
334
+
335
+ try:
336
+ with socket.create_connection((self._host, self._port), timeout=self._connection_timeout) as sock:
337
+ self._send(sock, data)
338
+ response_data = self._recv(sock)
339
+ response = json.loads(response_data)
340
+ return ResponseSchema(**response)
341
+ except (ConnectionError, socket.error, BrokenPipeError) as e:
342
+ return ResponseSchema(response_code=1, response_reason=f"Connection error: {e}")
343
+ except json.JSONDecodeError:
344
+ return ResponseSchema(response_code=1, response_reason="Invalid JSON response from server.")
345
+ except Exception as e:
346
+ return ResponseSchema(response_code=1, response_reason=str(e))
347
+
348
+ def _send(self, sock: socket.socket, data: bytes) -> None:
349
+ """
350
+ Send data over a socket connection with a length header.
351
+
352
+ Parameters
353
+ ----------
354
+ sock : socket.socket
355
+ The socket connection.
356
+ data : bytes
357
+ The data to send.
358
+
359
+ Raises
360
+ ------
361
+ ConnectionError
362
+ If sending data fails.
363
+ """
364
+
365
+ total_length = len(data)
366
+ if total_length == 0:
367
+ raise ValueError("Cannot send an empty message.")
368
+
369
+ try:
370
+ sock.sendall(total_length.to_bytes(8, "big"))
371
+ sock.sendall(data)
372
+ except (socket.error, BrokenPipeError):
373
+ raise ConnectionError("Failed to send data.")
374
+
375
+ def _recv(self, sock: socket.socket) -> str:
376
+ """
377
+ Receive data from a socket connection based on a length header.
378
+
379
+ Parameters
380
+ ----------
381
+ sock : socket.socket
382
+ The socket connection.
383
+
384
+ Returns
385
+ -------
386
+ str
387
+ The received data as a string.
388
+
389
+ Raises
390
+ ------
391
+ ConnectionError
392
+ If receiving data fails.
393
+ """
394
+
395
+ try:
396
+ length_header = self._recv_exact(sock, 8)
397
+ if not length_header:
398
+ raise ConnectionError("Incomplete length header received.")
399
+ total_length = int.from_bytes(length_header, "big")
400
+ data_bytes = self._recv_exact(sock, total_length)
401
+ if not data_bytes:
402
+ raise ConnectionError("Incomplete message received.")
403
+ return data_bytes.decode("utf-8")
404
+ except (socket.error, BrokenPipeError, ConnectionError):
405
+ raise ConnectionError("Failed to receive data.")
406
+
407
+ def _recv_exact(self, sock: socket.socket, num_bytes: int) -> Optional[bytes]:
408
+ """
409
+ Receive an exact number of bytes from a socket connection.
410
+
411
+ Parameters
412
+ ----------
413
+ sock : socket.socket
414
+ The socket connection.
415
+ num_bytes : int
416
+ The number of bytes to receive.
417
+
418
+ Returns
419
+ -------
420
+ Optional[bytes]
421
+ The received bytes, or None if the connection is closed.
422
+ """
423
+
424
+ data = bytearray()
425
+ while len(data) < num_bytes:
426
+ try:
427
+ packet = sock.recv(num_bytes - len(data))
428
+ if not packet:
429
+ return None
430
+ data.extend(packet)
431
+ except socket.timeout:
432
+ return None
433
+ except Exception:
434
+ return None
435
+ 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.