"Fossies" - the Fresh Open Source Software Archive

Member "buildbot-2.5.1/buildbot/data/types.py" (24 Nov 2019, 11071 Bytes) of package /linux/misc/buildbot-2.5.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "types.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.5.0_vs_2.5.1.

    1 # This file is part of Buildbot.  Buildbot is free software: you can
    2 # redistribute it and/or modify it under the terms of the GNU General Public
    3 # License as published by the Free Software Foundation, version 2.
    4 #
    5 # This program is distributed in the hope that it will be useful, but WITHOUT
    6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    7 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    8 # details.
    9 #
   10 # You should have received a copy of the GNU General Public License along with
   11 # this program; if not, write to the Free Software Foundation, Inc., 51
   12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
   13 #
   14 # Copyright Buildbot Team Members
   15 
   16 # See "Type Validation" in master/docs/developer/tests.rst
   17 import datetime
   18 import json
   19 import re
   20 
   21 from buildbot import util
   22 from buildbot.util import bytes2unicode
   23 
   24 
   25 class Type:
   26 
   27     name = None
   28     doc = None
   29 
   30     @property
   31     def ramlname(self):
   32         return self.name
   33 
   34     def valueFromString(self, arg):
   35         # convert a urldecoded bytestring as given in a URL to a value, or
   36         # raise an exception trying.  This parent method raises an exception,
   37         # so if the method is missing in a subclass, it cannot be created from
   38         # a string.
   39         raise TypeError
   40 
   41     def cmp(self, val, arg):
   42         argVal = self.valueFromString(arg)
   43         if val < argVal:
   44             return -1
   45         elif val == argVal:
   46             return 0
   47         return 1
   48 
   49     def validate(self, name, object):
   50         raise NotImplementedError
   51 
   52     def getSpec(self):
   53         r = dict(name=self.name)
   54         if self.doc is not None:
   55             r["doc"] = self.doc
   56         return r
   57 
   58 
   59 class NoneOk(Type):
   60 
   61     def __init__(self, nestedType):
   62         assert isinstance(nestedType, Type)
   63         self.nestedType = nestedType
   64         self.name = self.nestedType.name + " or None"
   65 
   66     @property
   67     def ramlname(self):
   68         return self.nestedType.ramlname
   69 
   70     def valueFromString(self, arg):
   71         return self.nestedType.valueFromString(arg)
   72 
   73     def cmp(self, val, arg):
   74         return self.nestedType.cmp(val, arg)
   75 
   76     def validate(self, name, object):
   77         if object is None:
   78             return
   79         for msg in self.nestedType.validate(name, object):
   80             yield msg
   81 
   82     def getSpec(self):
   83         r = self.nestedType.getSpec()
   84         r["can_be_null"] = True
   85         return r
   86 
   87     def toRaml(self):
   88         return self.nestedType.toRaml()
   89 
   90 
   91 class Instance(Type):
   92 
   93     types = ()
   94     ramlType = "unknown"
   95 
   96     @property
   97     def ramlname(self):
   98         return self.ramlType
   99 
  100     def validate(self, name, object):
  101         if not isinstance(object, self.types):
  102             yield "%s (%r) is not a %s" % (
  103                 name, object, self.name or repr(self.types))
  104 
  105     def toRaml(self):
  106         return self.ramlType
  107 
  108 
  109 class Integer(Instance):
  110 
  111     name = "integer"
  112     types = (int,)
  113     ramlType = "integer"
  114 
  115     def valueFromString(self, arg):
  116         return int(arg)
  117 
  118 
  119 class DateTime(Instance):
  120     name = "datetime"
  121     types = (datetime.datetime)
  122     ramlType = "date"
  123 
  124 
  125 class String(Instance):
  126 
  127     name = "string"
  128     types = (str,)
  129     ramlType = "string"
  130 
  131     def valueFromString(self, arg):
  132         val = util.bytes2unicode(arg)
  133         return val
  134 
  135 
  136 class Binary(Instance):
  137 
  138     name = "binary"
  139     types = (bytes,)
  140     ramlType = "string"
  141 
  142     def valueFromString(self, arg):
  143         return arg
  144 
  145 
  146 class Boolean(Instance):
  147 
  148     name = "boolean"
  149     types = (bool,)
  150     ramlType = "boolean"
  151 
  152     def valueFromString(self, arg):
  153         return util.string2boolean(arg)
  154 
  155 
  156 class Identifier(Type):
  157 
  158     name = "identifier"
  159     identRe = re.compile('^[a-zA-Z_-][a-zA-Z0-9._-]*$')
  160     ramlType = "string"
  161 
  162     def __init__(self, len=None, **kwargs):
  163         super().__init__(**kwargs)
  164         self.len = len
  165 
  166     def valueFromString(self, arg):
  167         val = util.bytes2unicode(arg)
  168         if not self.identRe.match(val) or len(val) > self.len or not val:
  169             raise TypeError
  170         return val
  171 
  172     def validate(self, name, object):
  173         if not isinstance(object, str):
  174             yield "%s - %r - is not a unicode string" % (name, object)
  175         elif not self.identRe.match(object):
  176             yield "%s - %r - is not an identifier" % (name, object)
  177         elif not object:
  178             yield "%s - identifiers cannot be an empty string" % (name,)
  179         elif len(object) > self.len:
  180             yield "%s - %r - is longer than %d characters" % (name, object,
  181                                                               self.len)
  182 
  183     def toRaml(self):
  184         return {'type': self.ramlType,
  185                 'pattern': self.identRe.pattern}
  186 
  187 
  188 class List(Type):
  189 
  190     name = "list"
  191     ramlType = "list"
  192 
  193     @property
  194     def ramlname(self):
  195         return self.of.ramlname
  196 
  197     def __init__(self, of=None, **kwargs):
  198         super().__init__(**kwargs)
  199         self.of = of
  200 
  201     def validate(self, name, object):
  202         if not isinstance(object, list):  # we want a list, and NOT a subclass
  203             yield "%s (%r) is not a %s" % (name, object, self.name)
  204             return
  205 
  206         for idx, elt in enumerate(object):
  207             for msg in self.of.validate("%s[%d]" % (name, idx), elt):
  208                 yield msg
  209 
  210     def valueFromString(self, arg):
  211         # valueFromString is used to process URL args, which come one at
  212         # a time, so we defer to the `of`
  213         return self.of.valueFromString(arg)
  214 
  215     def getSpec(self):
  216         return dict(type=self.name,
  217                     of=self.of.getSpec())
  218 
  219     def toRaml(self):
  220         return {'type': 'array', 'items': self.of.name}
  221 
  222 
  223 def maybeNoneOrList(k, v):
  224     if isinstance(v, NoneOk):
  225         return k + "?"
  226     if isinstance(v, List):
  227         return k + "[]"
  228     return k
  229 
  230 
  231 class SourcedProperties(Type):
  232 
  233     name = "sourcedproperties"
  234 
  235     def validate(self, name, object):
  236         if not isinstance(object, dict):  # we want a dict, and NOT a subclass
  237             yield "%s is not sourced properties (not a dict)" % (name,)
  238             return
  239         for k, v in object.items():
  240             if not isinstance(k, str):
  241                 yield "%s property name %r is not unicode" % (name, k)
  242             if not isinstance(v, tuple) or len(v) != 2:
  243                 yield "%s property value for '%s' is not a 2-tuple" % (name, k)
  244                 return
  245             propval, propsrc = v
  246             if not isinstance(propsrc, str):
  247                 yield "%s[%s] source %r is not unicode" % (name, k, propsrc)
  248             try:
  249                 json.loads(bytes2unicode(propval))
  250             except ValueError:
  251                 yield "%s[%r] value is not JSON-able" % (name, k)
  252 
  253     def toRaml(self):
  254         return {'type': "object",
  255                 'properties':
  256                 {'[]': {'type': 'object',
  257                         'properties': {
  258                             1: 'string',
  259                             2: 'integer | string | object | array | boolean'
  260                         }
  261                         }}}
  262 
  263 
  264 class Dict(Type):
  265     name = "dict"
  266 
  267     @property
  268     def ramlname(self):
  269         return self.toRaml()
  270 
  271     def __init__(self, **contents):
  272         self.contents = contents
  273         self.keys = set(contents)
  274 
  275     def validate(self, name, object):
  276         if not isinstance(object, dict):
  277             yield "%s (%r) is not a dictionary (got type %s)" \
  278                 % (name, object, type(object))
  279             return
  280 
  281         gotNames = set(object.keys())
  282 
  283         unexpected = gotNames - self.keys
  284         if unexpected:
  285             yield "%s has unexpected keys %s" % (name,
  286                                                  ", ".join([repr(n) for n in unexpected]))
  287 
  288         missing = self.keys - gotNames
  289         if missing:
  290             yield "%s is missing keys %s" % (name,
  291                                              ", ".join([repr(n) for n in missing]))
  292 
  293         for k in gotNames & self.keys:
  294             f = self.contents[k]
  295             for msg in f.validate("%s[%r]" % (name, k), object[k]):
  296                 yield msg
  297 
  298     def getSpec(self):
  299         return dict(type=self.name,
  300                     fields=[dict(name=k,
  301                                  type=v.name,
  302                                  type_spec=v.getSpec())
  303                             for k, v in self.contents.items()
  304                             ])
  305 
  306     def toRaml(self):
  307         return {'type': "object",
  308                 'properties': {maybeNoneOrList(k, v): v.ramlname for k, v in self.contents.items()}}
  309 
  310 
  311 class JsonObject(Type):
  312     name = "jsonobject"
  313     ramlname = 'object'
  314 
  315     def validate(self, name, object):
  316         if not isinstance(object, dict):
  317             yield "%s (%r) is not a dictionary (got type %s)" \
  318                 % (name, object, type(object))
  319             return
  320 
  321         # make sure JSON can represent it
  322         try:
  323             json.dumps(object)
  324         except Exception as e:
  325             yield "%s is not JSON-able: %s" % (name, e)
  326             return
  327 
  328     def toRaml(self):
  329         return "object"
  330 
  331 
  332 class Entity(Type):
  333 
  334     # NOTE: this type is defined by subclassing it in each resource type class.
  335     # Instances are generally accessed at e.g.,
  336     #  * buildsets.Buildset.entityType or
  337     #  * self.master.data.rtypes.buildsets.entityType
  338 
  339     name = None  # set in constructor
  340     fields = {}
  341     fieldNames = set([])
  342 
  343     def __init__(self, name):
  344         fields = {}
  345         for k, v in self.__class__.__dict__.items():
  346             if isinstance(v, Type):
  347                 fields[k] = v
  348         self.fields = fields
  349         self.fieldNames = set(fields)
  350         self.name = name
  351 
  352     def validate(self, name, object):
  353         # this uses isinstance, allowing dict subclasses as used by the DB API
  354         if not isinstance(object, dict):
  355             yield "%s (%r) is not a dictionary (got type %s)" \
  356                 % (name, object, type(object))
  357             return
  358 
  359         gotNames = set(object.keys())
  360 
  361         unexpected = gotNames - self.fieldNames
  362         if unexpected:
  363             yield "%s has unexpected keys %s" % (name,
  364                                                  ", ".join([repr(n) for n in unexpected]))
  365 
  366         missing = self.fieldNames - gotNames
  367         if missing:
  368             yield "%s is missing keys %s" % (name,
  369                                              ", ".join([repr(n) for n in missing]))
  370 
  371         for k in gotNames & self.fieldNames:
  372             f = self.fields[k]
  373             for msg in f.validate("%s[%r]" % (name, k), object[k]):
  374                 yield msg
  375 
  376     def getSpec(self):
  377         return dict(type=self.name,
  378                     fields=[dict(name=k,
  379                                  type=v.name,
  380                                  type_spec=v.getSpec())
  381                             for k, v in self.fields.items()
  382                             ])
  383 
  384     def toRaml(self):
  385         return {'type': "object",
  386                 'properties': {
  387                     maybeNoneOrList(k, v): {'type': v.ramlname, 'description': ''}
  388                     for k, v in self.fields.items()}}