Skip to content

Commit

Permalink
Add support for the export/import of Comment entities
Browse files Browse the repository at this point in the history
To make the export of `Comment` entities possible, the `QueryBuilder` had to
be extended to support retrieving comments for a given `Node`. The
following changes were applied:

 * Added support to join user to comment (`with_user`)
 * Added support to join comment to user (`with_comment`)

The `verdi export create` has a new flag to include or exclude the
export of comments for nodes that are to be exported, defaulting to include:

 `--include-comments/--exclude-comments`

Documentation has been updated to include new `QueryBuilder` join args
in table and the `metadata.json` example has been updated in documentation
to include correct Comment info.
  • Loading branch information
CasperWA authored and sphuber committed Feb 8, 2019
1 parent a000899 commit 4c2f591
Show file tree
Hide file tree
Showing 8 changed files with 444 additions and 53 deletions.
224 changes: 224 additions & 0 deletions aiida/backends/tests/export_and_import.py
Expand Up @@ -2234,3 +2234,227 @@ def test_exclude_logs_flag(self):

finally:
shutil.rmtree(tmp_folder, ignore_errors=True)


class TestComments(AiidaTestCase):
""" Tests for export/import of nodes with comments """

def setUp(self):
super(TestComments, self).setUp()
self.comments = [
"We're no strangers to love",
"You know the rules and so do I",
"A full commitment's what I'm thinking of",
"You wouldn't get this from any other guy"
]

def tearDown(self):
super(TestComments, self).tearDown()
from aiida.orm import Comment
comments = Comment.objects.all()
for comment in comments:
Comment.objects.delete(comment.id)

def test_exclude_comments_flag(self):
"""Test comments and associated commenting users are not exported when using `include_comments=False`."""
import os, shutil, tempfile
from aiida.orm import Comment, User, Data, QueryBuilder, Node
from aiida.orm.importexport import export

tmp_folder = tempfile.mkdtemp()

try:
# Create users, node, and comments
user_one = User.objects.get_default()
user_two = User(email="commenting@user.s").store()

node = Data().store()

comment_one = Comment(node, user_one, self.comments[0]).store()
comment_two = Comment(node, user_one, self.comments[1]).store()

comment_three = Comment(node, user_two, self.comments[2]).store()
comment_four = Comment(node, user_two, self.comments[3]).store()

# Get values prior to export
users_email = [u.email for u in [user_one, user_two]]
node_uuid = node.uuid
user_one_comments_uuid = [c.uuid for c in [comment_one, comment_two]]
user_two_comments_uuid = [c.uuid for c in [comment_three, comment_four]]

# Check that node belongs to user_one
self.assertEqual(node.get_user().email, users_email[0])

# Export nodes, excluding comments
export_file = os.path.join(tmp_folder, 'export.tar.gz')
export([node], outfile=export_file, silent=True, include_comments=False)

# Clean database and reimport exported file
self.reset_database()
import_data(export_file, silent=True)

# Get node, users, and comments
import_nodes = QueryBuilder().append(Node, project=['uuid']).all()
import_comments = QueryBuilder().append(Comment, project=['uuid']).all()
import_users = QueryBuilder().append(User, project=['email']).all()

# There should be exactly: 1 Node, 0 Comments, 1 User
self.assertEqual(len(import_nodes), 1)
self.assertEqual(len(import_comments), 0)
self.assertEqual(len(import_users), 1)

# Check it's the correct user (and node)
self.assertEqual(str(import_nodes[0][0]), node_uuid)
self.assertEqual(str(import_users[0][0]), users_email[0])

finally:
shutil.rmtree(tmp_folder, ignore_errors=True)

def test_calc_and_data_nodes_with_comments(self):
""" Test comments for CalculatioNode and Data node are correctly ex-/imported """
import os, shutil, tempfile
from aiida.orm import CalculationNode, Comment, User, Data, QueryBuilder, Node
from aiida.orm.importexport import export

tmp_folder = tempfile.mkdtemp()

try:
# Create user, nodes, and comments
user = User.objects.get_default()

calc_node = CalculationNode().store()
data_node = Data().store()

comment_one = Comment(calc_node, user, self.comments[0]).store()
comment_two = Comment(calc_node, user, self.comments[1]).store()

comment_three = Comment(data_node, user, self.comments[2]).store()
comment_four = Comment(data_node, user, self.comments[3]).store()

# Get values prior to export
user_email = user.email
calc_uuid = calc_node.uuid
data_uuid = data_node.uuid
calc_comments_uuid = [c.uuid for c in [comment_one, comment_two]]
data_comments_uuid = [c.uuid for c in [comment_three, comment_four]]

# Export nodes
export_file = os.path.join(tmp_folder, 'export.tar.gz')
export([calc_node, data_node], outfile=export_file, silent=True)

# Clean database and reimport exported file
self.reset_database()
import_data(export_file, silent=True)

