From 6ff71e5688d8dd2bbd8aafc865425040ffa2eac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20tom=20W=C3=B6rden?= <h.tomwoerden@indiscale.com> Date: Mon, 4 Mar 2024 13:45:57 +0100 Subject: [PATCH] ENH: xlsx template generator is complete --- .../table_json_conversion/table_generator.py | 30 ++++++++++------- .../example_template.xlsx | Bin 10059 -> 10190 bytes .../test_table_template_generator.py | 31 ++++++++++-------- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/caosadvancedtools/table_json_conversion/table_generator.py b/src/caosadvancedtools/table_json_conversion/table_generator.py index c69c4984..1e8f9392 100644 --- a/src/caosadvancedtools/table_json_conversion/table_generator.py +++ b/src/caosadvancedtools/table_json_conversion/table_generator.py @@ -124,20 +124,21 @@ class TableTemplateGenerator(ABC): if cpath[0] not in keys: raise ValueError(f"A foreign key definition is missing for path: \n{path}\n{keys}") keys = keys[cpath[0]] - if isinstance(keys, tuple): - selected_keys, keys = keys - elif isinstance(keys, list): + if isinstance(keys, list): selected_keys, keys = keys, None else: selected_keys, keys = None, keys cpath = cpath[1:] + if isinstance(keys, dict) and "__this__" in keys: + selected_keys = keys["__this__"] + if selected_keys is None: raise ValueError(f"A foreign key definition is missing for path:" f"\n{path}\n{foreign_keys}") return selected_keys def _treat_schema_element(self, schema: dict, sheets: dict = None, path: list = None, - foreign_keys: dict = None + foreign_keys: dict = None, level_in_sheet_name: int = 1 ) -> dict[str, tuple[str, list]]: """ recursively transforms elements from the schema into column definitions """ if not ("type" in schema or "enum" in schema or "oneOf" in schema or "anyOf" in schema): @@ -150,10 +151,11 @@ class TableTemplateGenerator(ABC): if 'type' in schema and schema['type'] == 'array': if 'type' in schema['items'] and schema['items']['type'] == 'object' and len(path) > 1: # list of references; special treatment # we add a new sheet - sheets[".".join(path)] = self._treat_schema_element(schema['items'], sheets, path, foreign_keys) + sheets[path[-1]] = self._treat_schema_element(schema['items'], sheets, path, + foreign_keys, + len(path)) for c in self._get_foreign_keys(foreign_keys, path[:-1]): - sheets[".".join(path)].update({".".join(path[:-1]+[c]): ( - ColumnType.FOREIGN, f"see sheet '{path[0]}'", path[:-1]+[c])}) + sheets[path[-1]].update({c: (ColumnType.FOREIGN, f"see sheet '{path[0]}'", path[:-1]+[c])}) # columns are added to the new sheet, thus we do not return columns return {} else: @@ -171,7 +173,8 @@ class TableTemplateGenerator(ABC): cols = {} for pname in schema["properties"].keys(): cols.update(self._treat_schema_element( - schema["properties"][pname], sheets, path+[pname], foreign_keys)) + schema["properties"][pname], sheets, path+[pname], foreign_keys, + level_in_sheet_name)) return cols else: description = schema['description'] if 'description' in schema else None @@ -179,17 +182,17 @@ class TableTemplateGenerator(ABC): # those are the leaves if 'type' not in schema: if 'enum' in schema: - return {".".join(path[1:]): (ctype, description, path)} + return {".".join(path[level_in_sheet_name:]): (ctype, description, path)} if 'anyOf' in schema: for d in schema['anyOf']: # currently the only case where this occurs is date formats assert d['type'] == 'string' assert d['format'] == 'date' or d['format'] == 'date-time' - return {".".join(path[1:]): (ctype, description, path)} + return {".".join(path[level_in_sheet_name:]): (ctype, description, path)} elif schema["type"] in ['string', 'number', 'integer', 'boolean']: if 'format' in schema and schema['format'] == 'data-url': return {} # file; ignore for now - return {".".join(path[1:]): (ctype, description, path)} + return {".".join(path[level_in_sheet_name:]): (ctype, description, path)} else: raise ValueError("Inappropriate JSON schema: The following part should define an" f" object with properties or a primitive type:\n{schema}\n") @@ -285,7 +288,10 @@ class XLSXTemplateGenerator(TableTemplateGenerator): del wb['Sheet'] # order sheets - for index, sn in enumerate(sorted(wb.sheetnames)): + # for index, sn in enumerate(sorted(wb.sheetnames)): + # wb.move_sheet(sn, index-wb.index(wb[sn])) + # reverse sheets + for index, sn in enumerate(wb.sheetnames[::-1]): wb.move_sheet(sn, index-wb.index(wb[sn])) return wb diff --git a/unittests/table_json_conversion/example_template.xlsx b/unittests/table_json_conversion/example_template.xlsx index e6b24f25ad52b40991708c4fc710d25eaabd741e..feb8e5e2d7150d015bef49c0dd9a4bbb1648f809 100644 GIT binary patch delta 8126 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 M*qZ%D`?R?K0dmV$m;e9( delta 7942 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 diff --git a/unittests/table_json_conversion/test_table_template_generator.py b/unittests/table_json_conversion/test_table_template_generator.py index a64ec083..b83682f9 100644 --- a/unittests/table_json_conversion/test_table_template_generator.py +++ b/unittests/table_json_conversion/test_table_template_generator.py @@ -112,7 +112,7 @@ def test_generate_sheets_from_schema(): with pytest.raises(ValueError, match="A foreign key definition is missing.*"): generator._generate_sheets_from_schema(schema) sdef = generator._generate_sheets_from_schema(schema, - foreign_keys={'Training': (['date', 'url'], {})}) + foreign_keys={'Training': {"__this__": ['date', 'url']}}) assert "Training" in sdef tdef = sdef['Training'] assert tdef['date'] == (ColumnType.SCALAR, 'date', ["Training", 'date']) @@ -127,14 +127,13 @@ def test_generate_sheets_from_schema(): assert tdef['participants'] == (ColumnType.SCALAR, None, ["Training", 'participants']) assert tdef['subjects'] == (ColumnType.LIST, None, ["Training", 'subjects']) assert tdef['remote'] == (ColumnType.SCALAR, None, ["Training", 'remote']) - cdef = sdef['Training.coach'] - assert cdef['coach.family_name'] == (ColumnType.SCALAR, None, ["Training", 'coach', 'family_name']) - assert cdef['coach.given_name'] == (ColumnType.SCALAR, None, ["Training", 'coach', 'given_name']) - assert cdef['coach.Organisation'] == (ColumnType.SCALAR, None, ["Training", 'coach', - 'Organisation']) - print(cdef) - assert cdef['Training.date'] == (ColumnType.FOREIGN, "see sheet 'Training'", ["Training", 'date']) - assert cdef['Training.url'] == (ColumnType.FOREIGN, "see sheet 'Training'", ["Training", 'url']) + cdef = sdef['coach'] + assert cdef['family_name'] == (ColumnType.SCALAR, None, ["Training", 'coach', 'family_name']) + 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']) def test_get_foreign_keys(): @@ -142,14 +141,14 @@ def test_get_foreign_keys(): fkd = {"Training": ['a']} assert ['a'] == generator._get_foreign_keys(fkd, ['Training']) - fkd = {"Training": (['a'], {})} + fkd = {"Training": {"__this__": ['a']}} assert ['a'] == generator._get_foreign_keys(fkd, ['Training']) fkd = {"Training": {'hallo'}} with pytest.raises(ValueError, match=r"A foreign key definition is missing for path:\n\['Training'\]\n{'Training': \{'hallo'\}\}"): generator._get_foreign_keys(fkd, ['Training']) - fkd = {"Training": (['a'], {'b': ['c']})} + fkd = {"Training": {"__this__": ['a'], 'b': ['c']}} assert ['c'] == generator._get_foreign_keys(fkd, ['Training', 'b']) with pytest.raises(ValueError, match=r"A foreign key definition is missing for.*"): @@ -167,7 +166,7 @@ def test_template_generator(): 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': (['date', 'url'], {})}, filepath=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")) @@ -190,8 +189,14 @@ def test_template_generator(): rp = os.path.join(di, fi) with open(rp) as sfi: schema = json.load(sfi) + fk_path = os.path.join(di, "foreign_keys"+fi[len('schema'):]) + if not os.path.exists(fk_path): + print(f"No foreign keys file for:\n{rp}") + continue + with open(fk_path) as sfi: + fk = json.load(sfi) generator.generate(schema=schema, - foreign_keys={}, + foreign_keys=fk, filepath=path) os.system(f'libreoffice {path}') -- GitLab