230 lines
7.3 KiB
Python
230 lines
7.3 KiB
Python
"""
|
|
@package pmsco.reports.base
|
|
base class of project reports
|
|
|
|
@author Matthias Muntwiler, matthias.muntwiler@psi.ch
|
|
|
|
@copyright (c) 2021 by Paul Scherrer Institut @n
|
|
Licensed under the Apache License, Version 2.0 (the "License"); @n
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
"""
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
from string import Template
|
|
import pmsco.config as config
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ProjectReport(config.ConfigurableObject):
|
|
"""
|
|
base class of project reports
|
|
|
|
what do we need to know from project?
|
|
- directories to resolve path names
|
|
- database session factory
|
|
- database job id
|
|
- calculation id
|
|
|
|
usage:
|
|
1. assign public attributes as necessary, leave defaults at None.
|
|
2. call validate() if necessary.
|
|
3. call set_database() if necessary.
|
|
4. load data into result_data by calling select_data() or by modifying result_data directly.
|
|
5. call create_report()
|
|
|
|
implementations of reports should not need to access any project members directly!
|
|
|
|
this class is under development
|
|
"""
|
|
|
|
## @var _project
|
|
# project object reference
|
|
#
|
|
|
|
## @var _dba
|
|
# database access (session factory)
|
|
#
|
|
|
|
## @var _modes
|
|
# compatible project modes
|
|
#
|
|
# set of modes in which the report can be used.
|
|
# if an incompatible project is assigned, the enabled property is set to False.
|
|
|
|
## @var canvas
|
|
# matplotlib canvas for graphical reports
|
|
#
|
|
# a FigureCanvas class reference of a matplotlib backend.
|
|
# the default is matplotlib.backends.backend_agg.FigureCanvasAgg
|
|
# which produces a bitmap file in PNG format.
|
|
# some other options are
|
|
# matplotlib.backends.backend_pdf.FigureCanvasPdf or
|
|
# matplotlib.backends.backend_svg.FigureCanvasSVG.
|
|
|
|
## @var enabled
|
|
# enable/disable the report
|
|
#
|
|
# the flag allows to temporarily enable or disable a report.
|
|
#
|
|
# the flag does not change the behaviour of the class.
|
|
# it is up to the caller to respect it.
|
|
#
|
|
# the validate method can set the flag to False if the project is not compatible.
|
|
|
|
## @var report_dir
|
|
# destination directory for report files
|
|
#
|
|
# this should be a Path or PathLike object.
|
|
# by default, the project's directories['report'] entry is used.
|
|
|
|
## @var base_filename
|
|
# base name of output files
|
|
#
|
|
# this value gets copied into the {base} placeholder of filename_format.
|
|
#
|
|
# by default, this is the stem of the output filename from the project settings.
|
|
|
|
## @var filename_format (str)
|
|
# format of the output file name
|
|
#
|
|
# the format method of the string will be used to produce individual names.
|
|
# possible fields are:
|
|
# base, job_id, job_name, mode, model, gen, particle, param0, param1
|
|
# where base corresponds to the stem of the output files produced by the calculators.
|
|
#
|
|
# a file extension according to the file format is appended.
|
|
|
|
## @var title_format (str)
|
|
# title of the plot
|
|
#
|
|
# the format method of the string will be used to produce individual titles.
|
|
# possible fields are:
|
|
# base, job_id, job_name, mode, model, gen, particle, param0, param1
|
|
|
|
## @var trigger_levels (set of str)
|
|
# events that may trigger creation of a report
|
|
#
|
|
# this attribute selects when a report is created.
|
|
# the following values are currently recognized.
|
|
# any other value disables the report.
|
|
# further modes may be implemented in the future.
|
|
#
|
|
# - `model` - every time a new model has been calculated.
|
|
# - `end` (default) - only once at the end of an optimization job.
|
|
#
|
|
# it is up to the calling code to respect this attribute.
|
|
# it does not affect the behaviour of the class.
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._project = None
|
|
self._dba = None
|
|
self._modes = set()
|
|
|
|
self.enabled = True
|
|
self.trigger_levels = {'end'}
|
|
self.canvas = None
|
|
self.report_dir = None
|
|
self.base_filename = None
|
|
self.filename_format = "${base}"
|
|
self.title_format = ""
|
|
|
|
def get_session(self):
|
|
"""
|
|
get a new database session context handler
|
|
|
|
@return: context handler which provides an sqlalchemy database session,
|
|
e.g. a pmsco.database.access.LockedSession() object.
|
|
"""
|
|
return self._dba.session()
|
|
|
|
def validate(self, project):
|
|
"""
|
|
validate the configuration (object properties).
|
|
|
|
@param project: pmsco.project.Project object,
|
|
or any object that contains equivalent directories and output_file attributes.
|
|
|
|
@return: None
|
|
"""
|
|
self._project = project
|
|
if self._project:
|
|
if self.report_dir is None:
|
|
self.report_dir = self._project.directories['report']
|
|
if self.base_filename is None:
|
|
self.base_filename = self._project.output_file.name
|
|
|
|
if self.enabled and self._project.mode not in self._modes:
|
|
self.enabled = False
|
|
logger.warning(f"project mode {self._project.mode} incompatible with {self.__class__.__name__}")
|
|
|
|
if self.report_dir:
|
|
Path(self.report_dir).mkdir(exist_ok=True)
|
|
|
|
def set_database(self, database_access):
|
|
"""
|
|
preparation steps for the report.
|
|
|
|
@param database_access: fully initialized pmsco.database.project.ProjectDatabase object
|
|
which provides database sessions.
|
|
|
|
@return: None
|
|
"""
|
|
self._dba = database_access
|
|
|
|
def resolve_template(self, template, mapping):
|
|
"""
|
|
resolve placeholders in template string
|
|
|
|
the function first tries to resolve using the project's resolve_path function
|
|
and reverts to plain python Template strings if no project is set.
|
|
|
|
@param template: template string using ${name}-style place holders.
|
|
name must be declared in project.directories or mapping.
|
|
non-string objects (e.g. Path) are converted to a string using the str function.
|
|
@param mapping: dictionary of name-value pairs for placeholders.
|
|
@return: resolved string
|
|
"""
|
|
try:
|
|
r = self._project.directories.resolve_path(template, mapping)
|
|
except AttributeError:
|
|
if template:
|
|
r = Template(str(template)).substitute(mapping)
|
|
else:
|
|
r = ""
|
|
return r
|
|
|
|
def select_data(self, jobs=-1, calcs=None):
|
|
"""
|
|
query data from the database
|
|
|
|
this method must be implemented by the sub-class.
|
|
|
|
@param jobs: filter by job.
|
|
the argument can be a singleton or sequence of orm.Job objects or numeric id.
|
|
if None, results from all jobs are loaded.
|
|
if -1 (default), results from the most recent job (by datetime field) are loaded.
|
|
|
|
@param calcs: filter by result.
|
|
the argument can be a singleton or sequence of CalcID objects.
|
|
if None (default), all results are loaded.
|
|
if -1, the most recent result is loaded.
|
|
|
|
@return: None
|
|
"""
|
|
pass
|
|
|
|
def create_report(self):
|
|
"""
|
|
generate or update the report from stored data.
|
|
|
|
this method must be implemented by the sub-class.
|
|
|
|
@return: None
|
|
"""
|
|
pass
|