# Get nodes and comments
builder = QueryBuilder()
builder.append(Node, tag='node', project=['uuid'])
builder.append(Comment, with_node='node', project=['uuid'])
nodes_and_comments = builder.all()

self.assertEqual(len(nodes_and_comments), len(self.comments))
for entry in nodes_and_comments:
self.assertEqual(len(entry), 2) # 1 Node + 1 Comment

import_node_uuid = str(entry[0])
import_comment_uuid = str(entry[1])

self.assertIn(import_node_uuid, [calc_uuid, data_uuid])
if import_node_uuid == calc_uuid:
# Calc node comments
self.assertIn(import_comment_uuid, calc_comments_uuid)
else:
# Data node comments
self.assertIn(import_comment_uuid, data_comments_uuid)

finally:
shutil.rmtree(tmp_folder, ignore_errors=True)

def test_multiple_user_comments_for_single_node(self):
""" Test multiple users commenting on a single CalculationNode """
import os, shutil, tempfile
from aiida.orm import CalculationNode, Comment, User, Node, QueryBuilder
from aiida.orm.importexport import export

tmp_folder = tempfile.mkdtemp()

try:
# Create users, node, and comments
user_one = User.objects.get_default()
user_two = User(email="commenting@user.s").store()

node = CalculationNode().store()

comment_one = Comment(node, user_one, self.comments[0]).store()
comment_two = Comment(node, user_one, self.comments[1]).store()

comment_three = Comment(node, user_two, self.comments[2]).store()
comment_four = Comment(node, user_two, self.comments[3]).store()

# Get values prior to export
users_email = [u.email for u in [user_one, user_two]]
node_uuid = str(node.uuid)
user_one_comments_uuid = [str(c.uuid) for c in [comment_one, comment_two]]
user_two_comments_uuid = [str(c.uuid) for c in [comment_three, comment_four]]

# Export node, along with comments and users recursively
export_file = os.path.join(tmp_folder, 'export.tar.gz')
export([node], outfile=export_file, silent=True)

# Clean database and reimport exported file
self.reset_database()
import_data(export_file, silent=True)

# Get node, users, and comments
builder = QueryBuilder()
builder.append(Node, tag='node', project=['uuid'])
builder.append(Comment, tag='comment', with_node='node', project=['uuid'])
builder.append(User, with_comment='comment', project=['email'])
entries = builder.all()

# Check that all 4 comments are retrieved, along with their respective node and user
self.assertEqual(len(entries), len(self.comments))

# Go through [Node.uuid, Comment.uuid, User.email]-entries
imported_node_uuids = set()
imported_user_one_comment_uuids = set()
imported_user_two_comment_uuids = set()
imported_user_emails = set()
for entry in entries:
self.assertEqual(len(entry), 3) # 1 Node + 1 Comment + 1 User

# Add node to set of imported nodes
imported_node_uuids.add(str(entry[0]))

# Add user to set of imported users
import_user_email = entry[2]
imported_user_emails.add(str(import_user_email))

# Add comment to set of imported comments pertaining to correct user
if import_user_email == users_email[0]:
# User_one comments
imported_user_one_comment_uuids.add(str(entry[1]))
else:
# User_two comments
imported_user_two_comment_uuids.add(str(entry[1]))

# Check same number of nodes (1) and users (2) were ex- and imported
self.assertEqual(len(imported_node_uuids), 1)
self.assertEqual(len(imported_user_emails), len(users_email))

# Check imported node equals exported node
self.assertSetEqual(imported_node_uuids, {node_uuid})

# Check imported user is part of exported users
self.assertSetEqual(imported_user_emails, set(users_email))

# Check same number of comments (2) pertaining to each user were ex- and imported
self.assertEqual(len(imported_user_one_comment_uuids), len(user_one_comments_uuid))
self.assertEqual(len(imported_user_two_comment_uuids), len(user_two_comments_uuid))

# Check imported comments equal exported comments pertaining to specific user
self.assertSetEqual(imported_user_one_comment_uuids, set(user_one_comments_uuid))
self.assertSetEqual(imported_user_two_comment_uuids, set(user_two_comments_uuid))

finally:
shutil.rmtree(tmp_folder, ignore_errors=True)
76 changes: 62 additions & 14 deletions aiida/backends/tests/orm/comments.py
Expand Up @@ -74,35 +74,83 @@ def test_comment_collection_delete(self):
Comment.objects.get(id=comment_pk)

def test_comment_querybuilder(self):
# pylint: disable=too-many-locals
"""Test querying for comments by joining on nodes in the QueryBuilder."""
user_one = self.user
user_two = orm.User(email="commenting@user.s").store()

node_one = orm.Data().store()
comment_one = Comment(node_one, self.user, 'comment_one').store()
comment_one = Comment(node_one, user_one, 'comment_one').store()

