{"action":"created","comment":{"url":"https://api.github.com/repos/aiidateam/aiida_core/pulls/comments/235898478","pull_request_review_id":177862897,"id":235898478,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzNTg5ODQ3OA==","diff_hunk":"@@ -0,0 +1,69 @@\n+# -*- coding: utf-8 -*-\n+\"\"\"Utilities for dealing with links between nodes.\"\"\"\n+from __future__ import absolute_import\n+\n+\n+def validate_link(source, target, link_type, link_label=None):\n+    \"\"\"\n+    Validate adding a link of the given type from a given node to ourself.\n+\n+    This function will first validate the types of the inputs, followed by the node and link types and validate\n+    whether in principle a link of that type between the nodes of these types is allowed.the\n+\n+    Subsequently, the validity of the \"degree\" of the proposed link is validated, which means validating the\n+    number of links of the given type from the given node type is allowed.\n+\n+    :param source: the node from which the link is coming\n+    :param target: the node to which the link is going\n+    :param link_type: the type of link\n+    :param link_label: optional link label\n+    :raise TypeError: if `source` or `target` is not a Node instance, or `link_type` is not a `LinkType` enum\n+    :raise ValueError: if the proposed link is invalid\n+    \"\"\"\n+    from aiida.common.links import LinkType\n+    from aiida.orm.data import Data\n+    from aiida.orm.implementation.general.node import AbstractNode\n+    from aiida.orm.node.process import CalculationNode, WorkflowNode\n+\n+    if not isinstance(link_type, LinkType):\n+        raise TypeError('the link_type should be a value from the LinkType enum')\n+\n+    if not isinstance(source, AbstractNode):\n+        raise TypeError('the source should be a Node instance')\n+\n+    if not isinstance(target, AbstractNode):\n+        raise TypeError('the target should be a Node instance')\n+\n+    if source.uuid == target.uuid:\n+        raise ValueError('cannot add a link to oneself')\n+\n+    # For each link type, defines a tuple that defines the valid types for the source and target node, as well as\n+    # the indegree character. If the indegree is `unique` that means that there can only be a single incoming link\n+    # of this type for the target node, regardless of the label. If instead it is `unique_label`, an infinite amount\n+    # of links can be incoming to the target node, as long as the link label is unique for the combination of source\n+    # type and link type. For example, a Data node can have infinite `RETURN` links from a WorkflowNode, even a single\n+    # WorkflowNode, as long as the link labels are unique.\n+    link_mapping = {\n+        LinkType.CALL_CALC: (WorkflowNode, CalculationNode, 'unique'),\n+        LinkType.CALL_WORK: (WorkflowNode, WorkflowNode, 'unique'),\n+        LinkType.INPUT_CALC: (Data, CalculationNode, 'unique_label'),\n+        LinkType.INPUT_WORK: (Data, WorkflowNode, 'unique_label'),\n+        LinkType.CREATE: (CalculationNode, Data, 'unique'),\n+        LinkType.RETURN: (WorkflowNode, Data, 'unique_label'),\n+    }\n+\n+    type_source, type_target, type_degree = link_mapping[link_type]\n+\n+    if not isinstance(source, type_source) or not isinstance(target, type_target):\n+        raise ValueError('cannot add a {} link from {} to {}'.format(link_type, type(source), type(target)))\n+\n+    incoming = dict(target.get_inputs(node_type=type(source), link_type=link_type, also_labels=True))\n+\n+    # If there are already existing incoming links, verify that the proposed link does not violate the indegree\n+    if type_degree == 'unique' and incoming:\n+        raise ValueError('node<{}> already has an incoming {} link'.format(target.uuid, link_type))\n+\n+    # If the indegree is `unique_label` than the pair (source, label) should be unique for the target node\n+    if type_degree == 'unique_label' and (source.uuid, link_label) in [(n.uuid, l) for l, n in incoming.items()]:","path":"aiida/orm/utils/links.py","position":67,"original_position":67,"commit_id":"9db79973d0dc58eaa939aa2d825092d348f7ba61","original_commit_id":"9db79973d0dc58eaa939aa2d825092d348f7ba61","user":{"login":"muhrin","id":1628907,"node_id":"MDQ6VXNlcjE2Mjg5MDc=","avatar_url":"https://avatars0.githubusercontent.com/u/1628907?v=4","gravatar_id":"","url":"https://api.github.com/users/muhrin","html_url":"https://github.com/muhrin","followers_url":"https://api.github.com/users/muhrin/followers","following_url":"https://api.github.com/users/muhrin/following{/other_user}","gists_url":"https://api.github.com/users/muhrin/gists{/gist_id}","starred_url":"https://api.github.com/users/muhrin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/muhrin/subscriptions","organizations_url":"https://api.github.com/users/muhrin/orgs","repos_url":"https://api.github.com/users/muhrin/repos","events_url":"https://api.github.com/users/muhrin/events{/privacy}","received_events_url":"https://api.github.com/users/muhrin/received_events","type":"User","site_admin":false},"body":"Consider changing to just check the link labels for uniqueness at this point.","created_at":"2018-11-23T10:26:29Z","updated_at":"2018-11-23T10:45:39Z","html_url":"https://github.com/aiidateam/aiida_core/pull/2220#discussion_r235898478","pull_request_url":"https://api.github.com/repos/aiidateam/aiida_core/pulls/2220","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/aiidateam/aiida_core/pulls/comments/235898478"},"html":{"href":"https://github.com/aiidateam/aiida_core/pull/2220#discussion_r235898478"},"pull_request":{"href":"https://api.github.com/repos/aiidateam/aiida_core/pulls/2220"}}},"pull_request":{"url":"https://api.github.com/repos/aiidateam/aiida_core/pulls/2220","id":232354524,"node_id":"MDExOlB1bGxSZXF1ZXN0MjMyMzU0NTI0","html_url":"https://github.com/aiidateam/aiida_core/pull/2220","diff_url":"https://github.com/aiidateam/aiida_core/pull/2220.diff","patch_url":"https://github.com/aiidateam/aiida_core/pull/2220.patch","issue_url":"https://api.github.com/repos/aiidateam/aiida_core/issues/2220","number":2220,"state":"open","locked":false,"title":"Implement new link types","user":{"login":"sphuber","id":6992332,"node_id":"MDQ6VXNlcjY5OTIzMzI=","avatar_url":"https://avatars3.githubusercontent.com/u/6992332?v=4","gravatar_id":"","url":"https://api.github.com/users/sphuber","html_url":"https://github.com/sphuber","followers_url":"https://api.github.com/users/sphuber/followers","following_url":"https://api.github.com/users/sphuber/following{/other_user}","gists_url":"https://api.github.com/users/sphuber/gists{/gist_id}","starred_url":"https://api.github.com/users/sphuber/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sphuber/subscriptions","organizations_url":"https://api.github.com/users/sphuber/orgs","repos_url":"https://api.github.com/users/sphuber/repos","events_url":"https://api.github.com/users/sphuber/events{/privacy}","received_events_url":"https://api.github.com/users/sphuber/received_events","type":"User","site_admin":false},"body":"Fixes #2177 \r\n\r\nWith the new ORM hierarchy in place, the corresponding link types can be defined:\r\n\r\n`CREATE`: `CalculationNode -> Data`\r\n`RETURN`: `WorkflowNode -> Data`\r\n`INPUT_CALC`: `Data -> CalculationNode`\r\n`INPUT_WORK`: `Data -> WorkflowNode`\r\n`CALL_CALC`: `WorkflowNode -> CalculationNode`\r\n`CALL_WORK`: `WorkflowNode -> WorkflowNode`\r\n\r\nThese rules are encoded in the `validate_link` function in `aiida.orm.utils.links`.\r\nThis function is called from `add_link_from`, which has been renamed to `add_incoming`,\r\nand validates the triple (source, link, target) representing the addition of a link\r\nfrom a source node to a target node.","created_at":"2018-11-20T15:17:46Z","updated_at":"2018-11-23T10:45:38Z","closed_at":null,"merged_at":null,"merge_commit_sha":"59ca0e7d33fc82e5bcc1012a5a603f09fa11d253","assignee":null,"assignees":[],"requested_reviewers":[{"login":"giovannipizzi","id":16132028,"node_id":"MDQ6VXNlcjE2MTMyMDI4","avatar_url":"https://avatars0.githubusercontent.com/u/16132028?v=4","gravatar_id":"","url":"https://api.github.com/users/giovannipizzi","html_url":"https://github.com/giovannipizzi","followers_url":"https://api.github.com/users/giovannipizzi/followers","following_url":"https://api.github.com/users/giovannipizzi/following{/other_user}","gists_url":"https://api.github.com/users/giovannipizzi/gists{/gist_id}","starred_url":"https://api.github.com/users/giovannipizzi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/giovannipizzi/subscriptions","organizations_url":"https://api.github.com/users/giovannipizzi/orgs","repos_url":"https://api.github.com/users/giovannipizzi/repos","events_url":"https://api.github.com/users/giovannipizzi/events{/privacy}","received_events_url":"https://api.github.com/users/giovannipizzi/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/aiidateam/aiida_core/pulls/2220/commits","review_comments_url":"https://api.github.com/repos/aiidateam/aiida_core/pulls/2220/comments","review_comment_url":"https://api.github.com/repos/aiidateam/aiida_core/pulls/comments{/number}","comments_url":"https://api.github.com/repos/aiidateam/aiida_core/issues/2220/comments","statuses_url":"https://api.github.com/repos/aiidateam/aiida_core/statuses/9db79973d0dc58eaa939aa2d825092d348f7ba61","head":{"label":"sphuber:fix_2177_implement_new_link_types","ref":"fix_2177_implement_new_link_types","sha":"9db79973d0dc58eaa939aa2d825092d348f7ba61","user":{"login":"sphuber","id":6992332,"node_id":"MDQ6VXNlcjY5OTIzMzI=","avatar_url":"https://avatars3.githubusercontent.com/u/6992332?v=4","gravatar_id":"","url":"https://api.github.com/users/sphuber","html_url":"https://github.com/sphuber","followers_url":"https://api.github.com/users/sphuber/followers","following_url":"https://api.github.com/users/sphuber/following{/other_user}","gists_url":"https://api.github.com/users/sphuber/gists{/gist_id}","starred_url":"https://api.github.com/users/sphuber/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sphuber/subscriptions","organizations_url":"https://api.github.com/users/sphuber/orgs","repos_url":"https://api.github.com/users/sphuber/repos","events_url":"https://api.github.com/users/sphuber/events{/privacy}","received_events_url":"https://api.github.com/users/sphuber/received_events","type":"User","site_admin":false},"repo":{"id":78537725,"node_id":"MDEwOlJlcG9zaXRvcnk3ODUzNzcyNQ==","name":"aiida_core","full_name":"sphuber/aiida_core","private":false,"owner":{"login":"sphuber","id":6992332,"node_id":"MDQ6VXNlcjY5OTIzMzI=","avatar_url":"https://avatars3.githubusercontent.com/u/6992332?v=4","gravatar_id":"","url":"https://api.github.com/users/sphuber","html_url":"https://github.com/sphuber","followers_url":"https://api.github.com/users/sphuber/followers","following_url":"https://api.github.com/users/sphuber/following{/other_user}","gists_url":"https://api.github.com/users/sphuber/gists{/gist_id}","starred_url":"https://api.github.com/users/sphuber/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sphuber/subscriptions","organizations_url":"https://api.github.com/users/sphuber/orgs","repos_url":"https://api.github.com/users/sphuber/repos","events_url":"https://api.github.com/users/sphuber/events{/privacy}","received_events_url":"https://api.github.com/users/sphuber/received_events","type":"User","site_admin":false},"html_url":"https://github.com/sphuber/aiida_core","description":"The official repository for the AiiDA code","fork":true,"url":"https://api.github.com/repos/sphuber/aiida_core","forks_url":"https://api.github.com/repos/sphuber/aiida_core/forks","keys_url":"https://api.github.com/repos/sphuber/aiida_core/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sphuber/aiida_core/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sphuber/aiida_core/teams","hooks_url":"https://api.github.com/repos/sphuber/aiida_core/hooks","issue_events_url":"https://api.github.com/repos/sphuber/aiida_core/issues/events{/number}","events_url":"https://api.github.com/repos/sphuber/aiida_core/events","assignees_url":"https://api.github.com/repos/sphuber/aiida_core/assignees{/user}","branches_url":"https://api.github.com/repos/sphuber/aiida_core/branches{/branch}","tags_url":"https://api.github.com/repos/sphuber/aiida_core/tags","blobs_url":"https://api.github.com/repos/sphuber/aiida_core/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sphuber/aiida_core/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sphuber/aiida_core/git/refs{/sha}","trees_url":"https://api.github.com/repos/sphuber/aiida_core/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sphuber/aiida_core/statuses/{sha}","languages_url":"https://api.github.com/repos/sphuber/aiida_core/languages","stargazers_url":"https://api.github.com/repos/sphuber/aiida_core/stargazers","contributors_url":"https://api.github.com/repos/sphuber/aiida_core/contributors","subscribers_url":"https://api.github.com/repos/sphuber/aiida_core/subscribers","subscription_url":"https://api.github.com/repos/sphuber/aiida_core/subscription","commits_url":"https://api.github.com/repos/sphuber/aiida_core/commits{/sha}","git_commits_url":"https://api.github.com/repos/sphuber/aiida_core/git/commits{/sha}","comments_url":"https://api.github.com/repos/sphuber/aiida_core/comments{/number}","issue_comment_url":"https://api.github.com/repos/sphuber/aiida_core/issues/comments{/number}","contents_url":"https://api.github.com/repos/sphuber/aiida_core/contents/{+path}","compare_url":"https://api.github.com/repos/sphuber/aiida_core/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sphuber/aiida_core/merges","archive_url":"https://api.github.com/repos/sphuber/aiida_core/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sphuber/aiida_core/downloads","issues_url":"https://api.github.com/repos/sphuber/aiida_core/issues{/number}","pulls_url":"https://api.github.com/repos/sphuber/aiida_core/pulls{/number}","milestones_url":"https://api.github.com/repos/sphuber/aiida_core/milestones{/number}","notifications_url":"https://api.github.com/repos/sphuber/aiida_core/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sphuber/aiida_core/labels{/name}","releases_url":"https://api.github.com/repos/sphuber/aiida_core/releases{/id}","deployments_url":"https://api.github.com/repos/sphuber/aiida_core/deployments","created_at":"2017-01-10T13:50:47Z","updated_at":"2018-08-15T07:54:59Z","pushed_at":"2018-11-23T09:42:44Z","git_url":"git://github.com/sphuber/aiida_core.git","ssh_url":"git@github.com:sphuber/aiida_core.git","clone_url":"https://github.com/sphuber/aiida_core.git","svn_url":"https://github.com/sphuber/aiida_core","homepage":null,"size":48075,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"base":{"label":"aiidateam:provenance_redesign","ref":"provenance_redesign","sha":"f2ee58824b15a96c9dfe57721289aa90dd04e6cc","user":{"login":"aiidateam","id":22181836,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIyMTgxODM2","avatar_url":"https://avatars3.githubusercontent.com/u/22181836?v=4","gravatar_id":"","url":"https://api.github.com/users/aiidateam","html_url":"https://github.com/aiidateam","followers_url":"https://api.github.com/users/aiidateam/followers","following_url":"https://api.github.com/users/aiidateam/following{/other_user}","gists_url":"https://api.github.com/users/aiidateam/gists{/gist_id}","starred_url":"https://api.github.com/users/aiidateam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aiidateam/subscriptions","organizations_url":"https://api.github.com/users/aiidateam/orgs","repos_url":"https://api.github.com/users/aiidateam/repos","events_url":"https://api.github.com/users/aiidateam/events{/privacy}","received_events_url":"https://api.github.com/users/aiidateam/received_events","type":"Organization","site_admin":false},"repo":{"id":77234579,"node_id":"MDEwOlJlcG9zaXRvcnk3NzIzNDU3OQ==","name":"aiida_core","full_name":"aiidateam/aiida_core","private":false,"owner":{"login":"aiidateam","id":22181836,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIyMTgxODM2","avatar_url":"https://avatars3.githubusercontent.com/u/22181836?v=4","gravatar_id":"","url":"https://api.github.com/users/aiidateam","html_url":"https://github.com/aiidateam","followers_url":"https://api.github.com/users/aiidateam/followers","following_url":"https://api.github.com/users/aiidateam/following{/other_user}","gists_url":"https://api.github.com/users/aiidateam/gists{/gist_id}","starred_url":"https://api.github.com/users/aiidateam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aiidateam/subscriptions","organizations_url":"https://api.github.com/users/aiidateam/orgs","repos_url":"https://api.github.com/users/aiidateam/repos","events_url":"https://api.github.com/users/aiidateam/events{/privacy}","received_events_url":"https://api.github.com/users/aiidateam/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/aiidateam/aiida_core","description":"The official repository for the AiiDA code","fork":false,"url":"https://api.github.com/repos/aiidateam/aiida_core","forks_url":"https://api.github.com/repos/aiidateam/aiida_core/forks","keys_url":"https://api.github.com/repos/aiidateam/aiida_core/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aiidateam/aiida_core/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aiidateam/aiida_core/teams","hooks_url":"https://api.github.com/repos/aiidateam/aiida_core/hooks","issue_events_url":"https://api.github.com/repos/aiidateam/aiida_core/issues/events{/number}","events_url":"https://api.github.com/repos/aiidateam/aiida_core/events","assignees_url":"https://api.github.com/repos/aiidateam/aiida_core/assignees{/user}","branches_url":"https://api.github.com/repos/aiidateam/aiida_core/branches{/branch}","tags_url":"https://api.github.com/repos/aiidateam/aiida_core/tags","blobs_url":"https://api.github.com/repos/aiidateam/aiida_core/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aiidateam/aiida_core/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aiidateam/aiida_core/git/refs{/sha}","trees_url":"https://api.github.com/repos/aiidateam/aiida_core/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aiidateam/aiida_core/statuses/{sha}","languages_url":"https://api.github.com/repos/aiidateam/aiida_core/languages","stargazers_url":"https://api.github.com/repos/aiidateam/aiida_core/stargazers","contributors_url":"https://api.github.com/repos/aiidateam/aiida_core/contributors","subscribers_url":"https://api.github.com/repos/aiidateam/aiida_core/subscribers","subscription_url":"https://api.github.com/repos/aiidateam/aiida_core/subscription","commits_url":"https://api.github.com/repos/aiidateam/aiida_core/commits{/sha}","git_commits_url":"https://api.github.com/repos/aiidateam/aiida_core/git/commits{/sha}","comments_url":"https://api.github.com/repos/aiidateam/aiida_core/comments{/number}","issue_comment_url":"https://api.github.com/repos/aiidateam/aiida_core/issues/comments{/number}","contents_url":"https://api.github.com/repos/aiidateam/aiida_core/contents/{+path}","compare_url":"https://api.github.com/repos/aiidateam/aiida_core/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aiidateam/aiida_core/merges","archive_url":"https://api.github.com/repos/aiidateam/aiida_core/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aiidateam/aiida_core/downloads","issues_url":"https://api.github.com/repos/aiidateam/aiida_core/issues{/number}","pulls_url":"https://api.github.com/repos/aiidateam/aiida_core/pulls{/number}","milestones_url":"https://api.github.com/repos/aiidateam/aiida_core/milestones{/number}","notifications_url":"https://api.github.com/repos/aiidateam/aiida_core/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aiidateam/aiida_core/labels{/name}","releases_url":"https://api.github.com/repos/aiidateam/aiida_core/releases{/id}","deployments_url":"https://api.github.com/repos/aiidateam/aiida_core/deployments","created_at":"2016-12-23T15:38:21Z","updated_at":"2018-11-22T16:18:49Z","pushed_at":"2018-11-23T09:42:45Z","git_url":"git://github.com/aiidateam/aiida_core.git","ssh_url":"git@github.com:aiidateam/aiida_core.git","clone_url":"https://github.com/aiidateam/aiida_core.git","svn_url":"https://github.com/aiidateam/aiida_core","homepage":null,"size":48417,"stargazers_count":132,"watchers_count":132,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":62,"mirror_url":null,"archived":false,"open_issues_count":266,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":62,"open_issues":266,"watchers":132,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/aiidateam/aiida_core/pulls/2220"},"html":{"href":"https://github.com/aiidateam/aiida_core/pull/2220"},"issue":{"href":"https://api.github.com/repos/aiidateam/aiida_core/issues/2220"},"comments":{"href":"https://api.github.com/repos/aiidateam/aiida_core/issues/2220/comments"},"review_comments":{"href":"https://api.github.com/repos/aiidateam/aiida_core/pulls/2220/comments"},"review_comment":{"href":"https://api.github.com/repos/aiidateam/aiida_core/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/aiidateam/aiida_core/pulls/2220/commits"},"statuses":{"href":"https://api.github.com/repos/aiidateam/aiida_core/statuses/9db79973d0dc58eaa939aa2d825092d348f7ba61"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"muhrin"}}