Source code for trackhub.validate

"""
This module contains functions that are used as validators for parameters. Each
is decorated with the @validator decorator, which accepts an arbitrary number
of examples that should correctly validate.

For example, here's a validator that would be used to ensure a string has
exactly one string character:

    @validator("a", "1")
    def one_char(v):
        return isinstance(v, string_types) and len(v) == 1
"""
import warnings
from .compatibility import string_types
from . import settings


class ValidationError(Exception):
    pass


def validator(*example):
    """
    Decorator that runs a self-test on the validator it decorates
    """

    def wrapper(func):

        # Try running validation on the function's own example . . . it better
        # work!
        example_string = []
        try:
            for ex in example:
                func(ex)
                example_string.append(ex)
            example_string = "Example value(s): %s" % (
                " or ".join("%r" % i for i in example_string)
            )

        except Exception as e:
            raise ValueError(
                "Error validating example (func=%s, example=%r)! "
                "\nOriginal error:\n\t%s: %s"
                % (func.__name__, example, e.__class__.__name__, e.message)
            )

        class Validator(object):
            """
            Class to wrap a function and display an example value.
            """

            _func = func

            def __call__(self, v):
                try:
                    result = func(v)
                except Exception as e:
                    result = False
                if not result and settings.VALIDATE:
                    raise ValidationError(
                        "Value {0} failed {1} validation; {2}".format(
                            v, func.__name__, example_string
                        )
                    )
                return result

            def __str__(self):
                return "<Validator [%s] at %s> sample: %s" % (
                    func.__name__,
                    id(func),
                    example,
                )

        return Validator()

    return wrapper


[docs] class Param(object):
[docs] def __init__(self, name, fmt, types, required, validator, min_bed_fields=None): """ Parameters ---------- name : str Name of the parameter fmt : list List of strings parsed from the "format" section of the spec from UCSC. Mostly used as an informal guide to the format. types : list List of track types this parameter applies to required : bool or list If True, all tracks must have it. If list, only those types must have it. validator : callable, set, or type Validation to run on user-provided values. If callable, must return True if the value passes. If set, validation will pass if the value is in the provided set. min_bed_fields : int Some parameters only work for a certain number of BED fields. Specify that here. Examples -------- >>> Param(name='test', fmt=['test <#>'], types=['bigBed'], required=False, validator=int).validate(999) True >>> Param(name='test', fmt=['test <#>'], types=['bigBed'], required=False, validator=int).validate('999') True >>> Param(name='test', fmt=['test <#>'], types=['bigBed'], required=False, validator=int).validate(0) True """ self.name = name self.fmt = fmt self.types = types self.required = required self.validator = validator
def __str__(self): return '<%s "%s" at %s>' % (self.__class__.__name__, self.name, id(self)) def validate(self, value): if isinstance(self.validator, set): if value in self.validator: return True if isinstance(self.validator, type): if isinstance(value, self.validator): return True else: # Otherwise, allow any exceptions to propagate up. self.validator(value) return True if hasattr(self.validator, "__call__"): return self.validator(value) elif value == self.validator: return True return False
@validator("tag=value", "tag1=val1 tag2=val2") def key_val(v): try: assert "=" in v items = v.split() assert len(items) == v.count("=") return True except AssertionError: raise ValidationError @validator("a,b,c") def CSV(v): # TODO: is a one-item list "chr1," or "chr1"? if isinstance(v, string_types): return True raise ValidationError @validator("a:b:c", "0:10:100") def ColSV3(v): nvalues = 3 if not isinstance(v, string_types): raise ValueError("not a string") vs = v.split(":") assert len(vs) == nvalues return True @validator("a:b") def ColSV2(v): nvalues = 2 if not isinstance(v, string_types): raise ValueError("not a string") vs = v.split(":") assert len(vs) == nvalues return True @validator("1:5", 3, "3") def ColSV2_numbers_or_single_number(v): v = str(v) vs = v.split(":") assert len(vs) in [1, 2] for i in vs: try: float(i) except ValueError: raise ValueError( "{0} in parameter {1} cannot be converted " "into a number".format(i, v) ) return True @validator("128,0,255") def RGB(v): if " " in v: raise ValueError("Space in RGB tuple") if "." in v: raise ValueError('"." in RGB tuple') assert isinstance(v, string_types), "RGB tuple is not a string" if "," not in v: raise ValueError("no commas in RGB tuple") rgb = v.split(",") assert len(rgb) == 3, "RGB tuple does not have 3 values" try: rgb = map(int, rgb) except ValueError: raise ValueError("RGB tuple does not contain ints") for i in rgb: assert 0 <= i <= 255 return True @validator("128,0,0 90,90,5") def RGBList(v): rgbs = v.split(" ") assert len(rgbs) == 2, "RGBList not a space-separated list of RGB tuples" for i in rgbs: RGB(i) return True @validator("off", 1) def off_or_int(v): try: int(v) return True except ValueError: if v == "off": return True raise @validator("chr21:33031596-33033258") def ucsc_position(v): try: chrom, pos = v.split(":") start, end = pos.split("-") start = int(start) end = int(end) except ValueError: raise ValueError("UCSC position string is formatted incorrectly") assert start >= 0, "start position must be a positive integer" assert end >= 0, "end position must be a positive integer" assert start < end, "start must be less than end" return True @validator("asdf1234_33", "AZ90") def alphanumeric_(v): valid = "abcdefghijklmnopqrstuvwxyz" valid += "ABCDEFGHIJKLMNOPQRSTUVWXYZ" valid += "01234556789" valid += "_" assert isinstance(v, string_types) for i in v: if i not in valid: return False return True @validator("one two three", "aaaaaaaaaaaaaaaaa") def short_label(v): assert isinstance(v, string_types) if len(v) > 17: warnings.warn( "shortLabel is limited to 17 characters " "in the browser, some characters will be truncated" ) return True @validator("a" * 76, "four five six") def long_label(v): assert isinstance(v, string_types) if len(v) > 76: warnings.warn( "longLabel is limited to 76 characters " "in the browser, some characters will be truncated" ) return True @validator("https://example.com", "path/to/a.html") def full_or_local_url(v): return isinstance(v, string_types) @validator("https://example.com", "path/to/a.html") def full_url(v): return isinstance(v, string_types) @validator(1, "1", "500") def int_like(v): try: int(v) return True except ValueError: return False @validator(1.0, "5.556") def float_like(v): try: float(v) return True except ValueError: return False @validator("#ff0000", "maroon") def hex_or_named(v): valid = "0123456789ABCDEF" try: if v.startswith("#"): assert len(v.upper()[1:]) == 6 for i in v[1:]: assert i in valid else: assert v in set( [ "black", "silver", "gray", "white", "maroon", "red", "purple", "fuchsia", "green", "lime", "olive", "yellow", "navy", "blue", "teal", "aqua", ] ) except AssertionError: return False return True @validator("bigBed", "bigBed 6+3") def tracktypes(v): return True