Skip to content

Copy non-callable Optional defaults so mutable defaults aren't shared (fixes #352)#353

Open
vineethsaivs wants to merge 1 commit into
keleshev:masterfrom
vineethsaivs:fix/optional-mutable-default-shared
Open

Copy non-callable Optional defaults so mutable defaults aren't shared (fixes #352)#353
vineethsaivs wants to merge 1 commit into
keleshev:masterfrom
vineethsaivs:fix/optional-mutable-default-shared

Conversation

@vineethsaivs

Copy link
Copy Markdown
Contributor

Fixes #352.

A non-callable default on an Optional (e.g. default=[] or default={}) was inserted into every validation result by reference, so the same object is shared across all validate() calls. Mutating one result then corrupts the default for every later call:

from schema import Schema, Optional
s = Schema({Optional("items", default=[]): list})
a = s.validate({}); a["items"].append(1)
b = s.validate({})
print(b["items"])   # [1]  -> should be []

Callable defaults (e.g. default=list) already returned a fresh object each time, which is why they don't have this bug.

Fix

Deep-copy non-callable defaults so each validation gets its own object:

new[default.key] = (
    _invoke_with_optional_kwargs(default.default, **kwargs)
    if callable(default.default)
    else copy.deepcopy(default.default)
)

Test

Added test_dict_optional_mutable_default_not_shared: mutating one result's default must not leak into a later validate(). It fails on master ([1]) and passes with this change. Full test_schema.py passes (121 tests).

A non-callable default on an Optional (e.g. default=[] or default={})
was inserted into every validate() result by reference, so mutating one
result corrupted the default for all later validate() calls. Deep-copy
non-callable defaults so each validation gets its own object; callable
defaults already returned fresh objects.

Fixes keleshev#352.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optional with a mutable default ([] / {}) is shared across all validate() calls

1 participant