Skip to content

Commit 5136e48

Browse files
committed
Use new endpoint to upload contract files
* Solves issue where contract files couldn't be uploaded when failed rows where disabled
1 parent f9feb40 commit 5136e48

3 files changed

Lines changed: 36 additions & 65 deletions

File tree

soda-core/src/soda_core/common/soda_cloud.py

Lines changed: 32 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from decimal import Decimal
99
from enum import Enum
1010
from logging import LogRecord
11-
from tempfile import TemporaryFile
1211
from time import sleep
1312
from typing import Any, Optional
1413

@@ -32,7 +31,7 @@
3231
from soda_core.common.logs import Location, Logs
3332
from soda_core.common.soda_cloud_dto import CheckAttribute, CheckAttributes
3433
from soda_core.common.statements.metadata_columns_query import ColumnMetadata
35-
from soda_core.common.version import SODA_CORE_VERSION, clean_soda_core_version
34+
from soda_core.common.version import SODA_CORE_VERSION
3635
from soda_core.common.yaml import SodaCloudYamlSource, YamlObject
3736
from soda_core.contracts.contract_publication import ContractPublicationResult
3837
from soda_core.contracts.contract_verification import (
@@ -234,10 +233,6 @@ def mark_scan_as_failed(
234233
request_log_name="mark_scan_as_failed",
235234
)
236235

237-
def upload_contract_file(self, contract_yaml_source_str: str, soda_cloud_file_path: str) -> Optional[str]:
238-
logger.debug(f"Sending results to Soda Cloud {Emoticons.CLOUD}")
239-
return self._upload_scan_yaml_file(yaml_str=contract_yaml_source_str, soda_cloud_file_path=soda_cloud_file_path)
240-
241236
def send_contract_result(self, contract_verification_result: ContractVerificationResult) -> Optional[dict]:
242237
"""
243238
Returns A scanId string if a 200 OK was received, None otherwise
@@ -276,9 +271,7 @@ def trigger_contract_skeleton_generation(self, dataset_identifier: DatasetIdenti
276271
logger.info(f"{Emoticons.OK_HAND} Contract skeleton generation triggered on Soda Cloud")
277272

278273
def send_contract_skeleton(self, contract_yaml_str: str, soda_cloud_file_path: str) -> None:
279-
file_id: Optional[str] = self._upload_scan_yaml_file(
280-
yaml_str=contract_yaml_str, soda_cloud_file_path=soda_cloud_file_path
281-
)
274+
file_id: Optional[str] = self._upload_contract_yaml_file(contract_yaml_str)
282275
if file_id:
283276
command_json_dict: dict = {
284277
"type": "sodaCoreUpsertDraftContract",
@@ -293,50 +286,30 @@ def send_contract_skeleton(self, contract_yaml_str: str, soda_cloud_file_path: s
293286

294287
logger.debug(f"Response from Soda Cloud: {response.json()}")
295288

296-
def _upload_scan_yaml_file(
297-
self,
298-
yaml_str: str,
299-
soda_cloud_file_path: str,
300-
) -> Optional[str]:
289+
def _upload_contract_yaml_file(self, contract_yaml: str) -> Optional[str]:
301290
"""
302291
Returns a Soda Cloud fileId or None if something is wrong.
303292
"""
304293
try:
305-
with TemporaryFile() as temp_file:
306-
rows_json_bytes = bytearray(yaml_str, "utf-8")
307-
temp_file.write(rows_json_bytes)
308-
309-
file_size_in_bytes = temp_file.tell()
310-
temp_file.seek(0)
311-
312-
headers = {
313-
"Authorization": self._get_token(),
314-
"Content-Type": "application/yaml",
315-
"Is-V3": "true",
316-
"File-Path": soda_cloud_file_path,
317-
"Soda-Library-Version": clean_soda_core_version(),
318-
}
319-
320-
if file_size_in_bytes == 0:
321-
# because of https://github.com/psf/requests/issues/4215 we can't send content size
322-
# when the size is 0 since requests blocks then on I/O indefinitely
323-
logger.warning("Empty file upload detected, not sending Content-Length header")
324-
else:
325-
headers["Content-Length"] = str(file_size_in_bytes)
326-
327-
upload_response = self._http_post(url=f"{self.api_url}/scan/upload", headers=headers, data=temp_file)
328-
upload_response_json = upload_response.json()
329-
330-
if isinstance(upload_response_json, dict) and "fileId" in upload_response_json:
331-
return upload_response_json.get("fileId")
332-
else:
333-
logger.critical(f"No fileId received in response: {upload_response_json}")
334-
return None
294+
upload_contract_command: dict = {
295+
"type": "sodaCoreUploadContractFile",
296+
"contents": contract_yaml,
297+
}
298+
response: Response = self._execute_command(
299+
command_json_dict=upload_contract_command, request_log_name="upload_contract_file"
300+
)
301+
response_json = response.json()
302+
if isinstance(response_json, dict) and "fileId" in response_json:
303+
return response_json.get("fileId")
304+
else:
305+
logger.critical(f"No fileId received in response: {response_json}")
306+
return None
335307
except Exception as e:
336308
logger.critical(
337-
msg=f"Soda cloud error: Could not upload contract " f"to Soda Cloud: {e}",
309+
msg=f"Soda cloud error: Could not upload contract file to Soda Cloud: {e}",
338310
exc_info=True,
339311
)
312+
return None
340313

341314
def test_connection(self) -> None:
342315
"""
@@ -368,12 +341,7 @@ def publish_contract(self, contract_yaml: Optional[ContractYaml]) -> ContractPub
368341
logger.error(f"Skipping contract publication because of insufficient permissions: {reason}")
369342
return ContractPublicationResult(contract=None)
370343

371-
soda_cloud_file_path: str = (
372-
contract_local_file_path if isinstance(contract_local_file_path, str) else "contract.yml"
373-
)
374-
file_id: Optional[str] = self._upload_scan_yaml_file(
375-
yaml_str=contract_yaml_str_original, soda_cloud_file_path=soda_cloud_file_path
376-
)
344+
file_id: Optional[str] = self._upload_contract_yaml_file(contract_yaml_str_original)
377345
if not file_id:
378346
logger.critical("Uploading the contract file failed")
379347
return ContractPublicationResult(contract=None)
@@ -382,7 +350,12 @@ def publish_contract(self, contract_yaml: Optional[ContractYaml]) -> ContractPub
382350
"type": "sodaCorePublishContract",
383351
"contract": {
384352
"fileId": file_id,
385-
"metadata": {"source": {"type": "local", "filePath": contract_local_file_path}},
353+
"metadata": {
354+
"source": {
355+
"type": "local",
356+
"filePath": contract_local_file_path,
357+
},
358+
},
386359
},
387360
}
388361
response: Response = self._execute_command(
@@ -509,12 +482,7 @@ def verify_contract_on_agent(
509482
logger.error(f"Skipping contract verification because of insufficient permissions: {reason}")
510483
return verification_result
511484

512-
soda_cloud_file_path: str = (
513-
contract_local_file_path if isinstance(contract_local_file_path, str) else "contract.yml"
514-
)
515-
file_id: Optional[str] = self._upload_scan_yaml_file(
516-
yaml_str=contract_yaml_str_original, soda_cloud_file_path=soda_cloud_file_path
517-
)
485+
file_id: Optional[str] = self._upload_contract_yaml_file(contract_yaml_str_original)
518486
if not file_id:
519487
logger.critical(f"Contract wasn't uploaded so skipping " "sending the results to Soda Cloud")
520488
return []
@@ -523,7 +491,12 @@ def verify_contract_on_agent(
523491
"type": "sodaCoreVerifyContract" if publish_results else "sodaCoreTestContract",
524492
"contract": {
525493
"fileId": file_id,
526-
"metadata": {"source": {"type": "local", "filePath": contract_local_file_path}},
494+
"metadata": {
495+
"source": {
496+
"type": "local",
497+
"filePath": contract_local_file_path,
498+
},
499+
},
527500
},
528501
"verbose": verbose,
529502
"variables": variables,

soda-core/src/soda_core/contracts/impl/contract_verification_impl.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -509,13 +509,10 @@ def verify(self) -> ContractVerificationResult:
509509
soda_cloud_file_id: Optional[str] = None
510510
sending_results_to_soda_cloud_failed: bool = False
511511
contract_yaml_source_str_original = self.contract_yaml.contract_yaml_source.yaml_str_original
512-
soda_cloud_file_path: str = f"{self.soda_qualified_dataset_name.lower()}.yml"
513512
soda_cloud_response_json: Optional[dict] = None
514513

515514
if self.soda_cloud and self.publish_results:
516-
soda_cloud_file_id = self.soda_cloud.upload_contract_file(
517-
contract_yaml_source_str=contract_yaml_source_str_original, soda_cloud_file_path=soda_cloud_file_path
518-
)
515+
soda_cloud_file_id = self.soda_cloud._upload_contract_yaml_file(contract_yaml_source_str_original)
519516

520517
contract_verification_result: ContractVerificationResult = ContractVerificationResult(
521518
contract=Contract(

soda-tests/tests/components/test_soda_cloud.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def test_soda_cloud_results(data_source_test_helper: DataSourceTestHelper, env_v
101101

102102
data_source_test_helper.assert_contract_pass(
103103
test_table=test_table,
104-
contract_yaml_str=f"""
104+
contract_yaml_str="""
105105
columns:
106106
- name: id
107107
- name: age
@@ -122,7 +122,8 @@ def test_soda_cloud_results(data_source_test_helper: DataSourceTestHelper, env_v
122122

123123
request_index = 0
124124
request_1: MockRequest = data_source_test_helper.soda_cloud.requests[request_index]
125-
assert request_1.url.endswith("api/scan/upload")
125+
assert request_1.url.endswith("api/command")
126+
assert request_1.json["type"] == "sodaCoreUploadContractFile"
126127

127128
request_index += 1
128129
request_2: MockRequest = data_source_test_helper.soda_cloud.requests[request_index]

0 commit comments

Comments
 (0)