Merge pull request #3003 from jmchilton/docstrings1

Docstring fixes for galaxy.exceptions and galaxy.jobs.metrics.
This commit is contained in:
Nicola Soranzo
2016-10-04 17:28:42 +01:00
committed by GitHub
14 changed files with 131 additions and 81 deletions
+17 -5
View File
@@ -1,14 +1,26 @@
"""
Custom exceptions for Galaxy
"""This module defines Galaxy's custom exceptions.
A Galaxy exception is an exception that extends :class:`MessageException` which
defines an HTTP status code (represented by the `status_code` attribute) and a
default error message.
New exceptions should be defined by adding an entry to `error_codes.json` in this
directory to define a default error message and a Galaxy "error code". A concrete
Python class should be added in this file defining an HTTP status code (as
`status_code`) and error code (`error_code`) object loaded dynamically from
`error_codes.json`.
Reflecting Galaxy's origins as a web application, these exceptions tend to be a
bit web-oriented. However this module is a dependency of modules and tools that
have nothing to do with the web - keep this in mind when defining exception names
and messages.
"""
from ..exceptions import error_codes
class MessageException( Exception ):
"""
Exception to make throwing errors from deep in controllers easier.
"""
"""Most generic Galaxy exception - indicates merely that some exceptional condition happened."""
# status code to be set when used with API.
status_code = 400
# Error code information embedded into API json responses.
+29 -16
View File
@@ -1,3 +1,7 @@
"""Defines the :class:`ErrorCode` class and instantiates concrete objects from JSON.
See the file error_codes.json for actual error code descriptions.
"""
from json import loads
from pkg_resources import resource_string
@@ -8,26 +12,35 @@ from pkg_resources import resource_string
UNKNOWN_ERROR_MESSAGE = "Unknown error occurred while processing request."
class ErrorCode( object ):
class ErrorCode(object):
"""Small class allowing object representation for error descriptions loaded from JSON."""
def __init__( self, code, default_error_message ):
def __init__(self, code, default_error_message):
"""Construct a :class:`ErrorCode` from supplied integer and error message."""
self.code = code
self.default_error_message = default_error_message or UNKNOWN_ERROR_MESSAGE
def __str__( self ):
return str( self.default_error_message )
def __str__(self):
"""Return the error code message."""
return str(self.default_error_message)
def __int__( self ):
return int( self.code )
def __repr__(self):
"""Return object representation of this error code."""
return "ErrorCode[code=%d,message=%s]" % (self.code, str(self.default_error_message))
@staticmethod
def from_dict( entry ):
name = entry.get("name")
code = entry.get("code")
message = entry.get("message")
return ( name, ErrorCode( code, message ) )
def __int__(self):
"""Return the error code integer."""
return int(self.code)
error_codes_json = resource_string( __name__, 'error_codes.json' ).decode( "UTF-8" )
for entry in loads( error_codes_json ):
name, error_code_obj = ErrorCode.from_dict( entry )
globals()[ name ] = error_code_obj
def _from_dict(entry):
"""Build a :class:`ErrorCode` object from a JSON entry."""
name = entry.get("name")
code = entry.get("code")
message = entry.get("message")
return (name, ErrorCode(code, message))
error_codes_json = resource_string(__name__, 'error_codes.json').decode("UTF-8")
for entry in loads(error_codes_json):
name, error_code_obj = _from_dict(entry)
globals()[name] = error_code_obj
+58 -45
View File
@@ -1,3 +1,15 @@
"""This module defines the job metrics collection framework for Galaxy jobs.
The framework consists of two parts - the :class:`JobMetrics` class and
individual :class:`JobInstrumenter` plugins.
A :class:`JobMetrics` object reads any number of plugins from a configuration
source such as an XML file, a YAML file, or a dictionary.
Each :class:`JobInstrumenter` plugin object describes how to inject a bits
of shell code into a job scripts (before and after tool commands run) and then
collect the output of these from a job directory.
"""
import collections
import logging
import os
@@ -7,110 +19,111 @@ from galaxy.util import plugin_config
from ..metrics import formatting
log = logging.getLogger( __name__ )
log = logging.getLogger(__name__)
DEFAULT_FORMATTER = formatting.JobMetricFormatter()
class JobMetrics( object ):
class JobMetrics(object):
"""Load and store a collection of :class:`JobInstrumenter` objects."""
def __init__( self, conf_file=None, **kwargs ):
"""
"""
def __init__(self, conf_file=None, **kwargs):
"""Load :class:`JobInstrumenter` objects from specified configuration file."""
self.plugin_classes = self.__plugins_dict()
self.default_job_instrumenter = JobInstrumenter.from_file( self.plugin_classes, conf_file, **kwargs )
self.job_instrumenters = collections.defaultdict( lambda: self.default_job_instrumenter )
self.default_job_instrumenter = JobInstrumenter.from_file(self.plugin_classes, conf_file, **kwargs)
self.job_instrumenters = collections.defaultdict(lambda: self.default_job_instrumenter)
def format( self, plugin, key, value ):
def format(self, plugin, key, value):
"""Find :class:`formatting.JobMetricFormatter` corresponding to instrumented plugin value."""
if plugin in self.plugin_classes:
plugin_class = self.plugin_classes[ plugin ]
formatter = plugin_class.formatter
else:
formatter = DEFAULT_FORMATTER
return formatter.format( key, value )
return formatter.format(key, value)
def set_destination_conf_file( self, destination_id, conf_file ):
instrumenter = JobInstrumenter.from_file( self.plugin_classes, conf_file )
self.set_destination_instrumenter( destination_id, instrumenter )
def set_destination_conf_file(self, destination_id, conf_file):
instrumenter = JobInstrumenter.from_file(self.plugin_classes, conf_file)
self.set_destination_instrumenter(destination_id, instrumenter)
def set_destination_conf_element( self, destination_id, element ):
instrumenter = JobInstrumenter( self.plugin_classes, ('xml', element) )
self.set_destination_instrumenter( destination_id, instrumenter )
def set_destination_conf_element(self, destination_id, element):
instrumenter = JobInstrumenter(self.plugin_classes, ('xml', element))
self.set_destination_instrumenter(destination_id, instrumenter)
def set_destination_instrumenter( self, destination_id, job_instrumenter=None ):
def set_destination_instrumenter(self, destination_id, job_instrumenter=None):
if job_instrumenter is None:
job_instrumenter = NULL_JOB_INSTRUMENTER
self.job_instrumenters[ destination_id ] = job_instrumenter
def collect_properties( self, destination_id, job_id, job_directory ):
return self.job_instrumenters[ destination_id ].collect_properties( job_id, job_directory )
def collect_properties(self, destination_id, job_id, job_directory):
return self.job_instrumenters[ destination_id ].collect_properties(job_id, job_directory)
def __plugins_dict( self ):
def __plugins_dict(self):
import galaxy.jobs.metrics.instrumenters
return plugin_config.plugins_dict( galaxy.jobs.metrics.instrumenters, 'plugin_type' )
return plugin_config.plugins_dict(galaxy.jobs.metrics.instrumenters, 'plugin_type')
class NullJobInstrumenter( object ):
class NullJobInstrumenter(object):
def pre_execute_commands( self, job_directory ):
def pre_execute_commands(self, job_directory):
return None
def post_execute_commands( self, job_directory ):
def post_execute_commands(self, job_directory):
return None
def collect_properties( self, job_id, job_directory ):
def collect_properties(self, job_id, job_directory):
return {}
NULL_JOB_INSTRUMENTER = NullJobInstrumenter()
class JobInstrumenter( object ):
class JobInstrumenter(object):
def __init__( self, plugin_classes, plugins_source, **kwargs ):
def __init__(self, plugin_classes, plugins_source, **kwargs):
self.extra_kwargs = kwargs
self.plugin_classes = plugin_classes
self.plugins = self.__plugins_from_source( plugins_source )
self.plugins = self.__plugins_from_source(plugins_source)
def pre_execute_commands( self, job_directory ):
def pre_execute_commands(self, job_directory):
commands = []
for plugin in self.plugins:
try:
plugin_commands = plugin.pre_execute_instrument( job_directory )
plugin_commands = plugin.pre_execute_instrument(job_directory)
if plugin_commands:
commands.extend( util.listify( plugin_commands ) )
commands.extend(util.listify(plugin_commands))
except Exception:
log.exception( "Failed to generate pre-execute commands for plugin %s" % plugin )
return "\n".join( [ c for c in commands if c ] )
log.exception("Failed to generate pre-execute commands for plugin %s" % plugin)
return "\n".join([ c for c in commands if c ])
def post_execute_commands( self, job_directory ):
def post_execute_commands(self, job_directory):
commands = []
for plugin in self.plugins:
try:
plugin_commands = plugin.post_execute_instrument( job_directory )
plugin_commands = plugin.post_execute_instrument(job_directory)
if plugin_commands:
commands.extend( util.listify( plugin_commands ) )
commands.extend(util.listify(plugin_commands))
except Exception:
log.exception( "Failed to generate post-execute commands for plugin %s" % plugin )
return "\n".join( [ c for c in commands if c ] )
log.exception("Failed to generate post-execute commands for plugin %s" % plugin)
return "\n".join([ c for c in commands if c ])
def collect_properties( self, job_id, job_directory ):
def collect_properties(self, job_id, job_directory):
per_plugin_properites = {}
for plugin in self.plugins:
try:
properties = plugin.job_properties( job_id, job_directory )
properties = plugin.job_properties(job_id, job_directory)
if properties:
per_plugin_properites[ plugin.plugin_type ] = properties
except Exception:
log.exception( "Failed to collect job properties for plugin %s" % plugin )
log.exception("Failed to collect job properties for plugin %s" % plugin)
return per_plugin_properites
def __plugins_from_source( self, plugins_source ):
def __plugins_from_source(self, plugins_source):
return plugin_config.load_plugins(self.plugin_classes, plugins_source, self.extra_kwargs)
@staticmethod
def from_file( plugin_classes, conf_file, **kwargs ):
if not conf_file or not os.path.exists( conf_file ):
def from_file(plugin_classes, conf_file, **kwargs):
if not conf_file or not os.path.exists(conf_file):
return NULL_JOB_INSTRUMENTER
plugins_source = plugin_config.plugin_source_from_path( conf_file )
return JobInstrumenter( plugin_classes, plugins_source, **kwargs )
plugins_source = plugin_config.plugin_source_from_path(conf_file)
return JobInstrumenter(plugin_classes, plugins_source, **kwargs)
+2 -3
View File
@@ -1,5 +1,4 @@
""" This module contains helper functions and data structures for interacting
with collectl and collectl generated data. More information on collectl can be
found at: http://collectl.sourceforge.net/.
"""Helper functions and data structures for interacting with collectl & data.
More information on collectl can be found at: http://collectl.sourceforge.net/.
"""
+1
View File
@@ -1,3 +1,4 @@
"""This module describes :class:`CollectlCli` - an abstraction for building collectl command lines."""
import logging
import subprocess
@@ -1,3 +1,7 @@
"""Abstractions describing collectl subsystems (specified with the collectl ``-s`` parameter).
Subsystems are essentially monitoring plugins available within collectl.
"""
from abc import ABCMeta
from abc import abstractmethod
+9 -9
View File
@@ -1,18 +1,18 @@
"""Utilities related to formatting job metrics for human consumption."""
class JobMetricFormatter( object ):
""" Format job metric key-value pairs for human consumption in Web UI. """
class JobMetricFormatter(object):
"""Format job metric key-value pairs for human consumption in Web UI."""
def format( self, key, value ):
return ( str( key ), str( value ) )
def format(self, key, value):
return (str(key), str(value))
# Formatting utilities
def seconds_to_str( value ):
def seconds_to_str(value):
"""Convert seconds to a simple simple string describing the amount of time."""
if value < 60:
return "%s seconds" % value
elif value < 3600:
return "%s minutes" % ( value / 60 )
return "%s minutes" % (value / 60)
else:
return "%s hours and %s minutes" % ( value / 3600, ( value % 3600 ) / 60 )
return "%s hours and %s minutes" % (value / 3600, (value % 3600) / 60)
@@ -1,3 +1,7 @@
"""This module describes the abstract interface for :class:`InstrumentPlugin`s.
These are responsible for collecting and formatting a coherent set of metrics.
"""
import os.path
from abc import ABCMeta
@@ -10,9 +14,7 @@ INSTRUMENT_FILE_PREFIX = "__instrument"
class InstrumentPlugin( object ):
""" A plugin describing how to instrument Galaxy jobs and retrieve metrics
from this instrumentation.
"""
"""Describes how to instrument job scripts and retrieve collected metrics."""
__metaclass__ = ABCMeta
formatter = formatting.JobMetricFormatter()
@@ -1,3 +1,4 @@
"""The module describes the ``collectl`` job metrics plugin."""
import logging
import os
import shutil
@@ -1,3 +1,4 @@
"""The module describes the ``core`` job metrics plugin."""
import logging
import time
@@ -1,3 +1,4 @@
"""The module describes the ``cpuinfo`` job metrics plugin."""
import logging
import re
@@ -1,3 +1,4 @@
"""The module describes the ``env`` job metrics plugin."""
import logging
import re
@@ -1,3 +1,4 @@
"""The module describes the ``meminfo`` job metrics plugin."""
import re
import sys
@@ -1,3 +1,4 @@
"""The module describes the ``uname`` job metrics plugin."""
from ..instrumenters import InstrumentPlugin
from ...metrics import formatting