From 3d9adf321c2c95e7e33581841d9e347eebfd1d14 Mon Sep 17 00:00:00 2001
From: Daniel <d.hornung@indiscale.com>
Date: Tue, 7 May 2024 12:55:46 +0200
Subject: [PATCH] ENH: checking of foreign keys and missing columns.

---
 .../table_json_conversion/convert.py          | 143 +++++++++++++++---
 .../data/multiple_choice_data_missing.xlsx    | Bin 0 -> 5996 bytes
 .../multiple_refs_data_wrong_foreign.xlsx     | Bin 0 -> 12914 bytes
 .../data/simple_data_missing.xlsx             | Bin 0 -> 8915 bytes
 .../data/simple_data_wrong_foreign.xlsx       | Bin 0 -> 8979 bytes
 .../table_json_conversion/test_read_data.py   |  73 ++++++++-
 6 files changed, 193 insertions(+), 23 deletions(-)
 create mode 100644 unittests/table_json_conversion/data/multiple_choice_data_missing.xlsx
 create mode 100644 unittests/table_json_conversion/data/multiple_refs_data_wrong_foreign.xlsx
 create mode 100644 unittests/table_json_conversion/data/simple_data_missing.xlsx
 create mode 100644 unittests/table_json_conversion/data/simple_data_wrong_foreign.xlsx

diff --git a/src/caosadvancedtools/table_json_conversion/convert.py b/src/caosadvancedtools/table_json_conversion/convert.py
index 94856167..5cc6fe24 100644
--- a/src/caosadvancedtools/table_json_conversion/convert.py
+++ b/src/caosadvancedtools/table_json_conversion/convert.py
@@ -22,12 +22,16 @@
 
 from __future__ import annotations
 
+import datetime
+import itertools
+import sys
 from functools import reduce
 from operator import getitem
 from types import SimpleNamespace
 from typing import Any, BinaryIO, Callable, TextIO, Union
+from warnings import warn
 
-from jsonschema import validate, ValidationError
+import jsonschema
 from openpyxl import load_workbook
 from openpyxl.worksheet.worksheet import Worksheet
 
@@ -42,6 +46,12 @@ def _strict_bool(value: Any) -> bool:
     raise TypeError(f"Not a good boolean: {repr(value)}")
 
 
+class ForeignError(KeyError):
+    def __init__(self, *args, definitions: list, message: str = ""):
+        super().__init__(message, *args)
+        self.definitions = definitions
+
+
 class XLSXConverter:
     """Class for conversion from XLSX to JSON.
 
@@ -56,7 +66,8 @@ documentation.
         "boolean": _strict_bool,
     }
 
-    def __init__(self, xlsx: Union[str, BinaryIO], schema: Union[dict, str, TextIO]):
+    def __init__(self, xlsx: Union[str, BinaryIO], schema: Union[dict, str, TextIO],
+                 strict: bool = False):
         """
 Parameters
 ----------
@@ -65,16 +76,29 @@ xlsx: Union[str, BinaryIO]
 
 schema: Union[dict, str, TextIO]
   Schema for validation of XLSX content.
+
+strict: bool, optional
+  If True, fail faster.
 """
         self._workbook = load_workbook(xlsx)
         self._schema = read_or_dict(schema)
         self._defining_path_index = xlsx_utils.get_defining_paths(self._workbook)
+        self._check_columns(fail_fast=strict)
         self._handled_sheets: set[str] = set()
         self._result: dict = {}
+        self._errors: dict = {}
 
-    def to_dict(self) -> dict:
+    def to_dict(self, validate: bool = False, collect_errors: bool = True) -> dict:
         """Convert the xlsx contents to a dict.
 
+Parameters
+----------
+validate: bool, optional
+  If True, validate the result against the schema.
+
+collect_errors: bool, optional
+  If True, do not fail at the first error, but try to collect as many errors as possible.
+
 Returns
 -------
 out: dict
