# coding=utf-8
# -*- python -*-
#
# This file is part of report software
#
# Copyright (c) 2016
# All rights reserved
#
# File author(s): Thomas Cokelaer <cokelaer@gmail.com>
#
# Distributed under the BSD 3-Clause License.
# See accompanying file LICENSE.txt distributed with this software
#
##############################################################################
"""Base classes to create HTML reports easily"""
import os
import shutil
import glob
import easydev
import pandas as pd
from jinja2.environment import Environment
from jinja2 import FileSystemLoader
from .htmltable import HTMLTable
__all__ = ['Report']
def _get_report_version():
# cannot use from report import version since it imports the module (not the
# package) due to identical name. Hopefully, easydev does help:
deps = easydev.get_dependencies('reports')
index = [x.project_name for x in deps].index('reports')
return deps[index].version
[docs]class Report(object):
"""A base class to create HTML pages
The :class:`Report` is used to
#. fetch Jinja templates and css from a user directory (by default a generic
set of files is provided as an example
#. fetch the CSS and images
#. hold variables and contents within a dictionary (:attr:`jinja`)
#. Create the HTML document in a local directory.
::
from report import Report
r = Report()
r.create_report(onweb=True)
The next step is for you to copy the templates in a new directory, edit them
and fill the :attr:`jinja` attribute to fulfil your needs::
from report import Report
r = Report("myreport_templates")
r.jinja["section1"] = "<h1></h1>"
r.create_report()
"""
def __init__(self,
searchpath=None,
filename='index.html',
directory='report',
overwrite=True,
verbose=True,
template_filename='index.html',
extra_css_list=[],
extra_js_list=[],
init_report=True):
""".. rubric:: Constructor
:param searchpath: where to find the jina templates.
If not provided, uses the generic template
:param filename: output filename (default to **index.html**)
:param directory: defaults to **report**
:param overwrite: default to True
:param verbose: default to True
:param template_filename: entry point of the jinja code
:param extra_css_list: where to find the extra css
:param extra_js_list: where to find the extra css
:param bool init_report: init the report that is create
the directories to store css and JS.
"""
self.verbose = verbose
self._directory = directory
self._filename = filename
self.extra_css_list = extra_css_list
self.extra_js_list = extra_js_list
# This contains the sections and their names
# Not used yet but could be in jinja templating
self.sections = []
self.section_names = []
#: flag to add dependencies
self.add_dependencies = False
# For jinja2 inheritance, we need to use the environment
# to indicate where are the parents' templates
if searchpath is None:
thispath = easydev.get_package_location('reports')
thispath += os.sep + "reports"
thispath += os.sep + "resources"
self.searchpath = os.sep.join([thispath, 'templates', "generic"])
else:
# path to the template provided by the user
self.searchpath = searchpath
# The JINJA environment
# TODO check that the path exists
self.env = Environment()
self.env.loader = FileSystemLoader(self.searchpath)
# input template file
self.template = self.env.get_template(template_filename)
# This dictionary will be used to populate the JINJA template
self.jinja = {
'time_now': self.get_time_now(),
"title": "Title to be defined",
'dependencies': self.get_table_dependencies().to_html(),
"report_version": _get_report_version()
}
# Directories to create
self._to_create = ['images', 'css', 'js',]
# Create directories and stored css/js/images
if init_report:
self._init_report()
def _get_filename(self):
return self._filename
def _set_filename(self, filename):
self._filename = filename
filename = property(_get_filename, _set_filename,
doc="The filename of the HTML document")
def _get_directory(self):
return self._directory
def _set_directory(self, directory):
self._directory = directory
directory = property(_get_directory, _set_directory,
doc="The directory where to save the HTML document")
def _get_abspath(self):
return self.directory + os.sep + self.filename
abspath = property(_get_abspath,
doc="The absolute path of the document (read only)")
def _init_report(self):
"""create the report directory and return the directory name"""
self.sections = []
self.section_names = []
# if the directory already exists, print a warning
try:
if os.path.isdir(self.directory) is False:
if self.verbose:
print("Created directory {}".format(self.directory))
os.mkdir(self.directory)
# list of directories created in the constructor
for this in self._to_create:
try:
os.mkdir(self.directory + os.sep + this)
except:
pass # already created ?
except Exception:
pass
finally:
# Once the main directory is created, copy files required
temp_path = easydev.get_package_location("reports")
temp_path += os.sep + "reports" + os.sep + "resources"
# Copy the CSS from reports/resources/css
filenames = glob.glob(os.sep.join([temp_path, "css", "*.css"]))
# If there are CSS in the directory with JINJA templates, use them
# as well
filenames += glob.glob(os.sep.join([self.searchpath, '*.css']))
# In addition, the user may also provide his own CSS as a list
filenames += self.extra_css_list
for filename in filenames:
target = os.sep.join([self.directory, 'css' ])
if os.path.isfile(target) is False:
shutil.copy(filename, target)
# We copy all javascript from reports resources
for filename in ['sorttable.js', 'highlight.pack.js', "jquery-1.12.3.min.js"]:
target = os.sep.join([self.directory, 'js', filename ])
if os.path.isfile(target) is False:
filename = os.sep.join([temp_path, "javascript", filename])
shutil.copy(filename, target)
for filename in self.extra_js_list:
basename = os.path.basename(filename)
target = os.sep.join([self.directory, 'js', basename ])
if os.path.isfile(target) is False:
shutil.copy(filename, target)
[docs] def to_html(self):
self.jinja['time_now'] = self.get_time_now()
return self.template.render(self.jinja)
[docs] def write(self):
with open(self.abspath, "w") as fh:
data = self.to_html()
fh.write(data)
[docs] def onweb(self):
"""Open the HTML document in a browser"""
from easydev import onweb
onweb(self.abspath)
[docs] def create_report(self, onweb=True):
self.write()
if onweb is True:
self.onweb()
[docs] def get_time_now(self):
"""Returns a time stamp"""
import datetime
import getpass
username = getpass.getuser()
# this is not working on some systems: os.environ["USERNAME"]
timenow = str(datetime.datetime.now())
timenow = timenow.split('.')[0]
msg = '<div class="date">Created on ' + timenow
msg += " by " + username +'</div>'
return msg
[docs] def get_table_dependencies(self, package="reports"):
"""Returns dependencies of the pipeline as an HTML/XML table
The dependencies are the python dependencies as returned by
pkg_resource module.
"""
dependencies = easydev.get_dependencies(package)
# TODO: Could re-use new method in HTMLTable for adding href
# but needs some extra work in the add_href method.
names = [x.project_name for x in dependencies]
versions = [x.version for x in dependencies]
links = ["""https://pypi.python.org/pypi/%s""" % p for p in names]
df = pd.DataFrame({
'package': ["""<a href="%s">%s</a>""" % (links[i], p)
for i, p in enumerate(names)],
'version': versions})
table = HTMLTable(df, name="dependencies", escape=False)
table.sort('package')
return table