Commit 3a4fe08e authored by Clément Schreiner's avatar Clément Schreiner
Browse files

Move plugins/__init__.py into plugins/api.py.

And allow plugins to do 'from debexpo.plugins.api import *' (with a
properly defined __all__) instead of importing everything explicitely.
parent 96ba6d19
# -*- coding: utf-8 -*-
#
# __init__.py — Helpful classes for plugins
#
# This file is part of debexpo - https://alioth.debian.org/projects/debexpo/
#
# Copyright © 2008 Jonny Lamb <jonny@debian.org>
# Copyright © 2012 Clément Schreiner <clement@mux.me>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
"""
Holds some helpful classes for plugins to use or extend.
"""
__author__ = 'Jonny Lamb'
__copyright__ = 'Copyright © 2008 Jonny Lamb'
__license__ = 'MIT'
import os.path
import logging
from inspect import getmembers, ismethod
from pylons import config
# template rendering
from mako.lookup import TemplateLookup
from mako.exceptions import TopLevelLookupException
from debexpo.lib import constants, helpers
from debexpo.model.meta import session
from debexpo.model.plugin_results import PluginResult
log = logging.getLogger(__name__)
# FIXME: there is probably a better way to define this
PLUGINS_TEMPLATE_DIRS = [os.path.join(path, "plugins")
for path in config["pylons.paths"]["templates"]]
#
# Decorators simplifying the writing of plugins
#
def importercmd(func):
"""
Makes a plugin method an importer command.
(i.e. the importer will call the method)
"""
func.importercmd = True
return func
def test_result(cls):
"""
Makes a plugin result model the result of a QA test.
"""
cls.test_result = True
return cls
#
# Bases classes for plugins
#
class BasePlugin(object):
"""
Base class for importer plugins.
"""
def __init__(self, name, package_version, models=None, **kw):
"""
Constructor for a plugin.
"""
self.name = name
# PackageVersion object for the package we're looking at
self.package_version = package_version
self.models = []
if models is not None:
self._load_models(models)
# for storing results retrieved from the database
self.results = {}
# other argumnets
for key in kw:
setattr(self, key, kw[key])
# sqlalchemy session
self.session = session
def run(self):
"""
Import data from the package.
"""
for name, method in getmembers(self, ismethod):
if getattr(method, 'importercmd', False):
method()
def load(self):
"""
Load all data previously imported into the database by this
plugin.
"""
for model in self.models:
q = self.session.query(model)
q = q.filter_by(package_version = self.package_version)
for result in q.all():
self._load_result(result)
return self.results
def _load_result(self, result):
self.results.setdefault(result.entity, list()).append(result)
def save(self):
""" Save DB changes """
self.session.commit()
log.debug('Added results to the database')
def new_result(self, result_cls, **data):
"""
Add a new result to the list of db objects. Returns the
instance of the result.
"""
result = result_cls(package_version = self.package_version)
for k, v in data.iteritems():
result[k] = v
self.session.add(result)
return result
@property
def nb_results(self):
"""
Number of results that have been retrieved from the DB.
"""
return sum(len(l) for l in self.results.itervalues())
def _find_template(self, name, render_format):
# Files to try out for plugin data rendering
try_files = [
"%s/%s.mako" % (name, render_format),
"%s/text.mako" % (name),
"default/%s.mako" % render_format,
"default/text.mako",
]
lookup = TemplateLookup(
directories = PLUGINS_TEMPLATE_DIRS,
input_encoding='utf-8',
output_encoding='utf-8',
)
for basefile in try_files:
try:
template = lookup.get_template(basefile)
except TopLevelLookupException:
continue
else:
break
else:
# No template file found, something weird happened
return "(!! no template found for %s plugin data)" % name
return template
def _load_model(self, model):
"""
Adds the given model to the ``models`` list attribute.
This is separated from the ``_load_models`` method to simplify
overriding in derived plugin classes, which might handle
models classes differently.
For example, QAPlugin instances have a ``test_result``
attribute.
"""
self.models.append(model)
def _load_models(self, models):
"""
Calls ``_load_model`` on all PluginResult subclasses found in
the given ``models`` sequence.
"""
for model in models:
if issubclass(model, PluginResult):
self._load_model(model)
def render(self, render_format, **render_args):
"""Render the plugin data to the given format"""
template = self._find_template(self.name, render_format)
return template.render_unicode(results = self.results,
h = helpers,
**render_args)
class QAPlugin(BasePlugin):
"""
Class for implementing QA plugins.
QA plugins have the concept of a test that can be passed or
failed.
Also, their templates look alike and could be abstracted
(i.e. having a standard 'simple test template' that work sith any
SimpleTestPlugin's results. 'tests results' can be considered the
main result, with other results giving complementary information.
The ``test_entity`` class attribute must be set the
PluginResult-class which represents this test result.
"""
# the PluginResult-derived class representing the result of the
# test
test_model = None
test_result = None # instance of that class, set by ``load``
@property
def test_severity(self):
"""
Returns the severity of the test result.
"""
if self.test_result is None:
log.debug('The plugin data has not been loaded, '
'or this plugin does not define a test')
return None
# FIXME: ugly
if self.test_result.severity == 0:
return 1
return self.test_result.severity
def new_test_result(self, **data):
"""
Returns an instance of the sqlalchemy object for the result of
the test.
"""
return self.new_result(self.test_model, **data)
def render(self, render_format, **render_args):
"""
Render the QA test's template.
"""
return super(QAPlugin, self).render(render_format,
test_result = self.test_result,
**render_args)
def _load_model(self, model):
"""
Overridding BasePlugin's ``_load_model`` to set the
``test_model`` attribute if found.
"""
if (self.test_result is None and
getattr(model, 'test_result', False)):
self.test_model = model
super(QAPlugin, self)._load_model(model)
def _load_result(self, result):
"""
Overridding BasePlugin's ``_load_result`` to set the
``test_result`` attribute if found.
"""
log.debug('Loading %s' % result)
if isinstance(result, self.test_model):
self.test_result = result
super(QAPlugin, self)._load_result(result)
# -*- coding: utf-8 -*-
#
# __init__.py — Helpful classes for plugins
#
# This file is part of debexpo - https://alioth.debian.org/projects/debexpo/
#
# Copyright © 2008 Jonny Lamb <jonny@debian.org>
# Copyright © 2012 Clément Schreiner <clement@mux.me>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
"""
Holds some helpful classes for plugins to use or extend.
"""
__author__ = 'Jonny Lamb'
__copyright__ = 'Copyright © 2008 Jonny Lamb'
__license__ = 'MIT'
__all__ = ['importercmd',
'test_result',
'BasePlugin',
'QAPlugin',
'PluginResult']
import os.path
import logging
from inspect import getmembers, ismethod
from pylons import config
# template rendering
from mako.lookup import TemplateLookup
from mako.exceptions import TopLevelLookupException
from debexpo.lib import constants, helpers
from debexpo.model.meta import session
from debexpo.model.plugin_results import PluginResult
log = logging.getLogger(__name__)
# FIXME: there is probably a better way to define this
PLUGINS_TEMPLATE_DIRS = [os.path.join(path, "plugins")
for path in config["pylons.paths"]["templates"]]
#
# Decorators simplifying the writing of plugins
#
def importercmd(func):
"""
Makes a plugin method an importer command.
(i.e. the importer will call the method)
"""
func.importercmd = True
return func
def test_result(cls):
"""
Makes a plugin result model the result of a QA test.
"""
cls.test_result = True
return cls
#
# Bases classes for plugins
#
class BasePlugin(object):
"""
Base class for importer plugins.
"""
def __init__(self, name, package_version, models=None, **kw):
"""
Constructor for a plugin.
"""
self.name = name
# PackageVersion object for the package we're looking at
self.package_version = package_version
self.models = []
if models is not None:
self._load_models(models)
# for storing results retrieved from the database
self.results = {}
# other argumnets
for key in kw:
setattr(self, key, kw[key])
# sqlalchemy session
self.session = session
def run(self):
"""
Import data from the package.
"""
for name, method in getmembers(self, ismethod):
if getattr(method, 'importercmd', False):
method()
def load(self):
"""
Load all data previously imported into the database by this
plugin.
"""
for model in self.models:
q = self.session.query(model)
q = q.filter_by(package_version = self.package_version)
for result in q.all():
self._load_result(result)
return self.results
def _load_result(self, result):
self.results.setdefault(result.entity, list()).append(result)
def save(self):
""" Save DB changes """
self.session.commit()
log.debug('Added results to the database')
def new_result(self, result_cls, **data):
"""
Add a new result to the list of db objects. Returns the
instance of the result.
"""
result = result_cls(package_version = self.package_version)
for k, v in data.iteritems():
result[k] = v
self.session.add(result)
return result
@property
def nb_results(self):
"""
Number of results that have been retrieved from the DB.
"""
return sum(len(l) for l in self.results.itervalues())
def _find_template(self, name, render_format):
# Files to try out for plugin data rendering
try_files = [
"%s/%s.mako" % (name, render_format),
"%s/text.mako" % (name),
"default/%s.mako" % render_format,
"default/text.mako",
]
lookup = TemplateLookup(
directories = PLUGINS_TEMPLATE_DIRS,
input_encoding='utf-8',
output_encoding='utf-8',
)
for basefile in try_files:
try:
template = lookup.get_template(basefile)
except TopLevelLookupException:
continue
else:
break
else:
# No template file found, something weird happened
return "(!! no template found for %s plugin data)" % name
return template
def _load_model(self, model):
"""
Adds the given model to the ``models`` list attribute.
This is separated from the ``_load_models`` method to simplify
overriding in derived plugin classes, which might handle
models classes differently.
For example, QAPlugin instances have a ``test_result``
attribute.
"""
self.models.append(model)
def _load_models(self, models):
"""
Calls ``_load_model`` on all PluginResult subclasses found in
the given ``models`` sequence.
"""
for model in models:
if issubclass(model, PluginResult):
self._load_model(model)
def render(self, render_format, **render_args):
"""Render the plugin data to the given format"""
template = self._find_template(self.name, render_format)
return template.render_unicode(results = self.results,
h = helpers,
**render_args)
class QAPlugin(BasePlugin):
"""
Class for implementing QA plugins.
QA plugins have the concept of a test that can be passed or
failed.
Also, their templates look alike and could be abstracted
(i.e. having a standard 'simple test template' that work sith any
SimpleTestPlugin's results. 'tests results' can be considered the
main result, with other results giving complementary information.
The ``test_entity`` class attribute must be set the
PluginResult-class which represents this test result.
"""
# the PluginResult-derived class representing the result of the
# test
test_model = None
test_result = None # instance of that class, set by ``load``
@property
def test_severity(self):
"""
Returns the severity of the test result.
"""
if self.test_result is None:
log.debug('The plugin data has not been loaded, '
'or this plugin does not define a test')
return None
# FIXME: ugly
if self.test_result.severity == 0:
return 1
return self.test_result.severity
def new_test_result(self, **data):
"""
Returns an instance of the sqlalchemy object for the result of
the test.
"""
return self.new_result(self.test_model, **data)
def render(self, render_format, **render_args):
"""
Render the QA test's template.
"""
return super(QAPlugin, self).render(render_format,
test_result = self.test_result,
**render_args)
def _load_model(self, model):
"""
Overridding BasePlugin's ``_load_model`` to set the
``test_model`` attribute if found.
"""
if (self.test_result is None and
getattr(model, 'test_result', False)):
self.test_model = model
super(QAPlugin, self)._load_model(model)
def _load_result(self, result):
"""
Overridding BasePlugin's ``_load_result`` to set the
``test_result`` attribute if found.
"""
log.debug('Loading %s' % result)
if isinstance(result, self.test_model):
self.test_result = result
super(QAPlugin, self)._load_result(result)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment