From 0356a3f1c307830f8ded56d823abca5611c594c9 Mon Sep 17 00:00:00 2001 From: Jared Deckard Date: Thu, 18 Dec 2025 23:57:28 -0600 Subject: [PATCH 1/4] Merge error store messages without rebuilding collections --- src/marshmallow/error_store.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) Index: marshmallow-3.20.2/src/marshmallow/error_store.py =================================================================== --- marshmallow-3.20.2.orig/src/marshmallow/error_store.py +++ marshmallow-3.20.2/src/marshmallow/error_store.py @@ -18,12 +18,19 @@ class ErrorStore: # field error -> store/merge error messages under field name key # schema error -> if string or list, store/merge under _schema key # -> if dict, store/merge with other top-level keys + messages = copy_containers(messages) if field_name != SCHEMA or not isinstance(messages, dict): messages = {field_name: messages} if index is not None: messages = {index: messages} self.errors = merge_errors(self.errors, messages) +def copy_containers(errors): + if isinstance(errors, list): + return [copy_containers(val) for val in errors] + if isinstance(errors, dict): + return {key: copy_containers(val) for key, val in errors.items()} + return errors def merge_errors(errors1, errors2): """Deeply merge two error messages. @@ -37,24 +44,26 @@ def merge_errors(errors1, errors2): return errors1 if isinstance(errors1, list): if isinstance(errors2, list): - return errors1 + errors2 + errors1.extend(errors2) + return errors1 if isinstance(errors2, dict): - return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))}) - return errors1 + [errors2] + errors2[SCHEMA] = merge_errors(errors1, errors2.get(SCHEMA)) + return errors2 + errors1.append(errors2) + return errors1 if isinstance(errors1, dict): - if isinstance(errors2, list): - return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)}) if isinstance(errors2, dict): - errors = dict(errors1) for key, val in errors2.items(): - if key in errors: - errors[key] = merge_errors(errors[key], val) + if key in errors1: + errors1[key] = merge_errors(errors1[key], val) else: - errors[key] = val - return errors - return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)}) + errors1[key] = val + return errors1 + errors1[SCHEMA] = merge_errors(errors1.get(SCHEMA), errors2) + return errors1 if isinstance(errors2, list): - return [errors1] + errors2 + return [errors1, *errors2] if isinstance(errors2, dict): - return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))}) + errors2[SCHEMA] = merge_errors(errors1, errors2.get(SCHEMA)) + return errors2 return [errors1, errors2] Index: marshmallow-3.20.2/tests/test_error_store.py =================================================================== --- marshmallow-3.20.2.orig/tests/test_error_store.py +++ marshmallow-3.20.2/tests/test_error_store.py @@ -1,7 +1,7 @@ from collections import namedtuple from marshmallow import missing -from marshmallow.error_store import merge_errors +from marshmallow.error_store import merge_errors, ErrorStore def test_missing_is_falsy(): @@ -141,3 +141,19 @@ class TestMergeErrors: assert {"field1": {"field2": ["error1", "error2"]}} == merge_errors( {"field1": {"field2": "error1"}}, {"field1": {"field2": "error2"}} ) + + def test_list_not_changed(self): + store = ErrorStore() + message = ["foo"] + store.store_error(message) + store.store_error(message) + assert message == ["foo"] + assert store.errors == {"_schema": ["foo", "foo"]} + + def test_dict_not_changed(self): + store = ErrorStore() + message = {"foo": ["bar"]} + store.store_error(message) + store.store_error(message) + assert message == {"foo": ["bar"]} + assert store.errors == {"foo": ["bar", "bar"]}