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")
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")
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")
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)
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)