Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cdisc_rules_engine/check_operators/dataframe_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ def equal_to_case_insensitive(self, other_value):
round_values=round_values,
),
axis=1,
meta=(None, "bool"),
)

@log_operator_execution
Expand Down
13 changes: 13 additions & 0 deletions cdisc_rules_engine/enums/execution_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,16 @@ class ExecutionStatus(BaseEnum):
SUCCESS = "success"
SKIPPED = "skipped"
EXECUTION_ERROR = "execution_error"
ISSUE_REPORTED = "issue_reported"
UNKNOWN_STATUS = "unknown_status"


class SkippedReason(BaseEnum):
COLUMN_NOT_FOUND_IN_DATA = "Column not found in data"
DOMAIN_NOT_FOUND = "Domain not found"
SCHEMA_VALIDATION_IS_OFF = "Schema validation is off"
OUTSIDE_SCOPE = "Outside scope"


class ExecutionError(BaseEnum):
AN_UNKNOWN_EXCEPTION_HAS_OCCURRED = "An unknown exception has occurred"
5 changes: 5 additions & 0 deletions cdisc_rules_engine/exceptions/custom_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,8 @@ class OperationError(EngineError):

class DatasetBuilderError(EngineError):
description = "Error occurred during dataset building"


class DateTimeParserError(EngineError):
code = 400
description = "Failure to parse a datetime string"
113 changes: 70 additions & 43 deletions cdisc_rules_engine/rules_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
from dateutil.parser._parser import ParserError
from business_rules import export_rule_data
from business_rules.engine import run
import os
from cdisc_rules_engine.config import config as default_config
from cdisc_rules_engine.enums.execution_status import ExecutionStatus
from cdisc_rules_engine.enums.execution_status import (
ExecutionError,
ExecutionStatus,
SkippedReason,
)
from cdisc_rules_engine.enums.rule_types import RuleTypes
from cdisc_rules_engine.exceptions.custom_exceptions import (
DatasetNotFoundError,
DateTimeParserError,
DomainNotFoundInDefineXMLError,
InvalidJSONFormat,
RuleFormatError,
Expand Down Expand Up @@ -245,13 +249,22 @@ def validate_single_dataset(
logger.info(
f"Skipped dataset {dataset_metadata.name}. Reason: {reason}"
)
error_obj = ValidationErrorContainer(
status=ExecutionStatus.SKIPPED.value,
message=reason,
error_obj = FailedValidationEntity(
dataset=dataset_metadata.filename,
domain=dataset_metadata.domain or dataset_metadata.rdomain or "",
error=SkippedReason.OUTSIDE_SCOPE.value,
message=reason,
)
return [error_obj.to_representation()]
return [
ValidationErrorContainer(
status=ExecutionStatus.SKIPPED.value,
message=reason,
dataset=dataset_metadata.filename,
domain=dataset_metadata.domain
or dataset_metadata.rdomain
or "",
errors=[error_obj],
).to_representation()
]
except Exception as e:
logger.trace(e)
logger.error(
Expand All @@ -265,7 +278,7 @@ def validate_single_dataset(
"""
)
error_obj: ValidationErrorContainer = self.handle_validation_exceptions(
e, dataset_metadata.full_path, dataset_metadata.full_path
e, dataset_metadata.filename
)
error_obj.domain = dataset_metadata.domain or dataset_metadata.rdomain or ""
# this wrapping into a list is necessary to keep return type consistent
Expand Down Expand Up @@ -462,153 +475,167 @@ def get_define_xml_value_level_metadata(
return define_xml_reader.extract_value_level_metadata(domain_name=domain_name)

def handle_validation_exceptions( # noqa
self, exception, dataset_path, file_name
self, exception, filename: str
) -> ValidationErrorContainer:
if isinstance(exception, DatasetNotFoundError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error="Dataset Not Found",
message=exception.message,
)
message = "rule execution error"
elif isinstance(exception, RuleFormatError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error="Rule format error",
message=exception.message,
)
message = "rule execution error"
elif isinstance(exception, AssertionError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error="Rule format error",
message="Rule contains invalid operator",
)
message = "rule execution error"
elif isinstance(exception, (KeyError, ParserError)):
elif isinstance(exception, KeyError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
error="Column not found in data",
dataset=filename,
error=SkippedReason.COLUMN_NOT_FOUND_IN_DATA.value,
message=exception.args[0],
)
message = "rule execution error"
errors = [error_obj]
return ValidationErrorContainer(
dataset=filename,
errors=errors,
message=message,
status=ExecutionStatus.SKIPPED.value,
)
elif isinstance(exception, ParserError):
error_obj = FailedValidationEntity(
dataset=filename,
error=DateTimeParserError.description,
message=exception.args[0],
)
message = "rule execution error"
elif isinstance(exception, DomainNotFoundInDefineXMLError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error=DomainNotFoundInDefineXMLError.description,
message=exception.args[0],
)
message = "rule execution error"
elif isinstance(exception, SchemaNotFoundError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error=SchemaNotFoundError.description,
message=exception.args[0],
)
message = "rule execution error"
elif isinstance(exception, InvalidSchemaProvidedError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error=InvalidSchemaProvidedError.description,
message=exception.args[0],
)
message = "rule execution error"
elif isinstance(exception, VariableMetadataNotFoundError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error=VariableMetadataNotFoundError.description,
message=exception.args[0],
)
message = "rule execution error"
elif isinstance(exception, InvalidJSONFormat):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error=InvalidJSONFormat.description,
message=exception.args[0],
)
message = "rule execution error"
elif isinstance(exception, PreprocessingError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error=PreprocessingError.description,
message=str(exception),
)
message = "rule evaluation error - preprocessing failed"
errors = [error_obj]
return ValidationErrorContainer(
dataset=os.path.basename(dataset_path),
dataset=filename,
errors=errors,
message=message,
status=ExecutionStatus.SKIPPED.value,
status=ExecutionStatus.EXECUTION_ERROR.value,
)

elif isinstance(exception, OperationError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error=OperationError.description,
message=str(exception),
)
message = "rule evaluation error - operation failed"
errors = [error_obj]
return ValidationErrorContainer(
dataset=os.path.basename(dataset_path),
dataset=filename,
errors=errors,
message=message,
status=ExecutionStatus.SKIPPED.value,
status=ExecutionStatus.EXECUTION_ERROR.value,
)
elif isinstance(exception, DatasetBuilderError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error=DatasetBuilderError.description,
message=str(exception),
)
message = "rule evaluation error - evaluation dataset failed to build"
errors = [error_obj]
return ValidationErrorContainer(
dataset=os.path.basename(dataset_path),
dataset=filename,
errors=errors,
message=message,
status=ExecutionStatus.SKIPPED.value,
status=ExecutionStatus.EXECUTION_ERROR.value,
)
elif isinstance(exception, FailedSchemaValidation):
if self.validate_xml:
error_obj = FailedValidationEntity(
error=FailedSchemaValidation.description,
message=exception.args[0],
dataset=os.path.basename(dataset_path),
dataset=filename,
)
message = "Schema Validation Error"
errors = [error_obj]
return ValidationErrorContainer(
errors=errors,
message=message,
status=ExecutionStatus.SUCCESS.value,
dataset=os.path.basename(dataset_path),
dataset=filename,
)
else:
message = "Skipped because schema validation is off"
error_obj = FailedValidationEntity(
error="Schema validation is off",
error=SkippedReason.SCHEMA_VALIDATION_IS_OFF.value,
message=message,
dataset=os.path.basename(dataset_path),
dataset=filename,
)
errors = [error_obj]
return ValidationErrorContainer(
dataset=os.path.basename(dataset_path),
dataset=filename,
errors=errors,
message=message,
status=ExecutionStatus.SKIPPED.value,
)
elif isinstance(exception, DomainNotFoundError):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
error="Domain not found",
dataset=filename,
error=SkippedReason.DOMAIN_NOT_FOUND.value,
message=str(exception),
)
message = "rule evaluation skipped - operation domain not found"
errors = [error_obj]
return ValidationErrorContainer(
dataset=os.path.basename(dataset_path),
dataset=filename,
errors=errors,
message=message,
status=ExecutionStatus.SKIPPED.value,
Expand All @@ -617,28 +644,28 @@ def handle_validation_exceptions( # noqa
exception, AttributeError
) and "'NoneType' object has no attribute" in str(exception):
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
dataset=filename,
error="Missing field during execution",
message="Missing field during execution, rule may not be applicable- unable to process dataset",
)
message = "rule evaluation skipped - missing metadata"
errors = [error_obj]
return ValidationErrorContainer(
dataset=os.path.basename(dataset_path),
dataset=filename,
errors=errors,
message=message,
status=ExecutionStatus.SKIPPED.value,
status=ExecutionStatus.EXECUTION_ERROR.value,
)
else:
error_obj = FailedValidationEntity(
dataset=os.path.basename(dataset_path),
error="An unknown exception has occurred",
dataset=filename,
error=ExecutionError.AN_UNKNOWN_EXCEPTION_HAS_OCCURRED.value,
message=str(exception),
)
message = "rule execution error"
errors = [error_obj]
return ValidationErrorContainer(
dataset=os.path.basename(dataset_path),
dataset=filename,
errors=errors,
message=message,
status=ExecutionStatus.EXECUTION_ERROR.value,
Expand Down
Loading
Loading