@@ -82,12 +106,62 @@ out: dict
         """
         self._handled_sheets = set()
         self._result = {}
+        self._errors = {}
         for sheetname in self._workbook.sheetnames:
             if sheetname not in self._handled_sheets:
-                self._handle_sheet(self._workbook[sheetname])
+                self._handle_sheet(self._workbook[sheetname], fail_later=collect_errors)
+        if validate:
+            jsonschema.validate(self._result, self._schema)
+        if self._errors:
+            raise RuntimeError("There were error while handling the XLSX file.")
         return self._result
 
-    def _handle_sheet(self, sheet: Worksheet) -> None:
+    def get_errors(self) -> dict:
+        """Return a dict with collected errors."""
+        return self._errors
+
+    def _check_columns(self, fail_fast: bool = False):
+        """Check if the columns correspond to the schema."""
+        def missing(path):
+            message = f"Missing column: {xlsx_utils.p2s(path)}"
+            if fail_fast:
+                raise ValueError(message)
+            else:
+                warn(message)
+        for sheetname in self._workbook.sheetnames:
+            sheet = self._workbook[sheetname]
+            parents: dict = {}
+            col_paths = []
+            for col in xlsx_utils.get_data_columns(sheet).values():
+                parents[xlsx_utils.p2s(col.path[:-1])] = col.path[:-1]
+                col_paths.append(col.path)
+            for path in parents.values():
+                subschema = xlsx_utils.get_subschema(path, self._schema)
+                if subschema.get("type") == "array":
+                    subschema = subschema["items"]
+                if "enum" in subschema:  # Was handled in parent level already
+                    continue
+                for child, content in subschema["properties"].items():
+                    child_path = path + [child]
+                    if content == {'type': 'string', 'format': 'data-url'}:
+                        continue  # skip files
+                    if content.get("type") == "array" and (
+                            content.get("items").get("type") == "object"):
+                        if child_path not in itertools.chain(*self._defining_path_index.values()):
+                            missing(child_path)
+                    elif content.get("type") == "array" and "enum" in content.get("items", []) and (
+                            content.get("uniqueItems") is True):
+                        # multiple choice
+                        for choice in content["items"]["enum"]:
+                            if child_path + [choice] not in col_paths:
+                                missing(child_path + [choice])
+                    elif content.get("type") == "object":
+                        pass
+                    else:
+                        if child_path not in col_paths:
+                            missing(child_path)
+
+    def _handle_sheet(self, sheet: Worksheet, fail_later: bool = False) -> None:
         """Add the contents of the sheet to the result (stored in ``self._result``).
 
 Each row in the sheet corresponds to one entry in an array in the result.  Which array exactly is
@@ -95,6 +169,11 @@ defined by the sheet's "proper name" and the content of the foreign columns.
 
 Look at ``xlsx_utils.get_path_position`` for the specification of the "proper name".
 
+
+Parameters
+----------
+fail_later: bool, optional
+  If True, do not fail with unresolvable foreign definitions, but collect all errors.
 """
         row_type_column = xlsx_utils.get_row_type_column_index(sheet)
         foreign_columns = xlsx_utils.get_foreign_key_columns(sheet)
@@ -106,7 +185,7 @@ Look at ``xlsx_utils.get_path_position`` for the specification of the "proper na
         if parent:
             parent_sheetname = xlsx_utils.get_worksheet_for_path(parent, self._defining_path_index)
             if parent_sheetname not in self._handled_sheets:
-                self._handle_sheet(self._workbook[parent_sheetname])
+                self._handle_sheet(self._workbook[parent_sheetname], fail_later=fail_later)
 
         # # We save single entries in lists, indexed by their foreign key contents.  Each entry
         # # consists of:
@@ -114,7 +193,7 @@ Look at ``xlsx_utils.get_path_position`` for the specification of the "proper na
         # # - data: The actual data of this entry, a dict.
         # entries: dict[str, list[SimpleNamespace]] = {}
 
-        for row in sheet.iter_rows(values_only=True):
+        for row_idx, row in enumerate(sheet.iter_rows(values_only=True)):
             # Skip non-data rows.
             if row[row_type_column] is not None:
                 continue
@@ -147,13 +226,18 @@ Look at ``xlsx_utils.get_path_position`` for the specification of the "proper na
                         _set_in_nested(mydict=data, path=path, value=value, prefix=parent, skip=1)
                     continue
 
-            # Find current position in tree
-            parent_dict = self._get_parent_dict(parent_path=parent, foreign=foreign)
-
-            # Append data to current position's list
-            if proper_name not in parent_dict:
-                parent_dict[proper_name] = []
-            parent_dict[proper_name].append(data)
+            try:
+                # Find current position in tree
+                parent_dict = self._get_parent_dict(parent_path=parent, foreign=foreign)
+
+                # Append data to current position's list
+                if proper_name not in parent_dict:
+                    parent_dict[proper_name] = []
+                parent_dict[proper_name].append(data)
+            except ForeignError as kerr:
+                if not fail_later:
+                    raise
+                self._errors[(sheet.title, row_idx)] = kerr.definitions
         self._handled_sheets.add(sheet.title)
 
     def _is_multiple_choice(self, path: list[str]) -> bool:
@@ -187,7 +271,12 @@ the values given in the ``foreign`` specification.
                     current_object = cand
                     break
             else:
-                raise KeyError("Cannot find an element which matches the foreign definitions")
+                message = "Cannot find an element which matches these foreign definitions:\n\n"
+                for name, value in group.definitions:
+                    message += f"{name}: {value}\n"
+                print(message, file=sys.stderr)
+                error = ForeignError(definitions=group.definitions, message=message)
+                raise error
         assert isinstance(current_object, dict)
         return current_object
 
@@ -208,8 +297,16 @@ This includes:
                 values = [self.PARSER[array_type](v) for v in value.split(";")]
                 return values
         try:
-            validate(value, subschema)
-        except ValidationError as verr:
+            # special case: datetime or date
+            if ("anyOf" in subschema):
+                if isinstance(value, datetime.datetime) and (
+                        {'type': 'string', 'format': 'date-time'} in subschema["anyOf"]):
+                    return value
+                if isinstance(value, datetime.date) and (
+                        {'type': 'string', 'format': 'date'} in subschema["anyOf"]):
+                    return value
+            jsonschema.validate(value, subschema)
+        except jsonschema.ValidationError as verr:
             print(verr)
             print(path)
             raise
@@ -359,7 +456,8 @@ mydict: dict
     return mydict
 
 
-def to_dict(xlsx: Union[str, BinaryIO], schema: Union[dict, str, TextIO]) -> dict:
+def to_dict(xlsx: Union[str, BinaryIO], schema: Union[dict, str, TextIO],
+            validate: bool = None, strict: bool = False) -> dict:
     """Convert the xlsx contents to a dict, it must follow a schema.
 
 Parameters
