Add new 'reporting' mini-framework
just does some basic stuff, but useful nonetheless to consolidate logic
This commit is contained in:
parent
86c3621d4d
commit
50768ed573
30
rattail/reporting/__init__.py
Normal file
30
rattail/reporting/__init__.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2019 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Reporting Framework
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
from .reports import Report, ReportParam
|
||||||
|
from .handlers import ReportHandler, get_report_handler
|
87
rattail/reporting/handlers.py
Normal file
87
rattail/reporting/handlers.py
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2019 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Report Handlers
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
from rattail.db import model
|
||||||
|
from rattail.util import load_entry_points, load_object
|
||||||
|
|
||||||
|
|
||||||
|
class ReportHandler(object):
|
||||||
|
"""
|
||||||
|
Base class for all report handlers. Also provides default implementation,
|
||||||
|
which is assumed to be sufficient for most needs.
|
||||||
|
"""
|
||||||
|
entry_point_section = 'rattail.reports'
|
||||||
|
|
||||||
|
def __init__(self, config=None, **kwargs):
|
||||||
|
self.config = config
|
||||||
|
self.enum = config.get_enum() if config else None
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
def get_reports(self):
|
||||||
|
"""
|
||||||
|
Returns a dict of available reports, which are registered via setuptools
|
||||||
|
entry points.
|
||||||
|
"""
|
||||||
|
return load_entry_points(self.entry_point_section)
|
||||||
|
|
||||||
|
def get_report(self, key):
|
||||||
|
"""
|
||||||
|
Fetch a report by key. If the report can be located, this will return an
|
||||||
|
instance thereof; otherwise returns ``None``.
|
||||||
|
"""
|
||||||
|
report = self.get_reports().get(key)
|
||||||
|
if report:
|
||||||
|
return report(self.config)
|
||||||
|
|
||||||
|
def generate_output(self, session, report, params, user, progress=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Generate and return output for the given report and params.
|
||||||
|
"""
|
||||||
|
data = report.make_data(session, params, progress=progress, **kwargs)
|
||||||
|
|
||||||
|
output = model.ReportOutput()
|
||||||
|
output.report_name = report.make_report_name(session, params, data, **kwargs)
|
||||||
|
output.filename = report.make_filename(session, params, data, **kwargs)
|
||||||
|
output.created_by = user
|
||||||
|
session.add(output)
|
||||||
|
session.flush()
|
||||||
|
|
||||||
|
path = output.filepath(self.config, makedirs=True)
|
||||||
|
report.write_file(session, params, data, path, progress=progress, **kwargs)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def get_report_handler(config):
|
||||||
|
"""
|
||||||
|
Create and return the configured :class:`ReportHandler` instance.
|
||||||
|
"""
|
||||||
|
spec = config.get('rattail.reports', 'handler')
|
||||||
|
if spec:
|
||||||
|
return load_object(spec)(config)
|
||||||
|
return ReportHandler(config)
|
127
rattail/reporting/reports.py
Normal file
127
rattail/reporting/reports.py
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2019 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Report Definitions
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from rattail.util import progress_loop
|
||||||
|
|
||||||
|
|
||||||
|
@six.python_2_unicode_compatible
|
||||||
|
class Report(object):
|
||||||
|
"""
|
||||||
|
Base class for all reports.
|
||||||
|
"""
|
||||||
|
type_key = None
|
||||||
|
name = None
|
||||||
|
param_sequence = []
|
||||||
|
|
||||||
|
def __init__(self, config=None, **kwargs):
|
||||||
|
self.config = config
|
||||||
|
self.enum = config.get_enum() if config else None
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.name or "")
|
||||||
|
|
||||||
|
def collect_static_params(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns the list of "static" params defined on the report class.
|
||||||
|
"""
|
||||||
|
params = []
|
||||||
|
possible_params = self.param_sequence or dir(self)
|
||||||
|
for name in possible_params:
|
||||||
|
|
||||||
|
# skip anything private-looking
|
||||||
|
if name.startswith('_'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
obj = getattr(self, name)
|
||||||
|
if isinstance(obj, ReportParam):
|
||||||
|
|
||||||
|
# make sure parameter knows its own name
|
||||||
|
obj.name = name
|
||||||
|
params.append(obj)
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
|
def make_params(self, session, **kwargs):
|
||||||
|
"""
|
||||||
|
Should return a list of :class:`ReportParam` objects, which will define
|
||||||
|
user input options when generating a new report. Default behavior is
|
||||||
|
to return the list of "static" params which are defined on the report
|
||||||
|
class itself, i.e. the result of :meth:`collect_static_params()`.
|
||||||
|
"""
|
||||||
|
return self.collect_static_params(**kwargs)
|
||||||
|
|
||||||
|
def make_data(self, session, params, progress=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Should generate and return a dict which contains all relevant data for
|
||||||
|
the report, and which will be used when writing the output file.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def make_report_name(self, session, params, data, **kwargs):
|
||||||
|
"""
|
||||||
|
Should return a "name" for the specific report being generated. This
|
||||||
|
name is set on the :class:`~rattail.db.model.reports.ReportOutput`
|
||||||
|
database record, for instance. It needn't be unique as far as the DB
|
||||||
|
schema is concerned.
|
||||||
|
"""
|
||||||
|
return self.name or self.type_key or ""
|
||||||
|
|
||||||
|
def make_filename(self, session, params, data, **kwargs):
|
||||||
|
"""
|
||||||
|
Should generate and return a filename for the report output, according
|
||||||
|
to the given params and data.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def write_file(self, session, params, data, path, progress=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Should write the output file for the report with the given data and to
|
||||||
|
the given filename.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def progress_loop(self, func, items, factory, **kwargs):
|
||||||
|
return progress_loop(func, items, factory, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ReportParam(object):
|
||||||
|
"""
|
||||||
|
Base class for report parameter definitions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, typ=str, required=True, doc="", **kwargs):
|
||||||
|
self.type = typ
|
||||||
|
self.required = required
|
||||||
|
self.__doc__ == doc
|
||||||
|
kwargs.pop('__doc__', None)
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
setattr(self, key, value)
|
Loading…
Reference in a new issue