diff --git a/src/plumpy/processes.py b/src/plumpy/processes.py index 41bd3609..5c8326b3 100644 --- a/src/plumpy/processes.py +++ b/src/plumpy/processes.py @@ -731,8 +731,18 @@ def on_create(self) -> None: """Entering the CREATED state.""" self._creation_time = time.time() - # This will parse the inputs with respect to the input portnamespace of the spec and validate them - raw_inputs = dict(self._raw_inputs) if self._raw_inputs else {} + def recursively_copy_dictionaries(value): + """Recursively copy the mapping but only create copies of the dictionaries not the values.""" + if isinstance(value, dict): + return {key: recursively_copy_dictionaries(subvalue) for key, subvalue in value.items()} + return value + + # This will parse the inputs with respect to the input portnamespace of the spec and validate them. The + # ``pre_process`` method of the inputs port namespace modifies its argument in place, and since the + # ``_raw_inputs`` should not be modified, we pass a clone of it. Note that we only need a clone of the nested + # dictionaries, so we don't use ``copy.deepcopy`` (which might seem like the obvious choice) as that will also + # create a clone of the values, which we don't want. + raw_inputs = recursively_copy_dictionaries(dict(self._raw_inputs)) if self._raw_inputs else {} self._parsed_inputs = self.spec().inputs.pre_process(raw_inputs) result = self.spec().inputs.validate(self._parsed_inputs) diff --git a/test/test_processes.py b/test/test_processes.py index e357fc4d..d0868e49 100644 --- a/test/test_processes.py +++ b/test/test_processes.py @@ -115,6 +115,28 @@ def define(cls, spec): with self.assertRaises(AttributeError): p.raw_inputs.b + def test_raw_inputs(self): + """Test that the ``raw_inputs`` are not mutated by the ``Process`` constructor. + + Regression test for https://github.com/aiidateam/plumpy/issues/250 + """ + + class Proc(Process): + + @classmethod + def define(cls, spec): + super().define(spec) + spec.input('a') + spec.input('nested.a') + spec.input('nested.b', default='default-value') + + inputs = {'a': 5, 'nested': {'a': 'value'}} + process = Proc(inputs) + + # Compare against a clone of the original inputs dictionary as the original is modified. It should not contain + # the default value of the ``nested.b`` port. + self.assertDictEqual(dict(process.raw_inputs), {'a': 5, 'nested': {'a': 'value'}}) + def test_inputs_default(self): class Proc(utils.DummyProcess):