@@ -370,10 +468,17 @@ xlsx: Union[str, BinaryIO]
 schema: Union[dict, str, TextIO]
   Schema for validation of XLSX content.
 
+validate: bool, optional
+  If True, validate the result against the schema.
+
+strict: bool, optional
+  If True, fail faster.
+
+
 Returns
 -------
 out: dict
   A dict representing the JSON with the extracted data.
     """
-    converter = XLSXConverter(xlsx, schema)
+    converter = XLSXConverter(xlsx, schema, strict=strict)
     return converter.to_dict()
diff --git a/unittests/table_json_conversion/data/multiple_choice_data_missing.xlsx b/unittests/table_json_conversion/data/multiple_choice_data_missing.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..10d76529ab11d2e79ca0651313dd50f0cc326341
GIT binary patch
literal 5996
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

literal 0
HcmV?d00001

diff --git a/unittests/table_json_conversion/data/multiple_refs_data_wrong_foreign.xlsx b/unittests/table_json_conversion/data/multiple_refs_data_wrong_foreign.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..8290260743fa92b27057e90e7dcead1124cd592b
GIT binary patch
literal 12914
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
FcmU6F=A{4t

literal 0
HcmV?d00001

diff --git a/unittests/table_json_conversion/data/simple_data_missing.xlsx b/unittests/table_json_conversion/data/simple_data_missing.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..6e6d3a39d965de81236f2ad14bd2116ac4d7669b
GIT binary patch
literal 8915
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

literal 0
HcmV?d00001

diff --git a/unittests/table_json_conversion/data/simple_data_wrong_foreign.xlsx b/unittests/table_json_conversion/data/simple_data_wrong_foreign.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..f2e88217bc0dd123ecb79e86173e97cc320139f3
GIT binary patch
literal 8979
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

literal 0
HcmV?d00001

diff --git a/unittests/table_json_conversion/test_read_data.py b/unittests/table_json_conversion/test_read_data.py
index eef9a9d8..3f4350b6 100644
--- a/unittests/table_json_conversion/test_read_data.py
+++ b/unittests/table_json_conversion/test_read_data.py
@@ -20,6 +20,7 @@
 """Testing the conversion from XLSX to JSON"""
 
 
+import datetime
 import json
 import os
 import re
@@ -39,17 +40,23 @@ def rfp(*pathcomponents):
 
 
 def convert_and_compare(xlsx_file: str, schema_file: str, known_good_file: str,
-                        strict: bool = False) -> dict:
+                        known_good_data: dict = None, strict: bool = False,
+                        validate: bool = True) -> dict:
     """Convert an XLSX file and compare to a known result.
 
