Skip to content

Commit

Permalink
Merge pull request #121 from massimiliano-della-rovere/develop
Browse files Browse the repository at this point in the history
Enhanced .validate()
  • Loading branch information
svituz authored Jul 24, 2024
2 parents a0c95a5 + 51cbbe4 commit d03c003
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 9 deletions.
5 changes: 3 additions & 2 deletions hl7apy/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,15 +740,16 @@ def to_er7(self, encoding_chars=None, trailing_children=False):

return separator.join(s)

def validate(self, report_file=None):
def validate(self, report_file=None, return_errors=False):
"""
Validate the HL7 element using the :attr:`STRICT <hl7apy.consts.VALIDATION_LEVEL.STRICT>` validation
level. It calls the :func:`Validator.validate <hl7apy.validation.Validator.validate>` method passing
the reference used in the instantiation of the element.
:param: report_file: the report file to pass to the validator
:param: return_errors: return errors and warnings instead of raising
"""
return Validator.validate(self, reference=self.reference, report_file=report_file)
return Validator.validate(self, reference=self.reference, report_file=report_file, return_errors=return_errors)

def is_z_element(self):
return False
Expand Down
40 changes: 33 additions & 7 deletions hl7apy/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,18 @@
from __future__ import absolute_import
import traceback

from collections import namedtuple

from hl7apy import load_reference
from hl7apy.consts import VALIDATION_LEVEL
from hl7apy.exceptions import ChildNotFound, ValidationError, ValidationWarning


ErrorsAndWarnings = namedtuple(
"ErrorsAndWarnings",
("is_valid", "errors", "warnings"))


class Validator(object):
"""
Class that handles validation. It defines validation levels and validate
Expand All @@ -38,7 +45,7 @@ def __init__(self, level):
self.level = level

@staticmethod
def validate(element, reference=None, report_file=None):
def validate(element, reference=None, report_file=None, return_errors=False):
"""
Checks if the :class:`Element <hl7apy.core.Element>` is a valid HL7 message according to the reference
specified. If the reference is not specified, it will be used the official HL7 structures for the
Expand All @@ -50,13 +57,18 @@ def validate(element, reference=None, report_file=None):
* the datatype of fields, components and subcomponents
* the values, in particular the length and the adherence with the HL7 table, if one is specified
It raises the first exception that it finds.
All errors that occur will be written to a file, if any of the two following conditions are met:
1. either the :attr:`report_file` is an instance (file-like object) with a .write() method, then report_file.write() is called;
2. or the :attr:`report_file` is a serializable (string-like object)
value that will be used to as the path to create a new_file, then new_file.write() is called; if the file already exists, it will be overwritten.
If :attr:`report_file` is specified, it will create a file with all the errors that occur.
If :attr:`return_errors` is False, the functions raises the first exception that it finds.
If :attr:`return_errors` is True, errors and warnings are returned using a namedtuple with "errors" and "warnings" list fields.
Else the funtion returns True.
:param element: :class:`Element <hl7apy.core.Element>`: The element to validate
:param reference: the reference to use. Usually is None or a message profile object
:param report_file: the name of the report file to create
:param report_file: the name of the report file to create, or an instance having a write() method
:return: The True if everything is ok
:raises: :exc:`ValidationError <hl7apy.exceptions.ValidationError>`: when errors occur
Expand Down Expand Up @@ -197,11 +209,25 @@ def _is_valid(el, ref, errs, warns):
_is_valid(element, reference, errors, warnings)

if report_file is not None:
with open(report_file, "w") as f:
try:
write = report_file.write
except AttributeError:
with open(report_file, "w") as f:
for e in errors:
f.write("Error: {}\n".format(e))
for w in warnings:
f.write("Warning: {}\n".format(w))
else:
for e in errors:
f.write("Error: {}\n".format(e))
write("Error: {}\n".format(e))
for w in warnings:
f.write("Warning: {}\n".format(w))
write("Warning: {}\n".format(w))

if return_errors:
return ErrorsAndWarnings(
is_valid=not errors,
errors=errors,
warnings=warnings)

if errors:
raise errors[0]
Expand Down
28 changes: 28 additions & 0 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from __future__ import absolute_import

from io import StringIO
import os
import re
import sys
Expand Down Expand Up @@ -388,6 +389,33 @@ def test_wd_type_field(self):
parsed_s = parse_segment(s, version='2.7')
self.assertRaises(ValidationError, parsed_s.validate)

def test_validate_with_param_report_file_as_string_io(self):
"""
Tests that, if you use stringIO to validate, warning and errors are written there
"""
string_io = StringIO()
msg = self._create_message(self.oml_o33)
self.assertTrue(msg.validate(report_file=string_io))
self.assertGreater(string_io.tell(), 0)

def test_validate_with_param_return_errors(self):
"""
Tests message is valid with warnings using return_errors=True
"""
msg = self._create_message(self.oml_o33)
errors_and_warnings = msg.validate(return_errors=True)
self.assertTrue(errors_and_warnings.is_valid)
self.assertEqual(len(errors_and_warnings.warnings), 2)

def test_well_structured_message_using_return_error(self):
"""
Tests that a valid message is validated using return_error
"""
msg = self._create_message(self.adt_a01)
errors_and_warnings = msg.validate(return_errors=True)
self.assertTrue(errors_and_warnings.is_valid)
self.assertEqual(len(errors_and_warnings.errors), 0)
self.assertEqual(len(errors_and_warnings.warnings), 0)

class TestMessageProfile(unittest.TestCase):

Expand Down

0 comments on commit d03c003

Please sign in to comment.