Skip to content

Commit

Permalink
ProcessBuilder: allow unsetting of inputs through attribute deletion (
Browse files Browse the repository at this point in the history
#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.
  • Loading branch information
ramirezfranciscof authored Oct 7, 2020
1 parent 1e1bdf2 commit bd6903d
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 6 deletions.
18 changes: 12 additions & 6 deletions aiida/engine/processes/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.aiida.net #
###########################################################################
# pylint: disable=cell-var-from-loop
"""Convenience classes to help building the input dictionaries for Processes."""
import collections

from aiida.orm import Node
from aiida.engine.processes.ports import PortNamespace

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

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

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

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

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

def fsetter(self, value):
def fsetter(self, value, name=name):
self._data[name] = value

fgetter.__doc__ = str(port)
Expand Down Expand Up @@ -112,6 +116,9 @@ def __setitem__(self, item, value):
def __delitem__(self, item):
self._data.__delitem__(item)

def __delattr__(self, item):
self._data.__delitem__(item)

def _update(self, *args, **kwds):
"""Update the values of the builder namespace passing a mapping as argument or individual keyword value pairs.
Expand Down Expand Up @@ -160,8 +167,6 @@ def _prune(self, value):
:param value: a nested mapping of port values
:return: the same mapping but without any nested namespace that is completely empty.
"""
from aiida.orm import Node

if isinstance(value, collections.abc.Mapping) and not isinstance(value, Node):
result = {}
for key, sub_value in value.items():
Expand All @@ -174,7 +179,7 @@ def _prune(self, value):
return value


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

def __init__(self, process_class):
Expand All @@ -188,4 +193,5 @@ def __init__(self, process_class):

@property
def process_class(self):
"""Return the process class for which this builder is constructed."""
return self._process_class
56 changes: 56 additions & 0 deletions tests/engine/processes/test_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved. #
# This file is part of the AiiDA code. #
# #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida-core #
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.aiida.net #
###########################################################################
"""Tests for `aiida.engine.processes.builder.ProcessBuilder`."""
import pytest

from aiida.calculations.arithmetic.add import ArithmeticAddCalculation
from aiida.engine.processes.builder import ProcessBuilder
from aiida import orm


def test_access_methods():
"""Test for the access methods (setter, getter, delete).
The setters are used again after calling the delete in order to check that they still work
after the deletion (including the validation process).
"""
node_numb = orm.Int(4)
node_dict = orm.Dict(dict={'value': 4})

# AS ITEMS
builder = ProcessBuilder(ArithmeticAddCalculation)

builder['x'] = node_numb
assert dict(builder) == {'metadata': {'options': {}}, 'x': node_numb}

del builder['x']
assert dict(builder) == {'metadata': {'options': {}}}

with pytest.raises(ValueError):
builder['x'] = node_dict

builder['x'] = node_numb
assert dict(builder) == {'metadata': {'options': {}}, 'x': node_numb}

# AS ATTRIBUTES
del builder
builder = ProcessBuilder(ArithmeticAddCalculation)

builder.x = node_numb
assert dict(builder) == {'metadata': {'options': {}}, 'x': node_numb}

del builder.x
assert dict(builder) == {'metadata': {'options': {}}}

with pytest.raises(ValueError):
builder.x = node_dict

builder.x = node_numb
assert dict(builder) == {'metadata': {'options': {}}, 'x': node_numb}

0 comments on commit bd6903d

Please sign in to comment.