"""
Classes related to parameter validation.
"""
import os, re, logging
from xml.etree.ElementTree import XML
from galaxy import model
log = logging.getLogger( __name__ )
[docs]class LateValidationError( Exception ):
def __init__( self, message ):
self.message = message
[docs]class Validator( object ):
"""
A validator checks that a value meets some conditions OR raises ValueError
"""
@classmethod
[docs] def from_element( cls, param, elem ):
type = elem.get( 'type', None )
assert type is not None, "Required 'type' attribute missing from validator"
return validator_types[type].from_element( param, elem )
[docs] def validate( self, value, history=None ):
raise TypeError( "Abstract Method" )
[docs]class RegexValidator( Validator ):
"""
Validator that evaluates a regular expression
>>> from galaxy.tools.parameters import ToolParameter
>>> p = ToolParameter.build( None, XML( '''
... <param name="blah" type="text" size="10" value="10">
... <validator type="regex" message="Not gonna happen">[Ff]oo</validator>
... </param>
... ''' ) )
>>> t = p.validate( "Foo" )
>>> t = p.validate( "foo" )
>>> t = p.validate( "Fop" )
Traceback (most recent call last):
...
ValueError: Not gonna happen
"""
@classmethod
[docs] def from_element( cls, param, elem ):
return cls( elem.get( 'message' ), elem.text )
def __init__( self, message, expression ):
self.message = message
# Compile later. RE objects used to not be thread safe. Not sure about
# the sre module.
self.expression = expression
[docs] def validate( self, value, history=None ):
if re.match( self.expression, value ) is None:
raise ValueError( self.message )
[docs]class ExpressionValidator( Validator ):
"""
Validator that evaluates a python expression using the value
>>> from galaxy.tools.parameters import ToolParameter
>>> p = ToolParameter.build( None, XML( '''
... <param name="blah" type="text" size="10" value="10">
... <validator type="expression" message="Not gonna happen">value.lower() == "foo"</validator>
... </param>
... ''' ) )
>>> t = p.validate( "Foo" )
>>> t = p.validate( "foo" )
>>> t = p.validate( "Fop" )
Traceback (most recent call last):
...
ValueError: Not gonna happen
"""
@classmethod
[docs] def from_element( cls, param, elem ):
return cls( elem.get( 'message' ), elem.text, elem.get( 'substitute_value_in_message' ) )
def __init__( self, message, expression, substitute_value_in_message ):
self.message = message
self.substitute_value_in_message = substitute_value_in_message
# Save compiled expression, code objects are thread safe (right?)
self.expression = compile( expression, '<string>', 'eval' )
[docs] def validate( self, value, history=None ):
if not( eval( self.expression, dict( value=value ) ) ):
message = self.message
if self.substitute_value_in_message:
message = message % value
raise ValueError( message )
[docs]class InRangeValidator( Validator ):
"""
Validator that ensures a number is in a specific range
>>> from galaxy.tools.parameters import ToolParameter
>>> p = ToolParameter.build( None, XML( '''
... <param name="blah" type="integer" size="10" value="10">
... <validator type="in_range" message="Not gonna happen" min="10" max="20"/>
... </param>
... ''' ) )
>>> t = p.validate( 10 )
>>> t = p.validate( 15 )
>>> t = p.validate( 20 )
>>> t = p.validate( 21 )
Traceback (most recent call last):
...
ValueError: Not gonna happen
"""
@classmethod
[docs] def from_element( cls, param, elem ):
return cls( elem.get( 'message', None ), elem.get( 'min' ), elem.get( 'max' ) )
def __init__( self, message, range_min, range_max ):
self.min = float( range_min if range_min is not None else '-inf' )
self.max = float( range_max if range_max is not None else 'inf' )
assert self.min <= self.max, 'min must be less than or equal to max'
# Remove unneeded 0s and decimal from floats to make message pretty.
self_min_str = str( self.min ).rstrip( '0' ).rstrip( '.' )
self_max_str = str( self.max ).rstrip( '0' ).rstrip( '.' )
self.message = message or "Value must be between %s and %s" % ( self_min_str, self_max_str )
[docs] def validate( self, value, history=None ):
if not( self.min <= float( value ) <= self.max ):
raise ValueError( self.message )
[docs]class LengthValidator( Validator ):
"""
Validator that ensures the length of the provided string (value) is in a specific range
>>> from galaxy.tools.parameters import ToolParameter
>>> p = ToolParameter.build( None, XML( '''
... <param name="blah" type="text" size="10" value="foobar">
... <validator type="length" min="2" max="8"/>
... </param>
... ''' ) )
>>> t = p.validate( "foo" )
>>> t = p.validate( "bar" )
>>> t = p.validate( "f" )
Traceback (most recent call last):
...
ValueError: Must have length of at least 2
>>> t = p.validate( "foobarbaz" )
Traceback (most recent call last):
...
ValueError: Must have length no more than 8
"""
@classmethod
[docs] def from_element( cls, param, elem ):
return cls( elem.get( 'message', None ), elem.get( 'min', None ), elem.get( 'max', None ) )
def __init__( self, message, length_min, length_max ):
self.message = message
if length_min is not None:
length_min = int( length_min )
if length_max is not None:
length_max = int( length_max )
self.min = length_min
self.max = length_max
[docs] def validate( self, value, history=None ):
if self.min is not None and len( value ) < self.min:
raise ValueError( self.message or ( "Must have length of at least %d" % self.min ) )
if self.max is not None and len( value ) > self.max:
raise ValueError( self.message or ( "Must have length no more than %d" % self.max ) )
[docs]class DatasetOkValidator( Validator ):
"""
Validator that checks if a dataset is in an 'ok' state
"""
def __init__( self, message=None ):
self.message = message
@classmethod
[docs] def from_element( cls, param, elem ):
return cls( elem.get( 'message', None ) )
[docs] def validate( self, value, history=None ):
if value and value.state != model.Dataset.states.OK:
if self.message is None:
self.message = "The selected dataset is still being generated, select another dataset or wait until it is completed"
raise ValueError( self.message )
[docs]class UnspecifiedBuildValidator( Validator ):
"""
Validator that checks for dbkey not equal to '?'
"""
def __init__( self, message=None ):
if message is None:
self.message = "Unspecified genome build, click the pencil icon in the history item to set the genome build"
else:
self.message = message
@classmethod
[docs] def from_element( cls, param, elem ):
return cls( elem.get( 'message', None ) )
[docs] def validate( self, value, history=None ):
#if value is None, we cannot validate
if value:
dbkey = value.metadata.dbkey
if isinstance( dbkey, list ):
dbkey = dbkey[0]
if dbkey == '?':
raise ValueError( self.message )
[docs]class NoOptionsValidator( Validator ):
"""Validator that checks for empty select list"""
def __init__( self, message=None ):
self.message = message
@classmethod
[docs] def from_element( cls, param, elem ):
return cls( elem.get( 'message', None ) )
[docs] def validate( self, value, history=None ):
if value is None:
if self.message is None:
self.message = "No options available for selection"
raise ValueError( self.message )
[docs]class EmptyTextfieldValidator( Validator ):
"""Validator that checks for empty text field"""
def __init__( self, message=None ):
self.message = message
@classmethod
[docs] def from_element( cls, param, elem ):
return cls( elem.get( 'message', None ) )
[docs] def validate( self, value, history=None ):
if value == '':
if self.message is None:
self.message = "Field requires a value"
raise ValueError( self.message )
validator_types = dict( expression=ExpressionValidator,
regex=RegexValidator,
in_range=InRangeValidator,
length=LengthValidator,
metadata=MetadataValidator,
unspecified_build=UnspecifiedBuildValidator,
no_options=NoOptionsValidator,
empty_field=EmptyTextfieldValidator,
dataset_metadata_in_file=MetadataInFileColumnValidator,
dataset_metadata_in_data_table=MetadataInDataTableColumnValidator,
dataset_ok_validator=DatasetOkValidator )
[docs]def get_suite():
"""Get unittest suite for this module"""
import doctest, sys
return doctest.DocTestSuite( sys.modules[__name__] )