Skip to content

Commit c361e07

Browse files
vdk-core: Support for dataframe ingestion (#3181)
This PR amends the send_object_for_ingestion method to be able to accept a pandas DataFrame object, extract its column_names and rows in list form, and pass them on to the send_tabular_data_for_ingestion method. --------- Signed-off-by: Gabriel Georgiev <gageorgiev@vmware.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent e6df56c commit c361e07

4 files changed

Lines changed: 43 additions & 2 deletions

File tree

projects/vdk-core/src/vdk/api/job_input.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,18 @@ def send_object_for_ingestion(
150150
The method then returns immediately unless ingester_wait_to_finish_after_every_send flag is set to true.
151151
The data is then processed and sent asynchronously by multiple background threads in parallel
152152
(the level of parallelism could be controlled by ingester_number_of_worker_threads configuration).
153+
Users should not modify a payload object after it has been passed to a send_object_for_ingestion call,
154+
as this can be thread-unsafe and cause inconsistensies in the data.
153155
154156
Arguments:
155-
payload: dict
157+
payload: Union[dict, "pandas.DataFrame"]
156158
The passed object will be translated to a row in destination table.
157159
Keys of the object are translated to columns in the table and values will populate a single row.
158160
161+
The payload argument can be either a dict or a pandas.DataFrame.
162+
The pandas library is an optional dependency; ensure it is installed and included in
163+
the job's requirements before deployment.
164+
159165
Note:
160166
This method hides technical complexities around @type and @id
161167
described in the specification, still you have the freedom to

projects/vdk-core/src/vdk/internal/builtin_plugins/ingestion/ingester_base.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing import List
1111
from typing import Optional
1212
from typing import Tuple
13+
from typing import Union
1314

1415
from vdk.api.job_input import IIngester
1516
from vdk.api.plugin.plugin_input import IIngesterPlugin
@@ -120,7 +121,7 @@ def __init__(
120121

121122
def send_object_for_ingestion(
122123
self,
123-
payload: dict,
124+
payload: Union[dict, "pandas.DataFrame"],
124125
destination_table: Optional[str],
125126
method: Optional[str],
126127
target: Optional[str] = None,
@@ -129,6 +130,16 @@ def send_object_for_ingestion(
129130
"""
130131
See parent doc
131132
"""
133+
if self.__object_is_data_frame(payload):
134+
return self.send_tabular_data_for_ingestion(
135+
rows=payload.itertuples(index=False),
136+
column_names=payload.columns.tolist(),
137+
destination_table=destination_table,
138+
method=method,
139+
target=target,
140+
collection_id=collection_id,
141+
)
142+
132143
if collection_id is None:
133144
collection_id = "{data_job_name}|{execution_id}".format(
134145
data_job_name=self._data_job_name, execution_id=self._op_id
@@ -703,3 +714,16 @@ def __verify_payload_format(self, payload_dict: dict):
703714
resolvable_by=ResolvableBy.USER_ERROR,
704715
)
705716
)
717+
718+
@staticmethod
719+
def __object_is_data_frame(obj: Union[dict, "pandas.DataFrame"]) -> bool:
720+
log.debug("Checking if object to be ingested is a DataFrame")
721+
try:
722+
import pandas
723+
724+
return isinstance(obj, pandas.DataFrame)
725+
except ImportError:
726+
log.debug(
727+
"`pandas` package not found in the current environment, object is (probably) not a dataframe"
728+
)
729+
return False

projects/vdk-core/tests/functional/ingestion/jobs/ingest-job/1_step.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright 2023-2024 Broadcom
22
# SPDX-License-Identifier: Apache-2.0
3+
from pandas import DataFrame
34
from vdk.api.job_input import IJobInput
45

56

@@ -23,3 +24,9 @@ def run(job_input: IJobInput):
2324
destination_table="tabular_table",
2425
method="memory",
2526
)
27+
28+
df = DataFrame.from_dict({"A": [1], "B": [2], "C": [3]})
29+
30+
job_input.send_object_for_ingestion(
31+
payload=df, destination_table="dataframe_table", method="memory"
32+
)

projects/vdk-core/tests/functional/ingestion/test_run_ingest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ def test_run_ingest():
5252
assert ingest_plugin.payloads[1].payload[0] == expected_rows_object
5353
assert ingest_plugin.payloads[1].destination_table == "tabular_table"
5454

55+
expected_df_object = {"A": 1, "B": 2, "C": 3}
56+
assert ingest_plugin.payloads[2].payload[0] == expected_df_object
57+
assert ingest_plugin.payloads[2].destination_table == "dataframe_table"
58+
5559

5660
def test_run_ingest_fails():
5761
runner = CliEntryBasedTestRunner(FailingIngestIntoMemoryPlugin())

0 commit comments

Comments
 (0)