+Exactly one of ``known_good_file`` and ``known_good_data`` should be non-empty.
+
 Returns
 -------
 json: dict
   The result of the conversion.
     """
-    result = convert.to_dict(xlsx=xlsx_file, schema=schema_file)
-    with open(known_good_file, encoding="utf-8") as myfile:
-        expected = json.load(myfile)
+    result = convert.to_dict(xlsx=xlsx_file, schema=schema_file, validate=validate)
+    if known_good_file:
+        with open(known_good_file, encoding="utf-8") as myfile:
+            expected = json.load(myfile)
+    else:
+        expected = known_good_data
     assert_equal_jsons(result, expected, allow_none=not strict, allow_empty=not strict)
     return result
 
@@ -70,6 +77,13 @@ def test_conversions():
                         known_good_file=rfp("data/multiple_choice_data.json"),
                         strict=True)
 
+    with open(rfp("data/simple_data.json"), encoding="utf-8") as myfile:
+        expected_datetime = json.load(myfile)
+        expected_datetime["Training"][0]["date"] = datetime.datetime(2023, 1, 1, 0, 0)
+    convert_and_compare(xlsx_file=rfp("data/simple_data_datetime.xlsx"),
+                        schema_file=rfp("data/simple_schema.json"),
+                        known_good_file="", known_good_data=expected_datetime)
+
     # Data loss when saving as xlsx
     with pytest.raises(AssertionError) as err:
         convert_and_compare(xlsx_file=rfp("data/simple_data_ascii_chars.xlsx"),
@@ -78,6 +92,57 @@ def test_conversions():
     assert str(err.value).startswith("Values at path ['Training', 0, ")
 
 
+def test_missing_columns():
+    with pytest.raises(ValueError) as caught:
+        convert.to_dict(xlsx=rfp("data/simple_data_missing.xlsx"),
+                        schema=rfp("data/simple_schema.json"), strict=True)
+    assert str(caught.value) == "Missing column: Training.coach.given_name"
+    with pytest.warns(UserWarning) as caught:
+        convert.to_dict(xlsx=rfp("data/simple_data_missing.xlsx"),
+                        schema=rfp("data/simple_schema.json"))
+    assert str(caught.pop().message) == "Missing column: Training.coach.given_name"
+    with pytest.warns(UserWarning) as caught:
+        convert.to_dict(xlsx=rfp("data/multiple_choice_data_missing.xlsx"),
+                        schema=rfp("data/multiple_choice_schema.json"))
+    messages = {str(w.message) for w in caught}
+    for expected in [
+            "Missing column: Training.skills.Communication",
+            "Missing column: Training.exam_types.Oral",
+    ]:
+        assert expected in messages
+
+
+def test_faulty_foreign():
+    # Simple wrong foreign key
+    converter = convert.XLSXConverter(xlsx=rfp("data/simple_data_wrong_foreign.xlsx"),
+                                      schema=rfp("data/simple_schema.json"))
+    with pytest.raises(RuntimeError):
+        converter.to_dict()
+    errors = converter.get_errors()
+    assert errors == {('Training.coach', 6): [['date', datetime.datetime(2023, 1, 2, 0, 0)],
+                                              ['url', 'www.indiscale.com']]}
+
+    # More extensive example
+    converter = convert.XLSXConverter(xlsx=rfp("data/multiple_refs_data_wrong_foreign.xlsx"),
+                                      schema=rfp("data/multiple_refs_schema.json"))
+    with pytest.raises(RuntimeError):
+        converter.to_dict()
+    errors = converter.get_errors()
+    assert errors == {
+        ('Training.Organisation.Person', 8): [
+            ['name', 'World Training Organization 2']],
+        ('Training.Organisation.Person', 9): [
+            ['date', '2024-03-21T14:12:00.000Z'],
+            ['url', 'www.getlinkahead.com']],
+        ('Training.participant', 6): [
+            ['date', '2024-03-21T14:12:00.000Z'],
+            ['url', None]],
+        ('Training.participant', 7): [
+            ['date', '2024-03-21T14:12:00.000Z'],
+            ['url', None]],
+    }
+
+
 def test_set_in_nested():
     """Test the ``_set_in_nested`` function."""
     set_in_nested = convert._set_in_nested  # pylint: disable=protected-access
-- 
GitLab