From 5f79ab55b7399b46cfe5255bc5ba3a855d446c4f Mon Sep 17 00:00:00 2001 From: Zach Maddox Date: Tue, 15 Sep 2026 15:26:02 -0400 Subject: [PATCH 1/2] support dlo output object schema --- src/datacustomcode/deploy.py | 8 +-- tests/test_deploy.py | 114 ++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 6 deletions(-) diff --git a/src/datacustomcode/deploy.py b/src/datacustomcode/deploy.py index e8c4ec4..cac2cdb 100644 --- a/src/datacustomcode/deploy.py +++ b/src/datacustomcode/deploy.py @@ -516,15 +516,13 @@ def create_data_transform( "version": "56.0", } - # outputDataObjects is only set for DMO-backed transforms. The server requires - # the schema of any DMO created/updated by the transform; DLO transforms use - # an existing materialized table and must not include this field. - if isinstance(data_transform_config.permissions.write, DmoPermission): - if not data_transform_config.dataObjects: + if not data_transform_config.dataObjects: + if isinstance(data_transform_config.permissions.write, DmoPermission): raise ValueError( "DMO transforms require 'dataObjects' in config.json describing " "the schema of each output DMO." ) + else: definition["outputDataObjects"] = [ _data_object_to_output(obj) for obj in data_transform_config.dataObjects ] diff --git a/tests/test_deploy.py b/tests/test_deploy.py index af804d3..87c25e5 100644 --- a/tests/test_deploy.py +++ b/tests/test_deploy.py @@ -1018,6 +1018,37 @@ def test_get_config_dmo_with_data_objects(self, mock_file): assert obj.fields[0].dataType == "text" assert obj.fields[0].keyQualifierFieldName == "KQ_Id1__c" + @patch( + "builtins.open", + new_callable=mock_open, + read_data=( + '{"sdkVersion": "0.1.14", "entryPoint": "entrypoint.py", ' + '"dataspace": "default", ' + '"permissions": {"read": {"dlo": ["input_dlo"]}, ' + '"write": {"dlo": ["output_dlo"]}}, ' + '"dataObjects": [{' + '"name": "output_dlo", ' + '"label": "Output DLO", ' + '"type": "dataLakeObject", ' + '"category": "profile", ' + '"fields": [{"name": "Id__c", "label": "Id", ' + '"dataType": "text", "isPrimaryKey": true, ' + '"keyQualifierFieldName": "KQ_Id1__c"}]' + "}]}" + ), + ) + def test_get_config_dlo_with_data_objects(self, mock_file): + """config.json parses the optional dataObjects schema for DLO writes.""" + result = get_config("/test/dir") + assert isinstance(result, DataTransformConfig) + assert result.dataObjects is not None + assert len(result.dataObjects) == 1 + obj = result.dataObjects[0] + assert obj.name == "output_dlo" + assert obj.category == "profile" + assert obj.fields[0].dataType == "text" + assert obj.fields[0].keyQualifierFieldName == "KQ_Id1__c" + class TestCreateDataTransform: @patch("datacustomcode.deploy.get_config") @@ -1286,12 +1317,93 @@ def test_create_data_transform_dmo_emits_output_data_objects( } ] + @patch("datacustomcode.deploy.get_config") + @patch("datacustomcode.deploy._make_api_call") + def test_create_data_transform_dlo_emits_output_data_objects( + self, mock_make_api_call, mock_get_config + ): + """DLO transforms include outputDataObjects with transformed field names.""" + access_token = AccessTokenResponse( + access_token="test_token", instance_url="https://instance.example.com" + ) + metadata = CodeExtensionMetadata( + name="test_package", + version="1.0.0", + description="DLO with schema", + computeType="CPU_M", + codeType="script", + ) + + data_transform_config = DataTransformConfig( + sdkVersion="0.1.14", + entryPoint="entrypoint.py", + dataspace="default", + permissions=Permissions( + read=DloPermission(dlo=["input_dlo"]), + write=DloPermission(dlo=["output_dlo"]), + ), + dataObjects=[ + DataObject( + name="output_dlo", + label="Output DLO", + type="dataLakeObject", + category="profile", + fields=[ + DataObjectField( + name="Id__c", + label="Id", + dataType="text", + isPrimaryKey=True, + keyQualifierFieldName="KQ_Id1__c", + ), + DataObjectField( + name="KQ_Id1__c", + label="Key Qualifier Id", + dataType="text", + isPrimaryKey=False, + keyQualifierFieldName=None, + ), + ], + ) + ], + ) + mock_make_api_call.return_value = {"id": "transform_id"} + + create_data_transform( + "/test/dir", access_token, metadata, data_transform_config + ) + + request_body = mock_make_api_call.call_args[1]["json"] + assert request_body["definition"]["outputDataObjects"] == [ + { + "category": "profile", + "fields": [ + { + "isPrimaryKey": True, + "keyQualifierField": "KQ_Id1__c", + "label": "Id", + "name": "Id__c", + "type": "text", + }, + { + "isPrimaryKey": False, + "label": "Key Qualifier Id", + "name": "KQ_Id1__c", + "type": "text", + }, + ], + "label": "Output DLO", + "name": "output_dlo", + "type": "dataLakeObject", + } + ] + @patch("datacustomcode.deploy.get_config") @patch("datacustomcode.deploy._make_api_call") def test_create_data_transform_dlo_omits_output_data_objects( self, mock_make_api_call, mock_get_config ): - """DLO transforms must not include outputDataObjects in the payload.""" + """DLO transforms without dataObjects omit outputDataObjects from the payload.""" access_token = AccessTokenResponse( access_token="test_token", instance_url="https://instance.example.com" ) From 47b47eebce28e175835e2f95c29ee8f2641a57e4 Mon Sep 17 00:00:00 2001 From: Zach Maddox Date: Tue, 15 Sep 2026 22:13:27 -0400 Subject: [PATCH 2/2] linter fix --- tests/test_deploy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_deploy.py b/tests/test_deploy.py index 87c25e5..c5b0f86 100644 --- a/tests/test_deploy.py +++ b/tests/test_deploy.py @@ -1403,7 +1403,7 @@ def test_create_data_transform_dlo_emits_output_data_objects( def test_create_data_transform_dlo_omits_output_data_objects( self, mock_make_api_call, mock_get_config ): - """DLO transforms without dataObjects omit outputDataObjects from the payload.""" + """DLO transforms without dataObjects omit outputDataObjects from the payload""" access_token = AccessTokenResponse( access_token="test_token", instance_url="https://instance.example.com" )