diff --git a/soda-core/src/soda_core/cli/soda.py b/soda-core/src/soda_core/cli/soda.py index 2ef52c427..ffc09cb07 100644 --- a/soda-core/src/soda_core/cli/soda.py +++ b/soda-core/src/soda_core/cli/soda.py @@ -60,8 +60,10 @@ def verify_contract( contract_verification_builder.with_soda_cloud_yaml_file(soda_cloud_file_path) contract_verification_result: ContractVerificationResult = contract_verification_builder.execute() - if not contract_verification_result.is_ok(): - exit(1) + if contract_verification_result.has_failures(): + exit(2) + elif contract_verification_result.has_errors(): + exit(3) def publish_contract(contract_file_paths: list[str] | None): @@ -108,7 +110,7 @@ def _test_data_source(data_source_file_path: str): if error_message: print(f"Could not connect {Emoticons.POLICE_CAR_LIGHT} using data source '{data_source_file_path}': " f"{error_message}") - exit(1) + exit(2) else: print(f"Success! Connection in '{data_source_file_path}' tested ok. {Emoticons.WHITE_CHECK_MARK}") @@ -144,116 +146,116 @@ def _test_soda_cloud(soda_cloud_file_path: str): error_msg = soda_cloud.test_connection() if error_msg: print(f"{Emoticons.POLICE_CAR_LIGHT} Could not connect to Soda Cloud: {error_msg}") - exit(1) + exit(3) else: print(f"{Emoticons.WHITE_CHECK_MARK} Success! Tested Soda Cloud credentials in '{soda_cloud_file_path}'") def main(): - print(dedent(""" - __| _ \| \ \\ - \__ \ ( | | _ \\ - ____/\___/___/_/ _\\ CLI 4.0.0.dev?? - """).strip("\n")) - - cli_parser = argparse.ArgumentParser(epilog="Run 'soda {command} -h' for help on a particular soda command") - - sub_parsers = cli_parser.add_subparsers(dest="command", help='Soda command description') - verify_parser = sub_parsers.add_parser('verify', help='Verify a contract') - - verify_parser.add_argument( - "-c", "--contract", - type=str, - nargs='+', - help="One or more contract file paths." - ) - verify_parser.add_argument( - "-ds", "--data-source", - type=str, - help="The data source configuration file." - ) - verify_parser.add_argument( - "-sc", "--soda-cloud", - type=str, - help="A Soda Cloud configuration file path." - ) - verify_parser.add_argument( - "-a", "--use-agent", - const=True, - action='store_const', - default=False, - help="Executes contract verification on Soda Agent instead of locally in this library." - ) - verify_parser.add_argument( - "-sp", "--skip-publish", - const=True, - action='store_const', - default=False, - help="Skips publishing of the contract when sending results to Soda Cloud. Precondition: The contract version " - "must already exist on Soda Cloud." - ) - verify_parser.add_argument( - "-v", "--verbose", - const=True, - action='store_const', - default=False, - help="Show more detailed logs on the console." - ) - - publish_parser = sub_parsers.add_parser('publish', help='Publish a contract (not yet implemented)') - publish_parser.add_argument( - "-c", "--contract", - type=str, - nargs='+', - help="One or more contract file paths." - ) - - create_data_source_parser = sub_parsers.add_parser( - name="create-data-source", - help="Create a data source YAML configuration file" - ) - create_data_source_parser.add_argument( - "-f", "--file", - type=str, - help="The path to the file to be created. (directories will be created if needed)" - ) - create_data_source_parser.add_argument( - "-t", "--type", - type=str, - default="postgres", - help="Type of the data source. Eg postgres" - ) - - test_parser = sub_parsers.add_parser('test-data-source', help='Test a data source connection') - test_parser.add_argument( - "-ds", "--data-source", - type=str, - help="The name of a configured data source to test." - ) - - create_soda_cloud_parser = sub_parsers.add_parser( - name="create-soda-cloud", - help="Create a Soda Cloud YAML configuration file" - ) - create_soda_cloud_parser.add_argument( - "-f", "--file", - type=str, - help="The path to the file to be created. (directories will be created if needed)" - ) - - test_parser = sub_parsers.add_parser('test-soda-cloud', help='Test the Soda Cloud connection') - test_parser.add_argument( - "-sc", "--soda-cloud", - type=str, - help="A Soda Cloud configuration file path." - ) - - args = cli_parser.parse_args() - - verbose = args.verbose if hasattr(args, "verbose") else False - configure_logging(verbose) - try: + print(dedent(""" + __| _ \| \ \\ + \__ \ ( | | _ \\ + ____/\___/___/_/ _\\ CLI 4.0.0.dev?? + """).strip("\n")) + + cli_parser = argparse.ArgumentParser(epilog="Run 'soda {command} -h' for help on a particular soda command") + + sub_parsers = cli_parser.add_subparsers(dest="command", help='Soda command description') + verify_parser = sub_parsers.add_parser('verify', help='Verify a contract') + + verify_parser.add_argument( + "-c", "--contract", + type=str, + nargs='+', + help="One or more contract file paths." + ) + verify_parser.add_argument( + "-ds", "--data-source", + type=str, + help="The data source configuration file." + ) + verify_parser.add_argument( + "-sc", "--soda-cloud", + type=str, + help="A Soda Cloud configuration file path." + ) + verify_parser.add_argument( + "-a", "--use-agent", + const=True, + action='store_const', + default=False, + help="Executes contract verification on Soda Agent instead of locally in this library." + ) + verify_parser.add_argument( + "-sp", "--skip-publish", + const=True, + action='store_const', + default=False, + help="Skips publishing of the contract when sending results to Soda Cloud. Precondition: The contract version " + "must already exist on Soda Cloud." + ) + verify_parser.add_argument( + "-v", "--verbose", + const=True, + action='store_const', + default=False, + help="Show more detailed logs on the console." + ) + + publish_parser = sub_parsers.add_parser('publish', help='Publish a contract (not yet implemented)') + publish_parser.add_argument( + "-c", "--contract", + type=str, + nargs='+', + help="One or more contract file paths." + ) + + create_data_source_parser = sub_parsers.add_parser( + name="create-data-source", + help="Create a data source YAML configuration file" + ) + create_data_source_parser.add_argument( + "-f", "--file", + type=str, + help="The path to the file to be created. (directories will be created if needed)" + ) + create_data_source_parser.add_argument( + "-t", "--type", + type=str, + default="postgres", + help="Type of the data source. Eg postgres" + ) + + test_parser = sub_parsers.add_parser('test-data-source', help='Test a data source connection') + test_parser.add_argument( + "-ds", "--data-source", + type=str, + help="The name of a configured data source to test." + ) + + create_soda_cloud_parser = sub_parsers.add_parser( + name="create-soda-cloud", + help="Create a Soda Cloud YAML configuration file" + ) + create_soda_cloud_parser.add_argument( + "-f", "--file", + type=str, + help="The path to the file to be created. (directories will be created if needed)" + ) + + test_parser = sub_parsers.add_parser('test-soda-cloud', help='Test the Soda Cloud connection') + test_parser.add_argument( + "-sc", "--soda-cloud", + type=str, + help="A Soda Cloud configuration file path." + ) + + args = cli_parser.parse_args() + + verbose = args.verbose if hasattr(args, "verbose") else False + configure_logging(verbose) + if args.command == "verify": verify_contract(args.contract, args.data_source, args.soda_cloud, args.skip_publish, args.use_agent) elif args.command == "publish": @@ -268,12 +270,10 @@ def main(): _test_soda_cloud(args.soda_cloud) else: cli_parser.print_help() + except Exception as e: - cli_parser.print_help() - print() traceback.print_exc() - exit(1) - exit(0) + exit(3) if __name__ == "__main__": diff --git a/soda-core/src/soda_core/common/data_source.py b/soda-core/src/soda_core/common/data_source.py index f9867f080..0784c6e61 100644 --- a/soda-core/src/soda_core/common/data_source.py +++ b/soda-core/src/soda_core/common/data_source.py @@ -6,7 +6,7 @@ from soda_core.common.data_source_connection import DataSourceConnection from soda_core.common.data_source_results import QueryResult, UpdateResult -from soda_core.common.logs import Logs +from soda_core.common.logs import Logs, Emoticons from soda_core.common.sql_dialect import SqlDialect from soda_core.common.statements.metadata_columns_query import MetadataColumnsQuery, ColumnMetadata from soda_core.common.statements.metadata_tables_query import MetadataTablesQuery @@ -135,10 +135,13 @@ def get_format_regex(self, format: str) -> str | None: if format is None: return None if self.format_regexes is None: - self.logs.error("'format_regexes' not configured in data source") + self.logs.error(f"{Emoticons.POLICE_CAR_LIGHT} 'format_regexes' not configured in data source") format_regex: str | None = self.format_regexes.get(format) if format_regex is None: - self.logs.error(f"Validity format regex '{format}' not configured in data source 'format_regexes'") + self.logs.error( + f"{Emoticons.POLICE_CAR_LIGHT} Validity format regex '{format}' not configured " + f"in data source 'format_regexes'" + ) return format_regex @classmethod diff --git a/soda-core/src/soda_core/common/data_source_connection.py b/soda-core/src/soda_core/common/data_source_connection.py index 840c354e0..ab071e374 100644 --- a/soda-core/src/soda_core/common/data_source_connection.py +++ b/soda-core/src/soda_core/common/data_source_connection.py @@ -3,8 +3,10 @@ from abc import abstractmethod, ABC from importlib.util import find_spec +from cli_ui import message + from soda_core.common.data_source_results import QueryResult, UpdateResult -from soda_core.common.logs import Logs +from soda_core.common.logs import Logs, Emoticons class DataSourceConnection(ABC): @@ -48,7 +50,10 @@ def open_connection(self) -> None: self._log_connection_properties_excl_pwd(self.connection_properties) self.connection = self._create_connection(self.connection_properties) except Exception as e: - self.logs.error(f"Could not connect to '{self.name}': {e}", exception=e) + self.logs.error( + message=f"{Emoticons.POLICE_CAR_LIGHT} Could not connect to '{self.name}': {e}", + exception=e + ) def _log_connection_properties_excl_pwd(self, connection_yaml_dict: dict): dict_without_pwd = { diff --git a/soda-core/src/soda_core/common/data_source_parser.py b/soda-core/src/soda_core/common/data_source_parser.py index 9074ae1f2..0c32609af 100644 --- a/soda-core/src/soda_core/common/data_source_parser.py +++ b/soda-core/src/soda_core/common/data_source_parser.py @@ -1,7 +1,7 @@ from __future__ import annotations from soda_core.common.data_source import DataSource -from soda_core.common.logs import Logs +from soda_core.common.logs import Logs, Emoticons from soda_core.common.yaml import YamlObject, YamlFileContent @@ -33,7 +33,8 @@ def parse(self) -> DataSource | None: connection_properties = connection_yaml.to_dict() elif self.spark_session is None: self.logs.error( - "Key 'connection' containing an object of data source connection configurations is required" + f"{Emoticons.POLICE_CAR_LIGHT} Key 'connection' containing an object of data source connection " + f"configurations is required" ) format_regexes: dict[str, str] = {} @@ -45,7 +46,8 @@ def parse(self) -> DataSource | None: format_regexes[k] = v else: self.logs.error( - message=f"Invalid regex value in 'format_regexes', expected string, was '{v}'", + message=f"{Emoticons.POLICE_CAR_LIGHT} Invalid regex value in 'format_regexes', " + f"expected string, was '{v}'", location=format_regexes_yaml.location ) diff --git a/soda-core/src/soda_core/common/logs.py b/soda-core/src/soda_core/common/logs.py index ca4977f18..a69f7bc41 100644 --- a/soda-core/src/soda_core/common/logs.py +++ b/soda-core/src/soda_core/common/logs.py @@ -31,7 +31,8 @@ def __init__( self.column: int | None = column def __str__(self) -> str: - return f"{self.file_path}[{self.line},{self.column}]" + src_description: str = self.file_path if self.file_path else "source file position " + return f"{src_description}[{self.line},{self.column}]" def __hash__(self) -> int: return hash((self.line, self.column)) @@ -65,10 +66,10 @@ def __init__( self.index: int | None = index def __str__(self): - location_str = f"At {self.location}: " if self.location else "" + location_str = f" | {self.location}" if self.location else "" doc_str = f" | see https://go.soda.io/{self.doc}" if self.doc else "" exception_str = f" | {self.exception}" if self.exception else "" - return f"{location_str}{self.message}{doc_str}{exception_str}" + return f"{self.message}{location_str}{doc_str}{exception_str}" def get_dict(self) -> dict: return { diff --git a/soda-core/src/soda_core/common/soda_cloud.py b/soda-core/src/soda_core/common/soda_cloud.py index c4a71938e..bdbc33e42 100644 --- a/soda-core/src/soda_core/common/soda_cloud.py +++ b/soda-core/src/soda_core/common/soda_cloud.py @@ -146,7 +146,9 @@ def send_contract_result(self, contract_result: ContractResult, skip_publish: bo if response.status_code == 200: self.logs.info(f"{Emoticons.OK_HAND} Results sent to Soda Cloud") else: - self.logs.error("Contract wasn't uploaded so skipping sending the results to Soda Cloud") + self.logs.error( + f"{Emoticons.POLICE_CAR_LIGHT} Contract wasn't uploaded so skipping " + f"sending the results to Soda Cloud") def _build_contract_result_json(self, contract_result: ContractResult, skip_publish: bool) -> dict: check_result_cloud_json_dicts = [ @@ -239,7 +241,8 @@ def _build_check_result_cloud_dict(self, check_result: CheckResult) -> dict: "definition": check_result.check.definition, "resourceAttributes": [], # TODO "location": { - "filePath": check_result.contract.source.local_file_path, + "filePath": (check_result.contract.source.local_file_path + if isinstance(check_result.contract.source.local_file_path, str) else "yamlstr.yml"), "line": check_result.check.contract_file_line, "col": check_result.check.contract_file_column }, @@ -311,10 +314,16 @@ def _upload_contract(self, yaml_str_source: str, soda_cloud_file_path: str) -> s if isinstance(upload_response_json, dict) and "fileId" in upload_response_json: return upload_response_json.get("fileId") else: - self.logs.error(f"No fileId received in response: {upload_response_json}") + self.logs.error( + f"{Emoticons.POLICE_CAR_LIGHT} No fileId received in response: {upload_response_json}" + ) return None except Exception as e: - self.logs.error(f"Soda cloud error: Could not upload contract: {e}", exception=e) + self.logs.error( + message=f"{Emoticons.POLICE_CAR_LIGHT} Soda cloud error: Could not upload contract " + f"to Soda Cloud: {e}", + exception=e + ) def test_connection(self) -> Optional[str]: """ @@ -349,7 +358,10 @@ def execute_contracts_on_agent(self, contract_yamls: list[ContractYaml]) -> list soda_cloud_file_path=soda_cloud_file_path ) if not file_id: - self.logs.error("Contract wasn't uploaded so skipping sending the results to Soda Cloud") + self.logs.error( + f"{Emoticons.POLICE_CAR_LIGHT} Contract wasn't uploaded so skipping " + "sending the results to Soda Cloud" + ) return [] verify_contract_command: dict = { @@ -416,7 +428,8 @@ def execute_contracts_on_agent(self, contract_yamls: list[ContractYaml]) -> list self.logs.debug(f"Expected dict for logs, but was {type(logs).__name__}") if not scan_is_finished: - self.logs.error("Max retries exceeded. Contract verification did not finish yet.") + self.logs.error(f"{Emoticons.POLICE_CAR_LIGHT} Max retries exceeded. " + f"Contract verification did not finish yet.") return [ContractResult( contract=Contract( @@ -473,7 +486,8 @@ def _poll_remote_scan_finished(self, scan_id: str, max_retry: int = 5) -> bool: ) sleep(time_to_wait_in_seconds) else: - self.logs.error(f"Failed to poll remote scan status. Response: {response}") + self.logs.error(f"{Emoticons.POLICE_CAR_LIGHT} Failed to poll remote scan status. " + f"Response: {response}") return False @@ -552,7 +566,7 @@ def _execute_cqrs_request( return response except Exception as e: - self.logs.error(f"Error while executing Soda Cloud {request_type} {request_log_name}", exception=e) + self.logs.error(f"{Emoticons.POLICE_CAR_LIGHT} Error while executing Soda Cloud {request_type} {request_log_name}", exception=e) def _http_post(self, request_log_name: str = None, **kwargs) -> Response: return requests.post(**kwargs) diff --git a/soda-core/src/soda_core/common/yaml.py b/soda-core/src/soda_core/common/yaml.py index bcc7f342a..263287ff9 100644 --- a/soda-core/src/soda_core/common/yaml.py +++ b/soda-core/src/soda_core/common/yaml.py @@ -285,7 +285,8 @@ def read_list(self, key: str, expected_element_type: type | None = None, require filtered_list.append(element) else: self.logs.error( - message=f"'{key}' expects a list of {expected_element_type.__name__}. " + message=f"{Emoticons.POLICE_CAR_LIGHT} YAML key '{key}' expects a " + f"list of {expected_element_type.__name__}. " f"But element {index} was {type(element).__name__}", location=self.create_location_from_yaml_dict_key(key) ) @@ -309,7 +310,8 @@ def read_list_of_objects(self, key: str) -> YamlList | None: element = list_value.yaml_list[i] if not isinstance(element, YamlObject): self.logs.error( - message=f"'{key}' expected list of objects. But element {i} was {type(element).__name__}", + message=f"{Emoticons.POLICE_CAR_LIGHT} YAML key '{key}' expected list of objects. " + f"But element {i} was {type(element).__name__}", location=self.create_location_from_yaml_dict_key(key) ) list_value.yaml_list[i] = None @@ -385,7 +387,7 @@ def read_value( if required: self.logs.error( - message=f"{key_description} is required", + message=f"{Emoticons.POLICE_CAR_LIGHT} {key_description} is required", location=self.location ) value = default_value @@ -399,7 +401,8 @@ def read_value( elif isinstance(value, list): actual_type_str = "YAML list" self.logs.error( - message=f"{key_description} expected a {expected_type.__name__}, but was {actual_type_str}", + message=f"{Emoticons.POLICE_CAR_LIGHT} {key_description} expected a {expected_type.__name__}, " + f"but was {actual_type_str}", location=self.create_location_from_yaml_dict_key(key) ) value = None diff --git a/soda-core/src/soda_core/contracts/impl/contract_verification_impl.py b/soda-core/src/soda_core/contracts/impl/contract_verification_impl.py index 41ee5a5fb..33f0f2faa 100644 --- a/soda-core/src/soda_core/contracts/impl/contract_verification_impl.py +++ b/soda-core/src/soda_core/contracts/impl/contract_verification_impl.py @@ -63,11 +63,11 @@ def __init__( data_source_parser: DataSourceParser = DataSourceParser(data_source_yaml_file_content) self.data_source = data_source_parser.parse() if self.data_source is None: - self.logs.error(f"No data source configured {Emoticons.POLICE_CAR_LIGHT}") + self.logs.error(f"{Emoticons.POLICE_CAR_LIGHT} No data source configured") elif data_source is not None or data_source_yaml_source is not None: self.logs.error( - f"When executing the contract verification on Soda Agent, " - f"a data source should not be configured {Emoticons.POLICE_CAR_LIGHT}" + f"{Emoticons.POLICE_CAR_LIGHT} When executing the contract verification on Soda Agent, " + f"a data source should not be configured" ) self.soda_cloud: SodaCloud | None = soda_cloud @@ -82,7 +82,7 @@ def __init__( self.contract_yamls: list[ContractYaml] = [] self.contract_impls: list[ContractImpl] = [] if contract_yaml_sources is None or len(contract_yaml_sources) == 0: - self.logs.error(f"No contracts configured {Emoticons.POLICE_CAR_LIGHT}") + self.logs.error(f"{Emoticons.POLICE_CAR_LIGHT} No contracts configured") else: for contract_yaml_source in contract_yaml_sources: contract_yaml: ContractYaml = ContractYaml.parse( @@ -123,7 +123,7 @@ def verify_contracts_on_agent(self, contract_yamls: list[ContractYaml]) -> list[ return self.soda_cloud.execute_contracts_on_agent(contract_yamls) else: self.logs.error( - f"Using the agent requires a Soda Cloud configuration {Emoticons.POLICE_CAR_LIGHT}" + f"{Emoticons.POLICE_CAR_LIGHT} Using the agent requires a Soda Cloud configuration" ) return [] @@ -237,7 +237,8 @@ def _get_data_timestamp(self, variables: dict[str, str], default: datetime) -> d except: pass self.logs.error( - f"Could not parse variable {now_variable_name} as a timestamp: {now_variable_timestamp_text}" + f"{Emoticons.POLICE_CAR_LIGHT} Could not parse variable {now_variable_name} " + f"as a timestamp: {now_variable_timestamp_text}" ) else: return default @@ -382,7 +383,7 @@ def _verify_duplicate_identities(cls, all_check_impls: list[CheckImpl], logs: Lo existing_check_impl: CheckImpl | None = checks_by_identity.get(check_impl.identity) if existing_check_impl: # TODO distill better diagnostic error message: which check in which column on which lines in the file - logs.error(f"Duplicate identity ({check_impl.identity})") + logs.error(f"{Emoticons.POLICE_CAR_LIGHT} Duplicate identity ({check_impl.identity})") checks_by_identity[check_impl.identity] = check_impl @@ -588,7 +589,7 @@ def create(cls, check_yaml: ThresholdCheckYaml, default_threshold: ThresholdImpl if total_config_count == 0: if default_threshold: return default_threshold - check_yaml.logs.error("Threshold required, but not specified") + check_yaml.logs.error(f"{Emoticons.POLICE_CAR_LIGHT} Threshold required, but not specified") return None if total_config_count == 1 and cls.__config_count([ @@ -614,8 +615,10 @@ def create(cls, check_yaml: ThresholdCheckYaml, default_threshold: ThresholdImpl must_be_less_than_or_equal=check_yaml.must_be_between.upper_bound, ) else: - check_yaml.logs.error("Threshold must_be_between range: " - "first value must be less than the second value") + check_yaml.logs.error( + f"{Emoticons.POLICE_CAR_LIGHT} Threshold must_be_between range: " + "first value must be less than the second value" + ) return None elif total_config_count == 1 and isinstance(check_yaml.must_be_not_between, RangeYaml): @@ -628,8 +631,10 @@ def create(cls, check_yaml: ThresholdCheckYaml, default_threshold: ThresholdImpl must_be_less_than_or_equal=check_yaml.must_be_not_between.lower_bound, ) else: - check_yaml.logs.error("Threshold must_be_not_between range: " - "first value must be less than the second value") + check_yaml.logs.error( + f"{Emoticons.POLICE_CAR_LIGHT} Threshold must_be_not_between range: " + "first value must be less than the second value" + ) return None else: lower_bound_count = cls.__config_count([check_yaml.must_be_greater_than, check_yaml.must_be_greater_than_or_equal]) @@ -790,7 +795,7 @@ def parse_check( metrics_resolver=metrics_resolver, ) else: - contract_impl.logs.error(f"Unknown check type '{check_yaml.type}'") + contract_impl.logs.error(f"{Emoticons.POLICE_CAR_LIGHT} Unknown check type '{check_yaml.type}'") def __init__( self, diff --git a/soda-core/src/soda_core/contracts/impl/contract_yaml.py b/soda-core/src/soda_core/contracts/impl/contract_yaml.py index f88b39d2d..2522c8ca5 100644 --- a/soda-core/src/soda_core/contracts/impl/contract_yaml.py +++ b/soda-core/src/soda_core/contracts/impl/contract_yaml.py @@ -4,7 +4,7 @@ from numbers import Number from typing import Optional -from soda_core.common.logs import Logs, Location +from soda_core.common.logs import Logs, Location, Emoticons from soda_core.common.yaml import YamlSource, YamlObject, YamlList, YamlValue, YamlFileContent @@ -109,7 +109,10 @@ def _parse_columns(self, contract_yaml_object: YamlObject) -> Optional[list[Opti f": {file_location}{locations_message}" if locations_message else "" ) - self.logs.error(f"Duplicate columns with name '{column_name}'{locations_message}") + self.logs.error( + f"{Emoticons.POLICE_CAR_LIGHT} Duplicate columns with " + f"name '{column_name}'{locations_message}" + ) return columns def _parse_checks( @@ -135,11 +138,11 @@ def _parse_checks( checks.append(check_yaml) else: self.logs.error( - f"Invalid check type '{check_type_name}'. " + f"{Emoticons.POLICE_CAR_LIGHT} Invalid check type '{check_type_name}'. " f"Existing check types: {CheckYaml.get_check_type_names()}" ) else: - self.logs.error(f"Checks must have a YAML object structure.") + self.logs.error(f"{Emoticons.POLICE_CAR_LIGHT} Checks must have a YAML object structure.") return checks @@ -163,7 +166,7 @@ def __init__(self, valid_reference_data_yaml: YamlObject): if self.dataset is None: self.has_configuration_error = True logs.error( - message="'dataset' is required. Must be the dataset name as a string " + message=f"{Emoticons.POLICE_CAR_LIGHT} 'dataset' is required. Must be the dataset name as a string " "or a list of strings representing the qualified name.", location=valid_reference_data_yaml.location ) @@ -199,8 +202,10 @@ def __init__(self, yaml_object: YamlObject): self.valid_reference_data = ValidReferenceDataYaml(valid_reference_data_yaml) non_reference_configurations: list[str] = self.get_non_reference_configurations() if non_reference_configurations: - yaml_object.logs.error("'valid_reference_data' is mutually exclusive with other " - f"missing and validity configurations: {non_reference_configurations}") + yaml_object.logs.error( + f"{Emoticons.POLICE_CAR_LIGHT} 'valid_reference_data' is mutually exclusive with other " + f"missing and validity configurations: {non_reference_configurations}" + ) self.has_valid_configuration_error: bool = ( ("invalid_values" in cfg_keys and self.invalid_values is None) diff --git a/soda-core/tests/soda_core/tests/components/manual_test_agent_flow.py b/soda-core/tests/soda_core/tests/components/manual_test_agent_flow.py index 2340a5605..fd1c3a1e5 100644 --- a/soda-core/tests/soda_core/tests/components/manual_test_agent_flow.py +++ b/soda-core/tests/soda_core/tests/components/manual_test_agent_flow.py @@ -14,7 +14,7 @@ def main(): load_dotenv(f"{project_root_dir}/.env", override=True) contract_yaml_str: str = dedent(""" - data_source: milan_throwaway + data_source: bus_nienu dataset_prefix: [nyc, public] dataset: bus_breakdown_and_delays columns: