We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Cannot filter on started_at in REST API
I wanted to list all builds in the last week using the REST API.  `.../api/v2/builds?order=-started` gets the list of all builds ever sorted in date order, but adding for example `started_at__ge=1598375285` to filter to just builds started after a specific timestamp results in an `invalid filter value for started_at__ge`.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `master/buildbot/data/types.py`
Content:
```
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 "{} ({}) is not a {}".format(name, repr(object), self.name or repr(self.types))
103 
104     def toRaml(self):
105         return self.ramlType
106 
107 
108 class Integer(Instance):
109 
110     name = "integer"
111     types = (int,)
112     ramlType = "integer"
113 
114     def valueFromString(self, arg):
115         return int(arg)
116 
117 
118 class DateTime(Instance):
119     name = "datetime"
120     types = (datetime.datetime)
121     ramlType = "date"
122 
123 
124 class String(Instance):
125 
126     name = "string"
127     types = (str,)
128     ramlType = "string"
129 
130     def valueFromString(self, arg):
131         val = util.bytes2unicode(arg)
132         return val
133 
134 
135 class Binary(Instance):
136 
137     name = "binary"
138     types = (bytes,)
139     ramlType = "string"
140 
141     def valueFromString(self, arg):
142         return arg
143 
144 
145 class Boolean(Instance):
146 
147     name = "boolean"
148     types = (bool,)
149     ramlType = "boolean"
150 
151     def valueFromString(self, arg):
152         return util.string2boolean(arg)
153 
154 
155 class Identifier(Type):
156 
157     name = "identifier"
158     identRe = re.compile('^[a-zA-Z_-][a-zA-Z0-9._-]*$')
159     ramlType = "string"
160 
161     def __init__(self, len=None, **kwargs):
162         super().__init__(**kwargs)
163         self.len = len
164 
165     def valueFromString(self, arg):
166         val = util.bytes2unicode(arg)
167         if not self.identRe.match(val) or len(val) > self.len or not val:
168             raise TypeError
169         return val
170 
171     def validate(self, name, object):
172         if not isinstance(object, str):
173             yield "{} - {} - is not a unicode string".format(name, repr(object))
174         elif not self.identRe.match(object):
175             yield "{} - {} - is not an identifier".format(name, repr(object))
176         elif not object:
177             yield "{} - identifiers cannot be an empty string".format(name)
178         elif len(object) > self.len:
179             yield "{} - {} - is longer than {} characters".format(name, repr(object), self.len)
180 
181     def toRaml(self):
182         return {'type': self.ramlType,
183                 'pattern': self.identRe.pattern}
184 
185 
186 class List(Type):
187 
188     name = "list"
189     ramlType = "list"
190 
191     @property
192     def ramlname(self):
193         return self.of.ramlname
194 
195     def __init__(self, of=None, **kwargs):
196         super().__init__(**kwargs)
197         self.of = of
198 
199     def validate(self, name, object):
200         if not isinstance(object, list):  # we want a list, and NOT a subclass
201             yield "{} ({}) is not a {}".format(name, repr(object), self.name)
202             return
203 
204         for idx, elt in enumerate(object):
205             for msg in self.of.validate("{}[{}]".format(name, idx), elt):
206                 yield msg
207 
208     def valueFromString(self, arg):
209         # valueFromString is used to process URL args, which come one at
210         # a time, so we defer to the `of`
211         return self.of.valueFromString(arg)
212 
213     def getSpec(self):
214         return dict(type=self.name,
215                     of=self.of.getSpec())
216 
217     def toRaml(self):
218         return {'type': 'array', 'items': self.of.name}
219 
220 
221 def maybeNoneOrList(k, v):
222     if isinstance(v, NoneOk):
223         return k + "?"
224     if isinstance(v, List):
225         return k + "[]"
226     return k
227 
228 
229 class SourcedProperties(Type):
230 
231     name = "sourcedproperties"
232 
233     def validate(self, name, object):
234         if not isinstance(object, dict):  # we want a dict, and NOT a subclass
235             yield "{} is not sourced properties (not a dict)".format(name)
236             return
237         for k, v in object.items():
238             if not isinstance(k, str):
239                 yield "{} property name {} is not unicode".format(name, repr(k))
240             if not isinstance(v, tuple) or len(v) != 2:
241                 yield "{} property value for '{}' is not a 2-tuple".format(name, k)
242                 return
243             propval, propsrc = v
244             if not isinstance(propsrc, str):
245                 yield "{}[{}] source {} is not unicode".format(name, k, repr(propsrc))
246             try:
247                 json.loads(bytes2unicode(propval))
248             except ValueError:
249                 yield "{}[{}] value is not JSON-able".format(name, repr(k))
250 
251     def toRaml(self):
252         return {'type': "object",
253                 'properties':
254                 {'[]': {'type': 'object',
255                         'properties': {
256                             1: 'string',
257                             2: 'integer | string | object | array | boolean'
258                         }
259                         }}}
260 
261 
262 class Dict(Type):
263     name = "dict"
264 
265     @property
266     def ramlname(self):
267         return self.toRaml()
268 
269     def __init__(self, **contents):
270         self.contents = contents
271         self.keys = set(contents)
272 
273     def validate(self, name, object):
274         if not isinstance(object, dict):
275             yield "{} ({}) is not a dictionary (got type {})".format(name, repr(object),
276                                                                      type(object))
277             return
278 
279         gotNames = set(object.keys())
280 
281         unexpected = gotNames - self.keys
282         if unexpected:
283             yield "{} has unexpected keys {}".format(name, ", ".join([repr(n) for n in unexpected]))
284 
285         missing = self.keys - gotNames
286         if missing:
287             yield "{} is missing keys {}".format(name, ", ".join([repr(n) for n in missing]))
288 
289         for k in gotNames & self.keys:
290             f = self.contents[k]
291             for msg in f.validate("{}[{}]".format(name, repr(k)), object[k]):
292                 yield msg
293 
294     def getSpec(self):
295         return dict(type=self.name,
296                     fields=[dict(name=k,
297                                  type=v.name,
298                                  type_spec=v.getSpec())
299                             for k, v in self.contents.items()
300                             ])
301 
302     def toRaml(self):
303         return {'type': "object",
304                 'properties': {maybeNoneOrList(k, v): v.ramlname for k, v in self.contents.items()}}
305 
306 
307 class JsonObject(Type):
308     name = "jsonobject"
309     ramlname = 'object'
310 
311     def validate(self, name, object):
312         if not isinstance(object, dict):
313             yield "{} ({}) is not a dictionary (got type {})".format(name, repr(object),
314                                                                      type(object))
315             return
316 
317         # make sure JSON can represent it
318         try:
319             json.dumps(object)
320         except Exception as e:
321             yield "{} is not JSON-able: {}".format(name, e)
322             return
323 
324     def toRaml(self):
325         return "object"
326 
327 
328 class Entity(Type):
329 
330     # NOTE: this type is defined by subclassing it in each resource type class.
331     # Instances are generally accessed at e.g.,
332     #  * buildsets.Buildset.entityType or
333     #  * self.master.data.rtypes.buildsets.entityType
334 
335     name = None  # set in constructor
336     fields = {}
337     fieldNames = set([])
338 
339     def __init__(self, name):
340         fields = {}
341         for k, v in self.__class__.__dict__.items():
342             if isinstance(v, Type):
343                 fields[k] = v
344         self.fields = fields
345         self.fieldNames = set(fields)
346         self.name = name
347 
348     def validate(self, name, object):
349         # this uses isinstance, allowing dict subclasses as used by the DB API
350         if not isinstance(object, dict):
351             yield "{} ({}) is not a dictionary (got type {})".format(name, repr(object),
352                                                                      type(object))
353             return
354 
355         gotNames = set(object.keys())
356 
357         unexpected = gotNames - self.fieldNames
358         if unexpected:
359             yield "{} has unexpected keys {}".format(name, ", ".join([repr(n) for n in unexpected]))
360 
361         missing = self.fieldNames - gotNames
362         if missing:
363             yield "{} is missing keys {}".format(name, ", ".join([repr(n) for n in missing]))
364 
365         for k in gotNames & self.fieldNames:
366             f = self.fields[k]
367             for msg in f.validate("{}[{}]".format(name, repr(k)), object[k]):
368                 yield msg
369 
370     def getSpec(self):
371         return dict(type=self.name,
372                     fields=[dict(name=k,
373                                  type=v.name,
374                                  type_spec=v.getSpec())
375                             for k, v in self.fields.items()
376                             ])
377 
378     def toRaml(self):
379         return {'type': "object",
380                 'properties': {
381                     maybeNoneOrList(k, v): {'type': v.ramlname, 'description': ''}
382                     for k, v in self.fields.items()}}
383
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/master/buildbot/data/types.py b/master/buildbot/data/types.py
--- a/master/buildbot/data/types.py
+++ b/master/buildbot/data/types.py
@@ -116,8 +116,25 @@
 
 
 class DateTime(Instance):
+
     name = "datetime"
-    types = (datetime.datetime)
+    types = (datetime.datetime,)
+
+    def valueFromString(self, arg):
+        return int(arg)
+
+    def validate(self, name, object):
+        if isinstance(object, datetime.datetime):
+            return
+        if isinstance(object, int):
+            try:
+                datetime.datetime.fromtimestamp(object)
+            except (OverflowError, OSError):
+                pass
+            else:
+                return
+        yield "{} ({}) is not a valid timestamp".format(name, object)
+
     ramlType = "date"

{"golden_diff": "diff --git a/master/buildbot/data/types.py b/master/buildbot/data/types.py\n--- a/master/buildbot/data/types.py\n+++ b/master/buildbot/data/types.py\n@@ -116,8 +116,25 @@\n \n \n class DateTime(Instance):\n+\n     name = \"datetime\"\n-    types = (datetime.datetime)\n+    types = (datetime.datetime,)\n+\n+    def valueFromString(self, arg):\n+        return int(arg)\n+\n+    def validate(self, name, object):\n+        if isinstance(object, datetime.datetime):\n+            return\n+        if isinstance(object, int):\n+            try:\n+                datetime.datetime.fromtimestamp(object)\n+            except (OverflowError, OSError):\n+                pass\n+            else:\n+                return\n+        yield \"{} ({}) is not a valid timestamp\".format(name, object)\n+\n     ramlType = \"date\"\n", "issue": "Cannot filter on started_at in REST API\nI wanted to list all builds in the last week using the REST API.  `.../api/v2/builds?order=-started` gets the list of all builds ever sorted in date order, but adding for example `started_at__ge=1598375285` to filter to just builds started after a specific timestamp results in an `invalid filter value for started_at__ge`.\r\n\n", "before_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\n# See \"Type Validation\" in master/docs/developer/tests.rst\nimport datetime\nimport json\nimport re\n\nfrom buildbot import util\nfrom buildbot.util import bytes2unicode\n\n\nclass Type:\n\n    name = None\n    doc = None\n\n    @property\n    def ramlname(self):\n        return self.name\n\n    def valueFromString(self, arg):\n        # convert a urldecoded bytestring as given in a URL to a value, or\n        # raise an exception trying.  This parent method raises an exception,\n        # so if the method is missing in a subclass, it cannot be created from\n        # a string.\n        raise TypeError\n\n    def cmp(self, val, arg):\n        argVal = self.valueFromString(arg)\n        if val < argVal:\n            return -1\n        elif val == argVal:\n            return 0\n        return 1\n\n    def validate(self, name, object):\n        raise NotImplementedError\n\n    def getSpec(self):\n        r = dict(name=self.name)\n        if self.doc is not None:\n            r[\"doc\"] = self.doc\n        return r\n\n\nclass NoneOk(Type):\n\n    def __init__(self, nestedType):\n        assert isinstance(nestedType, Type)\n        self.nestedType = nestedType\n        self.name = self.nestedType.name + \" or None\"\n\n    @property\n    def ramlname(self):\n        return self.nestedType.ramlname\n\n    def valueFromString(self, arg):\n        return self.nestedType.valueFromString(arg)\n\n    def cmp(self, val, arg):\n        return self.nestedType.cmp(val, arg)\n\n    def validate(self, name, object):\n        if object is None:\n            return\n        for msg in self.nestedType.validate(name, object):\n            yield msg\n\n    def getSpec(self):\n        r = self.nestedType.getSpec()\n        r[\"can_be_null\"] = True\n        return r\n\n    def toRaml(self):\n        return self.nestedType.toRaml()\n\n\nclass Instance(Type):\n\n    types = ()\n    ramlType = \"unknown\"\n\n    @property\n    def ramlname(self):\n        return self.ramlType\n\n    def validate(self, name, object):\n        if not isinstance(object, self.types):\n            yield \"{} ({}) is not a {}\".format(name, repr(object), self.name or repr(self.types))\n\n    def toRaml(self):\n        return self.ramlType\n\n\nclass Integer(Instance):\n\n    name = \"integer\"\n    types = (int,)\n    ramlType = \"integer\"\n\n    def valueFromString(self, arg):\n        return int(arg)\n\n\nclass DateTime(Instance):\n    name = \"datetime\"\n    types = (datetime.datetime)\n    ramlType = \"date\"\n\n\nclass String(Instance):\n\n    name = \"string\"\n    types = (str,)\n    ramlType = \"string\"\n\n    def valueFromString(self, arg):\n        val = util.bytes2unicode(arg)\n        return val\n\n\nclass Binary(Instance):\n\n    name = \"binary\"\n    types = (bytes,)\n    ramlType = \"string\"\n\n    def valueFromString(self, arg):\n        return arg\n\n\nclass Boolean(Instance):\n\n    name = \"boolean\"\n    types = (bool,)\n    ramlType = \"boolean\"\n\n    def valueFromString(self, arg):\n        return util.string2boolean(arg)\n\n\nclass Identifier(Type):\n\n    name = \"identifier\"\n    identRe = re.compile('^[a-zA-Z_-][a-zA-Z0-9._-]*$')\n    ramlType = \"string\"\n\n    def __init__(self, len=None, **kwargs):\n        super().__init__(**kwargs)\n        self.len = len\n\n    def valueFromString(self, arg):\n        val = util.bytes2unicode(arg)\n        if not self.identRe.match(val) or len(val) > self.len or not val:\n            raise TypeError\n        return val\n\n    def validate(self, name, object):\n        if not isinstance(object, str):\n            yield \"{} - {} - is not a unicode string\".format(name, repr(object))\n        elif not self.identRe.match(object):\n            yield \"{} - {} - is not an identifier\".format(name, repr(object))\n        elif not object:\n            yield \"{} - identifiers cannot be an empty string\".format(name)\n        elif len(object) > self.len:\n            yield \"{} - {} - is longer than {} characters\".format(name, repr(object), self.len)\n\n    def toRaml(self):\n        return {'type': self.ramlType,\n                'pattern': self.identRe.pattern}\n\n\nclass List(Type):\n\n    name = \"list\"\n    ramlType = \"list\"\n\n    @property\n    def ramlname(self):\n        return self.of.ramlname\n\n    def __init__(self, of=None, **kwargs):\n        super().__init__(**kwargs)\n        self.of = of\n\n    def validate(self, name, object):\n        if not isinstance(object, list):  # we want a list, and NOT a subclass\n            yield \"{} ({}) is not a {}\".format(name, repr(object), self.name)\n            return\n\n        for idx, elt in enumerate(object):\n            for msg in self.of.validate(\"{}[{}]\".format(name, idx), elt):\n                yield msg\n\n    def valueFromString(self, arg):\n        # valueFromString is used to process URL args, which come one at\n        # a time, so we defer to the `of`\n        return self.of.valueFromString(arg)\n\n    def getSpec(self):\n        return dict(type=self.name,\n                    of=self.of.getSpec())\n\n    def toRaml(self):\n        return {'type': 'array', 'items': self.of.name}\n\n\ndef maybeNoneOrList(k, v):\n    if isinstance(v, NoneOk):\n        return k + \"?\"\n    if isinstance(v, List):\n        return k + \"[]\"\n    return k\n\n\nclass SourcedProperties(Type):\n\n    name = \"sourcedproperties\"\n\n    def validate(self, name, object):\n        if not isinstance(object, dict):  # we want a dict, and NOT a subclass\n            yield \"{} is not sourced properties (not a dict)\".format(name)\n            return\n        for k, v in object.items():\n            if not isinstance(k, str):\n                yield \"{} property name {} is not unicode\".format(name, repr(k))\n            if not isinstance(v, tuple) or len(v) != 2:\n                yield \"{} property value for '{}' is not a 2-tuple\".format(name, k)\n                return\n            propval, propsrc = v\n            if not isinstance(propsrc, str):\n                yield \"{}[{}] source {} is not unicode\".format(name, k, repr(propsrc))\n            try:\n                json.loads(bytes2unicode(propval))\n            except ValueError:\n                yield \"{}[{}] value is not JSON-able\".format(name, repr(k))\n\n    def toRaml(self):\n        return {'type': \"object\",\n                'properties':\n                {'[]': {'type': 'object',\n                        'properties': {\n                            1: 'string',\n                            2: 'integer | string | object | array | boolean'\n                        }\n                        }}}\n\n\nclass Dict(Type):\n    name = \"dict\"\n\n    @property\n    def ramlname(self):\n        return self.toRaml()\n\n    def __init__(self, **contents):\n        self.contents = contents\n        self.keys = set(contents)\n\n    def validate(self, name, object):\n        if not isinstance(object, dict):\n            yield \"{} ({}) is not a dictionary (got type {})\".format(name, repr(object),\n                                                                     type(object))\n            return\n\n        gotNames = set(object.keys())\n\n        unexpected = gotNames - self.keys\n        if unexpected:\n            yield \"{} has unexpected keys {}\".format(name, \", \".join([repr(n) for n in unexpected]))\n\n        missing = self.keys - gotNames\n        if missing:\n            yield \"{} is missing keys {}\".format(name, \", \".join([repr(n) for n in missing]))\n\n        for k in gotNames & self.keys:\n            f = self.contents[k]\n            for msg in f.validate(\"{}[{}]\".format(name, repr(k)), object[k]):\n                yield msg\n\n    def getSpec(self):\n        return dict(type=self.name,\n                    fields=[dict(name=k,\n                                 type=v.name,\n                                 type_spec=v.getSpec())\n                            for k, v in self.contents.items()\n                            ])\n\n    def toRaml(self):\n        return {'type': \"object\",\n                'properties': {maybeNoneOrList(k, v): v.ramlname for k, v in self.contents.items()}}\n\n\nclass JsonObject(Type):\n    name = \"jsonobject\"\n    ramlname = 'object'\n\n    def validate(self, name, object):\n        if not isinstance(object, dict):\n            yield \"{} ({}) is not a dictionary (got type {})\".format(name, repr(object),\n                                                                     type(object))\n            return\n\n        # make sure JSON can represent it\n        try:\n            json.dumps(object)\n        except Exception as e:\n            yield \"{} is not JSON-able: {}\".format(name, e)\n            return\n\n    def toRaml(self):\n        return \"object\"\n\n\nclass Entity(Type):\n\n    # NOTE: this type is defined by subclassing it in each resource type class.\n    # Instances are generally accessed at e.g.,\n    #  * buildsets.Buildset.entityType or\n    #  * self.master.data.rtypes.buildsets.entityType\n\n    name = None  # set in constructor\n    fields = {}\n    fieldNames = set([])\n\n    def __init__(self, name):\n        fields = {}\n        for k, v in self.__class__.__dict__.items():\n            if isinstance(v, Type):\n                fields[k] = v\n        self.fields = fields\n        self.fieldNames = set(fields)\n        self.name = name\n\n    def validate(self, name, object):\n        # this uses isinstance, allowing dict subclasses as used by the DB API\n        if not isinstance(object, dict):\n            yield \"{} ({}) is not a dictionary (got type {})\".format(name, repr(object),\n                                                                     type(object))\n            return\n\n        gotNames = set(object.keys())\n\n        unexpected = gotNames - self.fieldNames\n        if unexpected:\n            yield \"{} has unexpected keys {}\".format(name, \", \".join([repr(n) for n in unexpected]))\n\n        missing = self.fieldNames - gotNames\n        if missing:\n            yield \"{} is missing keys {}\".format(name, \", \".join([repr(n) for n in missing]))\n\n        for k in gotNames & self.fieldNames:\n            f = self.fields[k]\n            for msg in f.validate(\"{}[{}]\".format(name, repr(k)), object[k]):\n                yield msg\n\n    def getSpec(self):\n        return dict(type=self.name,\n                    fields=[dict(name=k,\n                                 type=v.name,\n                                 type_spec=v.getSpec())\n                            for k, v in self.fields.items()\n                            ])\n\n    def toRaml(self):\n        return {'type': \"object\",\n                'properties': {\n                    maybeNoneOrList(k, v): {'type': v.ramlname, 'description': ''}\n                    for k, v in self.fields.items()}}\n", "path": "master/buildbot/data/types.py"}], "after_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\n# See \"Type Validation\" in master/docs/developer/tests.rst\nimport datetime\nimport json\nimport re\n\nfrom buildbot import util\nfrom buildbot.util import bytes2unicode\n\n\nclass Type:\n\n    name = None\n    doc = None\n\n    @property\n    def ramlname(self):\n        return self.name\n\n    def valueFromString(self, arg):\n        # convert a urldecoded bytestring as given in a URL to a value, or\n        # raise an exception trying.  This parent method raises an exception,\n        # so if the method is missing in a subclass, it cannot be created from\n        # a string.\n        raise TypeError\n\n    def cmp(self, val, arg):\n        argVal = self.valueFromString(arg)\n        if val < argVal:\n            return -1\n        elif val == argVal:\n            return 0\n        return 1\n\n    def validate(self, name, object):\n        raise NotImplementedError\n\n    def getSpec(self):\n        r = dict(name=self.name)\n        if self.doc is not None:\n            r[\"doc\"] = self.doc\n        return r\n\n\nclass NoneOk(Type):\n\n    def __init__(self, nestedType):\n        assert isinstance(nestedType, Type)\n        self.nestedType = nestedType\n        self.name = self.nestedType.name + \" or None\"\n\n    @property\n    def ramlname(self):\n        return self.nestedType.ramlname\n\n    def valueFromString(self, arg):\n        return self.nestedType.valueFromString(arg)\n\n    def cmp(self, val, arg):\n        return self.nestedType.cmp(val, arg)\n\n    def validate(self, name, object):\n        if object is None:\n            return\n        for msg in self.nestedType.validate(name, object):\n            yield msg\n\n    def getSpec(self):\n        r = self.nestedType.getSpec()\n        r[\"can_be_null\"] = True\n        return r\n\n    def toRaml(self):\n        return self.nestedType.toRaml()\n\n\nclass Instance(Type):\n\n    types = ()\n    ramlType = \"unknown\"\n\n    @property\n    def ramlname(self):\n        return self.ramlType\n\n    def validate(self, name, object):\n        if not isinstance(object, self.types):\n            yield \"{} ({}) is not a {}\".format(name, repr(object), self.name or repr(self.types))\n\n    def toRaml(self):\n        return self.ramlType\n\n\nclass Integer(Instance):\n\n    name = \"integer\"\n    types = (int,)\n    ramlType = \"integer\"\n\n    def valueFromString(self, arg):\n        return int(arg)\n\n\nclass DateTime(Instance):\n\n    name = \"datetime\"\n    types = (datetime.datetime,)\n\n    def valueFromString(self, arg):\n        return int(arg)\n\n    def validate(self, name, object):\n        if isinstance(object, datetime.datetime):\n            return\n        if isinstance(object, int):\n            try:\n                datetime.datetime.fromtimestamp(object)\n            except (OverflowError, OSError):\n                pass\n            else:\n                return\n        yield \"{} ({}) is not a valid timestamp\".format(name, object)\n\n    ramlType = \"date\"\n\n\nclass String(Instance):\n\n    name = \"string\"\n    types = (str,)\n    ramlType = \"string\"\n\n    def valueFromString(self, arg):\n        val = util.bytes2unicode(arg)\n        return val\n\n\nclass Binary(Instance):\n\n    name = \"binary\"\n    types = (bytes,)\n    ramlType = \"string\"\n\n    def valueFromString(self, arg):\n        return arg\n\n\nclass Boolean(Instance):\n\n    name = \"boolean\"\n    types = (bool,)\n    ramlType = \"boolean\"\n\n    def valueFromString(self, arg):\n        return util.string2boolean(arg)\n\n\nclass Identifier(Type):\n\n    name = \"identifier\"\n    identRe = re.compile('^[a-zA-Z_-][a-zA-Z0-9._-]*$')\n    ramlType = \"string\"\n\n    def __init__(self, len=None, **kwargs):\n        super().__init__(**kwargs)\n        self.len = len\n\n    def valueFromString(self, arg):\n        val = util.bytes2unicode(arg)\n        if not self.identRe.match(val) or len(val) > self.len or not val:\n            raise TypeError\n        return val\n\n    def validate(self, name, object):\n        if not isinstance(object, str):\n            yield \"{} - {} - is not a unicode string\".format(name, repr(object))\n        elif not self.identRe.match(object):\n            yield \"{} - {} - is not an identifier\".format(name, repr(object))\n        elif not object:\n            yield \"{} - identifiers cannot be an empty string\".format(name)\n        elif len(object) > self.len:\n            yield \"{} - {} - is longer than {} characters\".format(name, repr(object), self.len)\n\n    def toRaml(self):\n        return {'type': self.ramlType,\n                'pattern': self.identRe.pattern}\n\n\nclass List(Type):\n\n    name = \"list\"\n    ramlType = \"list\"\n\n    @property\n    def ramlname(self):\n        return self.of.ramlname\n\n    def __init__(self, of=None, **kwargs):\n        super().__init__(**kwargs)\n        self.of = of\n\n    def validate(self, name, object):\n        if not isinstance(object, list):  # we want a list, and NOT a subclass\n            yield \"{} ({}) is not a {}\".format(name, repr(object), self.name)\n            return\n\n        for idx, elt in enumerate(object):\n            for msg in self.of.validate(\"{}[{}]\".format(name, idx), elt):\n                yield msg\n\n    def valueFromString(self, arg):\n        # valueFromString is used to process URL args, which come one at\n        # a time, so we defer to the `of`\n        return self.of.valueFromString(arg)\n\n    def getSpec(self):\n        return dict(type=self.name,\n                    of=self.of.getSpec())\n\n    def toRaml(self):\n        return {'type': 'array', 'items': self.of.name}\n\n\ndef maybeNoneOrList(k, v):\n    if isinstance(v, NoneOk):\n        return k + \"?\"\n    if isinstance(v, List):\n        return k + \"[]\"\n    return k\n\n\nclass SourcedProperties(Type):\n\n    name = \"sourcedproperties\"\n\n    def validate(self, name, object):\n        if not isinstance(object, dict):  # we want a dict, and NOT a subclass\n            yield \"{} is not sourced properties (not a dict)\".format(name)\n            return\n        for k, v in object.items():\n            if not isinstance(k, str):\n                yield \"{} property name {} is not unicode\".format(name, repr(k))\n            if not isinstance(v, tuple) or len(v) != 2:\n                yield \"{} property value for '{}' is not a 2-tuple\".format(name, k)\n                return\n            propval, propsrc = v\n            if not isinstance(propsrc, str):\n                yield \"{}[{}] source {} is not unicode\".format(name, k, repr(propsrc))\n            try:\n                json.loads(bytes2unicode(propval))\n            except ValueError:\n                yield \"{}[{}] value is not JSON-able\".format(name, repr(k))\n\n    def toRaml(self):\n        return {'type': \"object\",\n                'properties':\n                {'[]': {'type': 'object',\n                        'properties': {\n                            1: 'string',\n                            2: 'integer | string | object | array | boolean'\n                        }\n                        }}}\n\n\nclass Dict(Type):\n    name = \"dict\"\n\n    @property\n    def ramlname(self):\n        return self.toRaml()\n\n    def __init__(self, **contents):\n        self.contents = contents\n        self.keys = set(contents)\n\n    def validate(self, name, object):\n        if not isinstance(object, dict):\n            yield \"{} ({}) is not a dictionary (got type {})\".format(name, repr(object),\n                                                                     type(object))\n            return\n\n        gotNames = set(object.keys())\n\n        unexpected = gotNames - self.keys\n        if unexpected:\n            yield \"{} has unexpected keys {}\".format(name, \", \".join([repr(n) for n in unexpected]))\n\n        missing = self.keys - gotNames\n        if missing:\n            yield \"{} is missing keys {}\".format(name, \", \".join([repr(n) for n in missing]))\n\n        for k in gotNames & self.keys:\n            f = self.contents[k]\n            for msg in f.validate(\"{}[{}]\".format(name, repr(k)), object[k]):\n                yield msg\n\n    def getSpec(self):\n        return dict(type=self.name,\n                    fields=[dict(name=k,\n                                 type=v.name,\n                                 type_spec=v.getSpec())\n                            for k, v in self.contents.items()\n                            ])\n\n    def toRaml(self):\n        return {'type': \"object\",\n                'properties': {maybeNoneOrList(k, v): v.ramlname for k, v in self.contents.items()}}\n\n\nclass JsonObject(Type):\n    name = \"jsonobject\"\n    ramlname = 'object'\n\n    def validate(self, name, object):\n        if not isinstance(object, dict):\n            yield \"{} ({}) is not a dictionary (got type {})\".format(name, repr(object),\n                                                                     type(object))\n            return\n\n        # make sure JSON can represent it\n        try:\n            json.dumps(object)\n        except Exception as e:\n            yield \"{} is not JSON-able: {}\".format(name, e)\n            return\n\n    def toRaml(self):\n        return \"object\"\n\n\nclass Entity(Type):\n\n    # NOTE: this type is defined by subclassing it in each resource type class.\n    # Instances are generally accessed at e.g.,\n    #  * buildsets.Buildset.entityType or\n    #  * self.master.data.rtypes.buildsets.entityType\n\n    name = None  # set in constructor\n    fields = {}\n    fieldNames = set([])\n\n    def __init__(self, name):\n        fields = {}\n        for k, v in self.__class__.__dict__.items():\n            if isinstance(v, Type):\n                fields[k] = v\n        self.fields = fields\n        self.fieldNames = set(fields)\n        self.name = name\n\n    def validate(self, name, object):\n        # this uses isinstance, allowing dict subclasses as used by the DB API\n        if not isinstance(object, dict):\n            yield \"{} ({}) is not a dictionary (got type {})\".format(name, repr(object),\n                                                                     type(object))\n            return\n\n        gotNames = set(object.keys())\n\n        unexpected = gotNames - self.fieldNames\n        if unexpected:\n            yield \"{} has unexpected keys {}\".format(name, \", \".join([repr(n) for n in unexpected]))\n\n        missing = self.fieldNames - gotNames\n        if missing:\n            yield \"{} is missing keys {}\".format(name, \", \".join([repr(n) for n in missing]))\n\n        for k in gotNames & self.fieldNames:\n            f = self.fields[k]\n            for msg in f.validate(\"{}[{}]\".format(name, repr(k)), object[k]):\n                yield msg\n\n    def getSpec(self):\n        return dict(type=self.name,\n                    fields=[dict(name=k,\n                                 type=v.name,\n                                 type_spec=v.getSpec())\n                            for k, v in self.fields.items()\n                            ])\n\n    def toRaml(self):\n        return {'type': \"object\",\n                'properties': {\n                    maybeNoneOrList(k, v): {'type': v.ramlname, 'description': ''}\n                    for k, v in self.fields.items()}}\n", "path": "master/buildbot/data/types.py"}]}