Skip to content

Commit dbad20d

Browse files
authored
Refactor schema evolution related code generation (#797)
* Factor out check into dedicated method * Factor out filtering of changes to same datatypes * Cleanup duplicated information * Make function more general in light of future use * Rename class to better transport intent Remove unnecessary duplicated information in members * Move functionality into separate method * Move functionality into separate method * Remove redundant storage of schema version * Remove unused code Not used and will most likely not be used in the future * Remove some code duplication * Remove unnecessary branch * Remove unnecessary information that is passed to jinja * Remove test that doesn't test anything * Remove no longer necessary nolint statements * Add clarifying comment
1 parent bc0816c commit dbad20d

File tree

7 files changed

+150
-170
lines changed

7 files changed

+150
-170
lines changed

python/podio_gen/cpp_generator.py

Lines changed: 73 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ def replace_component_in_paths(oldname, newname, paths):
3333
paths[index] = newPath
3434

3535

36+
def _versioned(typename, version):
37+
"""Return a versioned name of the typename"""
38+
return f"{typename}v{version}"
39+
40+
3641
class IncludeFrom(IntEnum):
3742
"""Enum to signify if an include is needed and from where it should come"""
3843

@@ -72,7 +77,6 @@ def __init__( # pylint: disable=too-many-arguments
7277
self.old_yamlfile = old_description
7378
self.evolution_file = evolution_file
7479
self.old_schema_version = None
75-
self.old_schema_version_int = None
7680
self.old_datamodel = None
7781
self.old_datamodels_components = set()
7882
self.old_datamodels_datatypes = set()
@@ -117,21 +121,7 @@ def do_process_component(self, name, component):
117121
component["includes"] = self._sort_includes(includes)
118122

119123
self._fill_templates("Component", component)
120-
# Add potentially older schema for schema evolution
121-
# based on ROOT capabilities for now
122-
if name in self.root_schema_dict:
123-
schema_evolutions = self.root_schema_dict[name]
124-
component = deepcopy(component)
125-
for schema_evolution in schema_evolutions:
126-
if isinstance(schema_evolution, RenamedMember):
127-
for member in component["Members"]:
128-
if member.name == schema_evolution.member_name_new:
129-
member.name = schema_evolution.member_name_old
130-
component["class"] = DataType(name + self.old_schema_version)
131-
else:
132-
raise NotImplementedError
133-
self._fill_templates("Component", component)
134-
self.root_schema_component_names.add(name + self.old_schema_version)
124+
self._preprocess_schema_evolution_component(name, component)
135125

136126
return component
137127

@@ -143,47 +133,7 @@ def do_process_datatype(self, name, datatype):
143133
self._preprocess_for_obj(datatype)
144134
self._preprocess_for_collection(datatype)
145135

146-
# ROOT schema evolution preparation
147-
# Compute and prepare the potential schema evolution parts
148-
schema_evolution_datatype = deepcopy(datatype)
149-
needs_schema_evolution = False
150-
for member in schema_evolution_datatype["Members"]:
151-
if member.is_array:
152-
if member.array_type in self.root_schema_dict:
153-
needs_schema_evolution = True
154-
replace_component_in_paths(
155-
member.array_type,
156-
member.array_type + self.old_schema_version,
157-
schema_evolution_datatype["includes_data"],
158-
)
159-
member.full_type = member.full_type.replace(
160-
member.array_type, member.array_type + self.old_schema_version
161-
)
162-
member.array_type = member.array_type + self.old_schema_version
163-
164-
else:
165-
if member.full_type in self.root_schema_dict:
166-
needs_schema_evolution = True
167-
# prepare the ROOT I/O rule
168-
replace_component_in_paths(
169-
member.full_type,
170-
member.full_type + self.old_schema_version,
171-
schema_evolution_datatype["includes_data"],
172-
)
173-
member.full_type = member.full_type + self.old_schema_version
174-
member.bare_type = member.bare_type + self.old_schema_version
175-
176-
if needs_schema_evolution:
177-
print(f" Preparing explicit schema evolution for {name}")
178-
schema_evolution_datatype["class"].bare_type = (
179-
schema_evolution_datatype["class"].bare_type + self.old_schema_version
180-
) # noqa
181-
schema_evolution_datatype["old_schema_version"] = self.old_schema_version_int
182-
self._fill_templates("Data", schema_evolution_datatype)
183-
self.root_schema_datatype_names.add(name + self.old_schema_version)
184-
self._fill_templates("Collection", datatype, schema_evolution_datatype)
185-
else:
186-
self._fill_templates("Collection", datatype)
136+
self._preprocess_schema_evolution_datatype(name, datatype)
187137

188138
self._fill_templates("Data", datatype)
189139
self._fill_templates("Object", datatype)
@@ -215,7 +165,7 @@ def do_process_link(self, _, link):
215165
for rel in ("From", "To"):
216166
rel_type = link[rel]
217167
include_header = f"{rel_type.bare_type}Collection"
218-
if self._is_interface(rel_type.full_type):
168+
if self._is_in(rel_type.full_type, "interfaces"):
219169
# Interfaces do not have a Collection header
220170
include_header = rel_type.bare_type
221171
link["include_types"].append(
@@ -253,7 +203,7 @@ def _preprocess_for_class(self, datatype):
253203
member.sub_members = self.datamodel.components[member.full_type]["Members"]
254204

255205
for relation in datatype["OneToOneRelations"]:
256-
if self._is_interface(relation.full_type):
206+
if self._is_in(relation.full_type, "interfaces"):
257207
relation.interface_types = self.datamodel.interfaces[relation.full_type]["Types"]
258208
if self._needs_include(relation.full_type):
259209
fwd_declarations[relation.namespace].append(relation.bare_type)
@@ -265,7 +215,7 @@ def _preprocess_for_class(self, datatype):
265215
includes.add('#include "podio/RelationRange.h"')
266216

267217
for relation in datatype["OneToManyRelations"]:
268-
if self._is_interface(relation.full_type):
218+
if self._is_in(relation.full_type, "interfaces"):
269219
relation.interface_types = self.datamodel.interfaces[relation.full_type]["Types"]
270220
if self._needs_include(relation.full_type):
271221
includes.add(self._build_include(relation))
@@ -335,7 +285,7 @@ def _preprocess_for_collection(self, datatype):
335285
for relation in datatype["OneToManyRelations"] + datatype["OneToOneRelations"]:
336286
if datatype["class"].bare_type != relation.bare_type:
337287
include_from = self._needs_include(relation.full_type)
338-
if self._is_interface(relation.full_type):
288+
if self._is_in(relation.full_type, "interfaces"):
339289
includes_cc.add(
340290
self._build_include_for_class(relation.bare_type, include_from)
341291
)
@@ -405,8 +355,7 @@ def _pre_process_schema_evolution(self):
405355
)
406356
comparator.read()
407357
comparator.compare()
408-
self.old_schema_version = f"v{comparator.datamodel_old.schema_version}"
409-
self.old_schema_version_int = comparator.datamodel_old.schema_version
358+
self.old_schema_version = comparator.datamodel_old.schema_version
410359
# some sanity checks
411360
if len(comparator.errors) > 0:
412361
print(
@@ -431,6 +380,65 @@ def _pre_process_schema_evolution(self):
431380
# add whatever is relevant to our ROOT schema evolution
432381
self.root_schema_dict.setdefault(item.klassname, []).append(item)
433382

383+
def _preprocess_schema_evolution_datatype(self, name, datatype):
384+
"""Preprocess this datatype (and generate the necessary code) in case
385+
schema evolution is necessary
386+
387+
NOTE: currently limited to support only ROOT schema evolution needs
388+
"""
389+
schema_evolution_datatype = deepcopy(datatype)
390+
needs_schema_evolution = False
391+
for member in schema_evolution_datatype["Members"]:
392+
member_type = member.array_type if member.is_array else member.full_type
393+
if member_type in self.root_schema_dict:
394+
needs_schema_evolution = True
395+
replace_component_in_paths(
396+
member_type,
397+
_versioned(member_type, self.old_schema_version),
398+
schema_evolution_datatype["includes_data"],
399+
)
400+
if member.is_array:
401+
member.full_type = member.full_type.replace(
402+
member.array_type, _versioned(member.array_type, self.old_schema_version)
403+
)
404+
member.array_type = _versioned(member.array_type, self.old_schema_version)
405+
else:
406+
member.full_type = _versioned(member.full_type, self.old_schema_version)
407+
member.bare_type = _versioned(member.bare_type, self.old_schema_version)
408+
409+
if needs_schema_evolution:
410+
print(f" Preparing explicit schema evolution for {name}")
411+
schema_evolution_datatype["class"].bare_type = _versioned(
412+
schema_evolution_datatype["class"].bare_type, self.old_schema_version
413+
)
414+
self._fill_templates("Data", schema_evolution_datatype)
415+
self.root_schema_datatype_names.add(_versioned(name, self.old_schema_version))
416+
417+
def _preprocess_schema_evolution_component(self, name, component):
418+
"""Preprocess this component (and generate the necessary code) in case
419+
schema evolution is necessary
420+
421+
NOTE: currently limited to support only ROOT schema evolution needs
422+
"""
423+
try:
424+
schema_evolutions = self.root_schema_dict[name]
425+
component = deepcopy(component)
426+
for schema_evolution in schema_evolutions:
427+
if isinstance(schema_evolution, RenamedMember):
428+
for member in component["Members"]:
429+
if member.name == schema_evolution.member_name_new:
430+
member.name = schema_evolution.member_name_old
431+
component["class"] = DataType(_versioned(name, self.old_schema_version))
432+
else:
433+
raise NotImplementedError
434+
435+
self._fill_templates("Component", component)
436+
self.root_schema_component_names.add(_versioned(name, self.old_schema_version))
437+
438+
except KeyError:
439+
# We didn't find any schema evolution for this component
440+
pass
441+
434442
def _invert_interfaces(self):
435443
"""'Invert' the interfaces to have a mapping of types and their usage in
436444
interfaces.
@@ -465,7 +473,7 @@ def _prepare_iorules(self):
465473
iorule = RootIoRule()
466474
iorule.sourceClass = type_name
467475
iorule.targetClass = type_name
468-
iorule.version = self.old_schema_version.lstrip("v")
476+
iorule.version = self.old_schema_version
469477
iorule.source = f"{member_type} {schema_change.member_name_old}"
470478
iorule.target = schema_change.member_name_new
471479
iorule.code = f"{iorule.target} = onfile.{schema_change.member_name_old};"
@@ -596,7 +604,7 @@ def _create_selection_xml(self):
596604
"old_schema_components": [
597605
DataType(d)
598606
for d in self.root_schema_datatype_names | self.root_schema_component_names
599-
], # noqa
607+
],
600608
"iorules": self.root_schema_iorules,
601609
}
602610

python/podio_gen/generator_base.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -259,14 +259,8 @@ def get_fn_format(tmpl):
259259

260260
return fn_templates
261261

262-
def _eval_template(self, template, data, old_schema_data=None):
262+
def _eval_template(self, template, data):
263263
"""Fill the specified template"""
264-
# merge the info of data and the old schema into a single dict
265-
if old_schema_data:
266-
data["OneToOneRelations_old"] = old_schema_data["OneToOneRelations"]
267-
data["OneToManyRelations_old"] = old_schema_data["OneToManyRelations"]
268-
data["VectorMembers_old"] = old_schema_data["VectorMembers"]
269-
270264
return self.env.get_template(template).render(data)
271265

272266
def _write_file(self, name, content):
@@ -284,7 +278,7 @@ def _write_file(self, name, content):
284278
changed = write_file_if_changed(fullname, content)
285279
self.any_changes = changed or self.any_changes
286280

287-
def _fill_templates(self, template_base, data, old_schema_data=None):
281+
def _fill_templates(self, template_base, data):
288282
"""Fill the template and write the results to file"""
289283
# Update the passed data with some global things that are the same for all
290284
# files
@@ -294,11 +288,17 @@ def _fill_templates(self, template_base, data, old_schema_data=None):
294288
for filename, template in self._get_filenames_templates(
295289
template_base, data["class"].bare_type
296290
):
297-
self._write_file(filename, self._eval_template(template, data, old_schema_data))
291+
self._write_file(filename, self._eval_template(template, data))
292+
293+
def _is_in(self, classname, category):
294+
"""Check whether classname is a member of the category (components,
295+
datatypes, interfaces)"""
296+
if category not in ("datatypes", "components", "interfaces"):
297+
raise ValueError(f"{category=} is not a valid category")
298298

299-
def _is_interface(self, classname):
300-
"""Check whether this is an interface type or a regular datatype"""
301-
all_interfaces = self.datamodel.interfaces
299+
all_classes = getattr(self.datamodel, category)
302300
if self.upstream_edm:
303-
all_interfaces = list(self.datamodel.interfaces) + list(self.upstream_edm.interfaces)
304-
return classname in all_interfaces
301+
all_classes = list(getattr(self.datamodel, category)) + list(
302+
getattr(self.upstream_edm, category)
303+
)
304+
return classname in all_classes

python/podio_gen/julia_generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def do_process_component(self, _, component):
5656
def do_process_datatype(self, _, datatype):
5757
"""Do the julia specific processing for a datatype"""
5858
if any(
59-
self._is_interface(r.full_type)
59+
self._is_in(r.full_type, "interfaces")
6060
for r in datatype["OneToOneRelations"] + datatype["OneToManyRelations"]
6161
):
6262
# Julia doesn't support any interfaces yet, so we have to also sort out

0 commit comments

Comments
 (0)