From 8fa32e7f37d6666bcc9a23f688ee9c2b0c4e591b Mon Sep 17 00:00:00 2001 From: Anton Agestam Date: Wed, 24 May 2023 17:24:11 +0200 Subject: [PATCH 1/2] AVRO-3760: [python] Fix resolution of future enum with default symbol --- lang/py/avro/io.py | 15 ++--- lang/py/avro/schema.py | 9 +++ lang/py/avro/test/test_io.py | 110 +++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 11 deletions(-) diff --git a/lang/py/avro/io.py b/lang/py/avro/io.py index 7b5576697eb..386e0bb9e61 100644 --- a/lang/py/avro/io.py +++ b/lang/py/avro/io.py @@ -89,17 +89,7 @@ import decimal import struct import warnings -from typing import ( - IO, - Deque, - Generator, - Iterable, - List, - Mapping, - Optional, - Sequence, - Union, -) +from typing import IO, Generator, Iterable, List, Mapping, Optional, Sequence, Union import avro.constants import avro.errors @@ -776,6 +766,9 @@ def read_enum(self, writers_schema: avro.schema.EnumSchema, readers_schema: avro # read data index_of_symbol = decoder.read_int() if index_of_symbol >= len(writers_schema.symbols): + default = writers_schema.default + if default is not None: + return default raise avro.errors.SchemaResolutionException( f"Can't access enum index {index_of_symbol} for enum with {len(writers_schema.symbols)} symbols", writers_schema, readers_schema ) diff --git a/lang/py/avro/schema.py b/lang/py/avro/schema.py index 3bc0c27af5a..2eb231402af 100644 --- a/lang/py/avro/schema.py +++ b/lang/py/avro/schema.py @@ -587,6 +587,15 @@ def symbols(self) -> Sequence[str]: return symbols raise Exception + @property + def default(self) -> Union[str, None]: + symbol = self.get_prop("default") + if isinstance(symbol, str): + return symbol + if symbol is None: + return None + raise Exception + doc = property(lambda self: self.get_prop("doc")) def match(self, writer): diff --git a/lang/py/avro/test/test_io.py b/lang/py/avro/test/test_io.py index b77c17fb98b..7d634d1b28c 100644 --- a/lang/py/avro/test/test_io.py +++ b/lang/py/avro/test/test_io.py @@ -644,6 +644,116 @@ def test_type_exception_record(self) -> None: with self.assertRaisesRegex(avro.errors.AvroTypeException, r"The datum \".*\" provided for \".*\" is not an example of the schema [\s\S]*"): write_datum(datum_to_write, writers_schema) + def test_can_read_future_enum_symbol_with_default(self) -> None: + default_symbol = "unknown" + future_symbol = "crc32_be" + + readers_schema = avro.schema.parse( + json.dumps( + { + "fields": [ + { + "name": "checksum_algorithm", + "type": { + "name": "ChecksumAlgorithm", + "symbols": [default_symbol, "xxhash3_64_be"], + "type": "enum", + "default": default_symbol, + }, + }, + ], + "name": "Test", + "type": "record", + } + ) + ) + # Writer adds the "crc32_be" symbol. + writers_schema = avro.schema.parse( + json.dumps( + { + "fields": [ + { + "name": "checksum_algorithm", + "type": { + "name": "ChecksumAlgorithm", + "symbols": [ + "unknown", + "xxhash3_64_be", + future_symbol, + ], + "type": "enum", + "default": default_symbol, + }, + } + ], + "name": "Test", + "type": "record", + } + ) + ) + + datum_to_write = {"checksum_algorithm": future_symbol} + + buffer, encoder, datum_writer = write_datum(datum_to_write, writers_schema) + buffer.seek(0) + decoder = avro.io.BinaryDecoder(buffer) + reader = avro.io.DatumReader(readers_schema) + datum_read = reader.read(decoder) + self.assertEqual(datum_read, {"checksum_algorithm": default_symbol}) + + def test_raises_error_for_future_enum_symbol_without_default(self) -> None: + future_symbol = "crc32_be" + + readers_schema = avro.schema.parse( + json.dumps( + { + "fields": [ + { + "name": "checksum_algorithm", + "type": { + "name": "ChecksumAlgorithm", + "symbols": ["xxhash3_64_be"], + "type": "enum", + }, + }, + ], + "name": "Test", + "type": "record", + } + ) + ) + # Writer adds the "crc32_be" symbol. + writers_schema = avro.schema.parse( + json.dumps( + { + "fields": [ + { + "name": "checksum_algorithm", + "type": { + "name": "ChecksumAlgorithm", + "symbols": [ + "xxhash3_64_be", + future_symbol, + ], + "type": "enum", + }, + } + ], + "name": "Test", + "type": "record", + } + ) + ) + + datum_to_write = {"checksum_algorithm": future_symbol} + + buffer, encoder, datum_writer = write_datum(datum_to_write, writers_schema) + buffer.seek(0) + decoder = avro.io.BinaryDecoder(buffer) + reader = avro.io.DatumReader(readers_schema) + with self.assertRaises(avro.errors.SchemaResolutionException): + reader.read(decoder) + def load_tests(loader: unittest.TestLoader, default_tests: None, pattern: None) -> unittest.TestSuite: """Generate test cases across many test schema.""" From 755d2609158925eff100e62e5f929086ee4f391d Mon Sep 17 00:00:00 2001 From: Ryan Skraba Date: Tue, 4 Aug 2026 19:22:30 +0200 Subject: [PATCH 2/2] Change error from review --- lang/py/avro/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/py/avro/schema.py b/lang/py/avro/schema.py index a35ab1d4718..360e88eb59b 100644 --- a/lang/py/avro/schema.py +++ b/lang/py/avro/schema.py @@ -722,7 +722,7 @@ def default(self) -> Union[str, None]: return symbol if symbol is None: return None - raise Exception + raise avro.errors.InvalidDefault(f"Enum default '{symbol}' is not a valid member of symbols '{self.symbols}'") @property def doc(self):