node_two = orm.Data().store()
comment_three = Comment(node_two, self.user, 'comment_three').store()
comment_four = Comment(node_two, self.user, 'comment_four').store()
comment_two = Comment(node_two, user_one, 'comment_two').store()
comment_three = Comment(node_two, user_one, 'comment_three').store()

node_three = orm.CalculationNode().store()
comment_four = Comment(node_three, user_two, 'new_user_comment').store()

node_four = orm.CalculationNode().store()
comment_five = Comment(node_four, user_one, 'user one comment').store()
comment_six = Comment(node_four, user_two, 'user two comment').store()

# Retrieve a node by joining on a specific comment
nodes = orm.QueryBuilder().append(
Comment, tag='comment', filters={
'id': comment_one.id
}).append(
orm.Node, with_comment='comment', project=['uuid']).all()
builder = orm.QueryBuilder()
builder.append(Comment, tag='comment', filters={'id': comment_one.id})
builder.append(orm.Node, with_comment='comment', project=['uuid'])
nodes = builder.all()

self.assertEqual(len(nodes), 1)
for node in nodes:
self.assertIn(str(node[0]), [node_one.uuid])

# Retrieve a comment by joining on a specific node
comments = orm.QueryBuilder().append(
orm.Node, tag='node', filters={
'id': node_two.id
}).append(
Comment, with_node='node', project=['uuid']).all()
builder = orm.QueryBuilder()
builder.append(orm.Node, tag='node', filters={'id': node_two.id})
builder.append(Comment, with_node='node', project=['uuid'])
comments = builder.all()

self.assertEqual(len(comments), 2)
for comment in comments:
self.assertIn(str(comment[0]), [comment_three.uuid, comment_four.uuid])
self.assertIn(str(comment[0]), [comment_two.uuid, comment_three.uuid])

# Retrieve a user by joining on a specific comment
builder = orm.QueryBuilder()
builder.append(Comment, tag='comment', filters={'id': comment_four.id})
builder.append(orm.User, with_comment='comment', project=['email'])
users = builder.all()

self.assertEqual(len(users), 1)
for user in users:
self.assertEqual(str(user[0]), user_two.email)

# Retrieve a comment by joining on a specific user
builder = orm.QueryBuilder()
builder.append(orm.User, tag='user', filters={'email': user_one.email})
builder.append(Comment, with_user='user', project=['uuid'])
comments = builder.all()

self.assertEqual(len(comments), 5)
for comment in comments:
self.assertIn(
str(comment[0]),
[self.comment.uuid, comment_one.uuid, comment_two.uuid, comment_three.uuid, comment_five.uuid])

# Retrieve users from comments of a single node by joining specific node
builder = orm.QueryBuilder()
builder.append(orm.Node, tag='node', filters={'id': node_four.id})
builder.append(Comment, tag='comments', with_node='node', project=['uuid'])
builder.append(orm.User, with_comment='comments', project=['email'])
comments_and_users = builder.all()

self.assertEqual(len(comments_and_users), 2)
for entry in comments_and_users:
self.assertEqual(len(entry), 2)

comment_uuid = str(entry[0])
user_email = str(entry[1])

self.assertIn(comment_uuid, [comment_five.uuid, comment_six.uuid])
self.assertIn(user_email, [user_one.email, user_two.email])

def test_objects_get(self):
"""Test getting a comment from the collection"""
Expand Down
8 changes: 7 additions & 1 deletion aiida/cmdline/commands/cmd_export.py
Expand Up @@ -93,9 +93,14 @@ def inspect(archive, version, data, meta_data):
default=True,
show_default=True,
help='Include or exclude logs for node(s) in export.')
@click.option(
'--include-comments/--exclude-comments',
default=True,
show_default=True,
help='Include or exclude comments for node(s) in export. (Will also export extra users who commented).')
@decorators.with_dbenv()
def create(output_file, codes, computers, groups, nodes, input_forward, create_reversed, return_reversed, call_reversed,
include_logs, force, archive_format):
include_comments, include_logs, force, archive_format):
"""
Export various entities, such as Codes, Computers, Groups and Nodes, to an archive file for backup or
sharing purposes.
Expand Down Expand Up @@ -123,6 +128,7 @@ def create(output_file, codes, computers, groups, nodes, input_forward, create_r
'create_reversed': create_reversed,
'return_reversed': return_reversed,
'call_reversed': call_reversed,
'include_comments': include_comments,
'include_logs': include_logs,
'overwrite': force
}
Expand Down
2 changes: 2 additions & 0 deletions aiida/orm/__init__.py
Expand Up @@ -18,6 +18,7 @@
from .node.data.code import Code

from .authinfos import *
from .comments import *
from .computers import *
from .entities import *
from .groups import *
Expand All @@ -39,6 +40,7 @@

__all__ = (_local +
authinfos.__all__ +
comments.__all__ +
computers.__all__ +
entities.__all__ +
groups.__all__ +
Expand Down

0 comments on commit 4c2f591

Please sign in to comment.