import collections
import pprint
import re
import voluptuous
import taskgraph
from taskgraph.util.keyed_by import evaluate_keyed_by, iter_dot_path
def validate_schema(schema, obj, msg_prefix):
if taskgraph.fast:
return
try:
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):
schema = arguments[-1]
fields = arguments[:-1]
def validator(obj):
if isinstance(obj, dict) and len(obj) == 1:
k, v = list(obj.items())[0]
if k.startswith("by-") and k[len("by-") :] in fields:
res = {}
for kk, vv in v.items():
try:
res[kk] = validator(vv)
except voluptuous.Invalid as e:
e.prepend([k, kk])
raise
return res
return Schema(schema)(obj)
return validator
def resolve_keyed_by(
item, field, item_name, defer=None, enforce_single_match=True, **extra_values
):
for container, subfield in iter_dot_path(item, field):
container[subfield] = evaluate_keyed_by(
value=container[subfield],
item_name=f"`{field}` in `{item_name}`",
defer=defer,
enforce_single_match=enforce_single_match,
attributes=dict(item, **extra_values),
)
return item
EXCEPTED_SCHEMA_IDENTIFIERS = [
"upstream-artifacts",
"artifact-map",
]
def check_schema(schema):
identifier_re = re.compile(r"^\$?[a-z][a-z0-9-]*$")
def excepted(item):
for esi in EXCEPTED_SCHEMA_IDENTIFIERS:
if isinstance(esi, str):
if f"[{esi!r}]" in item:
return True
elif esi(item):
return True
return False
def iter(path, sch):
def check_identifier(path, k):
if k in (str,) or k in (str, voluptuous.Extra):
pass
elif isinstance(k, voluptuous.NotIn):
pass
elif isinstance(k, str):
if not identifier_re.match(k) and not excepted(path):
raise RuntimeError(
"YAML schemas should use dashed lower-case identifiers, "
f"not {k!r} @ {path}"
)
elif isinstance(k, (voluptuous.Optional, voluptuous.Required)):
check_identifier(path, k.schema)
elif isinstance(k, (voluptuous.Any, voluptuous.All)):
for v in k.validators:
check_identifier(path, v)
elif not excepted(path):
raise RuntimeError(
f"Unexpected type in YAML schema: {type(k).__name__} @ {path}"
)
if isinstance(sch, collections.abc.Mapping): for k, v in sch.items():
child = f"{path}[{k!r}]"
check_identifier(child, k)
iter(child, v)
elif isinstance(sch, (list, tuple)):
for i, v in enumerate(sch):
iter(f"{path}[{i}]", v)
elif isinstance(sch, voluptuous.Any):
for v in sch.validators:
iter(path, v)
iter("schema", schema.schema)
class Schema(voluptuous.Schema):
def __init__(self, *args, check=True, **kwargs):
super().__init__(*args, **kwargs)
self.check = check
if not taskgraph.fast and self.check:
check_schema(self)
def extend(self, *args, **kwargs):
schema = super().extend(*args, **kwargs)
if self.check:
check_schema(schema)
schema.__class__ = Schema
return schema
def _compile(self, schema):
if taskgraph.fast:
return
return super()._compile(schema)
def __getitem__(self, item):
return self.schema[item]
OptimizationSchema = voluptuous.Any(
None,
{"index-search": [str]},
{"skip-unless-changed": [str]},
)
taskref_or_string = voluptuous.Any(
str,
{voluptuous.Required("task-reference"): str},
{voluptuous.Required("artifact-reference"): str},
)