From 047643f65e38b54ab2215ed13a967efd98231cc1 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Tue, 21 Apr 2020 12:56:36 +0100 Subject: [PATCH 1/7] Add documentation for `GroupPath` --- docs/source/working_with_aiida/groups.rst | 105 ++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/docs/source/working_with_aiida/groups.rst b/docs/source/working_with_aiida/groups.rst index 55bd82ebd5..86ee93946d 100644 --- a/docs/source/working_with_aiida/groups.rst +++ b/docs/source/working_with_aiida/groups.rst @@ -177,3 +177,108 @@ This is what AiiDA uses to load the correct class when reloading the group from assert isinstance(group, SubClassGroup) If the entry point is not currently registered, because the corresponding plugin package is not installed for example, AiiDA will issue a warning and fallback onto the ``Group`` base class. + +Group hierarchies with `GroupPath` +---------------------------------- + +Groups in AiiDA are inherently "flat", in that groups may only contain nodes and not other groups. +However, the `GroupPath` utility allows one to construct *virtual* group hierarchies based on delimited group labels. + +`GroupPath` is designed to work in much the same way as python's `pathlib.Path`, +whereby paths are denoted by forward slash characters '/' in group labels. +For example say we have the groups:: + + $ verdi group list + PK Label Type string User + ---- ----------------- ------------- -------------- + 1 base1/sub_group1 core user@email.com + 2 base1/sub_group2 core user@email.com + 3 base2/other/sub_group3 core user@email.com + +We can also access them as from the command-line as:: + + $ verdi group path ls -l + Path Sub-Groups + --------- ------------ + base1 2 + base2 1 + $ verdi group path ls base1 + base1/sub_group1 + base1/sub_group2 + +Or from the python interface:: + + In [1]: from aiida.tools.groups import GroupPath + In [2]: path = GroupPath("base1") + In [3]: print(list(path.children)) + Out[3]: [GroupPath('base1/sub_group2', cls=''), + GroupPath('base1/sub_group1', cls='')] + +The `GroupPath` can be constructed using indexing indexing or "divisors":: + + In [4]: path = GroupPath() + In [5]: path["base1"] == path / "base1" + Out[5]: True + +Using the `browse` attribute, you can also construct the paths as preceding attributes. +This is useful in interactive environments, whereby available paths will be shown in the tab-completion:: + + In [6]: path.browse.base1.sub_group2() + Out[6]: GroupPath('base1/sub_group2', cls='') + +To check the existence of a path element:: + + In [7]: "base1" in path + Out[7]: True + +A group may be "virtual", in which case its label does not directly relate to a group, +or the group can be retrieved with the `get_group()` method:: + + In [8]: path.is_virtual + Out[8]: True + In [9]: path.get_group() is None + Out[9]: True + In [10]: path["base1/sub_group1"].get_group() is None + Out[10]: True + In [11]: path["base1/sub_group1"].get_group() + Out[11]: + +Groups can be created and destroyed:: + + In [12]: path["base1/sub_group1"].delete_group() + In [13]: path["base1/sub_group1"].is_virtual + Out[13]: True + In [14]: path["base1/sub_group1"].get_or_create_group() + Out[14]: (, True) + In [15]: path["base1/sub_group1"].is_virtual + Out[15]: False + +To traverse paths, the `children` attribute can be used, or for recursive traversal:: + + In [16]: for subpath in path.walk(return_virtual=False): + ...: print(subpath) + ...: + GroupPath('base1/sub_group1', cls='') + GroupPath('base1/sub_group2', cls='') + GroupPath('base2/other/sub_group3', cls='') + +You can also traverse directly through the nodes of a path, +optionally filtering by node class and any other filters allowed by the `QueryBuilder`:: + + In [17]: from aiida.orm import Data + In [18]: data = Data() + In [19]: data.set_extra("key", "value") + In [20]: data.store() + Out[20]: + In [21]: path["base1/sub_group1"].get_group().add_nodes(data) + In [21]: next(path.walk_nodes(node_class=Data, filters={"extras.key": "value"})) + Out[21]: WalkNodeResult(group_path=GroupPath('base1/sub_group1', cls=''), + node=) + +Finally, you can also specify the `Group` subclasses (discussed above), +in which case only groups of that subclass will be handled:: + + In [22]: from aiida.orm import UpfFamily + In [23]: path2 = GroupPath(cls=UpfFamily) + In [24]: path2["base1"].get_or_create_group() + Out[24]: (, True) From e56269626b20d6214918420f197a99168378b447 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Tue, 21 Apr 2020 13:08:23 +0100 Subject: [PATCH 2/7] fix trailing whitespace --- docs/source/working_with_aiida/groups.rst | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/source/working_with_aiida/groups.rst b/docs/source/working_with_aiida/groups.rst index 86ee93946d..68a1d7bfd9 100644 --- a/docs/source/working_with_aiida/groups.rst +++ b/docs/source/working_with_aiida/groups.rst @@ -223,41 +223,41 @@ The `GroupPath` can be constructed using indexing indexing or "divisors":: Using the `browse` attribute, you can also construct the paths as preceding attributes. This is useful in interactive environments, whereby available paths will be shown in the tab-completion:: - In [6]: path.browse.base1.sub_group2() + In [6]: path.browse.base1.sub_group2() Out[6]: GroupPath('base1/sub_group2', cls='') To check the existence of a path element:: - In [7]: "base1" in path + In [7]: "base1" in path Out[7]: True A group may be "virtual", in which case its label does not directly relate to a group, or the group can be retrieved with the `get_group()` method:: - In [8]: path.is_virtual + In [8]: path.is_virtual Out[8]: True - In [9]: path.get_group() is None + In [9]: path.get_group() is None Out[9]: True - In [10]: path["base1/sub_group1"].get_group() is None + In [10]: path["base1/sub_group1"].get_group() is None Out[10]: True - In [11]: path["base1/sub_group1"].get_group() + In [11]: path["base1/sub_group1"].get_group() Out[11]: Groups can be created and destroyed:: - In [12]: path["base1/sub_group1"].delete_group() - In [13]: path["base1/sub_group1"].is_virtual + In [12]: path["base1/sub_group1"].delete_group() + In [13]: path["base1/sub_group1"].is_virtual Out[13]: True - In [14]: path["base1/sub_group1"].get_or_create_group() + In [14]: path["base1/sub_group1"].get_or_create_group() Out[14]: (, True) - In [15]: path["base1/sub_group1"].is_virtual + In [15]: path["base1/sub_group1"].is_virtual Out[15]: False To traverse paths, the `children` attribute can be used, or for recursive traversal:: - In [16]: for subpath in path.walk(return_virtual=False): - ...: print(subpath) - ...: + In [16]: for subpath in path.walk(return_virtual=False): + ...: print(subpath) + ...: GroupPath('base1/sub_group1', cls='') GroupPath('base1/sub_group2', cls='') GroupPath('base2/other/sub_group3', cls='') @@ -266,11 +266,11 @@ You can also traverse directly through the nodes of a path, optionally filtering by node class and any other filters allowed by the `QueryBuilder`:: In [17]: from aiida.orm import Data - In [18]: data = Data() + In [18]: data = Data() In [19]: data.set_extra("key", "value") - In [20]: data.store() - Out[20]: - In [21]: path["base1/sub_group1"].get_group().add_nodes(data) + In [20]: data.store() + Out[20]: + In [21]: path["base1/sub_group1"].get_group().add_nodes(data) In [21]: next(path.walk_nodes(node_class=Data, filters={"extras.key": "value"})) Out[21]: WalkNodeResult(group_path=GroupPath('base1/sub_group1', cls=''), node=) @@ -280,5 +280,5 @@ in which case only groups of that subclass will be handled:: In [22]: from aiida.orm import UpfFamily In [23]: path2 = GroupPath(cls=UpfFamily) - In [24]: path2["base1"].get_or_create_group() + In [24]: path2["base1"].get_or_create_group() Out[24]: (, True) From 2470e1cc2b817880548d7e3dc6466c033de7e55a Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Tue, 21 Apr 2020 13:47:50 +0100 Subject: [PATCH 3/7] Fix typo --- docs/source/working_with_aiida/groups.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/working_with_aiida/groups.rst b/docs/source/working_with_aiida/groups.rst index 68a1d7bfd9..4f2113f54d 100644 --- a/docs/source/working_with_aiida/groups.rst +++ b/docs/source/working_with_aiida/groups.rst @@ -214,7 +214,7 @@ Or from the python interface:: Out[3]: [GroupPath('base1/sub_group2', cls=''), GroupPath('base1/sub_group1', cls='')] -The `GroupPath` can be constructed using indexing indexing or "divisors":: +The `GroupPath` can be constructed using indexing or "divisors":: In [4]: path = GroupPath() In [5]: path["base1"] == path / "base1" @@ -276,7 +276,7 @@ optionally filtering by node class and any other filters allowed by the `QueryBu node=) Finally, you can also specify the `Group` subclasses (discussed above), -in which case only groups of that subclass will be handled:: +in which case only groups of that subclass will be recognised:: In [22]: from aiida.orm import UpfFamily In [23]: path2 = GroupPath(cls=UpfFamily) From d92f1d7ea5beb526ddf39b2ae052d39e487c60ca Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 24 Apr 2020 16:24:58 +0100 Subject: [PATCH 4/7] Update docs/source/working_with_aiida/groups.rst Co-Authored-By: Leopold Talirz --- docs/source/working_with_aiida/groups.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/working_with_aiida/groups.rst b/docs/source/working_with_aiida/groups.rst index 4f2113f54d..056e78af8c 100644 --- a/docs/source/working_with_aiida/groups.rst +++ b/docs/source/working_with_aiida/groups.rst @@ -253,7 +253,7 @@ Groups can be created and destroyed:: In [15]: path["base1/sub_group1"].is_virtual Out[15]: False -To traverse paths, the `children` attribute can be used, or for recursive traversal:: +To traverse paths, use the `children` attribute - for recursive traversal, use `walk`:: In [16]: for subpath in path.walk(return_virtual=False): ...: print(subpath) From 9b2f22299d7c6793f76489773414de98c7256a00 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 29 Apr 2020 19:15:40 +0100 Subject: [PATCH 5/7] Fix error in example --- docs/source/working_with_aiida/groups.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/working_with_aiida/groups.rst b/docs/source/working_with_aiida/groups.rst index 056e78af8c..52f2a12703 100644 --- a/docs/source/working_with_aiida/groups.rst +++ b/docs/source/working_with_aiida/groups.rst @@ -238,8 +238,8 @@ or the group can be retrieved with the `get_group()` method:: Out[8]: True In [9]: path.get_group() is None Out[9]: True - In [10]: path["base1/sub_group1"].get_group() is None - Out[10]: True + In [10]: path["base1/sub_group1"].is_virtual + Out[10]: False In [11]: path["base1/sub_group1"].get_group() Out[11]: From 506a6c676a8f96592bd2b6c930e3ffb536279203 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 29 Apr 2020 19:50:44 +0100 Subject: [PATCH 6/7] add more info on cls --- docs/source/working_with_aiida/groups.rst | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/source/working_with_aiida/groups.rst b/docs/source/working_with_aiida/groups.rst index 52f2a12703..d287cdc72d 100644 --- a/docs/source/working_with_aiida/groups.rst +++ b/docs/source/working_with_aiida/groups.rst @@ -195,7 +195,7 @@ For example say we have the groups:: 2 base1/sub_group2 core user@email.com 3 base2/other/sub_group3 core user@email.com -We can also access them as from the command-line as:: +We can also access them from the command-line as:: $ verdi group path ls -l Path Sub-Groups @@ -275,10 +275,21 @@ optionally filtering by node class and any other filters allowed by the `QueryBu Out[21]: WalkNodeResult(group_path=GroupPath('base1/sub_group1', cls=''), node=) -Finally, you can also specify the `Group` subclasses (discussed above), -in which case only groups of that subclass will be recognised:: +Finally, you can also specify the `Group` subclasses (as discussed above):: In [22]: from aiida.orm import UpfFamily In [23]: path2 = GroupPath(cls=UpfFamily) In [24]: path2["base1"].get_or_create_group() Out[24]: (, True) + +.. important:: + + A `GroupPath` instance will only recognise groups of the instantiated ``cls`` type. + The default ``cls`` is ``aiida.orm.Group``:: + + In [25]: orm.UpfFamily(label="a").store() + Out[25]: + In [26]: GroupPath("a").is_virtual + Out[26]: True + In [27]: GroupPath("a", cls=orm.UpfFamily).is_virtual + Out[27]: False From 3cc9a039d4f8e923884afedc64513dfc8e4f9bfb Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 30 Apr 2020 16:39:53 +0100 Subject: [PATCH 7/7] one line per sentence --- docs/source/working_with_aiida/groups.rst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/source/working_with_aiida/groups.rst b/docs/source/working_with_aiida/groups.rst index d287cdc72d..dc5379b6cf 100644 --- a/docs/source/working_with_aiida/groups.rst +++ b/docs/source/working_with_aiida/groups.rst @@ -184,8 +184,7 @@ Group hierarchies with `GroupPath` Groups in AiiDA are inherently "flat", in that groups may only contain nodes and not other groups. However, the `GroupPath` utility allows one to construct *virtual* group hierarchies based on delimited group labels. -`GroupPath` is designed to work in much the same way as python's `pathlib.Path`, -whereby paths are denoted by forward slash characters '/' in group labels. +`GroupPath` is designed to work in much the same way as python's `pathlib.Path`, whereby paths are denoted by forward slash characters '/' in group labels. For example say we have the groups:: $ verdi group list @@ -231,8 +230,7 @@ To check the existence of a path element:: In [7]: "base1" in path Out[7]: True -A group may be "virtual", in which case its label does not directly relate to a group, -or the group can be retrieved with the `get_group()` method:: +A group may be "virtual", in which case its label does not directly relate to a group, or the group can be retrieved with the `get_group()` method:: In [8]: path.is_virtual Out[8]: True @@ -262,8 +260,7 @@ To traverse paths, use the `children` attribute - for recursive traversal, use ` GroupPath('base1/sub_group2', cls='') GroupPath('base2/other/sub_group3', cls='') -You can also traverse directly through the nodes of a path, -optionally filtering by node class and any other filters allowed by the `QueryBuilder`:: +You can also traverse directly through the nodes of a path, optionally filtering by node class and any other filters allowed by the `QueryBuilder`:: In [17]: from aiida.orm import Data In [18]: data = Data()