From 680d922191d4f5ea1e08ab9948d0506e09360fc6 Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Thu, 18 Jul 2024 14:30:47 -0400 Subject: [PATCH] Add feature to validate map types (#848) * Add feature to validate map types * Bump to 8.7 on account of new validation behavior for MapSchema. --- Makefile | 2 +- schema_salad/validate.py | 48 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index b5e2cf7b..13de9598 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ DEVPKGS=-rdev-requirements.txt -rtest-requirements.txt -rmypy-requirements.txt COVBASE=coverage run --append PYTEST_EXTRA ?= -rs -VERSION=8.6.$(shell date +%Y%m%d%H%M%S --utc --date=`git log --first-parent \ +VERSION=8.7.$(shell date +%Y%m%d%H%M%S --utc --date=`git log --first-parent \ --max-count=1 --format=format:%cI`) ## all : default task (install schema-salad in dev mode) diff --git a/schema_salad/validate.py b/schema_salad/validate.py index 078f63b8..529a972e 100644 --- a/schema_salad/validate.py +++ b/schema_salad/validate.py @@ -94,6 +94,8 @@ def friendly(v: Any) -> Any: return avro_shortname(v.name) if isinstance(v, avro.schema.ArraySchema): return f"array of <{friendly(v.items)}>" + if isinstance(v, (avro.schema.MapSchema, avro.schema.NamedMapSchema)): + return f"map of <{friendly(v.values)}>" if isinstance(v, avro.schema.PrimitiveSchema): return v.type if isinstance(v, (avro.schema.UnionSchema, avro.schema.NamedUnionSchema)): @@ -258,10 +260,18 @@ def validate_ex( for s in expected_schema.schemas: if isinstance(datum, MutableSequence) and not isinstance(s, avro.schema.ArraySchema): continue - if isinstance(datum, MutableMapping) and not isinstance(s, avro.schema.RecordSchema): + if isinstance(datum, MutableMapping) and not isinstance( + s, (avro.schema.RecordSchema, avro.schema.MapSchema, avro.schema.NamedMapSchema) + ): continue if isinstance(datum, (bool, int, float, str)) and isinstance( - s, (avro.schema.ArraySchema, avro.schema.RecordSchema) + s, + ( + avro.schema.ArraySchema, + avro.schema.RecordSchema, + avro.schema.MapSchema, + avro.schema.NamedMapSchema, + ), ): continue if datum is not None and s.type == "null": @@ -437,6 +447,40 @@ def validate_ex( raise ValidationException("", None, errors, "*") return False return True + + if isinstance(expected_schema, (avro.schema.MapSchema, avro.schema.NamedMapSchema)): + if isinstance(datum, MutableMapping): + for key, val in datum.items(): + if not isinstance(key, str): + pass + try: + sl = SourceLine(datum, key, ValidationException) + if not validate_ex( + expected_schema.values, + val, + identifiers, + strict=strict, + foreign_properties=foreign_properties, + raise_ex=raise_ex, + strict_foreign_properties=strict_foreign_properties, + logger=logger, + skip_foreign_properties=skip_foreign_properties, + vocab=vocab, + ): + return False + except ValidationException as v: + if raise_ex: + source = v if debug else None + raise ValidationException("item is invalid because", sl, [v]) from source + return False + return True + if raise_ex: + raise ValidationException( + f"the value {vpformat(datum)} is not an object, " + f"expected object of {friendly(expected_schema.values)}" + ) + return False + if raise_ex: raise ValidationException(f"Unrecognized schema_type {schema_type}") return False