Skip to content

Commit bd6903d

Browse files
ProcessBuilder: allow unsetting of inputs through attribute deletion (#4419)
The builder object was already able to delete set inputs through the `__delitem__` method, but `__delattr__` was not implemented causing `del builder.input_name` to raise. This is not consistent with how these inputs can be set or accessed as both `__getattr__` and `__setattr__` are implemented. Implementing `__delattr__` brings the implementation up to par for all attribute methods.
1 parent 1e1bdf2 commit bd6903d

File tree

2 files changed

+68
-6
lines changed

2 files changed

+68
-6
lines changed

aiida/engine/processes/builder.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
# For further information on the license, see the LICENSE.txt file #
88
# For further information please visit http://www.aiida.net #
99
###########################################################################
10-
# pylint: disable=cell-var-from-loop
1110
"""Convenience classes to help building the input dictionaries for Processes."""
1211
import collections
1312

13+
from aiida.orm import Node
1414
from aiida.engine.processes.ports import PortNamespace
1515

1616
__all__ = ('ProcessBuilder', 'ProcessBuilderNamespace')
@@ -37,6 +37,10 @@ def __init__(self, port_namespace):
3737
self._valid_fields = []
3838
self._data = {}
3939

40+
# The name and port objects have to be passed to the defined functions as defaults for
41+
# their arguments, because this way the content at the time of defining the method is
42+
# saved. If they are used directly in the body, it will try to capture the value from
43+
# its enclosing scope at the time of being called.
4044
for name, port in port_namespace.items():
4145

4246
self._valid_fields.append(name)
@@ -48,14 +52,14 @@ def fgetter(self, name=name):
4852
return self._data.get(name)
4953
elif port.has_default():
5054

51-
def fgetter(self, name=name, default=port.default):
55+
def fgetter(self, name=name, default=port.default): # pylint: disable=cell-var-from-loop
5256
return self._data.get(name, default)
5357
else:
5458

5559
def fgetter(self, name=name):
5660
return self._data.get(name, None)
5761

58-
def fsetter(self, value):
62+
def fsetter(self, value, name=name):
5963
self._data[name] = value
6064

6165
fgetter.__doc__ = str(port)
@@ -112,6 +116,9 @@ def __setitem__(self, item, value):
112116
def __delitem__(self, item):
113117
self._data.__delitem__(item)
114118

119+
def __delattr__(self, item):
120+
self._data.__delitem__(item)
121+
115122
def _update(self, *args, **kwds):
116123
"""Update the values of the builder namespace passing a mapping as argument or individual keyword value pairs.
117124
@@ -160,8 +167,6 @@ def _prune(self, value):
160167
:param value: a nested mapping of port values
161168
:return: the same mapping but without any nested namespace that is completely empty.
162169
"""
163-
from aiida.orm import Node
164-
165170
if isinstance(value, collections.abc.Mapping) and not isinstance(value, Node):
166171
result = {}
167172
for key, sub_value in value.items():
@@ -174,7 +179,7 @@ def _prune(self, value):
174179
return value
175180

176181

177-
class ProcessBuilder(ProcessBuilderNamespace):
182+
class ProcessBuilder(ProcessBuilderNamespace): # pylint: disable=too-many-ancestors
178183
"""A process builder that helps setting up the inputs for creating a new process."""
179184

180185
def __init__(self, process_class):
@@ -188,4 +193,5 @@ def __init__(self, process_class):
188193

189194
@property
190195
def process_class(self):
196+
"""Return the process class for which this builder is constructed."""
191197
return self._process_class
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# -*- coding: utf-8 -*-
2+
###########################################################################
3+
# Copyright (c), The AiiDA team. All rights reserved. #
4+
# This file is part of the AiiDA code. #
5+
# #
6+
# The code is hosted on GitHub at https://github.com/aiidateam/aiida-core #
7+
# For further information on the license, see the LICENSE.txt file #
8+
# For further information please visit http://www.aiida.net #
9+
###########################################################################
10+
"""Tests for `aiida.engine.processes.builder.ProcessBuilder`."""
11+
import pytest
12+
13+
from aiida.calculations.arithmetic.add import ArithmeticAddCalculation
14+
from aiida.engine.processes.builder import ProcessBuilder
15+
from aiida import orm
16+
17+
18+
def test_access_methods():
19+
"""Test for the access methods (setter, getter, delete).
20+
21+
The setters are used again after calling the delete in order to check that they still work
22+
after the deletion (including the validation process).
23+
"""
24+
node_numb = orm.Int(4)
25+
node_dict = orm.Dict(dict={'value': 4})
26+
27+
# AS ITEMS
28+
builder = ProcessBuilder(ArithmeticAddCalculation)
29+
30+
builder['x'] = node_numb
31+
assert dict(builder) == {'metadata': {'options': {}}, 'x': node_numb}
32+
33+
del builder['x']
34+
assert dict(builder) == {'metadata': {'options': {}}}
35+
36+
with pytest.raises(ValueError):
37+
builder['x'] = node_dict
38+
39+
builder['x'] = node_numb
40+
assert dict(builder) == {'metadata': {'options': {}}, 'x': node_numb}
41+
42+
# AS ATTRIBUTES
43+
del builder
44+
builder = ProcessBuilder(ArithmeticAddCalculation)
45+
46+
builder.x = node_numb
47+
assert dict(builder) == {'metadata': {'options': {}}, 'x': node_numb}
48+
49+
del builder.x
50+
assert dict(builder) == {'metadata': {'options': {}}}
51+
52+
with pytest.raises(ValueError):
53+
builder.x = node_dict
54+
55+
builder.x = node_numb
56+
assert dict(builder) == {'metadata': {'options': {}}, 'x': node_numb}

0 commit comments

Comments
 (0)