Files
tubestation/taskcluster/taskgraph/transforms/base.py
Dustin J. Mitchell 31c2d2d209 Bug 1325465: add schema helper optionally_keyed_by; r=jmaher
MozReview-Commit-ID: 8CgYe2F3uxY
2016-12-22 21:02:29 +00:00

144 lines
4.6 KiB
Python

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, print_function, unicode_literals
import re
import copy
import pprint
import voluptuous
class TransformConfig(object):
"""A container for configuration affecting transforms. The `config`
argument to transforms is an instance of this class, possibly with
additional kind-specific attributes beyond those set here."""
def __init__(self, kind, path, config, params):
# the name of the current kind
self.kind = kind
# the path to the kind configuration directory
self.path = path
# the parsed contents of kind.yml
self.config = config
# the parameters for this task-graph generation run
self.params = params
class TransformSequence(object):
"""
Container for a sequence of transforms. Each transform is represented as a
callable taking (config, items) and returning a generator which will yield
transformed items. The resulting sequence has the same interface.
This is convenient to use in a file full of transforms, as it provides a
decorator, @transforms.add, that will add the decorated function to the
sequence.
"""
def __init__(self, transforms=None):
self.transforms = transforms or []
def __call__(self, config, items):
for xform in self.transforms:
items = xform(config, items)
if items is None:
raise Exception("Transform {} is not a generator".format(xform))
return items
def __repr__(self):
return '\n'.join(
['TransformSequence(['] +
[repr(x) for x in self.transforms] +
['])'])
def add(self, func):
self.transforms.append(func)
return func
def validate_schema(schema, obj, msg_prefix):
"""
Validate that object satisfies schema. If not, generate a useful exception
beginning with msg_prefix.
"""
try:
# deep copy the result since it may include mutable defaults
return copy.deepcopy(schema(obj))
except voluptuous.MultipleInvalid as exc:
msg = [msg_prefix]
for error in exc.errors:
msg.append(str(error))
raise Exception('\n'.join(msg) + '\n' + pprint.pformat(obj))
def optionally_keyed_by(*arguments):
"""
Mark a schema value as optionally keyed by any of a number of fields. The
schema is the last argument, and the remaining fields are taken to be the
field names. For example:
'some-value': optionally_keyed_by(
'test-platform', 'build-platform',
Any('a', 'b', 'c'))
"""
subschema = arguments[-1]
fields = arguments[:-1]
options = [subschema]
for field in fields:
options.append({'by-' + field: {basestring: subschema}})
return voluptuous.Any(*options)
def get_keyed_by(item, field, item_name, subfield=None):
"""
For values which can either accept a literal value, or be keyed by some
other attribute of the item, perform that lookup. For example, this supports
chunks:
by-test-platform:
macosx-10.11/debug: 13
win.*: 6
default: 12
The `item_name` parameter is used to generate useful error messages.
The `subfield` parameter, if specified, allows access to a second level
of the item dictionary: item[field][subfield]. For example, this supports
mozharness:
config:
by-test-platform:
default: ...
"""
value = item[field]
if not isinstance(value, dict):
return value
if subfield:
value = item[field][subfield]
if not isinstance(value, dict):
return value
keyed_by = value.keys()[0]
if len(value) > 1 or not keyed_by.startswith('by-'):
return value
values = value[keyed_by]
keyed_by = keyed_by[3:] # strip 'by-' off the keyed-by field name
if item[keyed_by] in values:
return values[item[keyed_by]]
for k in values.keys():
if re.match(k, item[keyed_by]):
return values[k]
if 'default' in values:
return values['default']
for k in item[keyed_by], 'default':
if k in values:
return values[k]
else:
raise Exception(
"Neither {} {} nor 'default' found while determining item {} in {}".format(
keyed_by, item[keyed_by], field, item_name))