-
Couldn't load subscription status.
- Fork 79
Artifact lineage #1577
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Artifact lineage #1577
Changes from all commits
0b60aab
9184963
61f72dd
f17a3eb
9f7b56d
cf2edcb
faabbd4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,8 +8,11 @@ | |
|
|
||
| from __future__ import division | ||
| from future.utils import viewitems | ||
| from itertools import chain | ||
| from datetime import datetime | ||
|
|
||
| import networkx as nx | ||
|
|
||
| import qiita_db as qdb | ||
|
|
||
|
|
||
|
|
@@ -646,6 +649,65 @@ def parents(self): | |
| return [Artifact(p_id) | ||
| for p_id in qdb.sql_connection.TRN.execute_fetchflatten()] | ||
|
|
||
| def _create_lineage_graph_from_edge_list(self, edge_list): | ||
| """Generates an artifact graph from the given `edge_list` | ||
|
|
||
| Parameters | ||
| ---------- | ||
| edge_list : list of (int, int) | ||
| List of (parent_artifact_id, artifact_id) | ||
|
|
||
| Returns | ||
| ------- | ||
| networkx.DiGraph | ||
| The graph representing the artifact lineage stored in `edge_list` | ||
| """ | ||
| lineage = nx.DiGraph() | ||
| # In case the edge list is empty, only 'self' is present in the graph | ||
| if edge_list: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is for the case when the list is empty, this should probably be set to check that. Right now anything can be passed as the edge_list and it will just return the single node graph. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you elaborate? this is the pythonic way of testing if a list is empty or not. |
||
| # By creating all the artifacts here we are saving DB calls | ||
| nodes = {a_id: Artifact(a_id) | ||
| for a_id in set(chain.from_iterable(edge_list))} | ||
|
|
||
| for parent, child in edge_list: | ||
| lineage.add_edge(nodes[parent], nodes[child]) | ||
| else: | ||
| lineage.add_node(self) | ||
|
|
||
| return lineage | ||
|
|
||
| @property | ||
| def ancestors(self): | ||
| """Returns the ancestors of the artifact | ||
|
|
||
| Returns | ||
| ------- | ||
| networkx.DiGraph | ||
| The ancestors of the artifact | ||
| """ | ||
| with qdb.sql_connection.TRN: | ||
| sql = """SELECT parent_id, artifact_id | ||
| FROM qiita.artifact_ancestry(%s)""" | ||
| qdb.sql_connection.TRN.add(sql, [self.id]) | ||
| edges = qdb.sql_connection.TRN.execute_fetchindex() | ||
| return self._create_lineage_graph_from_edge_list(edges) | ||
|
|
||
| @property | ||
| def descendants(self): | ||
| """Returns the descendants of the artifact | ||
|
|
||
| Returns | ||
| ------- | ||
| networkx.DiGraph | ||
| The descendants of the artifact | ||
| """ | ||
| with qdb.sql_connection.TRN: | ||
| sql = """SELECT parent_id, artifact_id | ||
| FROM qiita.artifact_descendants(%s)""" | ||
| qdb.sql_connection.TRN.add(sql, [self.id]) | ||
| edges = qdb.sql_connection.TRN.execute_fetchindex() | ||
| return self._create_lineage_graph_from_edge_list(edges) | ||
|
|
||
| @property | ||
| def children(self): | ||
| """Returns the list of children of the artifact | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |
| from functools import partial | ||
|
|
||
| import pandas as pd | ||
| import networkx as nx | ||
| from biom import example_table as et | ||
| from biom.util import biom_open | ||
|
|
||
|
|
@@ -496,6 +497,108 @@ def test_parents(self): | |
| exp_parents = [qdb.artifact.Artifact(2)] | ||
| self.assertEqual(qdb.artifact.Artifact(4).parents, exp_parents) | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be good to add a test in for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding |
||
| def test_create_lineage_graph_from_edge_list_empty(self): | ||
| tester = qdb.artifact.Artifact(1) | ||
| obs = tester._create_lineage_graph_from_edge_list([]) | ||
| self.assertTrue(isinstance(obs, nx.DiGraph)) | ||
| self.assertEqual(obs.nodes(), [tester]) | ||
| self.assertEqual(obs.edges(), []) | ||
|
|
||
| def test_create_lineage_graph_from_edge_list(self): | ||
| tester = qdb.artifact.Artifact(1) | ||
| obs = tester._create_lineage_graph_from_edge_list( | ||
| [(1, 2), (2, 4), (1, 3), (3, 4)]) | ||
| self.assertTrue(isinstance(obs, nx.DiGraph)) | ||
| exp = [qdb.artifact.Artifact(1), qdb.artifact.Artifact(2), | ||
| qdb.artifact.Artifact(3), qdb.artifact.Artifact(4)] | ||
| self.assertItemsEqual(obs.nodes(), exp) | ||
| exp = [(qdb.artifact.Artifact(1), qdb.artifact.Artifact(2)), | ||
| (qdb.artifact.Artifact(2), qdb.artifact.Artifact(4)), | ||
| (qdb.artifact.Artifact(1), qdb.artifact.Artifact(3)), | ||
| (qdb.artifact.Artifact(3), qdb.artifact.Artifact(4))] | ||
| self.assertItemsEqual(obs.edges(), exp) | ||
|
|
||
| def test_ancestors(self): | ||
| obs = qdb.artifact.Artifact(1).ancestors | ||
| self.assertTrue(isinstance(obs, nx.DiGraph)) | ||
| obs_nodes = obs.nodes() | ||
| self.assertEqual(obs_nodes, [qdb.artifact.Artifact(1)]) | ||
| obs_edges = obs.edges() | ||
| self.assertEqual(obs_edges, []) | ||
|
|
||
| obs = qdb.artifact.Artifact(2).ancestors | ||
| self.assertTrue(isinstance(obs, nx.DiGraph)) | ||
| obs_nodes = obs.nodes() | ||
| exp_nodes = [qdb.artifact.Artifact(1), qdb.artifact.Artifact(2)] | ||
| self.assertItemsEqual(obs_nodes, exp_nodes) | ||
| obs_edges = obs.edges() | ||
| exp_edges = [(qdb.artifact.Artifact(1), qdb.artifact.Artifact(2))] | ||
| self.assertItemsEqual(obs_edges, exp_edges) | ||
|
|
||
| obs = qdb.artifact.Artifact(3).ancestors | ||
| self.assertTrue(isinstance(obs, nx.DiGraph)) | ||
| obs_nodes = obs.nodes() | ||
| exp_nodes = [qdb.artifact.Artifact(1), qdb.artifact.Artifact(3)] | ||
| self.assertItemsEqual(obs_nodes, exp_nodes) | ||
| obs_edges = obs.edges() | ||
| exp_edges = [(qdb.artifact.Artifact(1), qdb.artifact.Artifact(3))] | ||
| self.assertItemsEqual(obs_edges, exp_edges) | ||
|
|
||
| obs = qdb.artifact.Artifact(4).ancestors | ||
| self.assertTrue(isinstance(obs, nx.DiGraph)) | ||
| obs_nodes = obs.nodes() | ||
| exp_nodes = [qdb.artifact.Artifact(1), qdb.artifact.Artifact(2), | ||
| qdb.artifact.Artifact(4)] | ||
| self.assertItemsEqual(obs_nodes, exp_nodes) | ||
| obs_edges = obs.edges() | ||
| exp_edges = [(qdb.artifact.Artifact(1), qdb.artifact.Artifact(2)), | ||
| (qdb.artifact.Artifact(2), qdb.artifact.Artifact(4))] | ||
| self.assertItemsEqual(obs_edges, exp_edges) | ||
|
|
||
| def test_descendants(self): | ||
| obs = qdb.artifact.Artifact(1).descendants | ||
| self.assertTrue(isinstance(obs, nx.DiGraph)) | ||
| obs_nodes = obs.nodes() | ||
| exp_nodes = [qdb.artifact.Artifact(1), qdb.artifact.Artifact(2), | ||
| qdb.artifact.Artifact(3), qdb.artifact.Artifact(4)] | ||
| self.assertItemsEqual(obs_nodes, exp_nodes) | ||
| obs_edges = obs.edges() | ||
| exp_edges = [(qdb.artifact.Artifact(1), qdb.artifact.Artifact(2)), | ||
| (qdb.artifact.Artifact(1), qdb.artifact.Artifact(3)), | ||
| (qdb.artifact.Artifact(2), qdb.artifact.Artifact(4))] | ||
| self.assertItemsEqual(obs_edges, exp_edges) | ||
|
|
||
| obs = qdb.artifact.Artifact(2).descendants | ||
| self.assertTrue(isinstance(obs, nx.DiGraph)) | ||
| obs_nodes = obs.nodes() | ||
| exp_nodes = [qdb.artifact.Artifact(2), qdb.artifact.Artifact(4)] | ||
| self.assertItemsEqual(obs_nodes, exp_nodes) | ||
| obs_edges = obs.edges() | ||
| exp_edges = [(qdb.artifact.Artifact(2), qdb.artifact.Artifact(4))] | ||
| self.assertItemsEqual(obs_edges, exp_edges) | ||
|
|
||
| obs = qdb.artifact.Artifact(3).descendants | ||
| self.assertTrue(isinstance(obs, nx.DiGraph)) | ||
| obs_nodes = obs.nodes() | ||
| self.assertItemsEqual(obs_nodes, [qdb.artifact.Artifact(3)]) | ||
| obs_edges = obs.edges() | ||
| self.assertItemsEqual(obs_edges, []) | ||
|
|
||
| obs = qdb.artifact.Artifact(4).descendants | ||
| self.assertTrue(isinstance(obs, nx.DiGraph)) | ||
| obs_nodes = obs.nodes() | ||
| self.assertItemsEqual(obs_nodes, [qdb.artifact.Artifact(4)]) | ||
| obs_edges = obs.edges() | ||
| self.assertItemsEqual(obs_edges, []) | ||
|
|
||
| def test_children(self): | ||
| exp = [qdb.artifact.Artifact(2), qdb.artifact.Artifact(3)] | ||
| self.assertEqual(qdb.artifact.Artifact(1).children, exp) | ||
| exp = [qdb.artifact.Artifact(4)] | ||
| self.assertEqual(qdb.artifact.Artifact(2).children, exp) | ||
| self.assertEqual(qdb.artifact.Artifact(3).children, []) | ||
| self.assertEqual(qdb.artifact.Artifact(4).children, []) | ||
|
|
||
| def test_prep_templates(self): | ||
| self.assertEqual( | ||
| qdb.artifact.Artifact(1).prep_templates, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the if statement below, edge_list here should be optional and set to None by default in the function definition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, the if statement is specifically for the case in which the list is empty, but it is not an optional parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, can you add a note to that effect? That was not clear from the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done