diff --git a/src/caosadvancedtools/table_json_conversion/table_generator.py b/src/caosadvancedtools/table_json_conversion/table_generator.py index 07fac8197cbee4d0fa3ce970e5602dd4068dc2c5..8794496fa369ba2e6804084ad784073dfc065cca 100644 --- a/src/caosadvancedtools/table_json_conversion/table_generator.py +++ b/src/caosadvancedtools/table_json_conversion/table_generator.py @@ -198,13 +198,15 @@ class TableTemplateGenerator(ABC): ) # and add the foreign keys that are necessary up to this point for array_path in array_paths: - keys = self._get_foreign_keys(foreign_keys, array_path) - for key in keys: - if key in sheets[sheetname]: + foreigns = self._get_foreign_keys(foreign_keys, array_path) + for foreign in foreigns: + internal_key = ".".join(array_path + [foreign]) + if internal_key in sheets[sheetname]: raise ValueError("The schema would lead to two columns with the same " - f"name which is forbidden: {key}") - sheets[sheetname][key] = (ColumnType.FOREIGN, f"see sheet '{path[0]}'", - array_path + [key]) + f"name, which is forbidden: {internal_key}") + ref_sheet = ".".join(array_path) + sheets[sheetname][internal_key] = ( + ColumnType.FOREIGN, f"see sheet '{ref_sheet}'", array_path + [foreign]) # Columns are added to the new sheet, thus we do not return any columns for the # current sheet. return {} diff --git a/unittests/table_json_conversion/example_template.xlsx b/unittests/table_json_conversion/example_template.xlsx index 6d9c7627e724144e7748dcfb36f2292be49b036e..1b8a8cd3d4e68c17d7d9d3fc2cb8c54236b39c22 100644 Binary files a/unittests/table_json_conversion/example_template.xlsx and b/unittests/table_json_conversion/example_template.xlsx differ diff --git a/unittests/table_json_conversion/model_multiple_refs.yml b/unittests/table_json_conversion/model_multiple_refs.yml index c95a97aca34137588aa3cd48458935d4cc2d7511..576ff1d59215f893cb5dec1fd5a90c5975713c60 100644 --- a/unittests/table_json_conversion/model_multiple_refs.yml +++ b/unittests/table_json_conversion/model_multiple_refs.yml @@ -4,7 +4,6 @@ Person: datatype: TEXT email: datatype: TEXT - Organisation: Training: recommended_properties: date: @@ -21,6 +20,8 @@ Training: datatype: Person responsible: datatype: Person + Organisation: + datatype: LIST<Organisation> supervisor_inherit: inherit_from_suggested: - Person @@ -31,3 +32,5 @@ Organisation: recommended_properties: Country: datatype: TEXT + Person: + datatype: LIST<Person> diff --git a/unittests/table_json_conversion/multiple_refs.xlsx b/unittests/table_json_conversion/multiple_refs.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..34bea9b9b9d29c9308ec5cff496c91e7ecaa45c9 Binary files /dev/null and b/unittests/table_json_conversion/multiple_refs.xlsx differ diff --git a/unittests/table_json_conversion/schema_multiple_refs.json b/unittests/table_json_conversion/schema_multiple_refs.json index 5678f72c242f3a4badd1db7a1ad7613acba5d83b..7acf5e0d8182d7b25376e48903078e3c652cb6df 100644 --- a/unittests/table_json_conversion/schema_multiple_refs.json +++ b/unittests/table_json_conversion/schema_multiple_refs.json @@ -45,21 +45,6 @@ }, "email": { "type": "string" - }, - "Organisation": { - "type": "object", - "required": [], - "additionalProperties": false, - "title": "Organisation", - "properties": { - "name": { - "type": "string", - "description": "The name of the Record to be created" - }, - "Country": { - "type": "string" - } - } } } } @@ -81,21 +66,6 @@ }, "email": { "type": "string" - }, - "Organisation": { - "type": "object", - "required": [], - "additionalProperties": false, - "title": "Organisation", - "properties": { - "name": { - "type": "string", - "description": "The name of the Record to be created" - }, - "Country": { - "type": "string" - } - } } } } @@ -115,21 +85,6 @@ }, "email": { "type": "string" - }, - "Organisation": { - "type": "object", - "required": [], - "additionalProperties": false, - "title": "Organisation", - "properties": { - "name": { - "type": "string", - "description": "The name of the Record to be created" - }, - "Country": { - "type": "string" - } - } } } }, @@ -148,19 +103,43 @@ }, "email": { "type": "string" - }, - "Organisation": { - "type": "object", - "required": [], - "additionalProperties": false, - "title": "Organisation", - "properties": { - "name": { - "type": "string", - "description": "The name of the Record to be created" - }, - "Country": { - "type": "string" + } + } + }, + "Organisation": { + "type": "array", + "items": { + "type": "object", + "required": [], + "additionalProperties": false, + "title": "Organisation", + "properties": { + "name": { + "type": "string", + "description": "The name of the Record to be created" + }, + "Country": { + "type": "string" + }, + "Person": { + "type": "array", + "items": { + "type": "object", + "required": [], + "additionalProperties": false, + "title": "Person", + "properties": { + "name": { + "type": "string", + "description": "The name of the Record to be created" + }, + "full_name": { + "type": "string" + }, + "email": { + "type": "string" + } + } } } } @@ -181,21 +160,6 @@ }, "email": { "type": "string" - }, - "Organisation": { - "type": "object", - "required": [], - "additionalProperties": false, - "title": "Organisation", - "properties": { - "name": { - "type": "string", - "description": "The name of the Record to be created" - }, - "Country": { - "type": "string" - } - } } } }, @@ -214,21 +178,6 @@ }, "email": { "type": "string" - }, - "Organisation": { - "type": "object", - "required": [], - "additionalProperties": false, - "title": "Organisation", - "properties": { - "name": { - "type": "string", - "description": "The name of the Record to be created" - }, - "Country": { - "type": "string" - } - } } } } @@ -250,21 +199,6 @@ }, "email": { "type": "string" - }, - "Organisation": { - "type": "object", - "required": [], - "additionalProperties": false, - "title": "Organisation", - "properties": { - "name": { - "type": "string", - "description": "The name of the Record to be created" - }, - "Country": { - "type": "string" - } - } } }, "$schema": "https://json-schema.org/draft/2020-12/schema" diff --git a/unittests/table_json_conversion/test_table_template_generator.py b/unittests/table_json_conversion/test_table_template_generator.py index af3b3a2d39c998183292ac0af1377df4fac02d7d..8115c187396baac2bcf7b58d2e891c02c4be28b2 100644 --- a/unittests/table_json_conversion/test_table_template_generator.py +++ b/unittests/table_json_conversion/test_table_template_generator.py @@ -22,6 +22,7 @@ import json import os import tempfile +from typing import Tuple import pytest from caosadvancedtools.table_json_conversion.table_generator import ( @@ -37,6 +38,47 @@ def rfp(*pathcomponents): return os.path.join(os.path.dirname(__file__), *pathcomponents) +def _compare_generated_to_known_good(schema_file: str, known_good: str, foreign_keys: dict = None, + outfile: str = None) -> Tuple: + """Generate an XLSX from the schema, then compare to known good output. + +Returns +------- +out: tuple + The generated and known good workbook objects. + """ + generator = XLSXTemplateGenerator() + if foreign_keys is None: + foreign_keys = {} + with open(schema_file, encoding="utf-8") as schema_input: + schema = json.load(schema_input) + if outfile is None: + outpath = os.path.join(tempfile.mkdtemp(), 'generated.xlsx') + else: + outpath = outfile + assert not os.path.exists(outpath) + generator.generate(schema=schema, + foreign_keys=foreign_keys, + filepath=outpath) + assert os.path.exists(outpath) + generated = load_workbook(outpath) # workbook can be read + good = load_workbook(known_good) + assert generated.sheetnames == good.sheetnames + for sheetname in good.sheetnames: + gen_sheet = generated[sheetname] + good_sheet = good[sheetname] + for irow, (erow, grow) in enumerate(zip(good_sheet.iter_rows(), gen_sheet.iter_rows())): + assert (good_sheet.row_dimensions[irow].hidden + == gen_sheet.row_dimensions[irow].hidden), f"row: {sheetname}, {irow}" + for icol, (ecol, gcol) in enumerate(zip(erow, grow)): + assert (good_sheet.column_dimensions[ecol.column_letter].hidden + == gen_sheet.column_dimensions[ecol.column_letter].hidden), ( + f"col: {sheetname}, {icol}") + cell = gen_sheet.cell(irow+1, icol+1) + assert ecol.value == gcol.value, f"Sheet: {sheetname}, cell: {cell.coordinate}" + return generated, good + + def test_generate_sheets_from_schema(): # trivial case; we do not support this schema = {} @@ -133,8 +175,8 @@ def test_generate_sheets_from_schema(): assert cdef['given_name'] == (ColumnType.SCALAR, None, ["Training", 'coach', 'given_name']) assert cdef['Organisation'] == (ColumnType.SCALAR, None, ["Training", 'coach', 'Organisation']) - assert cdef['date'] == (ColumnType.FOREIGN, "see sheet 'Training'", ["Training", 'date']) - assert cdef['url'] == (ColumnType.FOREIGN, "see sheet 'Training'", ["Training", 'url']) + assert cdef['Training.date'] == (ColumnType.FOREIGN, "see sheet 'Training'", ["Training", 'date']) + assert cdef['Training.url'] == (ColumnType.FOREIGN, "see sheet 'Training'", ["Training", 'url']) def test_get_foreign_keys(): @@ -163,29 +205,10 @@ def test_get_max_path_length(): def test_template_generator(): - generator = XLSXTemplateGenerator() - with open(rfp("model_schema.json")) as sfi: - schema = json.load(sfi) - path = os.path.join(tempfile.mkdtemp(), 'test.xlsx') - assert not os.path.exists(path) - generator.generate(schema=schema, - foreign_keys={'Training': {"__this__": ['date', 'url']}}, - filepath=path) - assert os.path.exists(path) - generated = load_workbook(path) # workbook can be read - example = load_workbook(rfp("example_template.xlsx")) - assert generated.sheetnames == example.sheetnames - for sheetname in example.sheetnames: - gen_sheet = generated[sheetname] - ex_sheet = example[sheetname] - for irow, (erow, grow) in enumerate(zip(ex_sheet.iter_rows(), gen_sheet.iter_rows())): - assert ex_sheet.row_dimensions[irow].hidden == gen_sheet.row_dimensions[irow].hidden - for icol, (ecol, gcol) in enumerate(zip(erow, grow)): - assert (ex_sheet.column_dimensions[ecol.column_letter].hidden - == gen_sheet.column_dimensions[ecol.column_letter].hidden) - cell = gen_sheet.cell(irow+1, icol+1) - assert ecol.value == gcol.value, f"Sheet: {sheetname}, cell: {cell.coordinate}" - + generated, _ = _compare_generated_to_known_good( + schema_file=rfp("model_schema.json"), known_good=rfp("example_template.xlsx"), + foreign_keys={'Training': {"__this__": ['date', 'url']}}, + outfile=None) # test some hidden ws = generated.active assert ws.row_dimensions[1].hidden is True @@ -212,3 +235,11 @@ def test_template_generator(): # TODO test escaping of values # TODO finish enum example + + +def test_model_with_multiple_refs(): + _compare_generated_to_known_good( + schema_file=rfp("schema_multiple_refs.json"), known_good=rfp("multiple_refs.xlsx"), + foreign_keys={'Training': {"__this__": ['date', 'url'], + "Organisation": ["name"]}}, + outfile=None)