Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion pyPRMS/dimensions/Dimension.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ def __init__(self, name: str,
else:
if strict:
if name in meta:
self.meta = meta[name]
# Copy the entry so instance modifications (the size
# setter writes self.meta['size']) do not mutate the
# caller-supplied metadata dict, which may be shared
# across instances.
self.meta = dict(meta[name])
else:
raise ValueError(f'`{self.name}` does not exist in metadata')
else:
Expand Down
6 changes: 5 additions & 1 deletion pyPRMS/parameters/Parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ def __init__(self, name: str,
else:
if strict:
if name in meta:
self.meta = meta[name]
# Copy the entry so creation-time modifications (e.g. the
# bounded maximum resolution below) do not mutate the
# caller-supplied metadata dict, which may be shared
# across Parameters instances.
self.meta = dict(meta[name])

# Add the dimensions for this parameter
for cname in self.meta['dimensions']:
Expand Down
32 changes: 32 additions & 0 deletions tests/func/test_Parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,35 @@ def test_add_adhoc_parameter_metadata(self, pdb_instance):
pdb_instance.add('foo')

assert pdb_instance.get('foo').__str__() == expected


class TestParametersSharedMetadata:

def test_shared_metadata_not_mutated(self):
Comment thread
pnorton-usgs marked this conversation as resolved.
"""Creating parameters and dimensions must not modify the supplied
metadata dictionary, which may be shared across instances.

Regression test: bounded-maximum resolution (Parameter) and the
dimension size setter (Dimension) wrote through to the caller's
metadata, so a second Parameters instance built from the same
metadata dict saw a numeric 'maximum' where a dimension name was
expected and raised on add() of a bounded parameter.
"""
prms_meta = MetaData(verbose=False).metadata
max_before = prms_meta['parameters']['outlet_sta']['maximum']
nobs_before = dict(prms_meta['dimensions']['nobs'])

for _ in range(2): # the second iteration crashed before the fix
pdb = Parameters(metadata=prms_meta)
pdb.dimensions.add(name='one', size=1)
pdb.dimensions.add(name='npoigages', size=5)
pdb.dimensions.add(name='nobs', size=5)
pdb.add(name='outlet_sta')

# the instance sees the resolved bound
assert pdb.get('outlet_sta').meta['maximum'] == 5

# the shared metadata is untouched
assert prms_meta['parameters']['outlet_sta']['maximum'] == max_before
assert 'bounded_dimension_name' not in prms_meta['parameters']['outlet_sta']
assert prms_meta['dimensions']['nobs'] == nobs_before