Skip to content

Commit

Permalink
ORM: deprecate double underscores in LinkManager contains (#5067)
Browse files Browse the repository at this point in the history
The use of double underscores in the interface of the `LinkManager` was
recently deprecated for v2.0, however, it unintentionally broke the
`__contains__` operator. Legacy code that was using code like:

    if 'some__nested__namespace' in node.inputs

which used to work, will now return false, breaking existing code. The
solution is to override the `__contains__` operator and check for the
presence of a double underscore in the key. If that is the case, now a
deprecation warning is emitted, but the key is split on the double
underscores and the namespaces are used to fetch the intended nested
dictionary before applying the actual contains check on the leaf node.
  • Loading branch information
sphuber authored Aug 12, 2021
1 parent efd1c3d commit bf80fde
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 5 deletions.
36 changes: 31 additions & 5 deletions aiida/orm/utils/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
to access members of other classes via TAB-completable attributes
(e.g. the class underlying `calculation.inputs` to allow to do `calculation.inputs.<label>`).
"""
import warnings

from aiida.common import AttributeDict
from aiida.common.links import LinkType
from aiida.common.exceptions import NotExistent, NotExistentAttributeError, NotExistentKeyError
from aiida.common.warnings import AiidaDeprecationWarning

__all__ = ('NodeLinksManager', 'AttributeManager')

Expand All @@ -25,6 +28,8 @@ class NodeLinksManager:
See an example of its use in `CalculationNode.inputs`.
"""

_namespace_separator = '__'

def __init__(self, node, link_type, incoming):
"""
Initialise the link manager.
Expand Down Expand Up @@ -90,17 +95,18 @@ def _get_node_by_link_label(self, label):
# Check whether the label contains a double underscore, in which case we want to warn the user that this is
# deprecated. However, we need to exclude labels that corresponds to dunder methods, i.e., those that start
# and end with a double underscore.
if '__' in label and not (label.startswith('__') and label.endswith('__')):
if (
self._namespace_separator in label and
not (label.startswith(self._namespace_separator) and label.endswith(self._namespace_separator))
):
import functools
import warnings
from aiida.common.warnings import AiidaDeprecationWarning
warnings.warn(
'dereferencing nodes with links containing double underscores is deprecated, simply replace '
'the double underscores with a single dot instead. For example: \n'
'`self.inputs.some__label` can be written as `self.inputs.some.label` instead.\n'
'Support for double underscores will be removed in `v3.0`.', AiidaDeprecationWarning
) # pylint: disable=no-member
namespaces = label.split('__')
namespaces = label.split(self._namespace_separator)
try:
return functools.reduce(lambda d, namespace: d.get(namespace), namespaces, attribute_dict)
except TypeError as exc:
Expand Down Expand Up @@ -142,6 +148,26 @@ def __getattr__(self, name):
f"Node<{self._node.pk}> does not have an {prefix} with link label '{name}'"
) from exception

def __contains__(self, key):
"""Override the operator of the base class to emit deprecation warning if double underscore is used in key."""
if self._namespace_separator in key:
warnings.warn(
'The use of double underscores in keys is deprecated. Please expand the namespaces manually:\n'
'instead of `nested__key in node.inputs`, use `nested in node.inputs and `key in node.inputs.nested`.',
AiidaDeprecationWarning
)
namespaces = key.split(self._namespace_separator)
leaf = namespaces.pop()
subdictionary = self
for namespace in namespaces:
try:
subdictionary = subdictionary[namespace]
except KeyError:
return False
return leaf in subdictionary

return key in self._get_keys()

def __getitem__(self, name):
"""
interface to get to the parser results as a dictionary.
Expand Down Expand Up @@ -185,7 +211,7 @@ def __init__(self, node):
# Possibly add checks here
# We cannot set `self._node` because it would go through the __setattr__ method
# which uses said _node by calling `self._node.set_attribute(name, value)`.
# Instead, we need to manually set it through the `self.__dict__` property.
# Instead, we need to manually set it through the `self.__dict__` property.
self.__dict__['_node'] = node

def __dir__(self):
Expand Down
27 changes: 27 additions & 0 deletions tests/orm/utils/test_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,30 @@ def test_link_manager_with_nested_namespaces(clear_database_before_test):

with pytest.raises(KeyError):
_ = calc.outputs['remote_folder__namespace']


def test_link_manager_contains(clear_database_before_test):
"""Test the ``__contains__`` method for the ``LinkManager``."""
data = orm.Data()
data.store()

calc = orm.CalculationNode()
calc.add_incoming(data, link_type=LinkType.INPUT_CALC, link_label='nested__sub__name')
calc.store()

assert 'nested' in calc.inputs
assert 'sub' in calc.inputs.nested
assert 'name' in calc.inputs.nested.sub

# Check that using a double-underscore-containing key with an ``in`` statement issues a warning but works
with pytest.warns(Warning, match=r'The use of double underscores in keys is deprecated..*'):
assert 'nested__sub__name' in calc.inputs

with pytest.warns(Warning, match=r'The use of double underscores in keys is deprecated..*'):
assert 'nested__sub' in calc.inputs

with pytest.warns(Warning, match=r'The use of double underscores in keys is deprecated..*'):
assert 'spaced__sub' not in calc.inputs

with pytest.warns(Warning, match=r'The use of double underscores in keys is deprecated..*'):
assert 'nested__namespace' not in calc.inputs

0 comments on commit bf80fde

Please sign in to comment.