Skip to content

Commit

Permalink
Updating exit codes and improving error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
tombaeyens committed Feb 25, 2025
1 parent cc54b2d commit 09b11c3
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 155 deletions.
222 changes: 111 additions & 111 deletions soda-core/src/soda_core/cli/soda.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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}")

Expand Down Expand Up @@ -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":
Expand All @@ -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__":
Expand Down
9 changes: 6 additions & 3 deletions soda-core/src/soda_core/common/data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions soda-core/src/soda_core/common/data_source_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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 = {
Expand Down
8 changes: 5 additions & 3 deletions soda-core/src/soda_core/common/data_source_parser.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -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] = {}
Expand All @@ -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
)

Expand Down
7 changes: 4 additions & 3 deletions soda-core/src/soda_core/common/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 09b11c3

Please sign in to comment.