Belle II Software development
json_objects.py
1
8import json
9import enum
10import functools
11
12# todo: shouldn't I call super().__init__() or similar to make sure that I
13# execute code from mother classes?? This seems to only have been done for
14# some of the subclasses here...
15
16"""
17Define datatypes for later serialization by json
18"""
19
20# todo: write a short overview over the many classes and their relationships here
21
22# ==============================================================================
23# Data classes
24# ==============================================================================
25
26
28
29 """
30 Base object for all json-serializable objects of the validation suite
31 """
32
33
34class Revision(JsonBase):
35
36 """
37 Contains information about a specific revision
38 """
39
41 self,
42 label,
43 git_hash=None,
44 creation_date=None,
45 packages=None,
46 creation_timezone=None,
47 ):
48 """
49 Create a new Revision object and fill all members
50 """
51
52
53 self.label = label
54
55
57 self.creation_date = creation_date
58
59
60 self.creation_timezone = creation_timezone
61
62
64 self.git_hash = git_hash
65
66
68 self.most_recent = False
69
70
71 self.packages = [] if (packages is None) else packages
72
73
75
76 """
77 Container for a list of revisions
78 """
79
80 def __init__(self, revisions):
81 """
82 Create a new Revisions object and fill all members
83 """
84
85
86 self.revisions = revisions
87
88
90
91 """
92 Contains information about a script and its execution output
93 """
94
96 self,
97 name,
98 path,
99 status,
100 log_url=None,
101 return_code=None,
102 input=None,
103 output=None,
104 issues=None
105 ):
106 """
107 Create a new Script object and fill all members
108 """
109
110
111 self.name = name
112
113 self.path = path
114
117 self.status = status
118
120 self.log_url = log_url
121
123 self.return_code = return_code
124
126 self.input = input
127
129 self.output = output
130
131 if not issues:
132 issues = []
133 self.issues = issues
134
135
137
138 """
139 Wrapper for a file containing a set of plots, only
140 root files up to now
141 """
142
143 def __init__(self, package, title, rootfile, plots, description=""):
144 """
145 Create a new PlotFile object and fill all members
146 """
147
148
149 self.package = package
150
151 self.title = title
152
153 self.rootfile = rootfile
154
155 self.plots = plots
156
157 self.description = description
158
159 self.n_shifter_plots = sum([not plot.is_expert for plot in self.plots])
160
161
163
164 """
165 Wrapper for one specific plot.
166 """
167
169 self,
170 is_expert=False,
171 description=None,
172 check=None,
173 contact=None,
174 width=None,
175 height=None,
176 issue=None,
177 ):
178 """
179 Create a new Plot object and fill all members
180 """
181
182
183 self.is_expert = is_expert
184
185 self.description = description
186
187 self.check = check
188
189 self.contact = contact
190
191 self.width = width
192
193 self.height = height
194
195 if not issue:
196 issue = []
197 self.issue = issue
198
199
201
202 """
203 Wrapper for NTuple lists. This is not a graphical plot, but a list of
204 values
205 """
206
207 def __init__(self, is_expert=False, description=None, check=None):
208 """
209 Create a new NTuple object and fill all members
210 """
211
212
213 self.is_expert = is_expert
214
215 self.description = description
216
217 self.check = check
218
219
221
222 """
223 Wrapper for user HTML Content. This is not a graphical plot but HTML
224 code which will be directly output on the validation website.
225
226 """
227
228 def __init__(self, is_expert=False, description=None, check=None):
229 """
230 Create a new NTuple object and fill all members
231 """
232
233
234 self.is_expert = is_expert
235
236 self.description = description
237
238 self.check = check
239
240
242
243 """
244 One high-level package of the validation suites which contains a set of
245 scripts and output plot files
246 """
247
248 def __init__(self, name, plotfiles=None, scriptfiles=None, fail_count=0):
249 """
250 Create a new NTuple object and fill all members
251 """
252
253 if not plotfiles:
254 plotfiles = []
255 if not scriptfiles:
256 scriptfiles = []
257
258
259 self.name = name
260
261 self.plotfiles = plotfiles
262
263 self.scriptfiles = scriptfiles
264
265 self.visible = True
266
267 self.fail_count = fail_count
268
269
270class ComparisonState(enum.Enum):
271
272 """
273 Enum to classify the comparison result of two validation plots
274 """
275
276
278 NotCompared = "not_compared"
279
281 NotSupported = "not_supported"
282
284 FailureTechnical = "technical_failure"
285
287 FailureStastical = "statistical_failure"
288
290 Equal = "equal"
291
292
294
295 """
296 Contains the comparison result of two plots
297 """
298
299 def __init__(self, state, chi2):
300 """
301 Create a new ComparisonResult object and fill all members
302 """
303
304
305 self.state = state
306
307 self.chi2 = chi2
308
309
311
312 """
313 Contains information about a file containing plots and the comparison which
314 have been performed for the content of this file
315 """
316
318 self,
319 package,
320 title,
321 rootfile,
322 compared_revisions=None,
323 plots=None,
324 has_reference=False,
325 ntuples=None,
326 html_content=None,
327 description=None,
328 ):
329 """
330 Create a new ComparisonPlotFile object and fill all members
331 """
332
333 if not plots:
334 plots = []
335 if not ntuples:
336 ntuples = []
337 if not html_content:
338 html_content = []
339
340 super().__init__(
341 package, title, rootfile, plots, description=description
342 )
343
344 self.compared_revision = compared_revisions
345
346 self.ntuples = ntuples
347
348 self.html_content = html_content
349
350
351 self.has_reference = has_reference
352
353
355 [plt for plt in self.plots if plt.comparison_result == "error"]
356 )
357
359 [
360 plt
361 for plt in self.plots
362 if (not plt.is_expert) and plt.comparison_result == "error"
363 ]
364 )
365
367 [plt for plt in self.plots if plt.comparison_result == "warning"]
368 )
369
372 [
373 plt
374 for plt in self.plots
375 if (not plt.is_expert) and plt.comparison_result == "warning"
376 ]
377 )
378
379
381 [not tuple.is_expert for tuple in self.ntuples]
382 )
383
384
385 self.show_shifter = bool(
387 )
388
389
391
392 """
393 One individual plot including its comparison outcome.
394 """
395
397 self,
398 title,
399 comparison_result=None,
400 png_filename=None,
401 pdf_filename=None,
402 contact=None,
403 description=None,
404 check=None,
405 is_expert=None,
406 plot_path=None,
407 comparison_text=None,
408 height=None,
409 width=None,
410 warnings=None,
411 ):
412 """
413 Create a new ComparisonPlot object and fill all members
414 """
415
416 # todo: move more into the base class
417 super().__init__(
418 is_expert=is_expert,
419 description=description,
420 check=check,
421 contact=contact,
422 height=height,
423 width=width,
424 )
425
426 self.title = title
427
428
429 self.comparison_result = comparison_result
430
431
432 self.comparison_text = comparison_text
433
434
435 self.png_filename = png_filename
436
437
438 self.pdf_filename = pdf_filename
439
440
442 self.plot_path = plot_path
443
444
445 if warnings is None:
446 warnings = []
447 self.warnings = warnings
448
449
451
452 """
453 Comparison outcome for NTuples
454 """
455
457 self,
458 title,
459 contact=None,
460 description=None,
461 check=None,
462 is_expert=None,
463 json_file_path=None,
464 ):
465 """
466 Create a new ComparisonNTuple object and fill all members
467 """
468
469 # todo: move more into the base class
470 super().__init__(
471 is_expert=is_expert, description=description, check=check
472 )
473
474 self.title = title
475
476 self.contact = contact
477
479 self.json_file_path = json_file_path
480
481
483
484 """
485 Compiled HTLM Content
486 """
487
489 self,
490 title,
491 contact=None,
492 description=None,
493 check=None,
494 is_expert=None,
495 html_content=None,
496 ):
497 """
498 Create a new ComparisonNTuple object and fill all members
499 """
500
501 # todo: move more into the base class
502 super().__init__(
503 is_expert=is_expert, description=description, check=check
504 )
505
506 self.title = title
507
508 self.contact = contact
509
511 self.html_content = html_content
512
513
515
516 """
517 Information about a Package which was used in a comparison operation
518 """
519
521 self, name, plotfiles=None, scriptfiles=None, ntuplefiles=None
522 ):
523 """
524 Create a new ComparisonPackage object and fill all members
525 """
526
527 if not plotfiles:
528 plotfiles = []
529 if not scriptfiles:
530 scriptfiles = []
531 if not ntuplefiles:
532 ntuplefiles = []
533
534 super().__init__(name, plotfiles=plotfiles, scriptfiles=scriptfiles)
535
536
537 self.comparison_error = sum([pf.comparison_error for pf in plotfiles])
538
540 [pf.comparison_error_shifter for pf in plotfiles]
541 )
542
544 [pf.comparison_warning for pf in plotfiles]
545 )
546
549 [pf.comparison_warning_shifter for pf in plotfiles]
550 )
551
552
554
555 """
556 Revision information enriched by the information gained during
557 a comparison.
558 """
559
560 def __init__(self, label, git_hash=None, creation_date=None, color=None):
561 """
562 Create a new ComparisonRevision object and fill all members
563 """
564
565 # todo: creation_date
566 super().__init__(label, git_hash=git_hash, creation_date=None)
567
569 self.color = color
570
571
573
574 """
575 Contains information and plots generated for comparisons
576 between revisions
577 """
578
579 def __init__(self, revisions=None, packages=None):
580 """
581 Create a new ComparisonRevision object and fill all members
582 """
583
584 if not revisions:
585 revisions = []
586 if not packages:
587 packages = []
588
589
590 self.revisions = revisions
591
592 self.packages = packages
593 sorted_revs = sorted(revisions, key=lambda x: x.label)
594
595 self.label = functools.reduce(
596 lambda x, y: x + "_" + y.label, sorted_revs, ""
597 )[1:]
598
599
600# ==============================================================================
601# Functions
602# ==============================================================================
603
604
605def dump(file_name, obj):
606 """
607 Output a tree of objects into a json file
608 """
609
610 with open(file_name, "w+") as f:
611 json.dump(dump_rec(obj), f, sort_keys=True, indent=4)
612
613
614def dumps(obj):
615 """
616 Convert a tree of python objects into a json file
617 """
618
619 kk = dump_rec(obj)
620 return json.dumps(kk, sort_keys=True, indent=4)
621
622
623def dump_rec(top_object):
624 """
625 Recursive generating of dictionary from a tree
626 of JsonBase objects
627 """
628
629 this_dict = {}
630 # iterate through the dictionary of the top object
631 for (k, v) in top_object.__dict__.items():
632
633 # list which needs special handing ?
634 if isinstance(v, list):
635 obj_list = []
636 for it in v:
637 # one of our object's in the list, which needs
638 # special treatment?
639 if isinstance(it, JsonBase):
640 # yen, recurse in to the object
641 obj_list.append(dump_rec(it))
642 else:
643 # no, just add value to list
644 obj_list.append(it)
645
646 # store compiled list with corresponding key
647 this_dict[k] = obj_list
648 # one of our objects, which might be nested and
649 # needs special treatment ?
650 elif isinstance(v, JsonBase):
651 this_dict[k] = dump_rec(v)
652 # treat enum classes
653 elif isinstance(v, enum.Enum):
654 this_dict[k] = v.value
655 # regular value, just store with correct key
656 else:
657 this_dict[k] = v
658 return this_dict
title
Text used as title for the ntuple item.
def __init__(self, title, contact=None, description=None, check=None, is_expert=None, html_content=None)
contact
name of contact person
html_content
path to the json file which contains the individual numbers of the ntuple
title
Text used as title for the ntuple item.
contact
name of contact person
json_file_path
path to the json file which contains the individual numbers of the ntuple (must be relative to html d...
def __init__(self, title, contact=None, description=None, check=None, is_expert=None, json_file_path=None)
comparison_warning_shifter
the number of comparisons of shifter plots which resulted in a warning
comparison_error
the number of failed comparisons in this package
def __init__(self, name, plotfiles=None, scriptfiles=None, ntuplefiles=None)
comparison_error_shifter
the number of failed comparisons of shifter plots in this package
comparison_warning
the number of comparisons which resulted in a warning
has_reference
true if a reference file is available for this plot file
def __init__(self, package, title, rootfile, compared_revisions=None, plots=None, has_reference=False, ntuples=None, html_content=None, description=None)
comparison_warning_shifter
the number of comparisons of shifter plots in this file which resulted in a warning
comparison_error
the number of failed comparisons in this file
show_shifter
Show to shifter, i.e.
comparison_error_shifter
the number of failed comparisons of shifter plots in this file
ntuples
the ntuples which were compared
comparison_warning
the number of comparisons which resulted in a warning
compared_revision
label of the revision which were used in this comparison
html_content
user's html content
n_shifter_ntuples
Number of shifter ntuples.
title
tile used to display this plot
pdf_filename
the filename of the pdf file plotted with the comparison graphs
warnings
Warnings ("no contact" person etc.)
comparison_result
text string for the comparison outcome
plot_path
path were the png and pdf files are located (relative to the html directory; has to end with trailing...
def __init__(self, title, comparison_result=None, png_filename=None, pdf_filename=None, contact=None, description=None, check=None, is_expert=None, plot_path=None, comparison_text=None, height=None, width=None, warnings=None)
png_filename
the filename of the png file plotted with the comparison graphs
comparison_text
verbose text describing the outcome of the comparison
chi2
the chi2 value computed during the comparison
state
a string containing a description of the comparison's outcome
def __init__(self, state, chi2)
color
the color which was used for this revision in the comparison plots
def __init__(self, label, git_hash=None, creation_date=None, color=None)
label
the unique label of this comparison
packages
the list of packages looked at in this comparison
revisions
the list of revisions used in this comparison
def __init__(self, revisions=None, packages=None)
description
telling description for this HTML code
def __init__(self, is_expert=False, description=None, check=None)
is_expert
true if this is marked as an expert-only HTML code
check
what should be checked for in this HTML code
description
telling description for this ntuple
def __init__(self, is_expert=False, description=None, check=None)
is_expert
true if this is marked as an expert-only ntuple list
check
what should be checked for in this ntuple ?
visible
true if this package is displayed on the default validation website
name
name of the package
def __init__(self, name, plotfiles=None, scriptfiles=None, fail_count=0)
plotfiles
list of plotfiles which were produced by the scripts in this package
scriptfiles
scripts which were run or skipped as this package was executed
fail_count
contains the number how many scripts failed to execute with error
title
Display name of this file.
description
Description of plot file.
plots
list of plots which are contained inside this plot file
n_shifter_plots
Number of shifter plots.
package
name of the package which created this file
rootfile
filename of the root file
def __init__(self, package, title, rootfile, plots, description="")
description
telling description for this plot
contact
Who is the contact person for this plot ?
height
height of the plot in pixels
width
width of the plot in pixels
def __init__(self, is_expert=False, description=None, check=None, contact=None, width=None, height=None, issue=None)
issue
linked issues
is_expert
true if this is marked as an expert-only plot
check
What should be checked for in this plot ?
label
label (or tag) used to display this revision
Definition: json_objects.py:53
packages
list of packages contained in this revision
Definition: json_objects.py:71
def __init__(self, label, git_hash=None, creation_date=None, packages=None, creation_timezone=None)
Definition: json_objects.py:47
most_recent
is this the most recent revision in the list this revision is contained ?
Definition: json_objects.py:68
git_hash
The git commit hash which has the HEAD while the validation scripts were executed.
Definition: json_objects.py:64
creation_date
date when the validation output of this revision was created, as datetime object
Definition: json_objects.py:57
creation_timezone
timezone used by the creation date
Definition: json_objects.py:60
revisions
the actual list
Definition: json_objects.py:86
def __init__(self, revisions)
Definition: json_objects.py:80
status
Output status of the script execution, can be one of the strings "failed", "finished",...
input
input files for the script as declared in the header
path
path the script file is located
return_code
integer which is the return code of the script execution
issues
linked issues
name
the name of the script file
def __init__(self, name, path, status, log_url=None, return_code=None, input=None, output=None, issues=None)
log_url
location where the log output of the script execution can be found
output
output files produced by the script as declared in the header