Isolate caller-supplied metadata with deepcopy - #65
Merged
Conversation
Use two separate deep copies in Parameters.__init__: 1. __full_metadata: pristine copy, never mutated by the instance. Used by create_subset() to build child Parameters instances that need the original unresolved metadata (dimension names in bounded 'maximum' fields, original dimension sizes, etc.). 2. _working_metadata: separate working copy that self.metadata, self.__dimensions, and all Parameter instances are free to mutate (bounded resolution, dimension size changes, etc.). This supersedes the need for shallow copies in Parameter.__init__ and Dimension.__init__ since the caller's dict is never passed into the internal machinery. The approach also prevents create_subset() from crashing due to mutated __full_metadata, which would happen if __full_metadata and self.metadata shared sub-dicts. Addresses the same root cause as PR #64 (caller-supplied metadata being mutated across Parameters instances) but at a higher level, providing stronger guarantees.
This was referenced Aug 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Prevents caller-supplied metadata from being mutated by
Parametersinstances. Related to #64.Problem
Parameters.__init__previously shared sub-dicts betweenself.__full_metadataandself.metadata. When bounded parameter resolution or dimension size changes mutatedself.metadata, those changes leaked into__full_metadata. This causedcreate_subset()to pass already-resolved metadata (numericmaximuminstead of dimension name) to childParametersinstances, crashing on bounded parameteradd().The same root cause also affected callers who reuse a single
MetaData().metadatadict across multipleParametersinstances (e.g. pywatershed'sDomainSubsettests).Fix
Use two separate
deepcopycalls inParameters.__init__:__full_metadata— pristine copy, never mutated by the instance. Passed tocreate_subset()for building childParametersinstances._working_metadata— separate working copy thatself.metadata,self.__dimensions, and allParameterinstances are free to mutate (bounded resolution, dimension size changes, etc.).Since the caller's dict is never passed into the internal machinery, shallow copies in leaf classes (
Parameter.__init__,Dimension.__init__) are unnecessary — though harmless if also applied.Testing
All 302 tests pass locally (Python 3.13).