fix bug where strict options were not catching extra fields
MODIFY
fastavro/_write.pyx
fastavro/_write.pyx
@@ -352,19 +352,18 @@ cdef write_record(bytearray fo, object datum, dict schema, dict named_schemas, d
cdef dict field
cdef dict d_datum
fields = schema["fields"]
+
+ extras = set(datum) - set(field["name"] for field in fields)
+ if (options.get("strict") or options.get("strict_allow_default")) and extras:
+ raise ValueError(
+ f'record contains more fields than the schema specifies: {", ".join(extras)}'
+ )
+
try:
d_datum = <dict?>(datum)
except TypeError:
# Slower, general-purpose code where datum is something besides a dict,
# e.g. a collections.OrderedDict or collections.defaultdict.
- if (options.get("strict") or options.get("strict_allow_default")) and len(
- datum
- ) > len(schema["fields"]):
- field_names = [field["name"] for field in fields]
- extras = ", ".join(set(datum) - set(field_names))
- raise ValueError(
- f"record contains more fields than the schema specifies: {extras}"
- )
for field in fields:
name = field["name"]
if name not in datum:
@@ -380,14 +379,6 @@ cdef write_record(bytearray fo, object datum, dict schema, dict named_schemas, d
write_data(fo, datum_value, field["type"], named_schemas, name, options)
else:
# Faster, special-purpose code where datum is a Python dict.
- if (options.get("strict") or options.get("strict_allow_default")) and len(
- d_datum
- ) > len(schema["fields"]):
- field_names = [field["name"] for field in fields]
- extras = ", ".join(set(d_datum) - set(field_names))
- raise ValueError(
- f"record contains more fields than the schema specifies: {extras}"
- )
for field in fields:
name = field["name"]
if name not in d_datum:

MODIFY
fastavro/_write_py.py
fastavro/_write_py.py
@@ -234,13 +234,10 @@ def write_record(encoder, datum, schema, named_schemas, fname, options):
that they are declared. In other words, a record is encoded as just the
concatenation of the encodings of its fields. Field values are encoded per
their schema."""
- if (options.get("strict") or options.get("strict_allow_default")) and len(
- datum
- ) > len(schema["fields"]):
- field_names = [field["name"] for field in schema["fields"]]
- extras = ", ".join(set(datum) - set(field_names))
+ extras = set(datum) - set(field["name"] for field in schema["fields"])
+ if (options.get("strict") or options.get("strict_allow_default")) and extras:
raise ValueError(
- f"record contains more fields than the schema specifies: {extras}"
+ f'record contains more fields than the schema specifies: {", ".join(extras)}'
)
for field in schema["fields"]:
name = field["name"]

MODIFY
tests/test_fastavro.py
tests/test_fastavro.py
@@ -3261,3 +3261,18 @@ def test_disable_tuple_notation_option():
writer_kwargs={"disable_tuple_notation": True},
)
assert new_records == [{"foo": ["string", "0"]}]
+
+
+def test_strict_allow_default_bug():
+ """https://github.com/fastavro/fastavro/issues/638"""
+ schema = {
+ "namespace": "namespace",
+ "name": "name",
+ "type": "record",
+ "fields": [{"name": "some_field", "type": "string", "default": "test"}],
+ }
+
+ test_record = {"eggs": "eggs"}
+
+ with pytest.raises(ValueError, match="record contains more fields .*? eggs"):
+ roundtrip(schema, [test_record], writer_kwargs={"strict_allow_default": True})

MODIFY
tests/test_json.py
tests/test_json.py
@@ -859,3 +859,18 @@ def test_disable_tuple_notation_option():
writer_kwargs={"disable_tuple_notation": True},
)
assert new_records == [{"foo": ["string", "0"]}]
+
+
+def test_strict_allow_default_bug():
+ """https://github.com/fastavro/fastavro/issues/638"""
+ schema = {
+ "namespace": "namespace",
+ "name": "name",
+ "type": "record",
+ "fields": [{"name": "some_field", "type": "string", "default": "test"}],
+ }
+
+ test_record = {"eggs": "eggs"}
+
+ with pytest.raises(ValueError, match="record contains more fields .*? eggs"):
+ roundtrip(schema, [test_record], writer_kwargs={"strict_allow_default": True})

MODIFY
tests/test_schemaless.py
tests/test_schemaless.py
@@ -251,3 +251,18 @@ def test_disable_tuple_notation_option():
schema, {"foo": ("string", "0")}, writer_kwargs={"disable_tuple_notation": True}
)
assert new_record == {"foo": ["string", "0"]}
+
+
+def test_strict_allow_default_bug():
+ """https://github.com/fastavro/fastavro/issues/638"""
+ schema = {
+ "namespace": "namespace",
+ "name": "name",
+ "type": "record",
+ "fields": [{"name": "some_field", "type": "string", "default": "test"}],
+ }
+
+ test_record = {"eggs": "eggs"}
+
+ with pytest.raises(ValueError, match="record contains more fields .*? eggs"):
+ roundtrip(schema, test_record, writer_kwargs={"strict_allow_default": True})

fix bug where strict options were not catching extra fields