Skip to content
Snippets Groups Projects
Commit 6ff71e56 authored by Henrik tom Wörden's avatar Henrik tom Wörden
Browse files

ENH: xlsx template generator is complete

parent e25679e1
No related branches found
No related tags found
4 merge requests!100WIP: Filling XLSX: Seems to be working.,!94ENH: add framework for converting json schema into table templates,!93Filling XLSX: Everything except multiple choice.,!92ENH: xlsx template generator
Pipeline #48090 failed
...@@ -124,20 +124,21 @@ class TableTemplateGenerator(ABC): ...@@ -124,20 +124,21 @@ class TableTemplateGenerator(ABC):
if cpath[0] not in keys: if cpath[0] not in keys:
raise ValueError(f"A foreign key definition is missing for path: \n{path}\n{keys}") raise ValueError(f"A foreign key definition is missing for path: \n{path}\n{keys}")
keys = keys[cpath[0]] keys = keys[cpath[0]]
if isinstance(keys, tuple): if isinstance(keys, list):
selected_keys, keys = keys
elif isinstance(keys, list):
selected_keys, keys = keys, None selected_keys, keys = keys, None
else: else:
selected_keys, keys = None, keys selected_keys, keys = None, keys
cpath = cpath[1:] cpath = cpath[1:]
if isinstance(keys, dict) and "__this__" in keys:
selected_keys = keys["__this__"]
if selected_keys is None: if selected_keys is None:
raise ValueError(f"A foreign key definition is missing for path:" raise ValueError(f"A foreign key definition is missing for path:"
f"\n{path}\n{foreign_keys}") f"\n{path}\n{foreign_keys}")
return selected_keys return selected_keys
def _treat_schema_element(self, schema: dict, sheets: dict = None, path: list = None, 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]]: ) -> dict[str, tuple[str, list]]:
""" recursively transforms elements from the schema into column definitions """ """ 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): if not ("type" in schema or "enum" in schema or "oneOf" in schema or "anyOf" in schema):
...@@ -150,10 +151,11 @@ class TableTemplateGenerator(ABC): ...@@ -150,10 +151,11 @@ class TableTemplateGenerator(ABC):
if 'type' in schema and schema['type'] == 'array': 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 if 'type' in schema['items'] and schema['items']['type'] == 'object' and len(path) > 1: # list of references; special treatment
# we add a new sheet # 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]): for c in self._get_foreign_keys(foreign_keys, path[:-1]):
sheets[".".join(path)].update({".".join(path[:-1]+[c]): ( sheets[path[-1]].update({c: (ColumnType.FOREIGN, f"see sheet '{path[0]}'", path[:-1]+[c])})
ColumnType.FOREIGN, f"see sheet '{path[0]}'", path[:-1]+[c])})
# columns are added to the new sheet, thus we do not return columns # columns are added to the new sheet, thus we do not return columns
return {} return {}
else: else:
...@@ -171,7 +173,8 @@ class TableTemplateGenerator(ABC): ...@@ -171,7 +173,8 @@ class TableTemplateGenerator(ABC):
cols = {} cols = {}
for pname in schema["properties"].keys(): for pname in schema["properties"].keys():
cols.update(self._treat_schema_element( 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 return cols
else: else:
description = schema['description'] if 'description' in schema else None description = schema['description'] if 'description' in schema else None
...@@ -179,17 +182,17 @@ class TableTemplateGenerator(ABC): ...@@ -179,17 +182,17 @@ class TableTemplateGenerator(ABC):
# those are the leaves # those are the leaves
if 'type' not in schema: if 'type' not in schema:
if 'enum' 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: if 'anyOf' in schema:
for d in schema['anyOf']: for d in schema['anyOf']:
# currently the only case where this occurs is date formats # currently the only case where this occurs is date formats
assert d['type'] == 'string' assert d['type'] == 'string'
assert d['format'] == 'date' or d['format'] == 'date-time' 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']: elif schema["type"] in ['string', 'number', 'integer', 'boolean']:
if 'format' in schema and schema['format'] == 'data-url': if 'format' in schema and schema['format'] == 'data-url':
return {} # file; ignore for now return {} # file; ignore for now
return {".".join(path[1:]): (ctype, description, path)} return {".".join(path[level_in_sheet_name:]): (ctype, description, path)}
else: else:
raise ValueError("Inappropriate JSON schema: The following part should define an" raise ValueError("Inappropriate JSON schema: The following part should define an"
f" object with properties or a primitive type:\n{schema}\n") f" object with properties or a primitive type:\n{schema}\n")
...@@ -285,7 +288,10 @@ class XLSXTemplateGenerator(TableTemplateGenerator): ...@@ -285,7 +288,10 @@ class XLSXTemplateGenerator(TableTemplateGenerator):
del wb['Sheet'] del wb['Sheet']
# order sheets # 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])) wb.move_sheet(sn, index-wb.index(wb[sn]))
return wb return wb
......
No preview for this file type
...@@ -112,7 +112,7 @@ def test_generate_sheets_from_schema(): ...@@ -112,7 +112,7 @@ def test_generate_sheets_from_schema():
with pytest.raises(ValueError, match="A foreign key definition is missing.*"): with pytest.raises(ValueError, match="A foreign key definition is missing.*"):
generator._generate_sheets_from_schema(schema) generator._generate_sheets_from_schema(schema)
sdef = 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 assert "Training" in sdef
tdef = sdef['Training'] tdef = sdef['Training']
assert tdef['date'] == (ColumnType.SCALAR, 'date', ["Training", 'date']) assert tdef['date'] == (ColumnType.SCALAR, 'date', ["Training", 'date'])
...@@ -127,14 +127,13 @@ def test_generate_sheets_from_schema(): ...@@ -127,14 +127,13 @@ def test_generate_sheets_from_schema():
assert tdef['participants'] == (ColumnType.SCALAR, None, ["Training", 'participants']) assert tdef['participants'] == (ColumnType.SCALAR, None, ["Training", 'participants'])
assert tdef['subjects'] == (ColumnType.LIST, None, ["Training", 'subjects']) assert tdef['subjects'] == (ColumnType.LIST, None, ["Training", 'subjects'])
assert tdef['remote'] == (ColumnType.SCALAR, None, ["Training", 'remote']) assert tdef['remote'] == (ColumnType.SCALAR, None, ["Training", 'remote'])
cdef = sdef['Training.coach'] cdef = sdef['coach']
assert cdef['coach.family_name'] == (ColumnType.SCALAR, None, ["Training", 'coach', 'family_name']) assert cdef['family_name'] == (ColumnType.SCALAR, None, ["Training", 'coach', 'family_name'])
assert cdef['coach.given_name'] == (ColumnType.SCALAR, None, ["Training", 'coach', 'given_name']) assert cdef['given_name'] == (ColumnType.SCALAR, None, ["Training", 'coach', 'given_name'])
assert cdef['coach.Organisation'] == (ColumnType.SCALAR, None, ["Training", 'coach', assert cdef['Organisation'] == (ColumnType.SCALAR, None, ["Training", 'coach',
'Organisation']) 'Organisation'])
print(cdef) assert cdef['date'] == (ColumnType.FOREIGN, "see sheet 'Training'", ["Training", 'date'])
assert cdef['Training.date'] == (ColumnType.FOREIGN, "see sheet 'Training'", ["Training", 'date']) assert cdef['url'] == (ColumnType.FOREIGN, "see sheet 'Training'", ["Training", 'url'])
assert cdef['Training.url'] == (ColumnType.FOREIGN, "see sheet 'Training'", ["Training", 'url'])
def test_get_foreign_keys(): def test_get_foreign_keys():
...@@ -142,14 +141,14 @@ def test_get_foreign_keys(): ...@@ -142,14 +141,14 @@ def test_get_foreign_keys():
fkd = {"Training": ['a']} fkd = {"Training": ['a']}
assert ['a'] == generator._get_foreign_keys(fkd, ['Training']) assert ['a'] == generator._get_foreign_keys(fkd, ['Training'])
fkd = {"Training": (['a'], {})} fkd = {"Training": {"__this__": ['a']}}
assert ['a'] == generator._get_foreign_keys(fkd, ['Training']) assert ['a'] == generator._get_foreign_keys(fkd, ['Training'])
fkd = {"Training": {'hallo'}} fkd = {"Training": {'hallo'}}
with pytest.raises(ValueError, match=r"A foreign key definition is missing for path:\n\['Training'\]\n{'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']) 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']) assert ['c'] == generator._get_foreign_keys(fkd, ['Training', 'b'])
with pytest.raises(ValueError, match=r"A foreign key definition is missing for.*"): with pytest.raises(ValueError, match=r"A foreign key definition is missing for.*"):
...@@ -167,7 +166,7 @@ def test_template_generator(): ...@@ -167,7 +166,7 @@ def test_template_generator():
schema = json.load(sfi) schema = json.load(sfi)
path = os.path.join(tempfile.mkdtemp(), 'test.xlsx') path = os.path.join(tempfile.mkdtemp(), 'test.xlsx')
assert not os.path.exists(path) 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) assert os.path.exists(path)
generated = load_workbook(path) # workbook can be read generated = load_workbook(path) # workbook can be read
example = load_workbook(rfp("example_template.xlsx")) example = load_workbook(rfp("example_template.xlsx"))
...@@ -190,8 +189,14 @@ def test_template_generator(): ...@@ -190,8 +189,14 @@ def test_template_generator():
rp = os.path.join(di, fi) rp = os.path.join(di, fi)
with open(rp) as sfi: with open(rp) as sfi:
schema = json.load(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, generator.generate(schema=schema,
foreign_keys={}, foreign_keys=fk,
filepath=path) filepath=path)
os.system(f'libreoffice {path}') os.system(f'libreoffice {path}')
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment