Beispiel #1
0
def optionalRef(validator: jsonschema.Draft7Validator, optionalRef: Dict,
                instance: Any,
                schema: Dict) -> Iterator[jsonschema.ValidationError]:
    """
    optionalRef behaves like $ref, except that it also allows the value to be null.

    This is logically equivalent to an anyOf with a {"type": "null"} element, but it has better
    error messages.

    Example: The "internal" property of the experiment config may be a literal null:

        "internal": {
            "type": [
                "object",
                "null"
            ],
            "optionalRef": "http://determined.ai/schemas/expconf/v0/internal.json",
            "default": null
        }
    """
    if instance is None:
        return

    yield from validator.descend(instance,
                                 schema={"$ref": optionalRef},
                                 schema_path="optionalRef")
Beispiel #2
0
def conditional(validator: jsonschema.Draft7Validator, conditional: Dict,
                instance: Any,
                schema: Dict) -> Iterator[jsonschema.ValidationError]:
    """
    conditional enforces one subschema only when some other schema is satisified.  The other schema
    can be negated by marking it as "unless" insted of "when".

    Only the error from the "enforce" clause is ever shown.

    Example: when records per epoch not set, forbid epoch lengths:

        "conditional": {
            "when": {
                ... (schema that checks if records_per_epoch is unset) ...
            },
            "enforce": {
                ... (schema that checks that epoch lengths are not used) ...
            }
        }
    """
    when = conditional.get("when")
    unless = conditional.get("unless")
    enforce = conditional["enforce"]

    if when is not None:
        if list(validator.descend(instance, schema=when, schema_path="when")):
            # "when" clause failed, return early.
            return
    else:
        assert unless is not None, "invalid schema"
        if not list(
                validator.descend(
                    instance, schema=unless, schema_path="unless")):
            # "unless" clause passed, returned early.
            return

    # Enforce the "enforce" clause.
    yield from validator.descend(instance,
                                 schema=enforce,
                                 schema_path="enforce")
Beispiel #3
0
def eventually(
    validator: jsonschema.Draft7Validator,
    eventually: Any,
    instance: List,
    schema: Dict,
) -> Iterator[jsonschema.ValidationError]:
    """
    eventually allows for two-step validation, by only enforcing the specified subschemas
    during the completeness validation phase. This is a requirement specific to Determined.

    One use case is when it is necessary to enforce a `oneOf` on two fields that are
    `eventuallyRequired`. If the `oneOf` is evaluated during the sanity validation phase, it will
    always fail, if for example, the user is using cluster default values, but if validation
    for this subschema is held off until completeness validation, it will validate correctly.

    Example: eventually require one of connection string and account url to be specified:

    "eventually": {
        "checks": {
            "Exactly one of connection_string or account_url must be set": {
                "oneOf": [
                    {
                        "eventuallyRequired": [
                            "connection_string"
                        ]
                    },
                    {
                        "eventuallyRequired": [
                            "account_url"
                        ]
                    }
                ]
            }
        }
    }
    """
    yield from validator.descend(instance,
                                 schema=eventually,
                                 schema_path="eventually")
Beispiel #4
0
def checks(validator: jsonschema.Draft7Validator, checks: Dict, instance: Any,
           schema: Dict) -> Iterator[jsonschema.ValidationError]:
    """
    checks is a simple extension that returns a specific error if a subschema fails to match.

    The keys of the "checks" dictionary are the user-facing messages, and the values are the
    subschemas that must match.

    Example:

        "checks": {
            "you must specify an entrypoint that references the trial class":{
                ... (schema which allows Native API or requires that entrypoint is set) ...
            },
            "you requested a bayesian search but hyperband is way better": {
                ... (schema which checks if you try searcher.name=baysian) ...
            }
        }
    """
    for msg, subschema in schema["checks"].items():
        errors = list(validator.descend(instance, schema=subschema))
        if errors:
            yield jsonschema.ValidationError(msg)
Beispiel #5
0
def union(
    validator: jsonschema.Draft7Validator, det_one_of: Dict, instance: Any, schema: Dict
) -> Iterator[jsonschema.ValidationError]:
    """
    union is for custom error messages with union types.  The built-in oneOf keyword has the same
    validation behavior but awful error handling.  If you had the following invalid hyperparameter:

        hyperparameters:
          - learning_rate:
              type: double
              min: 0.001
              max: 0.005

    would you return an error saying:

        "your double hparam has invalid fields 'min' and 'max' but needs 'minval' and 'maxval'",

    or would you say:

        "your int hparam has type=double but needs type=int and 'minval' and 'maxval'"?

    Obviously you want the first option, because we treat the "type" key as special, and we can
    uniquely identify which subschema should match against the provided data based on the "type".

    The union extension provides this exact behavior.

    Example: The "additionalProperties" schema for the hyperparameters dict:

        "union": {
            "items": [
                {
                    "unionKey": "const:type=int",
                    "$ref": ...
                },
                {
                    "unionKey": "const:type=double",
                    "$ref": ...
                },
                ...
            ]
        }

    When the oneOf validation logic is not met, the error chosen is based on the first unionKey to
    evaluate to true.  In this case, the "const:" means a certain key ("type") must match a certain
    value ("int" or "double") for that subschema's error message to be chosen.
    """
    selected_errors = None
    valid = []

    for idx, item in enumerate(det_one_of["items"]):
        errors = list(validator.descend(instance, schema=item, schema_path=idx))
        if errors:
            key = item["unionKey"]
            if not selected_errors and _evaluate_unionKey(key, instance):
                selected_errors = errors
        else:
            valid.append(item)

    if len(valid) == 1:
        # No errors.
        return

    if len(valid) > 1:
        yield jsonschema.ValidationError(f"bug in validation! Multiple schemas matched: {valid}")
        return

    if selected_errors:
        yield from selected_errors
        return

    default_message = det_one_of.get("defaultMessage", "union failed to validate")
    yield jsonschema.ValidationError(default_message)