From b926cf3876f1301af55935d3f4d471efd51049e9 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Thu, 1 May 2014 16:44:19 -0600 Subject: [PATCH 01/57] inital study create function --- qiita_db/study.py | 73 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index fba4953f0..41125d8a4 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -19,8 +19,18 @@ # The full license is in the file LICENSE, distributed with this software. # ----------------------------------------------------------------------------- +from datetime import date + from .base import QiitaStatusObject -from .exceptions import QiitaDBNotImplementedError +from .exceptions import QiitaDBNotImplementedError, QiitaDBExecutionError +from .sql_connection import SQLConnectionHandler + + +REQUIRED_KEYS = set(("timeseries_type_id", "lab_person_id", "mixs_compliant", + "metadata_complete", "number_samples_collected", + "number_samples_promised", "portal_type", + "principal_investigator_id", "study_title", "study_alias", + "study_description", "study_abstract")) class Study(QiitaStatusObject): @@ -43,15 +53,72 @@ class Study(QiitaStatusObject): """ @staticmethod - def create(owner): + def create(owner, info, investigation=None): """Creates a new study on the database Parameters ---------- owner : str the user id of the study' owner + info: dict + the information attached to the study + investigation: dict + if the study is part of an investigation, the information needed to + create the investigation or add study to investigation + + Raises + ------ + QiitaDBExecutionError + All required keys not passed or non-db columns in info dictionary + + Notes + ----- + If investigation_id passed in investigation database, will assume that + study is part of that investigation. Otherwise need to pass the + following keys in investigation: "name", "description", + "contact_person_id" """ - raise QiitaDBNotImplementedError() + # make sure required keys are in the info dict + if len(REQUIRED_KEYS.difference(set(info))) > 0: + raise QiitaDBExecutionError("Required keys missing: %s" % + REQUIRED_KEYS.difference(set(info))) + + conn_handler = SQLConnectionHandler() + # make sure dictionary only has keys for available columns in db + sql = ("select column_name from information_schema.columns where " + "table_name='study'") + cols = set(conn_handler.fetchone(sql)) + if len(set(info).difference(cols)) > 0: + raise QiitaDBExecutionError("Non-database keys found: %s" % + set(info).difference(cols)) + + # Insert study into database + sql = ("INSERT INTO qiita.study (email,study_status_id,first_contact," + "%s) VALUES (%s) RETURNING study_id" % (','.join(info.keys()), + '%s' * (len(info)+3))) + data = [owner, 1, date.today().strftime("%B %d, %Y")] + # make sure data in same order as sql column names + for col in info.keys(): + data.append(info[col]) + study_id = conn_handler.fetchone(sql, data)[0] + + # Insert investigation information if necessary + if investigation: + if "investigation_id" in investigation: + # investigation already exists + inv_id = investigation["investigation_id"] + else: + # investigation does not exist in db so create it and add study + sql = ("INSERT INTO qiita.investigation(name, description," + "contact_person_id) VALUES (%s,%s,%s) RETURNING " + "investigation_id") + data = (investigation["name"], investigation["description"], + investigation["contact_person_id"]) + inv_id = conn_handler.fetchone(sql, data)[0] + # add study to investigation + sql = ("INSERT INTO qiita.investigation_study (investigation_id, " + "study_id) VALUES (%s, %s)") + conn_handler.execute(sql, (inv_id, study_id)) @staticmethod def delete(id_): From 406e5c129569fa45522fad98e4b76c65221dcd45 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Thu, 1 May 2014 16:57:58 -0600 Subject: [PATCH 02/57] fixed return and SQL --- qiita_db/study.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 41125d8a4..960347223 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -85,9 +85,9 @@ def create(owner, info, investigation=None): conn_handler = SQLConnectionHandler() # make sure dictionary only has keys for available columns in db - sql = ("select column_name from information_schema.columns where " - "table_name='study'") - cols = set(conn_handler.fetchone(sql)) + sql = ("SELECT column_name FROM information_schema.columns WHERE " + "table_name = %s") + cols = set(conn_handler.fetchone(sql, ("study", ))) if len(set(info).difference(cols)) > 0: raise QiitaDBExecutionError("Non-database keys found: %s" % set(info).difference(cols)) @@ -120,6 +120,8 @@ def create(owner, info, investigation=None): "study_id) VALUES (%s, %s)") conn_handler.execute(sql, (inv_id, study_id)) + return Study(study_id) + @staticmethod def delete(id_): """Deletes the study `id_` from the database @@ -134,7 +136,6 @@ def delete(id_): @property def name(self): """Returns the name of the study""" - raise QiitaDBNotImplementedError() @name.setter def name(self, name): From 5acb8c1db532be83a34e262436ae80f738f74362 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 19 May 2014 10:51:53 -0600 Subject: [PATCH 03/57] break dict info checks out to util.py funcs --- qiita_db/study.py | 31 +++++++++++++--------------- qiita_db/util.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 960347223..0dba215f0 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -22,15 +22,16 @@ from datetime import date from .base import QiitaStatusObject +from .util import check_required, check_table_cols from .exceptions import QiitaDBNotImplementedError, QiitaDBExecutionError from .sql_connection import SQLConnectionHandler -REQUIRED_KEYS = set(("timeseries_type_id", "lab_person_id", "mixs_compliant", - "metadata_complete", "number_samples_collected", - "number_samples_promised", "portal_type", - "principal_investigator_id", "study_title", "study_alias", - "study_description", "study_abstract")) +REQUIRED_KEYS = {"timeseries_type_id", "lab_person_id", "mixs_compliant", + "metadata_complete", "number_samples_collected", + "number_samples_promised", "portal_type", + "principal_investigator_id", "study_title", "study_alias", + "study_description", "study_abstract"} class Study(QiitaStatusObject): @@ -79,28 +80,21 @@ def create(owner, info, investigation=None): "contact_person_id" """ # make sure required keys are in the info dict - if len(REQUIRED_KEYS.difference(set(info))) > 0: - raise QiitaDBExecutionError("Required keys missing: %s" % - REQUIRED_KEYS.difference(set(info))) + check_required(info, REQUIRED_KEYS) conn_handler = SQLConnectionHandler() # make sure dictionary only has keys for available columns in db - sql = ("SELECT column_name FROM information_schema.columns WHERE " - "table_name = %s") - cols = set(conn_handler.fetchone(sql, ("study", ))) - if len(set(info).difference(cols)) > 0: - raise QiitaDBExecutionError("Non-database keys found: %s" % - set(info).difference(cols)) + check_table_cols(conn_handler, info, "study") # Insert study into database sql = ("INSERT INTO qiita.study (email,study_status_id,first_contact," "%s) VALUES (%s) RETURNING study_id" % (','.join(info.keys()), '%s' * (len(info)+3))) - data = [owner, 1, date.today().strftime("%B %d, %Y")] # make sure data in same order as sql column names + data = [owner, 1, date.today().strftime("%B %d, %Y")] for col in info.keys(): data.append(info[col]) - study_id = conn_handler.fetchone(sql, data)[0] + study_id = conn_handler.execute_fetchone(sql, data)[0] # Insert investigation information if necessary if investigation: @@ -114,7 +108,7 @@ def create(owner, info, investigation=None): "investigation_id") data = (investigation["name"], investigation["description"], investigation["contact_person_id"]) - inv_id = conn_handler.fetchone(sql, data)[0] + inv_id = conn_handler.execute_fetchone(sql, data)[0] # add study to investigation sql = ("INSERT INTO qiita.investigation_study (investigation_id, " "study_id) VALUES (%s, %s)") @@ -136,6 +130,9 @@ def delete(id_): @property def name(self): """Returns the name of the study""" + conn_handler = SQLConnectionHandler() + sql = "SELECT name FROM qiita.study WHERE study_id = %s" + return conn_handler.execute_fetchone(sql, self.id_)[0] @name.setter def name(self, name): diff --git a/qiita_db/util.py b/qiita_db/util.py index e24ba3e2e..ede53b71e 100644 --- a/qiita_db/util.py +++ b/qiita_db/util.py @@ -1,6 +1,8 @@ #!/usr/bin/env python from __future__ import division +from .exceptions import QiitaDBExecutionError + # ----------------------------------------------------------------------------- # Copyright (c) 2014--, The Qiita Development Team. # @@ -48,3 +50,52 @@ def scrub_data(s): ret = s.replace("'", "") ret = ret.replace(";", "") return ret + + +def check_required(keys, required): + """Makes sure all required columns are in a list + + Parameters + ---------- + keys: iterable + list, set, or other iterable holding the keys in the dictionary + required: set + set of column names required for a table + + Raises + ------ + QiitaDBExecutionError + If not all required keys are in keys + """ + if not isinstance(required, set): + raise ValueError("required keys list must be set type object") + if len(required.difference(set(keys))) > 0: + raise QiitaDBExecutionError("Required keys missing: %s" % + required.difference(set(keys))) + + +def check_table_cols(conn_handler, keys, table): + """Makes sure all keys correspond to coumn headers in a table + + Parameters + ---------- + conn_handler: SQLConnectionHandler object + Previously opened conection to the database + keys: iterable + list, set, or other iterable holding the keys in the dictionary + table: str + name of the table to check column names + + Raises + ------ + QiitaDBExecutionError + If keys exist that are not in the table + """ + sql = ("SELECT column_name FROM information_schema.columns WHERE " + "table_name = %s") + cols = conn_handler.execute_fetchone(sql, (table, )) + if len(cols) == 0: + raise RuntimeError("Unable to fetch column names for table %s" % table) + if len(set(keys).difference(cols)) > 0: + raise QiitaDBExecutionError("Non-database keys found: %s" % + set(keys).difference(cols)) From e1f0d27a724311aef9cf324f1ef8f04ccfd1881f Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 19 May 2014 11:12:14 -0600 Subject: [PATCH 04/57] initial fleshout of properties --- qiita_db/study.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 0dba215f0..25c446ee9 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -80,7 +80,7 @@ def create(owner, info, investigation=None): "contact_person_id" """ # make sure required keys are in the info dict - check_required(info, REQUIRED_KEYS) + check_required(info, REQUIRED_KEYS) conn_handler = SQLConnectionHandler() # make sure dictionary only has keys for available columns in db @@ -143,7 +143,9 @@ def name(self, name): name : str The new study name """ - raise QiitaDBNotImplementedError() + conn_handler = SQLConnectionHandler() + sql = "UPDATE qiita.study SET name = %s WHERE study_id = %s" + return conn_handler.execute(sql, (name, self.id_)) @property def sample_ids(self): @@ -151,12 +153,17 @@ def sample_ids(self): The sample IDs are returned as a list of strings in alphabetical order. """ - raise QiitaDBNotImplementedError() + conn_handler = SQLConnectionHandler() + sql = ("SELECT sample_id FROM qiita.required_sample_info WHERE " + "study_id = %s ORDER BY sample_id") + return conn_handler.execute_fetchone(sql, self.id_) @property def info(self): """Dict with any other information attached to the study""" - raise QiitaDBNotImplementedError() + conn_handler = SQLConnectionHandler() + sql = "SELECT * FROM qiita.study WHERE study_id = %s" + return conn_handler.execute_fetchone(sql, self.id_) @info.setter def info(self, info): @@ -166,7 +173,20 @@ def info(self, info): ---------- info : dict """ - raise QiitaDBNotImplementedError() + conn_handler = SQLConnectionHandler() + check_table_cols(conn_handler, info, "study") + + data = [] + sql = "UPDATE qiita.study SET " + # items() used for py3 compatability + # build query with data values in correct order for SQL statement + for key, val in info.items(): + sql = ' '.join((sql, key, "=", "%s,")) + data.append(val) + sql = ' '.join((sql[-1], "WHERE study_id = %s")) + data.append(self.id_) + + conn_handler.execute(sql, data) def add_samples(self, samples): """Adds the samples listed in `samples` to the study From 14430680ae063e1969807e6fffe82ceca8b2a017 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 21 May 2014 13:10:21 -0600 Subject: [PATCH 05/57] updating populate_test_db and teardown_qiita_schema --- qiita_db/test/test_study.py | 29 +++++++++++++++++++ qiita_db/util.py | 58 +++++++++++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 qiita_db/test/test_study.py diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py new file mode 100644 index 000000000..4a2b68ff5 --- /dev/null +++ b/qiita_db/test/test_study.py @@ -0,0 +1,29 @@ +from unittest import TestCase, main + +from ..qiita_db.study import Study +from ..exceptions import QiitaDBExecutionError, QiitaDBConnectionError +from ..sql_connection import SQLConnectionHandler + + +# ----------------------------------------------------------------------------- +# Copyright (c) 2014--, The Qiita Development Team. +# +# Distributed under the terms of the BSD 3-clause License. +# +# The full license is in the file LICENSE, distributed with this software. +# ----------------------------------------------------------------------------- + +#ASSUMING EMPTY DATABASE ALREADY MADE +class TestStudy(TestCase): + def SetUp(self): + conn = SQLConnectionHandler() + populate_test_db(conn) + + def TearDown(self): + pass + + def test_create_study(): + + +if __name__ == "__main__": + main() diff --git a/qiita_db/util.py b/qiita_db/util.py index ede53b71e..b824f294e 100644 --- a/qiita_db/util.py +++ b/qiita_db/util.py @@ -1,5 +1,6 @@ #!/usr/bin/env python from __future__ import division +from os.path import abspath, dirname, join from .exceptions import QiitaDBExecutionError @@ -70,7 +71,7 @@ def check_required(keys, required): if not isinstance(required, set): raise ValueError("required keys list must be set type object") if len(required.difference(set(keys))) > 0: - raise QiitaDBExecutionError("Required keys missing: %s" % + raise RuntimeError("Required keys missing: %s" % required.difference(set(keys))) @@ -92,10 +93,63 @@ def check_table_cols(conn_handler, keys, table): If keys exist that are not in the table """ sql = ("SELECT column_name FROM information_schema.columns WHERE " - "table_name = %s") + "table_name = %s") cols = conn_handler.execute_fetchone(sql, (table, )) if len(cols) == 0: raise RuntimeError("Unable to fetch column names for table %s" % table) if len(set(keys).difference(cols)) > 0: raise QiitaDBExecutionError("Non-database keys found: %s" % set(keys).difference(cols)) + + + +def populate_test_db(conn_handler, schemapath=None, initpath=None, + testdatapath=None): + """Populates the test database using the file initialzie_test.sql + + Parameters + ---------- + conn_handler: SQLConnectionHandler object + Previously opened conection to the database + schemapath: str, optional + Path to the test database schema sql file (default qiita.sql) + testdatapath: str, optional + Path to the test database setup sql file (default initialize.sql) + testdatapath: str, optional + Path to the test database data sql file (default initialize_test.sql) + """ + # make sure we are on test database + if not conn_handler.execute_fetchone("SELECT test FROM settings")[0]: + raise IOError("Trying to test using non-test database!") + + if testdatapath is None: + path = dirname(abspath(__file__)) + testdatapath = join((path, "setup/initialize_test.sql")) + if initpath is None: + path = dirname(abspath(__file__)) + initpath = join((path, "setup/initialize.sql")) + if schemapath is None: + path = dirname(abspath(__file__)) + schemapath = join((path, "setup/qiita.sql")) + # build schema, then populate it + with open(schemapath) as fin: + conn_handler.execute(fin.read()) + with open(initpath) as fin: + conn_handler.execute(fin.read()) + with open(testdatapath) as fin: + conn_handler.execute(fin.read()) + + + +def teardown_qiita_schema(conn_handler): + """removes qiita schema from test database + + Parameters + ---------- + conn_handler: SQLConnectionHandler object + Previously opened conection to the database + """ + # make sure we are on test database + if not conn_handler.execute_fetchone("SELECT test FROM settings")[0]: + raise IOError("Trying to test using non-test database!") + conn_handler.execute("DROP SCHEMA qiita CASCADE") From 62bfcd8c8027e91a70b82846387cd6b370028fbe Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 21 May 2014 13:11:10 -0600 Subject: [PATCH 06/57] pep8 cleanup --- qiita_db/util.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qiita_db/util.py b/qiita_db/util.py index b824f294e..ff1c10125 100644 --- a/qiita_db/util.py +++ b/qiita_db/util.py @@ -72,7 +72,7 @@ def check_required(keys, required): raise ValueError("required keys list must be set type object") if len(required.difference(set(keys))) > 0: raise RuntimeError("Required keys missing: %s" % - required.difference(set(keys))) + required.difference(set(keys))) def check_table_cols(conn_handler, keys, table): @@ -102,7 +102,6 @@ def check_table_cols(conn_handler, keys, table): set(keys).difference(cols)) - def populate_test_db(conn_handler, schemapath=None, initpath=None, testdatapath=None): """Populates the test database using the file initialzie_test.sql @@ -140,7 +139,6 @@ def populate_test_db(conn_handler, schemapath=None, initpath=None, conn_handler.execute(fin.read()) - def teardown_qiita_schema(conn_handler): """removes qiita schema from test database From bea579c5f97bb2a74fdd418a667ce6347a67cffb Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 21 May 2014 13:59:41 -0600 Subject: [PATCH 07/57] added clean_sql-results --- qiita_db/util.py | 66 +++++++++++------------------------------------- 1 file changed, 15 insertions(+), 51 deletions(-) diff --git a/qiita_db/util.py b/qiita_db/util.py index ff1c10125..852fa22c4 100644 --- a/qiita_db/util.py +++ b/qiita_db/util.py @@ -52,6 +52,21 @@ def scrub_data(s): ret = ret.replace(";", "") return ret +def clean_sql_result(results): + """ removes single value list of lists from psycopg2 and returns list of + items + + Parameters + ---------- + results: list of lists + list from psycopg2 in the form [[item1], [item2], [item3], ...] + + Returns + ------- + list: [item1, item2, item3, ...] + """ + return [i[0] for i in results] + def check_required(keys, required): """Makes sure all required columns are in a list @@ -100,54 +115,3 @@ def check_table_cols(conn_handler, keys, table): if len(set(keys).difference(cols)) > 0: raise QiitaDBExecutionError("Non-database keys found: %s" % set(keys).difference(cols)) - - -def populate_test_db(conn_handler, schemapath=None, initpath=None, - testdatapath=None): - """Populates the test database using the file initialzie_test.sql - - Parameters - ---------- - conn_handler: SQLConnectionHandler object - Previously opened conection to the database - schemapath: str, optional - Path to the test database schema sql file (default qiita.sql) - testdatapath: str, optional - Path to the test database setup sql file (default initialize.sql) - testdatapath: str, optional - Path to the test database data sql file (default initialize_test.sql) - """ - # make sure we are on test database - if not conn_handler.execute_fetchone("SELECT test FROM settings")[0]: - raise IOError("Trying to test using non-test database!") - - if testdatapath is None: - path = dirname(abspath(__file__)) - testdatapath = join((path, "setup/initialize_test.sql")) - if initpath is None: - path = dirname(abspath(__file__)) - initpath = join((path, "setup/initialize.sql")) - if schemapath is None: - path = dirname(abspath(__file__)) - schemapath = join((path, "setup/qiita.sql")) - # build schema, then populate it - with open(schemapath) as fin: - conn_handler.execute(fin.read()) - with open(initpath) as fin: - conn_handler.execute(fin.read()) - with open(testdatapath) as fin: - conn_handler.execute(fin.read()) - - -def teardown_qiita_schema(conn_handler): - """removes qiita schema from test database - - Parameters - ---------- - conn_handler: SQLConnectionHandler object - Previously opened conection to the database - """ - # make sure we are on test database - if not conn_handler.execute_fetchone("SELECT test FROM settings")[0]: - raise IOError("Trying to test using non-test database!") - conn_handler.execute("DROP SCHEMA qiita CASCADE") From 34b5a963e5efdfeeae2392ead6ff12ce337a0839 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 21 May 2014 14:18:28 -0600 Subject: [PATCH 08/57] fleshing out the study object --- qiita_db/study.py | 144 ++++++++++++++++++++++++++++-------- qiita_db/test/test_study.py | 9 ++- 2 files changed, 122 insertions(+), 31 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 25c446ee9..d3d61b2fe 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -22,8 +22,8 @@ from datetime import date from .base import QiitaStatusObject -from .util import check_required, check_table_cols -from .exceptions import QiitaDBNotImplementedError, QiitaDBExecutionError +from .util import check_required, check_table_cols, clean_sql_result +from .exceptions import QiitaDBNotImplementedError from .sql_connection import SQLConnectionHandler @@ -41,16 +41,27 @@ class Study(QiitaStatusObject): Attributes ---------- name - sample_ids info + status + sample_ids + shared_with + pmids + investigations + metadata + raw_data + preprocessed_data + processed_data Methods ------- - add_samples(samples) - Adds the samples listed in `samples` to the study + share_with(email) + Shares the study with given user - remove_samples(samples) - Removes the samples listed in `samples` from the study + def add_raw_data(raw_data_id): + Associates raw data with the study + + add_pmid(self, pmid): + Adds PMID to study """ @staticmethod @@ -127,6 +138,7 @@ def delete(id_): """ raise QiitaDBNotImplementedError() +# --- Attributes --- @property def name(self): """Returns the name of the study""" @@ -147,23 +159,12 @@ def name(self, name): sql = "UPDATE qiita.study SET name = %s WHERE study_id = %s" return conn_handler.execute(sql, (name, self.id_)) - @property - def sample_ids(self): - """Returns the IDs of all samples in study - - The sample IDs are returned as a list of strings in alphabetical order. - """ - conn_handler = SQLConnectionHandler() - sql = ("SELECT sample_id FROM qiita.required_sample_info WHERE " - "study_id = %s ORDER BY sample_id") - return conn_handler.execute_fetchone(sql, self.id_) - @property def info(self): """Dict with any other information attached to the study""" conn_handler = SQLConnectionHandler() sql = "SELECT * FROM qiita.study WHERE study_id = %s" - return conn_handler.execute_fetchone(sql, self.id_) + return dict(conn_handler.execute_fetchone(sql, self.id_)) @info.setter def info(self, info): @@ -188,20 +189,105 @@ def info(self, info): conn_handler.execute(sql, data) - def add_samples(self, samples): - """Adds the samples listed in `samples` to the study + @property + def status(self): + """Returns the study_status_id for the study""" + conn_handler = SQLConnectionHandler() + sql = "SELECT study_status_id FROM qiita.study WHERE study_id = %s " + return conn_handler.execute_fetchone(sql, self.id_)[0] + + @property + def sample_ids(self): + """Returns the IDs of all samples in study + + The sample IDs are returned as a list of strings in alphabetical order. + """ + conn_handler = SQLConnectionHandler() + sql = ("SELECT sample_id FROM qiita.required_sample_info WHERE " + "study_id = %s ORDER BY sample_id") + return conn_handler.execute_fetchone(sql, self.id_) + + @property + def shared_with(self): + """list of users the study is shared with""" + conn_handler = SQLConnectionHandler() + sql = "SELECT * FROM qiita.study_users WHERE study_id = %s" + return list(conn_handler.execute_fetchone(sql, self.id_)) + + @property + def pmids(self): + """ Returns list of paper PMIDs from this study """ + conn_handler = SQLConnectionHandler() + sql = "SELECT pmid FROM qiita.study_pmid WHERE study_id = %s" + return clean_sql_result(conn_handler.execute_fetchall(sql, self.id_)) + + @property + def investigations(self): + """ Returns list of investigation_id this study is part of """ + conn_handler = SQLConnectionHandler() + sql = ("SELECT investigation_id FROM qiita.investigation_study WHERE " + "study_id = %s") + return clean_sql_result(conn_handler.execute_fetchall(sql, self.id_)) + + @property + def metadata(self): + """ Returns list of metadata columns """ + conn_handler = SQLConnectionHandler() + sql = ("SELECT column_name FROM qiita.study_sample_columns WHERE " + "study_id = %s") + return clean_sql_result(conn_handler.execute_fetchall(sql, self.id_)) + + @property + def raw_data(self): + """ Returns list of data objects with raw data info """ + raise NotImplementedError(Study.raw_data) + + @property + def preprocessed_data(self): + """ Returns list of data objects with preprocessed data info """ + raise NotImplementedError(Study.preprocessed_data) + + + @property + def processed_data(self): + """ Returns list of data objects with processed data info """ + raise NotImplementedError(Study.processed_data) + +# --- methods --- + def share_with(self, email): + """Shares the study with given user + Parameters ---------- - samples : list of strings - The sample Ids to be added to the study + email: str + email of the user to share with """ - raise QiitaDBNotImplementedError() + conn_handler = SQLConnectionHandler() + sql = ("INSERT INTO qiita.study_users (study_id, email) VALUES " + "(%s, %s)") + conn_handler.execute_fetchone(sql, (self.id_, email)) + + def add_raw_data(self, raw_data_id): + """Associates raw data with the study - def remove_samples(self, samples): - """Removes the samples listed in `samples` from the study Parameters ---------- - samples : list of strings - The sample Ids to be removed from the study + raw_data_id: int + ID of the raw data to associate with study """ - raise QiitaDBNotImplementedError() + conn_handler = SQLConnectionHandler() + sql = ("INSERT INTO qiita.study_raw_data (study_id, raw_data_id) " + "VALUES (%s, %s)") + conn_handler.execute_fetchone(sql, (self.id_, raw_data_id)) + + def add_pmid(self, pmid): + """Adds PMID to study + + Parameters + ---------- + pmid: int + pmid to associate with study + """ + conn_handler = SQLConnectionHandler() + sql = "INSERT INTO qiita.study_pmid (study_id, pmid) VALUES (%s, %s)" + conn_handler.execute_fetchone(sql, (self.id_, pmid)) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 4a2b68ff5..a04166dba 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -1,6 +1,7 @@ from unittest import TestCase, main from ..qiita_db.study import Study +from qiita_db.util import populate_test_db, teardown_qiita_schema from ..exceptions import QiitaDBExecutionError, QiitaDBConnectionError from ..sql_connection import SQLConnectionHandler @@ -13,16 +14,20 @@ # The full license is in the file LICENSE, distributed with this software. # ----------------------------------------------------------------------------- -#ASSUMING EMPTY DATABASE ALREADY MADE + +# ALL TESTS ASSUME EMPTY qiita DATABASE EXISTS class TestStudy(TestCase): def SetUp(self): conn = SQLConnectionHandler() populate_test_db(conn) def TearDown(self): - pass + conn = SQLConnectionHandler() + teardown_qiita_schema(conn) def test_create_study(): + """Insert a study into the database""" + Study.create('qiita@foo.bar', ) if __name__ == "__main__": From 04acc849b82a36040706931cc822cfd09546cacc Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 21 May 2014 14:43:02 -0600 Subject: [PATCH 09/57] docstrings --- qiita_db/study.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index d3d61b2fe..63e9a3669 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -40,17 +40,25 @@ class Study(QiitaStatusObject): Attributes ---------- - name - info - status - sample_ids - shared_with - pmids - investigations - metadata - raw_data - preprocessed_data - processed_data + name: str + name of the study + info: dict + Major information about the study, keyed by db column name + status: int + Status of the study + sample_ids: list of str + All sample_ids associated with the study + shared_with: list of str + Emails of users the study is shared with + pmids: list of str + PMIDs assiciated with the study + investigations: list of int + Investigation ids of all investigations study is part of + metadata: list of str + Metadata column names available from study + raw_data: list of data objects + preprocessed_data: list of data objects + processed_data: list of data objects Methods ------- From d53b091de5d01de6acb383ad549986850b2b068c Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 21 May 2014 15:29:34 -0600 Subject: [PATCH 10/57] fleshing out tests --- qiita_db/study.py | 34 ++++++++---------- qiita_db/test/test_study.py | 71 +++++++++++++++++++++++++++++++++++-- qiita_db/util.py | 5 +-- 3 files changed, 86 insertions(+), 24 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 63e9a3669..dacdc0805 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -73,7 +73,7 @@ def add_raw_data(raw_data_id): """ @staticmethod - def create(owner, info, investigation=None): + def create(owner, info, investigation_id=None): """Creates a new study on the database Parameters @@ -82,9 +82,8 @@ def create(owner, info, investigation=None): the user id of the study' owner info: dict the information attached to the study - investigation: dict - if the study is part of an investigation, the information needed to - create the investigation or add study to investigation + investigation_id: int + if the study is part of an investigation, the id to associate with Raises ------ @@ -114,24 +113,12 @@ def create(owner, info, investigation=None): for col in info.keys(): data.append(info[col]) study_id = conn_handler.execute_fetchone(sql, data)[0] - - # Insert investigation information if necessary - if investigation: - if "investigation_id" in investigation: - # investigation already exists - inv_id = investigation["investigation_id"] - else: - # investigation does not exist in db so create it and add study - sql = ("INSERT INTO qiita.investigation(name, description," - "contact_person_id) VALUES (%s,%s,%s) RETURNING " - "investigation_id") - data = (investigation["name"], investigation["description"], - investigation["contact_person_id"]) - inv_id = conn_handler.execute_fetchone(sql, data)[0] - # add study to investigation + + # add study to investigation if necessary + if investigation_id: sql = ("INSERT INTO qiita.investigation_study (investigation_id, " "study_id) VALUES (%s, %s)") - conn_handler.execute(sql, (inv_id, study_id)) + conn_handler.execute(sql, (investigation_id, study_id)) return Study(study_id) @@ -204,6 +191,13 @@ def status(self): sql = "SELECT study_status_id FROM qiita.study WHERE study_id = %s " return conn_handler.execute_fetchone(sql, self.id_)[0] + @status.setter + def status(self, status_id): + """Sets the study_status_id for the study""" + conn_handler = SQLConnectionHandler() + sql = "UPDATE qiita.study SET study_status_id = %s WHERE study_id = %s" + return conn_handler.execute_fetchone(sql, (status_id, self.id_))[0] + @property def sample_ids(self): """Returns the IDs of all samples in study diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index a04166dba..abdfcec24 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -1,6 +1,6 @@ from unittest import TestCase, main -from ..qiita_db.study import Study +from ..study import Study from qiita_db.util import populate_test_db, teardown_qiita_schema from ..exceptions import QiitaDBExecutionError, QiitaDBConnectionError from ..sql_connection import SQLConnectionHandler @@ -21,13 +21,80 @@ def SetUp(self): conn = SQLConnectionHandler() populate_test_db(conn) + self.info = { + + } + def TearDown(self): conn = SQLConnectionHandler() teardown_qiita_schema(conn) def test_create_study(): """Insert a study into the database""" - Study.create('qiita@foo.bar', ) + Study.create('test@foo.bar', self.info) + + def test_create_study_with_investigation(self): + """Insert a study into the database""" + Study.create('test@foo.bar', self.info, 0) + + def test_insert_missing_requred(self): + """ Insert a study that is missing a required info key""" + raise NotImplementedError() + + def test_insert_unknown_db_col(self): + """ Insert a study with an info key not in the database""" + raise NotImplementedError() + + def test_retrieve_name(self): + raise NotImplementedError() + + def test_set_name(self): + raise NotImplementedError() + + def test_retrieve_info(self): + raise NotImplementedError() + + def test_set_info(self): + raise NotImplementedError() + + def test_retrieve_status(self): + raise NotImplementedError() + + def test_set_status(self): + raise NotImplementedError() + + def test_retrieve_sample_ids(self): + raise NotImplementedError() + + def test_retrieve_shared_with(self): + raise NotImplementedError() + + def test_retrieve_pmids(self): + raise NotImplementedError() + + def test_retrieve_investigations(self): + raise NotImplementedError() + + def test_retrieve_metadata(self): + raise NotImplementedError() + + def test_retrieve_raw_data(self): + raise NotImplementedError() + + def test_retrieve_preprocessed_data(self): + raise NotImplementedError() + + def test_retrieve_processed_data(self): + raise NotImplementedError() + + def test_share_with(self): + raise NotImplementedError() + + def test_add_raw_data(self): + raise NotImplementedError() + + def test_add_pmid(self): + raise NotImplementedError() if __name__ == "__main__": diff --git a/qiita_db/util.py b/qiita_db/util.py index 852fa22c4..95dc544ef 100644 --- a/qiita_db/util.py +++ b/qiita_db/util.py @@ -52,14 +52,15 @@ def scrub_data(s): ret = ret.replace(";", "") return ret + def clean_sql_result(results): - """ removes single value list of lists from psycopg2 and returns list of + """Parses single value list of lists from psycopg2 and returns list of items Parameters ---------- results: list of lists - list from psycopg2 in the form [[item1], [item2], [item3], ...] + list in the form [[item1], [item2], [item3], ...] Returns ------- From 8507bb19a1bb4b2f4a7e0ef36d8640f008a006ea Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 21 May 2014 15:49:19 -0600 Subject: [PATCH 11/57] add study test info --- qiita_db/study.py | 1 - qiita_db/test/test_study.py | 20 ++++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index dacdc0805..d71937ab2 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -249,7 +249,6 @@ def preprocessed_data(self): """ Returns list of data objects with preprocessed data info """ raise NotImplementedError(Study.preprocessed_data) - @property def processed_data(self): """ Returns list of data objects with processed data info """ diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index abdfcec24..692b276e9 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -22,14 +22,30 @@ def SetUp(self): populate_test_db(conn) self.info = { - + "name": "Chickens", + "emp_person_id": 2, + "first_contact": "2014-05-21", + "timeseries_type_id": 0, + "lab_person_id": 0, + "metadata_complete": True, + "mixs_compliant": True, + "number_samples_collected": 25, + "number_samples_promised": 28, + "portal_type_id": 3, + "principal_investigator_id": 3, + "study_title": "Fried chicken microbiome", + "study_alias": "FCM", + "study_description": ("Microbiome of people who eat nothing but " + "fried chicken"), + "study_abstract": ("We wanted to see if we could get funding for " + "giving people heart attacks") } def TearDown(self): conn = SQLConnectionHandler() teardown_qiita_schema(conn) - def test_create_study(): + def test_create_study(self): """Insert a study into the database""" Study.create('test@foo.bar', self.info) From cbf85097d006406c59811143f4c6085cf6257907 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 21 May 2014 15:57:14 -0600 Subject: [PATCH 12/57] study test changes --- qiita_db/test/test_study.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 692b276e9..d02f19931 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -1,9 +1,9 @@ from unittest import TestCase, main -from ..study import Study +from qiita_db.study import Study from qiita_db.util import populate_test_db, teardown_qiita_schema -from ..exceptions import QiitaDBExecutionError, QiitaDBConnectionError -from ..sql_connection import SQLConnectionHandler +from qiita_db.exceptions import QiitaDBExecutionError, QiitaDBConnectionError +from qiita_db.sql_connection import SQLConnectionHandler # ----------------------------------------------------------------------------- @@ -17,7 +17,7 @@ # ALL TESTS ASSUME EMPTY qiita DATABASE EXISTS class TestStudy(TestCase): - def SetUp(self): + def setUp(self): conn = SQLConnectionHandler() populate_test_db(conn) @@ -41,7 +41,7 @@ def SetUp(self): "giving people heart attacks") } - def TearDown(self): + def tearDown(self): conn = SQLConnectionHandler() teardown_qiita_schema(conn) From cb606d50f178bd728ab971f73d453c01babfc49f Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Fri, 23 May 2014 11:13:16 -0600 Subject: [PATCH 13/57] added efo addition for study --- qiita_db/study.py | 16 +++++++++++++++- qiita_db/test/test_study.py | 10 ++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index d71937ab2..30db62a03 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -100,6 +100,14 @@ def create(owner, info, investigation_id=None): # make sure required keys are in the info dict check_required(info, REQUIRED_KEYS) + # Save study_experimental_factor data for insertion + efo = None + if "study_experimental_factor" in info: + efo = info["study_experimental_factor"] + if not isinstance(efo, list) or not isinstance(efo, tuple): + efo = [efo] + info.pop("study_experimental_factor") + conn_handler = SQLConnectionHandler() # make sure dictionary only has keys for available columns in db check_table_cols(conn_handler, info, "study") @@ -113,7 +121,13 @@ def create(owner, info, investigation_id=None): for col in info.keys(): data.append(info[col]) study_id = conn_handler.execute_fetchone(sql, data)[0] - + + if efo: + # insert efo information into database + sql = ("INSERT INTO forge.study_experimental_factor (study_id, " + "efo_id) VALUES (%s, %s)") + conn_handler.executemany(sql, zip([study_id] * len(efo), efo)) + # add study to investigation if necessary if investigation_id: sql = ("INSERT INTO qiita.investigation_study (investigation_id, " diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index d02f19931..0e4d127a1 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -1,7 +1,7 @@ from unittest import TestCase, main from qiita_db.study import Study -from qiita_db.util import populate_test_db, teardown_qiita_schema +from qiita_core.util import qiita_test_checker from qiita_db.exceptions import QiitaDBExecutionError, QiitaDBConnectionError from qiita_db.sql_connection import SQLConnectionHandler @@ -15,11 +15,9 @@ # ----------------------------------------------------------------------------- -# ALL TESTS ASSUME EMPTY qiita DATABASE EXISTS +@qiita_test_checker() class TestStudy(TestCase): def setUp(self): - conn = SQLConnectionHandler() - populate_test_db(conn) self.info = { "name": "Chickens", @@ -41,10 +39,6 @@ def setUp(self): "giving people heart attacks") } - def tearDown(self): - conn = SQLConnectionHandler() - teardown_qiita_schema(conn) - def test_create_study(self): """Insert a study into the database""" Study.create('test@foo.bar', self.info) From 84163efb81078890e76c6dfed4cd3d5476bbc961 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Fri, 23 May 2014 12:35:33 -0600 Subject: [PATCH 14/57] fix check_table_cols function --- qiita_db/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiita_db/util.py b/qiita_db/util.py index 88dff13ab..e0b74b1c9 100644 --- a/qiita_db/util.py +++ b/qiita_db/util.py @@ -110,7 +110,7 @@ def check_table_cols(conn_handler, keys, table): """ sql = ("SELECT column_name FROM information_schema.columns WHERE " "table_name = %s") - cols = conn_handler.execute_fetchone(sql, (table, )) + cols = clean_sql_result(conn_handler.execute_fetchall(sql, (table, ))) if len(cols) == 0: raise RuntimeError("Unable to fetch column names for table %s" % table) if len(set(keys).difference(cols)) > 0: From bc187583e214c3cf96730700087b986cd4f3aba5 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Fri, 23 May 2014 13:30:02 -0600 Subject: [PATCH 15/57] study insert now works --- qiita_db/sql_connection.py | 7 ++++++- qiita_db/study.py | 12 ++++++------ qiita_db/test/test_study.py | 13 ++++++++----- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/qiita_db/sql_connection.py b/qiita_db/sql_connection.py index c178ddd87..e5cd3e1df 100644 --- a/qiita_db/sql_connection.py +++ b/qiita_db/sql_connection.py @@ -98,7 +98,12 @@ def _sql_executor(self, sql, sql_args=None, many=False): self._connection.commit() except PostgresError, e: self._connection.rollback() - raise QiitaDBExecutionError("Error running SQL query: %s", e) + try: + err_sql = cur.mogrify(sql, sql_args) + except: + err_sql = sql + raise QiitaDBExecutionError(("\nError running SQL query: %s" + "\nError: %s" % (err_sql, e))) def execute_fetchall(self, sql, sql_args=None): """ Executes a fetchall SQL query diff --git a/qiita_db/study.py b/qiita_db/study.py index 30db62a03..37c9f9b93 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -29,7 +29,7 @@ REQUIRED_KEYS = {"timeseries_type_id", "lab_person_id", "mixs_compliant", "metadata_complete", "number_samples_collected", - "number_samples_promised", "portal_type", + "number_samples_promised", "portal_type_id", "principal_investigator_id", "study_title", "study_alias", "study_description", "study_abstract"} @@ -104,7 +104,7 @@ def create(owner, info, investigation_id=None): efo = None if "study_experimental_factor" in info: efo = info["study_experimental_factor"] - if not isinstance(efo, list) or not isinstance(efo, tuple): + if not isinstance(efo, list) and not isinstance(efo, tuple): efo = [efo] info.pop("study_experimental_factor") @@ -114,17 +114,17 @@ def create(owner, info, investigation_id=None): # Insert study into database sql = ("INSERT INTO qiita.study (email,study_status_id,first_contact," - "%s) VALUES (%s) RETURNING study_id" % (','.join(info.keys()), - '%s' * (len(info)+3))) + "reprocess, %s) VALUES (%s) RETURNING study_id" % + (','.join(info.keys()), ','.join(['%s'] * (len(info)+4)))) # make sure data in same order as sql column names - data = [owner, 1, date.today().strftime("%B %d, %Y")] + data = [owner, 1, date.today().strftime("%B %d, %Y"), 'FALSE'] for col in info.keys(): data.append(info[col]) study_id = conn_handler.execute_fetchone(sql, data)[0] if efo: # insert efo information into database - sql = ("INSERT INTO forge.study_experimental_factor (study_id, " + sql = ("INSERT INTO qiita.study_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)") conn_handler.executemany(sql, zip([study_id] * len(efo), efo)) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 0e4d127a1..75d24f443 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -20,11 +20,9 @@ class TestStudy(TestCase): def setUp(self): self.info = { - "name": "Chickens", "emp_person_id": 2, - "first_contact": "2014-05-21", - "timeseries_type_id": 0, - "lab_person_id": 0, + "timeseries_type_id": 1, + "lab_person_id": 1, "metadata_complete": True, "mixs_compliant": True, "number_samples_collected": 25, @@ -44,9 +42,14 @@ def test_create_study(self): Study.create('test@foo.bar', self.info) def test_create_study_with_investigation(self): - """Insert a study into the database""" + """Insert a study into the database with an investingation""" Study.create('test@foo.bar', self.info, 0) + def test_create_study_with_efo(self): + """Insert a study into the database with efo information""" + self.info["study_experimental_factor"] = [1, 2] + Study.create('test@foo.bar', self.info) + def test_insert_missing_requred(self): """ Insert a study that is missing a required info key""" raise NotImplementedError() From b0ef52e694cb7d457b8ed0629aaddc10c5c097f7 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Fri, 23 May 2014 13:36:50 -0600 Subject: [PATCH 16/57] check errors raised properly --- qiita_db/test/test_study.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 75d24f443..4aa0fa316 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -52,11 +52,16 @@ def test_create_study_with_efo(self): def test_insert_missing_requred(self): """ Insert a study that is missing a required info key""" - raise NotImplementedError() + self.info.pop("study_title") + self.assertRaises(RuntimeError, Study.create, 'test@foo.bar', + self.info) def test_insert_unknown_db_col(self): """ Insert a study with an info key not in the database""" raise NotImplementedError() + self.info["SHOULDNOTBEHERE"] = "BWAHAHAHAHAHA" + self.assertRaises(RuntimeError, Study.create, 'test@foo.bar', + self.info) def test_retrieve_name(self): raise NotImplementedError() From f2f805d70fb7fa022c7a19e9ac1a9d35d0f1af18 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Fri, 23 May 2014 14:39:01 -0600 Subject: [PATCH 17/57] more tests added --- qiita_db/study.py | 68 +++++++++++++++++++++++-------------- qiita_db/test/test_study.py | 16 +++++---- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 37c9f9b93..267cf8f9a 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -73,7 +73,7 @@ def add_raw_data(raw_data_id): """ @staticmethod - def create(owner, info, investigation_id=None): + def create(cls, owner, info, investigation_id=None): """Creates a new study on the database Parameters @@ -134,7 +134,7 @@ def create(owner, info, investigation_id=None): "study_id) VALUES (%s, %s)") conn_handler.execute(sql, (investigation_id, study_id)) - return Study(study_id) + return cls(study_id) @staticmethod def delete(id_): @@ -149,31 +149,34 @@ def delete(id_): # --- Attributes --- @property - def name(self): - """Returns the name of the study""" + def title(self): + """Returns the title of the study""" conn_handler = SQLConnectionHandler() - sql = "SELECT name FROM qiita.study WHERE study_id = %s" - return conn_handler.execute_fetchone(sql, self.id_)[0] + sql = "SELECT study_title FROM qiita.study WHERE study_id = %s" + return conn_handler.execute_fetchone(sql, (self._id, ))[0] - @name.setter - def name(self, name): - """Sets the name of the study + @title.setter + def title(self, title): + """Sets the title of the study Parameters ---------- - name : str - The new study name + title : str + The new study title """ conn_handler = SQLConnectionHandler() - sql = "UPDATE qiita.study SET name = %s WHERE study_id = %s" - return conn_handler.execute(sql, (name, self.id_)) + sql = "UPDATE qiita.study SET study_title = %s WHERE study_id = %s" + return conn_handler.execute(sql, (title, self._id)) @property def info(self): - """Dict with any other information attached to the study""" + """Dict with all information attached to the study""" conn_handler = SQLConnectionHandler() sql = "SELECT * FROM qiita.study WHERE study_id = %s" - return dict(conn_handler.execute_fetchone(sql, self.id_)) + info = conn_handler.execute_fetchone(sql, self._id) + efo = clean_sql_result(conn_handler.execute_fetchall(sql, self._id)) + info["study_experimental_factor"] = efo + return info @info.setter def info(self, info): @@ -186,6 +189,14 @@ def info(self, info): conn_handler = SQLConnectionHandler() check_table_cols(conn_handler, info, "study") + # Save study_experimental_factor data for insertion + efo = None + if "study_experimental_factor" in info: + efo = info["study_experimental_factor"] + if not isinstance(efo, list) and not isinstance(efo, tuple): + efo = [efo] + info.pop("study_experimental_factor") + data = [] sql = "UPDATE qiita.study SET " # items() used for py3 compatability @@ -194,8 +205,13 @@ def info(self, info): sql = ' '.join((sql, key, "=", "%s,")) data.append(val) sql = ' '.join((sql[-1], "WHERE study_id = %s")) - data.append(self.id_) + data.append(self._id) + if efo: + # insert efo information into database + sql = ("INSERT INTO qiita.study_experimental_factor (study_id, " + "efo_id) VALUES (%s, %s)") + conn_handler.executemany(sql, zip([self._id] * len(efo), efo)) conn_handler.execute(sql, data) @property @@ -203,14 +219,14 @@ def status(self): """Returns the study_status_id for the study""" conn_handler = SQLConnectionHandler() sql = "SELECT study_status_id FROM qiita.study WHERE study_id = %s " - return conn_handler.execute_fetchone(sql, self.id_)[0] + return conn_handler.execute_fetchone(sql, self._id)[0] @status.setter def status(self, status_id): """Sets the study_status_id for the study""" conn_handler = SQLConnectionHandler() sql = "UPDATE qiita.study SET study_status_id = %s WHERE study_id = %s" - return conn_handler.execute_fetchone(sql, (status_id, self.id_))[0] + return conn_handler.execute_fetchone(sql, (status_id, self._id))[0] @property def sample_ids(self): @@ -221,21 +237,21 @@ def sample_ids(self): conn_handler = SQLConnectionHandler() sql = ("SELECT sample_id FROM qiita.required_sample_info WHERE " "study_id = %s ORDER BY sample_id") - return conn_handler.execute_fetchone(sql, self.id_) + return conn_handler.execute_fetchone(sql, self._id) @property def shared_with(self): """list of users the study is shared with""" conn_handler = SQLConnectionHandler() sql = "SELECT * FROM qiita.study_users WHERE study_id = %s" - return list(conn_handler.execute_fetchone(sql, self.id_)) + return list(conn_handler.execute_fetchone(sql, self._id)) @property def pmids(self): """ Returns list of paper PMIDs from this study """ conn_handler = SQLConnectionHandler() sql = "SELECT pmid FROM qiita.study_pmid WHERE study_id = %s" - return clean_sql_result(conn_handler.execute_fetchall(sql, self.id_)) + return clean_sql_result(conn_handler.execute_fetchall(sql, self._id)) @property def investigations(self): @@ -243,7 +259,7 @@ def investigations(self): conn_handler = SQLConnectionHandler() sql = ("SELECT investigation_id FROM qiita.investigation_study WHERE " "study_id = %s") - return clean_sql_result(conn_handler.execute_fetchall(sql, self.id_)) + return clean_sql_result(conn_handler.execute_fetchall(sql, self._id)) @property def metadata(self): @@ -251,7 +267,7 @@ def metadata(self): conn_handler = SQLConnectionHandler() sql = ("SELECT column_name FROM qiita.study_sample_columns WHERE " "study_id = %s") - return clean_sql_result(conn_handler.execute_fetchall(sql, self.id_)) + return clean_sql_result(conn_handler.execute_fetchall(sql, self._id)) @property def raw_data(self): @@ -280,7 +296,7 @@ def share_with(self, email): conn_handler = SQLConnectionHandler() sql = ("INSERT INTO qiita.study_users (study_id, email) VALUES " "(%s, %s)") - conn_handler.execute_fetchone(sql, (self.id_, email)) + conn_handler.execute_fetchone(sql, (self._id, email)) def add_raw_data(self, raw_data_id): """Associates raw data with the study @@ -293,7 +309,7 @@ def add_raw_data(self, raw_data_id): conn_handler = SQLConnectionHandler() sql = ("INSERT INTO qiita.study_raw_data (study_id, raw_data_id) " "VALUES (%s, %s)") - conn_handler.execute_fetchone(sql, (self.id_, raw_data_id)) + conn_handler.execute_fetchone(sql, (self._id, raw_data_id)) def add_pmid(self, pmid): """Adds PMID to study @@ -305,4 +321,4 @@ def add_pmid(self, pmid): """ conn_handler = SQLConnectionHandler() sql = "INSERT INTO qiita.study_pmid (study_id, pmid) VALUES (%s, %s)" - conn_handler.execute_fetchone(sql, (self.id_, pmid)) + conn_handler.execute_fetchone(sql, (self._id, pmid)) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 4aa0fa316..732e34aeb 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -2,8 +2,6 @@ from qiita_db.study import Study from qiita_core.util import qiita_test_checker -from qiita_db.exceptions import QiitaDBExecutionError, QiitaDBConnectionError -from qiita_db.sql_connection import SQLConnectionHandler # ----------------------------------------------------------------------------- @@ -18,6 +16,7 @@ @qiita_test_checker() class TestStudy(TestCase): def setUp(self): + self.study = Study(1) self.info = { "emp_person_id": 2, @@ -63,11 +62,13 @@ def test_insert_unknown_db_col(self): self.assertRaises(RuntimeError, Study.create, 'test@foo.bar', self.info) - def test_retrieve_name(self): - raise NotImplementedError() + def test_retrieve_title(self): + self.assertEqual(self.study.title, ('Identification of the Microbiomes' + ' for Cannabis Soils')) - def test_set_name(self): - raise NotImplementedError() + def test_set_title(self): + self.study.title = "Weed Soils" + self.assertEqual(self.study.title, "Weed Soils") def test_retrieve_info(self): raise NotImplementedError() @@ -75,6 +76,9 @@ def test_retrieve_info(self): def test_set_info(self): raise NotImplementedError() + def test_set_info_with_env_factors(self): + raise NotImplementedError() + def test_retrieve_status(self): raise NotImplementedError() From caa68acfc9c8c2cc44b8578a57a46e799ba35d93 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Sun, 25 May 2014 11:49:11 -0600 Subject: [PATCH 18/57] Added StudyPerson object --- qiita_db/study.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 267cf8f9a..ae825329f 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -21,10 +21,11 @@ from datetime import date -from .base import QiitaStatusObject +from .base import QiitaStatusObject, QiitaObject from .util import check_required, check_table_cols, clean_sql_result from .exceptions import QiitaDBNotImplementedError from .sql_connection import SQLConnectionHandler +from qiita_core.exceptions import QiitaStudyError REQUIRED_KEYS = {"timeseries_type_id", "lab_person_id", "mixs_compliant", @@ -127,6 +128,8 @@ def create(cls, owner, info, investigation_id=None): sql = ("INSERT INTO qiita.study_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)") conn_handler.executemany(sql, zip([study_id] * len(efo), efo)) + else: + raise QiitaStudyError("EFO information is required!") # add study to investigation if necessary if investigation_id: @@ -322,3 +325,93 @@ def add_pmid(self, pmid): conn_handler = SQLConnectionHandler() sql = "INSERT INTO qiita.study_pmid (study_id, pmid) VALUES (%s, %s)" conn_handler.execute_fetchone(sql, (self._id, pmid)) + + +class StudyPerson(QiitaObject): + """Object handling information pertaining to people involved in a study + + Attributes + ---------- + name: str + name of the person + email: str + email of the person + address: str, optional + address of the person + phone: str, optional + phone number of the person + """ + @staticmethod + def create(cls, name, email, address=None, phone=None): + # Make sure person doesn't already exist + conn_handler = SQLConnectionHandler() + sql = ("SELECT study_person_id FROM qiita.study WHERE name = %s AND " + "email = %s") + spid = conn_handler.execute_fetchone(sql, (name, email))[0] + if spid: + return StudyPerson(spid) + + # Doesn't exist so insert new person + sql = ("INSERT INTO qiita.study_person (name, email, address, phone) " + "VALUES (%s, %s, %s, %s) RETURNING study_person_id") + spid = conn_handler.execute_fetchone(sql, (name, email, address, + phone))[0] + return StudyPerson(spid) + + @staticmethod + def delete(self): + raise NotImplementedError() + + # Properties + @property + def name(self): + conn_handler = SQLConnectionHandler() + sql = "SELECT name FROM qiita.study_person WHERE study_person_id = %s" + return conn_handler.execute_fetchone(sql, (self._id, ))[0] + + @name.setter + def name(self, value): + conn_handler = SQLConnectionHandler() + sql = ("UPDATE qiita.study_person SET name = %s WHERE " + "study_person_id = %s") + conn_handler.execute(sql, (value, self._id)) + + @property + def email(self): + conn_handler = SQLConnectionHandler() + sql = "SELECT email FROM qiita.study_person WHERE study_person_id = %s" + return conn_handler.execute_fetchone(sql, (self._id, ))[0] + + @email.setter + def email(self, value): + conn_handler = SQLConnectionHandler() + sql = ("UPDATE qiita.study_person SET email = %s WHERE " + "study_person_id = %s") + conn_handler.execute(sql, (value, self._id)) + + @property + def address(self): + conn_handler = SQLConnectionHandler() + sql = ("SELECT address FROM qiita.study_person WHERE study_person_id =" + " %s") + return conn_handler.execute_fetchone(sql, (self._id, ))[0] + + @address.setter + def address(self, value): + conn_handler = SQLConnectionHandler() + sql = ("UPDATE qiita.study_person SET address = %s WHERE " + "study_person_id = %s") + conn_handler.execute(sql, (value, self._id)) + + @property + def phone(self): + conn_handler = SQLConnectionHandler() + sql = "SELECT phone FROM qiita.study_person WHERE study_person_id = %s" + return conn_handler.execute_fetchone(sql, (self._id, ))[0] + + @phone.setter + def phone(self, value): + conn_handler = SQLConnectionHandler() + sql = ("UPDATE qiita.study_person SET phone = %s WHERE " + "study_person_id = %s") + conn_handler.execute(sql, (value, self._id)) From a0b3b090c336a53fad99d13155509ba2c3d8ad1f Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Sun, 25 May 2014 11:53:14 -0600 Subject: [PATCH 19/57] make study creation/update check for objects and pull ids --- qiita_db/study.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index ae825329f..461feca4c 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -117,10 +117,13 @@ def create(cls, owner, info, investigation_id=None): sql = ("INSERT INTO qiita.study (email,study_status_id,first_contact," "reprocess, %s) VALUES (%s) RETURNING study_id" % (','.join(info.keys()), ','.join(['%s'] * (len(info)+4)))) - # make sure data in same order as sql column names + # make sure data in same order as sql column names, and ids are used data = [owner, 1, date.today().strftime("%B %d, %Y"), 'FALSE'] - for col in info.keys(): - data.append(info[col]) + for col, val in info.items(): + if isinstance(val, QiitaObject): + data.append(val.id_) + else: + data.append(val) study_id = conn_handler.execute_fetchone(sql, data)[0] if efo: @@ -206,7 +209,10 @@ def info(self, info): # build query with data values in correct order for SQL statement for key, val in info.items(): sql = ' '.join((sql, key, "=", "%s,")) - data.append(val) + if isinstance(val, QiitaObject): + data.append(val.id_) + else: + data.append(val) sql = ' '.join((sql[-1], "WHERE study_id = %s")) data.append(self._id) From 1df40c53a8a560eeae7b95c0fb56642962187191 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Thu, 29 May 2014 15:26:15 -0600 Subject: [PATCH 20/57] everything but delete written --- qiita_db/study.py | 83 ++++++++++-------- qiita_db/support_files/populate_test_db.sql | 3 + qiita_db/test/test_study.py | 94 ++++++++++++++++----- 3 files changed, 127 insertions(+), 53 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 461feca4c..76c32f060 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -73,7 +73,7 @@ def add_raw_data(raw_data_id): Adds PMID to study """ - @staticmethod + @classmethod def create(cls, owner, info, investigation_id=None): """Creates a new study on the database @@ -105,7 +105,7 @@ def create(cls, owner, info, investigation_id=None): efo = None if "study_experimental_factor" in info: efo = info["study_experimental_factor"] - if not isinstance(efo, list) and not isinstance(efo, tuple): + if isinstance(efo, str): efo = [efo] info.pop("study_experimental_factor") @@ -140,9 +140,10 @@ def create(cls, owner, info, investigation_id=None): "study_id) VALUES (%s, %s)") conn_handler.execute(sql, (investigation_id, study_id)) + cls._table = "study" return cls(study_id) - @staticmethod + @classmethod def delete(id_): """Deletes the study `id_` from the database @@ -179,8 +180,8 @@ def info(self): """Dict with all information attached to the study""" conn_handler = SQLConnectionHandler() sql = "SELECT * FROM qiita.study WHERE study_id = %s" - info = conn_handler.execute_fetchone(sql, self._id) - efo = clean_sql_result(conn_handler.execute_fetchall(sql, self._id)) + info = dict(conn_handler.execute_fetchone(sql, (self._id, ))) + efo = clean_sql_result(conn_handler.execute_fetchall(sql, (self._id,))) info["study_experimental_factor"] = efo return info @@ -228,14 +229,14 @@ def status(self): """Returns the study_status_id for the study""" conn_handler = SQLConnectionHandler() sql = "SELECT study_status_id FROM qiita.study WHERE study_id = %s " - return conn_handler.execute_fetchone(sql, self._id)[0] + return conn_handler.execute_fetchone(sql, (self._id, ))[0] @status.setter def status(self, status_id): """Sets the study_status_id for the study""" conn_handler = SQLConnectionHandler() sql = "UPDATE qiita.study SET study_status_id = %s WHERE study_id = %s" - return conn_handler.execute_fetchone(sql, (status_id, self._id))[0] + conn_handler.execute(sql, (status_id, self._id)) @property def sample_ids(self): @@ -246,21 +247,24 @@ def sample_ids(self): conn_handler = SQLConnectionHandler() sql = ("SELECT sample_id FROM qiita.required_sample_info WHERE " "study_id = %s ORDER BY sample_id") - return conn_handler.execute_fetchone(sql, self._id) + return clean_sql_result(conn_handler.execute_fetchall(sql, + (self._id, ))) @property def shared_with(self): """list of users the study is shared with""" conn_handler = SQLConnectionHandler() - sql = "SELECT * FROM qiita.study_users WHERE study_id = %s" - return list(conn_handler.execute_fetchone(sql, self._id)) + sql = "SELECT email FROM qiita.study_users WHERE study_id = %s" + return clean_sql_result(conn_handler.execute_fetchall(sql, + (self._id, ))) @property def pmids(self): """ Returns list of paper PMIDs from this study """ conn_handler = SQLConnectionHandler() sql = "SELECT pmid FROM qiita.study_pmid WHERE study_id = %s" - return clean_sql_result(conn_handler.execute_fetchall(sql, self._id)) + return clean_sql_result(conn_handler.execute_fetchall(sql, + (self._id, ))) @property def investigations(self): @@ -268,7 +272,8 @@ def investigations(self): conn_handler = SQLConnectionHandler() sql = ("SELECT investigation_id FROM qiita.investigation_study WHERE " "study_id = %s") - return clean_sql_result(conn_handler.execute_fetchall(sql, self._id)) + return clean_sql_result(conn_handler.execute_fetchall(sql, + (self._id, ))) @property def metadata(self): @@ -276,22 +281,47 @@ def metadata(self): conn_handler = SQLConnectionHandler() sql = ("SELECT column_name FROM qiita.study_sample_columns WHERE " "study_id = %s") - return clean_sql_result(conn_handler.execute_fetchall(sql, self._id)) + return clean_sql_result(conn_handler.execute_fetchall(sql, + (self._id, ))) @property def raw_data(self): """ Returns list of data objects with raw data info """ - raise NotImplementedError(Study.raw_data) + conn_handler = SQLConnectionHandler() + sql = ("SELECT raw_data_id FROM qiita.study_raw_data WHERE " + "study_id = %s") + raw_ids = clean_sql_result(conn_handler.execute_fetchall(sql, + (self._id, ))) + raw_data = [] + for rid in raw_ids: + raw_data.append(RawData(rid)) + return raw_data @property def preprocessed_data(self): """ Returns list of data objects with preprocessed data info """ - raise NotImplementedError(Study.preprocessed_data) + conn_handler = SQLConnectionHandler() + sql = ("SELECT preprocessed_data_id FROM qiita.study_preprocessed_data" + " WHERE study_id = %s") + pre_ids = clean_sql_result(conn_handler.execute_fetchall(sql, + (self._id, ))) + preprocessed_data = [] + for pid in pre_ids: + preprocessed_data.append(PreprocessedData(pid)) + return preprocessed_data @property def processed_data(self): """ Returns list of data objects with processed data info """ - raise NotImplementedError(Study.processed_data) + conn_handler = SQLConnectionHandler() + sql = ("SELECT processed_data_id FROM qiita.study_processed_data" + " WHERE study_id = %s") + pro_ids = clean_sql_result(conn_handler.execute_fetchall(sql, + (self._id, ))) + processed_data = [] + for pid in pro_ids: + processed_data.append(ProcessedData(pid)) + return processed_data # --- methods --- def share_with(self, email): @@ -305,20 +335,7 @@ def share_with(self, email): conn_handler = SQLConnectionHandler() sql = ("INSERT INTO qiita.study_users (study_id, email) VALUES " "(%s, %s)") - conn_handler.execute_fetchone(sql, (self._id, email)) - - def add_raw_data(self, raw_data_id): - """Associates raw data with the study - - Parameters - ---------- - raw_data_id: int - ID of the raw data to associate with study - """ - conn_handler = SQLConnectionHandler() - sql = ("INSERT INTO qiita.study_raw_data (study_id, raw_data_id) " - "VALUES (%s, %s)") - conn_handler.execute_fetchone(sql, (self._id, raw_data_id)) + conn_handler.execute(sql, (self._id, email)) def add_pmid(self, pmid): """Adds PMID to study @@ -330,7 +347,7 @@ def add_pmid(self, pmid): """ conn_handler = SQLConnectionHandler() sql = "INSERT INTO qiita.study_pmid (study_id, pmid) VALUES (%s, %s)" - conn_handler.execute_fetchone(sql, (self._id, pmid)) + conn_handler.execute(sql, (self._id, pmid)) class StudyPerson(QiitaObject): @@ -347,7 +364,7 @@ class StudyPerson(QiitaObject): phone: str, optional phone number of the person """ - @staticmethod + @classmethod def create(cls, name, email, address=None, phone=None): # Make sure person doesn't already exist conn_handler = SQLConnectionHandler() @@ -364,7 +381,7 @@ def create(cls, name, email, address=None, phone=None): phone))[0] return StudyPerson(spid) - @staticmethod + @classmethod def delete(self): raise NotImplementedError() diff --git a/qiita_db/support_files/populate_test_db.sql b/qiita_db/support_files/populate_test_db.sql index 3f269b8e7..ba7717d2e 100644 --- a/qiita_db/support_files/populate_test_db.sql +++ b/qiita_db/support_files/populate_test_db.sql @@ -36,6 +36,9 @@ INSERT INTO qiita.study (email, study_status_id, emp_person_id, first_contact, -- Insert study_users (share study 1 with shared user) INSERT INTO qiita.study_users (study_id, email) VALUES (1, 'shared@foo.bar'); +-- Insert PMIDs for study +INSERT INTO qiita.study_pmid (study_id, pmid) VALUES (1, '123456'), (1, '7891011'); + -- Insert an investigation INSERT INTO qiita.investigation (name, description, contact_person_id) VALUES ('TestInvestigation', 'An investigation for testing purposes', 3); diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 732e34aeb..211e914fc 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -2,6 +2,7 @@ from qiita_db.study import Study from qiita_core.util import qiita_test_checker +from qiita_db.exceptions import QiitaDBExecutionError # ----------------------------------------------------------------------------- @@ -22,6 +23,7 @@ def setUp(self): "emp_person_id": 2, "timeseries_type_id": 1, "lab_person_id": 1, + "study_experimental_factor": [1, 2], "metadata_complete": True, "mixs_compliant": True, "number_samples_collected": 25, @@ -42,12 +44,10 @@ def test_create_study(self): def test_create_study_with_investigation(self): """Insert a study into the database with an investingation""" - Study.create('test@foo.bar', self.info, 0) + Study.create('test@foo.bar', self.info, 1) - def test_create_study_with_efo(self): - """Insert a study into the database with efo information""" - self.info["study_experimental_factor"] = [1, 2] - Study.create('test@foo.bar', self.info) + def test_delete(self): + raise NotImplementedError() def test_insert_missing_requred(self): """ Insert a study that is missing a required info key""" @@ -57,9 +57,8 @@ def test_insert_missing_requred(self): def test_insert_unknown_db_col(self): """ Insert a study with an info key not in the database""" - raise NotImplementedError() self.info["SHOULDNOTBEHERE"] = "BWAHAHAHAHAHA" - self.assertRaises(RuntimeError, Study.create, 'test@foo.bar', + self.assertRaises(QiitaDBExecutionError, Study.create, 'test@foo.bar', self.info) def test_retrieve_title(self): @@ -71,7 +70,45 @@ def test_set_title(self): self.assertEqual(self.study.title, "Weed Soils") def test_retrieve_info(self): - raise NotImplementedError() + exp = { + 'mixs_compliant': True, + 'metadata_complete': True, + 'reprocess': False, + 'study_status_id': 2, + 'number_samples_promised': 27, + 'emp_person_id': 2L, + 'funding': None, + 'vamps_id': None, + 'first_contact': '2014-05-19 16:10', + 'principal_investigator_id': 3, + 'timeseries_type_id': 1, + 'study_abstract': ('This is a preliminary study to examine the ' + 'microbiota associated with the Cannabis plant.' + ' Soils samples from the bulk soil, soil ' + 'associated with the roots, and the rhizosphere' + ' were extracted and the DNA sequenced. Roots ' + 'from three independent plants of different ' + 'strains were examined. These roots were ' + 'obtained November 11, 2011 from plants that ' + 'had been harvested in the summer. Future ' + 'studies will attempt to analyze the soils and ' + 'rhizospheres from the same location at ' + 'different time points in the plant ' + 'lifecycle.'), + 'email': 'test@foo.bar', + 'spatial_series': False, + 'study_description': 'Analysis of the Cannabis Plant Microbiome', + 'study_experimental_factor': [1], + 'portal_type_id': 2, + 'study_alias': 'Cannabis Soils', + 'study_id': 1, + 'most_recent_contact': '2014-05-19 16:11', + 'lab_person_id': 1, + 'study_title': ('Identification of the Microbiomes for Cannabis ' + 'Soils'), + 'number_samples_collected': 27} + + self.assertEqual(self.study.info, exp) def test_set_info(self): raise NotImplementedError() @@ -80,25 +117,41 @@ def test_set_info_with_env_factors(self): raise NotImplementedError() def test_retrieve_status(self): - raise NotImplementedError() + self.assertEqual(self.study.status, 2) def test_set_status(self): - raise NotImplementedError() + self.study.status = 1 + self.assertEqual(self.study.status, 1) def test_retrieve_sample_ids(self): - raise NotImplementedError() + exp = set(['SKB8.640193', 'SKD8.640184', 'SKB7.640196', 'SKM9.640192', + 'SKM4.640180', 'SKM5.640177', 'SKB5.640181', 'SKD6.640190', + 'SKB2.640194', 'SKD2.640178', 'SKM7.640188', 'SKB1.640202', + 'SKD1.640179', 'SKD3.640198', 'SKM8.640201', 'SKM2.640199', + 'SKB9.640200', 'SKD5.640186', 'SKM3.640197', 'SKD9.640182', + 'SKB4.640189', 'SKD7.640191', 'SKM6.640187', 'SKD4.640185', + 'SKB3.640195', 'SKB6.640176', 'SKM1.640183']) + obs = set(self.study.sample_ids) + self.assertEqual(obs, exp) def test_retrieve_shared_with(self): - raise NotImplementedError() + self.assertEqual(self.study.shared_with, ['shared@foo.bar']) def test_retrieve_pmids(self): - raise NotImplementedError() + exp = ['123456', '7891011'] + self.assertEqual(self.study.pmids, exp) def test_retrieve_investigations(self): - raise NotImplementedError() + self.assertEqual(self.study.investigations, [1]) def test_retrieve_metadata(self): - raise NotImplementedError() + exp = ['LATITUDE', 'ENV_FEATURE', 'Description_duplicate', 'LONGITUDE', + 'TOT_ORG_CARB', 'ANONYMIZED_NAME', 'PH', 'COUNTRY', 'ENV_BIOME', + 'ALTITUDE', 'SAMP_SALINITY', 'TOT_NITRO', 'TEMP', 'ELEVATION', + 'WATER_CONTENT_SOIL', 'COMMON_NAME', 'HOST_TAXID', 'DEPTH', + 'TAXON_ID', 'TEXTURE', 'ASSIGNED_FROM_GEO', + 'SEASON_ENVIRONMENT', 'sample_id'] + self.assertEqual(self.study.metadata, exp) def test_retrieve_raw_data(self): raise NotImplementedError() @@ -110,13 +163,14 @@ def test_retrieve_processed_data(self): raise NotImplementedError() def test_share_with(self): - raise NotImplementedError() - - def test_add_raw_data(self): - raise NotImplementedError() + self.study.share_with('admin@foo.bar') + self.assertEqual(self.study.shared_with, ['shared@foo.bar', + 'admin@foo.bar']) def test_add_pmid(self): - raise NotImplementedError() + self.study.add_pmid("4544444") + exp = ['123456', '7891011', '4544444'] + self.assertEqual(self.study.pmids, exp) if __name__ == "__main__": From 741de070098031eda0e8822d4880b375602d365e Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Thu, 29 May 2014 15:59:13 -0600 Subject: [PATCH 21/57] more tests --- qiita_db/study.py | 3 +++ qiita_db/test/test_study.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 76c32f060..1d6fe2d9e 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -22,6 +22,7 @@ from datetime import date from .base import QiitaStatusObject, QiitaObject +# from .data import RawData, PreprocessedData, ProcessedData from .util import check_required, check_table_cols, clean_sql_result from .exceptions import QiitaDBNotImplementedError from .sql_connection import SQLConnectionHandler @@ -183,6 +184,8 @@ def info(self): info = dict(conn_handler.execute_fetchone(sql, (self._id, ))) efo = clean_sql_result(conn_handler.execute_fetchall(sql, (self._id,))) info["study_experimental_factor"] = efo + + #TODO: Convert everythin from ids to objects return info @info.setter diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 211e914fc..0ac072f6f 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -40,11 +40,13 @@ def setUp(self): def test_create_study(self): """Insert a study into the database""" - Study.create('test@foo.bar', self.info) + obs = Study.create('test@foo.bar', self.info) + self.assertEqual(obs.id, 2) def test_create_study_with_investigation(self): """Insert a study into the database with an investingation""" Study.create('test@foo.bar', self.info, 1) + self.assertEqual(obs.id, 2) def test_delete(self): raise NotImplementedError() From ee9b677f3addfbb3877e04becebb76ec83cc6d3d Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Fri, 30 May 2014 15:21:03 -0600 Subject: [PATCH 22/57] More test and study updates for bugfixes --- qiita_db/study.py | 177 ++++++++----- qiita_db/support_files/populate_test_db.sql | 2 +- qiita_db/test/test_study.py | 275 +++++++++++++++++--- 3 files changed, 339 insertions(+), 115 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 1d6fe2d9e..18672831d 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python from __future__ import division """ @@ -23,8 +22,9 @@ from .base import QiitaStatusObject, QiitaObject # from .data import RawData, PreprocessedData, ProcessedData +from .user import User + from .util import check_required, check_table_cols, clean_sql_result -from .exceptions import QiitaDBNotImplementedError from .sql_connection import SQLConnectionHandler from qiita_core.exceptions import QiitaStudyError @@ -73,32 +73,30 @@ def add_raw_data(raw_data_id): add_pmid(self, pmid): Adds PMID to study """ + _table = "study" @classmethod - def create(cls, owner, info, investigation_id=None): + def create(cls, owner, info, investigation=None): """Creates a new study on the database Parameters ---------- - owner : str + owner : User object the user id of the study' owner info: dict the information attached to the study - investigation_id: int + investigation_id: Investigation object if the study is part of an investigation, the id to associate with Raises ------ QiitaDBExecutionError All required keys not passed or non-db columns in info dictionary - - Notes - ----- - If investigation_id passed in investigation database, will assume that - study is part of that investigation. Otherwise need to pass the - following keys in investigation: "name", "description", - "contact_person_id" """ + #make sure not passing a study id in the info dict + if "study_id" in info: + raise QiitaStudyError("Can't pass study_id in info dict!") + # make sure required keys are in the info dict check_required(info, REQUIRED_KEYS) @@ -109,43 +107,42 @@ def create(cls, owner, info, investigation_id=None): if isinstance(efo, str): efo = [efo] info.pop("study_experimental_factor") + else: + raise QiitaStudyError("EFO information is required!") conn_handler = SQLConnectionHandler() # make sure dictionary only has keys for available columns in db check_table_cols(conn_handler, info, "study") # Insert study into database - sql = ("INSERT INTO qiita.study (email,study_status_id,first_contact," - "reprocess, %s) VALUES (%s) RETURNING study_id" % + sql = ("INSERT INTO qiita.{0} (email,study_status_id,first_contact," + "reprocess, %s) VALUES (%s) RETURNING " + "study_id".format(cls._table) % (','.join(info.keys()), ','.join(['%s'] * (len(info)+4)))) # make sure data in same order as sql column names, and ids are used - data = [owner, 1, date.today().strftime("%B %d, %Y"), 'FALSE'] + data = [owner.id, 1, date.today().strftime("%B %d, %Y"), 'FALSE'] for col, val in info.items(): if isinstance(val, QiitaObject): - data.append(val.id_) + data.append(val.id) else: data.append(val) study_id = conn_handler.execute_fetchone(sql, data)[0] - if efo: - # insert efo information into database - sql = ("INSERT INTO qiita.study_experimental_factor (study_id, " - "efo_id) VALUES (%s, %s)") - conn_handler.executemany(sql, zip([study_id] * len(efo), efo)) - else: - raise QiitaStudyError("EFO information is required!") + # insert efo information into database + sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " + "efo_id) VALUES (%s, %s)".format(cls._table)) + conn_handler.executemany(sql, zip([study_id] * len(efo), efo)) # add study to investigation if necessary - if investigation_id: + if investigation: sql = ("INSERT INTO qiita.investigation_study (investigation_id, " "study_id) VALUES (%s, %s)") - conn_handler.execute(sql, (investigation_id, study_id)) + conn_handler.execute(sql, (investigation.id, study_id)) - cls._table = "study" return cls(study_id) @classmethod - def delete(id_): + def delete(cls, id_): """Deletes the study `id_` from the database Parameters @@ -153,14 +150,23 @@ def delete(id_): id_ : The object identifier """ - raise QiitaDBNotImplementedError() + # delete raw data + # drop sample_x dynamic table + # delete study row from study table (cascades to everything else) + + def __eq__(self, other): + return other._id == self._id + + def __ne__(self, other): + return other._id != self._id # --- Attributes --- @property def title(self): """Returns the title of the study""" conn_handler = SQLConnectionHandler() - sql = "SELECT study_title FROM qiita.study WHERE study_id = %s" + sql = ("SELECT study_title FROM qiita.{0} WHERE " + "study_id = %s".format(self._table)) return conn_handler.execute_fetchone(sql, (self._id, ))[0] @title.setter @@ -173,19 +179,32 @@ def title(self, title): The new study title """ conn_handler = SQLConnectionHandler() - sql = "UPDATE qiita.study SET study_title = %s WHERE study_id = %s" + sql = ("UPDATE qiita.{0} SET study_title = %s WHERE " + "study_id = %s".format(self._table)) return conn_handler.execute(sql, (title, self._id)) @property def info(self): """Dict with all information attached to the study""" conn_handler = SQLConnectionHandler() - sql = "SELECT * FROM qiita.study WHERE study_id = %s" + sql = "SELECT * FROM qiita.{0} WHERE study_id = %s".format(self._table) info = dict(conn_handler.execute_fetchone(sql, (self._id, ))) efo = clean_sql_result(conn_handler.execute_fetchall(sql, (self._id,))) info["study_experimental_factor"] = efo - #TODO: Convert everythin from ids to objects + # Convert everything from ids to objects + info['email'] = User(info['email']) + if info['principal_investigator_id'] is not None: + info['principal_investigator_id'] = StudyPerson( + info['principal_investigator_id']) + if info['lab_person_id'] is not None: + info['lab_person_id'] = StudyPerson( + info['lab_person_id']) + if info['emp_person_id'] is not None: + info['emp_person_id'] = StudyPerson( + info['emp_person_id']) + # remove id since not needed + info.pop("study_id") return info @info.setter @@ -197,7 +216,6 @@ def info(self, info): info : dict """ conn_handler = SQLConnectionHandler() - check_table_cols(conn_handler, info, "study") # Save study_experimental_factor data for insertion efo = None @@ -207,14 +225,16 @@ def info(self, info): efo = [efo] info.pop("study_experimental_factor") + check_table_cols(conn_handler, info, "study") + data = [] - sql = "UPDATE qiita.study SET " + sql = "UPDATE qiita.{0} SET ".format(self._table) # items() used for py3 compatability # build query with data values in correct order for SQL statement for key, val in info.items(): sql = ' '.join((sql, key, "=", "%s,")) if isinstance(val, QiitaObject): - data.append(val.id_) + data.append(val.id) else: data.append(val) sql = ' '.join((sql[-1], "WHERE study_id = %s")) @@ -222,8 +242,8 @@ def info(self, info): if efo: # insert efo information into database - sql = ("INSERT INTO qiita.study_experimental_factor (study_id, " - "efo_id) VALUES (%s, %s)") + sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " + "efo_id) VALUES (%s, %s)".format(self._table)) conn_handler.executemany(sql, zip([self._id] * len(efo), efo)) conn_handler.execute(sql, data) @@ -231,14 +251,16 @@ def info(self, info): def status(self): """Returns the study_status_id for the study""" conn_handler = SQLConnectionHandler() - sql = "SELECT study_status_id FROM qiita.study WHERE study_id = %s " + sql = ("SELECT study_status_id FROM qiita.{0} WHERE " + "study_id = %s".format(self._table)) return conn_handler.execute_fetchone(sql, (self._id, ))[0] @status.setter def status(self, status_id): """Sets the study_status_id for the study""" conn_handler = SQLConnectionHandler() - sql = "UPDATE qiita.study SET study_status_id = %s WHERE study_id = %s" + sql = ("UPDATE qiita.{0} SET study_status_id = %s WHERE " + "study_id = %s".format(self._table)) conn_handler.execute(sql, (status_id, self._id)) @property @@ -249,7 +271,7 @@ def sample_ids(self): """ conn_handler = SQLConnectionHandler() sql = ("SELECT sample_id FROM qiita.required_sample_info WHERE " - "study_id = %s ORDER BY sample_id") + "study_id = %s ORDER BY sample_id".format(self._table)) return clean_sql_result(conn_handler.execute_fetchall(sql, (self._id, ))) @@ -257,7 +279,8 @@ def sample_ids(self): def shared_with(self): """list of users the study is shared with""" conn_handler = SQLConnectionHandler() - sql = "SELECT email FROM qiita.study_users WHERE study_id = %s" + sql = ("SELECT email FROM qiita.{0}_users WHERE " + "study_id = %s".format(self._table)) return clean_sql_result(conn_handler.execute_fetchall(sql, (self._id, ))) @@ -265,7 +288,8 @@ def shared_with(self): def pmids(self): """ Returns list of paper PMIDs from this study """ conn_handler = SQLConnectionHandler() - sql = "SELECT pmid FROM qiita.study_pmid WHERE study_id = %s" + sql = ("SELECT pmid FROM qiita.{0}_pmid WHERE " + "study_id = %s".format(self._table)) return clean_sql_result(conn_handler.execute_fetchall(sql, (self._id, ))) @@ -282,8 +306,8 @@ def investigations(self): def metadata(self): """ Returns list of metadata columns """ conn_handler = SQLConnectionHandler() - sql = ("SELECT column_name FROM qiita.study_sample_columns WHERE " - "study_id = %s") + sql = ("SELECT column_name FROM qiita.{0}_sample_columns WHERE " + "study_id = %s".format(self._table)) return clean_sql_result(conn_handler.execute_fetchall(sql, (self._id, ))) @@ -349,7 +373,8 @@ def add_pmid(self, pmid): pmid to associate with study """ conn_handler = SQLConnectionHandler() - sql = "INSERT INTO qiita.study_pmid (study_id, pmid) VALUES (%s, %s)" + sql = ("INSERT INTO qiita.{0}_pmid (study_id, pmid) " + "VALUES (%s, %s)".format(self._table)) conn_handler.execute(sql, (self._id, pmid)) @@ -367,77 +392,87 @@ class StudyPerson(QiitaObject): phone: str, optional phone number of the person """ + _table = "study_person" + @classmethod def create(cls, name, email, address=None, phone=None): # Make sure person doesn't already exist conn_handler = SQLConnectionHandler() - sql = ("SELECT study_person_id FROM qiita.study WHERE name = %s AND " - "email = %s") - spid = conn_handler.execute_fetchone(sql, (name, email))[0] - if spid: - return StudyPerson(spid) - - # Doesn't exist so insert new person - sql = ("INSERT INTO qiita.study_person (name, email, address, phone) " - "VALUES (%s, %s, %s, %s) RETURNING study_person_id") - spid = conn_handler.execute_fetchone(sql, (name, email, address, - phone))[0] - return StudyPerson(spid) + sql = ("SELECT study_person_id FROM qiita.{0} WHERE name = %s AND " + "email = %s".format(cls._table)) + spid = conn_handler.execute_fetchone(sql, (name, email)) + if spid is None: + # Doesn't exist so insert new person + sql = ("INSERT INTO qiita.{0} (name, email, address, phone) VALUES" + " (%s, %s, %s, %s) RETURNING " + "study_person_id".format(cls._table)) + spid = conn_handler.execute_fetchone(sql, (name, email, address, + phone)) + return cls(spid[0]) @classmethod - def delete(self): + def delete(cls, self): raise NotImplementedError() + def __eq__(self, other): + return other._id == self._id + + def __ne__(self, other): + return other._id != self._id + # Properties @property def name(self): conn_handler = SQLConnectionHandler() - sql = "SELECT name FROM qiita.study_person WHERE study_person_id = %s" + sql = ("SELECT name FROM qiita.{0} WHERE " + "study_person_id = %s".format(self._table)) return conn_handler.execute_fetchone(sql, (self._id, ))[0] @name.setter def name(self, value): conn_handler = SQLConnectionHandler() - sql = ("UPDATE qiita.study_person SET name = %s WHERE " - "study_person_id = %s") + sql = ("UPDATE qiita.{0} SET name = %s WHERE " + "study_person_id = %s".format(self._table)) conn_handler.execute(sql, (value, self._id)) @property def email(self): conn_handler = SQLConnectionHandler() - sql = "SELECT email FROM qiita.study_person WHERE study_person_id = %s" + sql = ("SELECT email FROM qiita.{0} WHERE " + "study_person_id = %s".format(self._table)) return conn_handler.execute_fetchone(sql, (self._id, ))[0] @email.setter def email(self, value): conn_handler = SQLConnectionHandler() - sql = ("UPDATE qiita.study_person SET email = %s WHERE " - "study_person_id = %s") + sql = ("UPDATE qiita.{0} SET email = %s WHERE " + "study_person_id = %s".format(self._table)) conn_handler.execute(sql, (value, self._id)) @property def address(self): conn_handler = SQLConnectionHandler() - sql = ("SELECT address FROM qiita.study_person WHERE study_person_id =" - " %s") + sql = ("SELECT address FROM qiita.{0} WHERE study_person_id =" + " %s".format(self._table)) return conn_handler.execute_fetchone(sql, (self._id, ))[0] @address.setter def address(self, value): conn_handler = SQLConnectionHandler() - sql = ("UPDATE qiita.study_person SET address = %s WHERE " - "study_person_id = %s") + sql = ("UPDATE qiita.{0} SET address = %s WHERE " + "study_person_id = %s".format(self._table)) conn_handler.execute(sql, (value, self._id)) @property def phone(self): conn_handler = SQLConnectionHandler() - sql = "SELECT phone FROM qiita.study_person WHERE study_person_id = %s" + sql = ("SELECT phone FROM qiita.{0} WHERE " + "study_person_id = %s".format(self._table)) return conn_handler.execute_fetchone(sql, (self._id, ))[0] @phone.setter def phone(self, value): conn_handler = SQLConnectionHandler() - sql = ("UPDATE qiita.study_person SET phone = %s WHERE " - "study_person_id = %s") + sql = ("UPDATE qiita.{0} SET phone = %s WHERE " + "study_person_id = %s".format(self._table)) conn_handler.execute(sql, (value, self._id)) diff --git a/qiita_db/support_files/populate_test_db.sql b/qiita_db/support_files/populate_test_db.sql index ba7717d2e..28ab535ab 100644 --- a/qiita_db/support_files/populate_test_db.sql +++ b/qiita_db/support_files/populate_test_db.sql @@ -16,7 +16,7 @@ INSERT INTO qiita.qiita_user (email, user_level_id, password, name, -- Insert some study persons INSERT INTO qiita.study_person (name, email, address, phone) VALUES - ('LabDude', 'lab_dude@foo.bar', '123 lab street', NULL), + ('LabDude', 'lab_dude@foo.bar', '123 lab street', '121-222-3333'), ('empDude', 'emp_dude@foo.bar', '123 emp street', NULL), ('PIDude', 'PI_dude@foo.bar', '123 PI street', NULL); diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 0ac072f6f..3f1b02524 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -1,8 +1,12 @@ from unittest import TestCase, main +from datetime import date -from qiita_db.study import Study +from qiita_db.study import Study, StudyPerson +from qiita_db.investigation import Investigation +from qiita_db.user import User from qiita_core.util import qiita_test_checker from qiita_db.exceptions import QiitaDBExecutionError +from qiita_db.sql_connection import SQLConnectionHandler # ----------------------------------------------------------------------------- @@ -13,6 +17,67 @@ # The full license is in the file LICENSE, distributed with this software. # ----------------------------------------------------------------------------- +@qiita_test_checker() +class TestStudyPerson(TestCase): + def setUp(self): + self.studyperson = StudyPerson(1) + + def test_create_studyperson(self): + new = StudyPerson.create('SomeDude', 'somedude@foo.bar', + '111 fake street', '111-121-1313') + self.assertEqual(new.id, 4) + conn = SQLConnectionHandler() + obs = conn.execute_fetchone("SELECT * FROM qiita.study_person WHERE " + "study_person_id = 4") + self.assertEqual(obs["study_person_id"], 4) + self.assertEqual(obs["email"], 'somedude@foo.bar') + self.assertEqual(obs["name"], 'SomeDude') + self.assertEqual(obs["address"], '111 fake street') + self.assertEqual(obs["phone"], '111-121-1313') + + def test_create_studyperson_already_exists(self): + new = StudyPerson.create('LabDude', 'lab_dude@foo.bar') + self.assertEqual(new.id, 1) + conn = SQLConnectionHandler() + obs = conn.execute_fetchone("SELECT * FROM qiita.study_person WHERE " + "study_person_id = 1") + self.assertEqual(obs["study_person_id"], 1) + self.assertEqual(obs["email"], 'lab_dude@foo.bar') + self.assertEqual(obs["name"], 'LabDude') + self.assertEqual(obs["address"], '123 lab street') + self.assertEqual(obs["phone"], '121-222-3333') + + def test_delete_studyperson(self): + raise NotImplementedError() + + def test_retrieve_name(self): + self.assertEqual(self.studyperson.name, 'LabDude') + + def test_set_name(self): + self.studyperson.name = 'NewDude' + self.assertEqual(self.studyperson.name, 'NewDude') + + def test_retrieve_email(self): + self.assertEqual(self.studyperson.email, 'lab_dude@foo.bar') + + def test_set_email(self): + self.studyperson.email = 'new@foo.bar' + self.assertEqual(self.studyperson.email, 'new@foo.bar') + + def test_retrieve_address(self): + self.assertEqual(self.studyperson.address, '123 lab street') + + def test_set_address(self): + self.studyperson.address = '123 nonsense road' + self.assertEqual(self.studyperson.address, '123 nonsense road') + + def test_retrieve_phone(self): + self.assertEqual(self.studyperson.phone, '121-222-3333') + + def test_set_phone(self): + self.studyperson.phone = '111111111111111111111' + self.assertEqual(self.studyperson.phone, '111111111111111111111') + @qiita_test_checker() class TestStudy(TestCase): @@ -20,35 +85,131 @@ def setUp(self): self.study = Study(1) self.info = { - "emp_person_id": 2, "timeseries_type_id": 1, - "lab_person_id": 1, "study_experimental_factor": [1, 2], "metadata_complete": True, "mixs_compliant": True, "number_samples_collected": 25, "number_samples_promised": 28, "portal_type_id": 3, - "principal_investigator_id": 3, "study_title": "Fried chicken microbiome", "study_alias": "FCM", "study_description": ("Microbiome of people who eat nothing but " "fried chicken"), "study_abstract": ("We wanted to see if we could get funding for " - "giving people heart attacks") + "giving people heart attacks"), + "emp_person_id": StudyPerson(2), + "principal_investigator_id": StudyPerson(3), + "lab_person_id": StudyPerson(1) } - def test_create_study(self): + def test_create_study_min_data(self): """Insert a study into the database""" - obs = Study.create('test@foo.bar', self.info) + obs = Study.create(User('test@foo.bar'), self.info) self.assertEqual(obs.id, 2) + exp = {'mixs_compliant': True, 'metadata_complete': True, + 'reprocess': False, 'study_status_id': 1, + 'number_samples_promised': 28, 'emp_person_id': 2, + 'funding': None, 'vamps_id': None, + 'first_contact': date.today().strftime("%B %d, %Y"), + 'principal_investigator_id': 3, 'timeseries_type_id': 1, + 'study_abstract': ('We wanted to see if we could get funding ' + 'for giving people heart attacks'), + 'email': 'test@foo.bar', 'spatial_series': None, + 'study_description': ('Microbiome of people who eat nothing but' + ' fried chicken'), + 'portal_type_id': 3, 'study_alias': 'FCM', 'study_id': 2, + 'most_recent_contact': None, 'lab_person_id': 1, + 'study_title': 'Fried chicken microbiome', + 'number_samples_collected': 25} + conn = SQLConnectionHandler() + obsins = dict(conn.execute_fetchone("SELECT * FROM qiita.study WHERE " + "study_id = 2")) + self.assertEqual(obsins, exp) + + #make sure EFO went in to table correctly + efo = conn.execute_fetchall("SELECT efo_id FROM " + "qiita.study_experimental_factor WHERE " + "study_id = 2") + obsefo = [x[0] for x in efo] + self.assertEqual(obsefo, [1, 2]) def test_create_study_with_investigation(self): - """Insert a study into the database with an investingation""" - Study.create('test@foo.bar', self.info, 1) + """Insert a study into the database with an investigation""" + obs = Study.create(User('test@foo.bar'), self.info, Investigation(1)) + self.assertEqual(obs.id, 2) + exp = {'mixs_compliant': True, 'metadata_complete': True, + 'reprocess': False, 'study_status_id': 1, + 'number_samples_promised': 28, 'emp_person_id': 2, + 'funding': None, 'vamps_id': None, + 'first_contact': date.today().strftime("%B %d, %Y"), + 'principal_investigator_id': 3, 'timeseries_type_id': 1, + 'study_abstract': ('We wanted to see if we could get funding ' + 'for giving people heart attacks'), + 'email': 'test@foo.bar', 'spatial_series': None, + 'study_description': ('Microbiome of people who eat nothing but' + ' fried chicken'), + 'portal_type_id': 3, 'study_alias': 'FCM', 'study_id': 2, + 'most_recent_contact': None, 'lab_person_id': 1, + 'study_title': 'Fried chicken microbiome', + 'number_samples_collected': 25} + + # make sure study inserted correctly + conn = SQLConnectionHandler() + obsins = dict(conn.execute_fetchone("SELECT * FROM qiita.study WHERE " + "study_id = 2")) + self.assertEqual(obsins, exp) + + # make sure EFO went in to table correctly + efo = conn.execute_fetchall("SELECT efo_id FROM " + "qiita.study_experimental_factor WHERE " + "study_id = 2") + obsefo = [x[0] for x in efo] + self.assertEqual(obsefo, [1, 2]) + + # check the investigation was assigned + obs3 = conn.execute_fetchall("SELECT * from qiita.investigation_study " + "WHERE study_id = 2") + self.assertEqual(obs3, [[1, 2]]) + + def test_create_study_all_data(self): + """Insert a study into the database with every info field""" + self.info.update({ + 'vamps_id': 1111111, + 'funding': 'FundAgency', + 'spatial_series': True, + 'metadata_complete': False, + }) + obs = Study.create(User('test@foo.bar'), self.info) self.assertEqual(obs.id, 2) + exp = {'mixs_compliant': True, 'metadata_complete': False, + 'reprocess': False, 'study_status_id': 1, + 'number_samples_promised': 28, 'emp_person_id': 2, + 'funding': 'FundAgency', 'vamps_id': '1111111', + 'first_contact': 'May 30, 2014', + 'principal_investigator_id': 3,'timeseries_type_id': 1, + 'study_abstract': ('We wanted to see if we could get funding ' + 'for giving people heart attacks'), + 'email': 'test@foo.bar', 'spatial_series': True, + 'study_description': ('Microbiome of people who eat nothing ' + 'but fried chicken'), + 'portal_type_id': 3, 'study_alias': 'FCM', 'study_id': 2, + 'most_recent_contact': None, 'lab_person_id': 1, + 'study_title': 'Fried chicken microbiome', + 'number_samples_collected': 25} + conn = SQLConnectionHandler() + obsins = dict(conn.execute_fetchone("SELECT * FROM qiita.study WHERE " + "study_id = 2")) + self.assertEqual(obsins, exp) - def test_delete(self): + #make sure EFO went in to table correctly + efo = conn.execute_fetchall("SELECT efo_id FROM " + "qiita.study_experimental_factor WHERE " + "study_id = 2") + obsefo = [x[0] for x in efo] + self.assertEqual(obsefo, [1, 2]) + + def test_delete_study(self): raise NotImplementedError() def test_insert_missing_requred(self): @@ -73,50 +234,78 @@ def test_set_title(self): def test_retrieve_info(self): exp = { - 'mixs_compliant': True, - 'metadata_complete': True, - 'reprocess': False, - 'study_status_id': 2, - 'number_samples_promised': 27, - 'emp_person_id': 2L, - 'funding': None, - 'vamps_id': None, + 'mixs_compliant': True, 'metadata_complete': True, + 'reprocess': False, 'study_status_id': 2, + 'number_samples_promised': 27, 'emp_person_id': StudyPerson(2), + 'funding': None, 'vamps_id': None, 'first_contact': '2014-05-19 16:10', - 'principal_investigator_id': 3, + 'principal_investigator_id': StudyPerson(3), 'timeseries_type_id': 1, - 'study_abstract': ('This is a preliminary study to examine the ' - 'microbiota associated with the Cannabis plant.' - ' Soils samples from the bulk soil, soil ' - 'associated with the roots, and the rhizosphere' - ' were extracted and the DNA sequenced. Roots ' - 'from three independent plants of different ' - 'strains were examined. These roots were ' - 'obtained November 11, 2011 from plants that ' - 'had been harvested in the summer. Future ' - 'studies will attempt to analyze the soils and ' - 'rhizospheres from the same location at ' - 'different time points in the plant ' - 'lifecycle.'), - 'email': 'test@foo.bar', - 'spatial_series': False, - 'study_description': 'Analysis of the Cannabis Plant Microbiome', - 'study_experimental_factor': [1], - 'portal_type_id': 2, + 'study_abstract': ("This is a preliminary study to examine the " + "microbiota associated with the Cannabis plant." + " Soils samples from the bulk soil, soil " + "associated with the roots, and the rhizosphere" + " were extracted and the DNA sequenced. Roots " + "from three independent plants of different " + "strains were examined. These roots were " + "obtained November 11, 2011 from plants that " + "had been harvested in the summer. Future " + "studies will attempt to analyze the soils and " + "rhizospheres from the same location at diff" + "erent time points in the plant lifecycle."), + 'email': User('test@foo.bar'), 'spatial_series': False, + 'study_description': ('Analysis of the Cannabis Plant Microbiome'), + 'study_experimental_factor': [1], 'portal_type_id': 2, 'study_alias': 'Cannabis Soils', - 'study_id': 1, 'most_recent_contact': '2014-05-19 16:11', - 'lab_person_id': 1, + 'lab_person_id': StudyPerson(1), 'study_title': ('Identification of the Microbiomes for Cannabis ' 'Soils'), 'number_samples_collected': 27} - self.assertEqual(self.study.info, exp) def test_set_info(self): - raise NotImplementedError() + newinfo = { + "timeseries_type_id": 2, + "study_experimental_factor": [3, 4], + "metadata_complete": False, + "number_samples_collected": 28, + "lab_person_id": StudyPerson(2) + } + + exp = { + 'mixs_compliant': True, 'metadata_complete': False, + 'reprocess': False, 'study_status_id': 2, + 'number_samples_promised': 27, 'emp_person_id': StudyPerson(2), + 'funding': None, 'vamps_id': None, + 'first_contact': '2014-05-19 16:10', + 'principal_investigator_id': StudyPerson(3), + 'timeseries_type_id': 1, + 'study_abstract': ("This is a preliminary study to examine the " + "microbiota associated with the Cannabis plant." + " Soils samples from the bulk soil, soil " + "associated with the roots, and the rhizosphere" + " were extracted and the DNA sequenced. Roots " + "from three independent plants of different " + "strains were examined. These roots were " + "obtained November 11, 2011 from plants that " + "had been harvested in the summer. Future " + "studies will attempt to analyze the soils and " + "rhizospheres from the same location at diff" + "erent time points in the plant lifecycle."), + 'email': User('test@foo.bar'), 'spatial_series': False, + 'study_description': ('Analysis of the Cannabis Plant Microbiome'), + 'study_experimental_factor': [1, 3, 4], 'portal_type_id': 2, + 'study_alias': 'Cannabis Soils', + 'most_recent_contact': '2014-05-19 16:11', + 'lab_person_id': StudyPerson(2), + 'study_title': ('Identification of the Microbiomes for Cannabis ' + 'Soils'), + 'number_samples_collected': 28} + + self.study.info = newinfo + self.assertEqual(self.study.info, exp) - def test_set_info_with_env_factors(self): - raise NotImplementedError() def test_retrieve_status(self): self.assertEqual(self.study.status, 2) From 7748910a036b63ac15ee03bcb55bb69d93f255e6 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 2 Jun 2014 12:05:27 -0600 Subject: [PATCH 23/57] more test code and bugfixes --- qiita_db/study.py | 14 +++------- qiita_db/test/test_study.py | 51 ++++++++----------------------------- 2 files changed, 14 insertions(+), 51 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 18672831d..d0bbb4009 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -21,7 +21,7 @@ from datetime import date from .base import QiitaStatusObject, QiitaObject -# from .data import RawData, PreprocessedData, ProcessedData +from .data import RawData, PreprocessedData, ProcessedData from .user import User from .util import check_required, check_table_cols, clean_sql_result @@ -93,7 +93,7 @@ def create(cls, owner, info, investigation=None): QiitaDBExecutionError All required keys not passed or non-db columns in info dictionary """ - #make sure not passing a study id in the info dict + # make sure not passing a study id in the info dict if "study_id" in info: raise QiitaStudyError("Can't pass study_id in info dict!") @@ -154,12 +154,6 @@ def delete(cls, id_): # drop sample_x dynamic table # delete study row from study table (cascades to everything else) - def __eq__(self, other): - return other._id == self._id - - def __ne__(self, other): - return other._id != self._id - # --- Attributes --- @property def title(self): @@ -237,15 +231,15 @@ def info(self, info): data.append(val.id) else: data.append(val) - sql = ' '.join((sql[-1], "WHERE study_id = %s")) + sql = ' '.join((sql[:-1], "WHERE study_id = %s")) data.append(self._id) + conn_handler.execute(sql, data) if efo: # insert efo information into database sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)".format(self._table)) conn_handler.executemany(sql, zip([self._id] * len(efo), efo)) - conn_handler.execute(sql, data) @property def status(self): diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 3f1b02524..284a873dc 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -3,6 +3,7 @@ from qiita_db.study import Study, StudyPerson from qiita_db.investigation import Investigation +from qiita_db.data import PreprocessedData, RawData, ProcessedData from qiita_db.user import User from qiita_core.util import qiita_test_checker from qiita_db.exceptions import QiitaDBExecutionError @@ -47,9 +48,6 @@ def test_create_studyperson_already_exists(self): self.assertEqual(obs["address"], '123 lab street') self.assertEqual(obs["phone"], '121-222-3333') - def test_delete_studyperson(self): - raise NotImplementedError() - def test_retrieve_name(self): self.assertEqual(self.studyperson.name, 'LabDude') @@ -124,10 +122,10 @@ def test_create_study_min_data(self): 'number_samples_collected': 25} conn = SQLConnectionHandler() obsins = dict(conn.execute_fetchone("SELECT * FROM qiita.study WHERE " - "study_id = 2")) + "study_id = 2")) self.assertEqual(obsins, exp) - #make sure EFO went in to table correctly + # make sure EFO went in to table correctly efo = conn.execute_fetchall("SELECT efo_id FROM " "qiita.study_experimental_factor WHERE " "study_id = 2") @@ -138,36 +136,8 @@ def test_create_study_with_investigation(self): """Insert a study into the database with an investigation""" obs = Study.create(User('test@foo.bar'), self.info, Investigation(1)) self.assertEqual(obs.id, 2) - exp = {'mixs_compliant': True, 'metadata_complete': True, - 'reprocess': False, 'study_status_id': 1, - 'number_samples_promised': 28, 'emp_person_id': 2, - 'funding': None, 'vamps_id': None, - 'first_contact': date.today().strftime("%B %d, %Y"), - 'principal_investigator_id': 3, 'timeseries_type_id': 1, - 'study_abstract': ('We wanted to see if we could get funding ' - 'for giving people heart attacks'), - 'email': 'test@foo.bar', 'spatial_series': None, - 'study_description': ('Microbiome of people who eat nothing but' - ' fried chicken'), - 'portal_type_id': 3, 'study_alias': 'FCM', 'study_id': 2, - 'most_recent_contact': None, 'lab_person_id': 1, - 'study_title': 'Fried chicken microbiome', - 'number_samples_collected': 25} - - # make sure study inserted correctly - conn = SQLConnectionHandler() - obsins = dict(conn.execute_fetchone("SELECT * FROM qiita.study WHERE " - "study_id = 2")) - self.assertEqual(obsins, exp) - - # make sure EFO went in to table correctly - efo = conn.execute_fetchall("SELECT efo_id FROM " - "qiita.study_experimental_factor WHERE " - "study_id = 2") - obsefo = [x[0] for x in efo] - self.assertEqual(obsefo, [1, 2]) - # check the investigation was assigned + conn = SQLConnectionHandler() obs3 = conn.execute_fetchall("SELECT * from qiita.investigation_study " "WHERE study_id = 2") self.assertEqual(obs3, [[1, 2]]) @@ -187,7 +157,7 @@ def test_create_study_all_data(self): 'number_samples_promised': 28, 'emp_person_id': 2, 'funding': 'FundAgency', 'vamps_id': '1111111', 'first_contact': 'May 30, 2014', - 'principal_investigator_id': 3,'timeseries_type_id': 1, + 'principal_investigator_id': 3, 'timeseries_type_id': 1, 'study_abstract': ('We wanted to see if we could get funding ' 'for giving people heart attacks'), 'email': 'test@foo.bar', 'spatial_series': True, @@ -199,10 +169,10 @@ def test_create_study_all_data(self): 'number_samples_collected': 25} conn = SQLConnectionHandler() obsins = dict(conn.execute_fetchone("SELECT * FROM qiita.study WHERE " - "study_id = 2")) + "study_id = 2")) self.assertEqual(obsins, exp) - #make sure EFO went in to table correctly + # make sure EFO went in to table correctly efo = conn.execute_fetchall("SELECT efo_id FROM " "qiita.study_experimental_factor WHERE " "study_id = 2") @@ -306,7 +276,6 @@ def test_set_info(self): self.study.info = newinfo self.assertEqual(self.study.info, exp) - def test_retrieve_status(self): self.assertEqual(self.study.status, 2) @@ -345,13 +314,13 @@ def test_retrieve_metadata(self): self.assertEqual(self.study.metadata, exp) def test_retrieve_raw_data(self): - raise NotImplementedError() + self.assertEqual(self.raw_data, [RawData(1)]) def test_retrieve_preprocessed_data(self): - raise NotImplementedError() + self.assertEqual(self.preprocessed_data, [PreprocessedData(1)]) def test_retrieve_processed_data(self): - raise NotImplementedError() + self.assertEqual(self.processed_data, [ProcessedData(1)]) def test_share_with(self): self.study.share_with('admin@foo.bar') From 843c4582a61bd4387d6ba96a6bbd49e3a57fc240 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 2 Jun 2014 13:44:50 -0600 Subject: [PATCH 24/57] initial complete study object --- qiita_db/study.py | 15 +++---- qiita_db/test/test_study.py | 87 +++++++++++++++++++------------------ 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index d0bbb4009..a6de3b135 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -25,6 +25,7 @@ from .user import User from .util import check_required, check_table_cols, clean_sql_result +from .metadata_template import SampleTemplate from .sql_connection import SQLConnectionHandler from qiita_core.exceptions import QiitaStudyError @@ -152,7 +153,8 @@ def delete(cls, id_): """ # delete raw data # drop sample_x dynamic table - # delete study row from study table (cascades to everything else) + # delete study row from study table (and cascade to satelite tables) + raise NotImplementedError() # --- Attributes --- @property @@ -299,11 +301,7 @@ def investigations(self): @property def metadata(self): """ Returns list of metadata columns """ - conn_handler = SQLConnectionHandler() - sql = ("SELECT column_name FROM qiita.{0}_sample_columns WHERE " - "study_id = %s".format(self._table)) - return clean_sql_result(conn_handler.execute_fetchall(sql, - (self._id, ))) + return SampleTemplate(self._id) @property def raw_data(self): @@ -335,8 +333,9 @@ def preprocessed_data(self): def processed_data(self): """ Returns list of data objects with processed data info """ conn_handler = SQLConnectionHandler() - sql = ("SELECT processed_data_id FROM qiita.study_processed_data" - " WHERE study_id = %s") + sql = ("SELECT processed_data_id FROM qiita.processed_data WHERE " + "preprocessed_data_id IN (SELECT preprocessed_data_id FROM " + "qiita.study_preprocessed_data where study_id = %s)") pro_ids = clean_sql_result(conn_handler.execute_fetchall(sql, (self._id, ))) processed_data = [] diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 284a873dc..a796f9ea8 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -4,6 +4,7 @@ from qiita_db.study import Study, StudyPerson from qiita_db.investigation import Investigation from qiita_db.data import PreprocessedData, RawData, ProcessedData +from qiita_db.metadata_template import SampleTemplate from qiita_db.user import User from qiita_core.util import qiita_test_checker from qiita_db.exceptions import QiitaDBExecutionError @@ -156,7 +157,7 @@ def test_create_study_all_data(self): 'reprocess': False, 'study_status_id': 1, 'number_samples_promised': 28, 'emp_person_id': 2, 'funding': 'FundAgency', 'vamps_id': '1111111', - 'first_contact': 'May 30, 2014', + 'first_contact': date.today().strftime("%B %d, %Y"), 'principal_investigator_id': 3, 'timeseries_type_id': 1, 'study_abstract': ('We wanted to see if we could get funding ' 'for giving people heart attacks'), @@ -170,6 +171,9 @@ def test_create_study_all_data(self): conn = SQLConnectionHandler() obsins = dict(conn.execute_fetchone("SELECT * FROM qiita.study WHERE " "study_id = 2")) + for thing, val in exp.iteritems(): + if val != obsins[thing]: + print thing, val, obsins[thing] self.assertEqual(obsins, exp) # make sure EFO went in to table correctly @@ -179,9 +183,6 @@ def test_create_study_all_data(self): obsefo = [x[0] for x in efo] self.assertEqual(obsefo, [1, 2]) - def test_delete_study(self): - raise NotImplementedError() - def test_insert_missing_requred(self): """ Insert a study that is missing a required info key""" self.info.pop("study_title") @@ -240,38 +241,44 @@ def test_set_info(self): "study_experimental_factor": [3, 4], "metadata_complete": False, "number_samples_collected": 28, - "lab_person_id": StudyPerson(2) + "lab_person_id": StudyPerson(2), + "vamps_id": 111222 } exp = { - 'mixs_compliant': True, 'metadata_complete': False, - 'reprocess': False, 'study_status_id': 2, - 'number_samples_promised': 27, 'emp_person_id': StudyPerson(2), - 'funding': None, 'vamps_id': None, - 'first_contact': '2014-05-19 16:10', - 'principal_investigator_id': StudyPerson(3), - 'timeseries_type_id': 1, - 'study_abstract': ("This is a preliminary study to examine the " - "microbiota associated with the Cannabis plant." - " Soils samples from the bulk soil, soil " - "associated with the roots, and the rhizosphere" - " were extracted and the DNA sequenced. Roots " - "from three independent plants of different " - "strains were examined. These roots were " - "obtained November 11, 2011 from plants that " - "had been harvested in the summer. Future " - "studies will attempt to analyze the soils and " - "rhizospheres from the same location at diff" - "erent time points in the plant lifecycle."), - 'email': User('test@foo.bar'), 'spatial_series': False, - 'study_description': ('Analysis of the Cannabis Plant Microbiome'), - 'study_experimental_factor': [1, 3, 4], 'portal_type_id': 2, - 'study_alias': 'Cannabis Soils', - 'most_recent_contact': '2014-05-19 16:11', - 'lab_person_id': StudyPerson(2), - 'study_title': ('Identification of the Microbiomes for Cannabis ' - 'Soils'), - 'number_samples_collected': 28} + 'mixs_compliant': True, + 'metadata_complete': False, + 'reprocess': False, + 'study_status_id': 2, + 'number_samples_promised': 27, + 'emp_person_id': StudyPerson(2), + 'funding': None, + 'vamps_id': '111222', + 'first_contact': '2014-05-19 16:10', + 'principal_investigator_id': StudyPerson(3), + 'timeseries_type_id': 2, + 'study_abstract': ('This is a preliminary study to examine the ' + 'microbiota associated with the Cannabis plant. ' + 'Soils samples from the bulk soil, soil ' + 'associated with the roots, and the rhizosphere ' + 'were extracted and the DNA sequenced. Roots from' + ' three independent plants of different strains ' + 'were examined. These roots were obtained ' + 'November 11, 2011 from plants that had been ' + 'harvested in the summer. Future studies will ' + 'attempt to analyze the soils and rhizospheres ' + 'from the same location at different time points ' + 'in the plant lifecycle.'), + 'email': User('test@foo.bar'), + 'spatial_series': False, + 'study_description': 'Analysis of the Cannabis Plant Microbiome', + 'study_experimental_factor': [1], + 'portal_type_id': 2, + 'study_alias': 'Cannabis Soils', + 'most_recent_contact': '2014-05-19 16:11', + 'lab_person_id': StudyPerson(2), + 'study_title':'Identification of the Microbiomes for Cannabis Soils', + 'number_samples_collected': 28} self.study.info = newinfo self.assertEqual(self.study.info, exp) @@ -305,22 +312,16 @@ def test_retrieve_investigations(self): self.assertEqual(self.study.investigations, [1]) def test_retrieve_metadata(self): - exp = ['LATITUDE', 'ENV_FEATURE', 'Description_duplicate', 'LONGITUDE', - 'TOT_ORG_CARB', 'ANONYMIZED_NAME', 'PH', 'COUNTRY', 'ENV_BIOME', - 'ALTITUDE', 'SAMP_SALINITY', 'TOT_NITRO', 'TEMP', 'ELEVATION', - 'WATER_CONTENT_SOIL', 'COMMON_NAME', 'HOST_TAXID', 'DEPTH', - 'TAXON_ID', 'TEXTURE', 'ASSIGNED_FROM_GEO', - 'SEASON_ENVIRONMENT', 'sample_id'] - self.assertEqual(self.study.metadata, exp) + self.assertEqual(self.study.metadata, SampleTemplate(1)) def test_retrieve_raw_data(self): - self.assertEqual(self.raw_data, [RawData(1)]) + self.assertEqual(self.study.raw_data, [RawData(1)]) def test_retrieve_preprocessed_data(self): - self.assertEqual(self.preprocessed_data, [PreprocessedData(1)]) + self.assertEqual(self.study.preprocessed_data, [PreprocessedData(1)]) def test_retrieve_processed_data(self): - self.assertEqual(self.processed_data, [ProcessedData(1)]) + self.assertEqual(self.study.processed_data, [ProcessedData(1)]) def test_share_with(self): self.study.share_with('admin@foo.bar') From 6d144b4684f51887ad936f8088b44da1c2e58836 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 2 Jun 2014 14:08:12 -0600 Subject: [PATCH 25/57] More tests, 99% coverage --- qiita_db/study.py | 14 ++------------ qiita_db/test/test_study.py | 24 ++++++++++++++++++------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index a6de3b135..fb09263e0 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -105,7 +105,7 @@ def create(cls, owner, info, investigation=None): efo = None if "study_experimental_factor" in info: efo = info["study_experimental_factor"] - if isinstance(efo, str): + if isinstance(efo, int): efo = [efo] info.pop("study_experimental_factor") else: @@ -217,7 +217,7 @@ def info(self, info): efo = None if "study_experimental_factor" in info: efo = info["study_experimental_factor"] - if not isinstance(efo, list) and not isinstance(efo, tuple): + if isinstance(efo, int): efo = [efo] info.pop("study_experimental_factor") @@ -403,16 +403,6 @@ def create(cls, name, email, address=None, phone=None): phone)) return cls(spid[0]) - @classmethod - def delete(cls, self): - raise NotImplementedError() - - def __eq__(self, other): - return other._id == self._id - - def __ne__(self, other): - return other._id != self._id - # Properties @property def name(self): diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index a796f9ea8..14de7a4b4 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -9,7 +9,7 @@ from qiita_core.util import qiita_test_checker from qiita_db.exceptions import QiitaDBExecutionError from qiita_db.sql_connection import SQLConnectionHandler - +from qiita_core.exceptions import QiitaStudyError # ----------------------------------------------------------------------------- # Copyright (c) 2014--, The Qiita Development Team. @@ -85,7 +85,7 @@ def setUp(self): self.info = { "timeseries_type_id": 1, - "study_experimental_factor": [1, 2], + "study_experimental_factor": 1, "metadata_complete": True, "mixs_compliant": True, "number_samples_collected": 25, @@ -131,7 +131,7 @@ def test_create_study_min_data(self): "qiita.study_experimental_factor WHERE " "study_id = 2") obsefo = [x[0] for x in efo] - self.assertEqual(obsefo, [1, 2]) + self.assertEqual(obsefo, [1]) def test_create_study_with_investigation(self): """Insert a study into the database with an investigation""" @@ -181,15 +181,27 @@ def test_create_study_all_data(self): "qiita.study_experimental_factor WHERE " "study_id = 2") obsefo = [x[0] for x in efo] - self.assertEqual(obsefo, [1, 2]) + self.assertEqual(obsefo, [1]) - def test_insert_missing_requred(self): + def test_create_missing_requred(self): """ Insert a study that is missing a required info key""" self.info.pop("study_title") self.assertRaises(RuntimeError, Study.create, 'test@foo.bar', self.info) - def test_insert_unknown_db_col(self): + def test_create_study_id(self): + """Insert a study with study_id present""" + self.info.update({"study_id": 1}) + self.assertRaises(QiitaStudyError, Study.create, 'test@foo.bar', + self.info) + + def test_create_no_efo(self): + """Insert a study no experimental factors passed""" + self.info.pop("study_experimental_factor") + self.assertRaises(QiitaStudyError, Study.create, 'test@foo.bar', + self.info) + + def test_create_unknown_db_col(self): """ Insert a study with an info key not in the database""" self.info["SHOULDNOTBEHERE"] = "BWAHAHAHAHAHA" self.assertRaises(QiitaDBExecutionError, Study.create, 'test@foo.bar', From 61ed70bed64d7567e3749eb7cb71675930c7e864 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 2 Jun 2014 14:22:40 -0600 Subject: [PATCH 26/57] Docstring stuff --- qiita_db/study.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index fb09263e0..cb1f2e021 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -1,15 +1,24 @@ from __future__ import division -""" -Objects for dealing with Qiita studies +"""Objects for dealing with Qiita studies -This module provides the implementation of the Study class. +This module provides the implementation of the Study class. It allows access to +all basic information including name and pmids associated with the study, as +well as returning objects for the data, metadata, owner, and shared users. It +is the central hub for creating, deleting, and accessing a study in the +database. Classes ------- -- `QittaStudy` -- A Qiita study class + +.. autosummary:: + :toctree: generated/ + + Study + StudyPerson """ + # ----------------------------------------------------------------------------- # Copyright (c) 2014--, The Qiita Development Team. # @@ -85,7 +94,7 @@ def create(cls, owner, info, investigation=None): owner : User object the user id of the study' owner info: dict - the information attached to the study + the information attached to the study. investigation_id: Investigation object if the study is part of an investigation, the id to associate with @@ -93,6 +102,12 @@ def create(cls, owner, info, investigation=None): ------ QiitaDBExecutionError All required keys not passed or non-db columns in info dictionary + + Notes + ----- + All keys in info, except the efo, must be equal to columns in the + database. EFO information is stored as a list under the key + 'study_experimental_factor', the name of the table it is stored in. """ # make sure not passing a study id in the info dict if "study_id" in info: From 7f8bfb383d746f4136c1944841d993325a1f3511 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 2 Jun 2014 17:25:01 -0600 Subject: [PATCH 27/57] ALL THE DOCSTRINGS --- qiita_db/study.py | 251 +++++++++++++++++++++++++++--------- qiita_db/test/test_study.py | 10 +- qiita_db/util.py | 18 +-- 3 files changed, 198 insertions(+), 81 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index cb1f2e021..33fcde352 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -1,6 +1,10 @@ from __future__ import division -"""Objects for dealing with Qiita studies +r""" +Study and StudyPerson objects (:mod:`qiita_db.study`) +================================================================ + +.. currentmodule:: skbio.core.distance This module provides the implementation of the Study class. It allows access to all basic information including name and pmids associated with the study, as @@ -17,6 +21,47 @@ Study StudyPerson + +Examples +-------- +Studdies are attached to people. These people have names, emails, addresses, +and phone numbers. The email and name are the minimum required information. +>>> person = StudyPerson.create('Some Dude', 'somedude@foo.bar', + address='111 fake street', + phone='111-121-1313') # doctest: +SKIP +>>> person.name # doctest: +SKIP +Some dude +>>> person.email # doctest: +SKIP +somedude@foobar +>>> person.address # doctest: +SKIP +111 fake street +>>> person.phone # doctest: +SKIP +111-121-1313 + +A study requres a minimum of information to be created. Note that the people +must be passed as StudyPerson objects and the owner as a User object. +>>> info = { +... "timeseries_type_id": 1, +... "study_experimental_factor": 1, +... "metadata_complete": True, +... "mixs_compliant": True, +... "number_samples_collected": 25, +... "number_samples_promised": 28, +... "portal_type_id": 3, +... "study_title": "Study Title", +... "study_alias": "TST", +... "study_description": ("Some description of the study goes here"), +... "study_abstract": ("Some abstract goes here"), +... "emp_person_id": StudyPerson(2), +... "principal_investigator_id": StudyPerson(3), +... "lab_person_id": StudyPerson(1)} # doctest: +SKIP +>>> owner = User('owner@foo.bar') # doctest: +SKIP +>>> Study(owner, info) # doctest: +SKIP + +You can also add a study to an investigation by passing the investigation +object while creating the study. +>>> investigation = Investigation(1) # doctest: +SKIP +>>> Study(owner, info, investigation) # doctest: +SKIP """ # ----------------------------------------------------------------------------- @@ -32,8 +77,8 @@ from .base import QiitaStatusObject, QiitaObject from .data import RawData, PreprocessedData, ProcessedData from .user import User - -from .util import check_required, check_table_cols, clean_sql_result +from .investigation import Investigation +from .util import check_required, check_table_cols from .metadata_template import SampleTemplate from .sql_connection import SQLConnectionHandler from qiita_core.exceptions import QiitaStudyError @@ -60,26 +105,26 @@ class Study(QiitaStatusObject): Status of the study sample_ids: list of str All sample_ids associated with the study - shared_with: list of str + shared_with: list of User objects Emails of users the study is shared with pmids: list of str PMIDs assiciated with the study - investigations: list of int - Investigation ids of all investigations study is part of - metadata: list of str - Metadata column names available from study - raw_data: list of data objects - preprocessed_data: list of data objects - processed_data: list of data objects + investigations: list of Investigation objects + All investigations study is part of + metadata: Metadata object + Metadata object tied to this study + raw_data: list of RawData objects + All raw data attached to the study + preprocessed_data: list of PreprocessedData objects + All preprocessed data attached to the study + processed_data: list of ProcessedData objects + All processed data attached to the study Methods ------- - share_with(email) + share_with(User_obj) Shares the study with given user - def add_raw_data(raw_data_id): - Associates raw data with the study - add_pmid(self, pmid): Adds PMID to study """ @@ -106,8 +151,9 @@ def create(cls, owner, info, investigation=None): Notes ----- All keys in info, except the efo, must be equal to columns in the - database. EFO information is stored as a list under the key - 'study_experimental_factor', the name of the table it is stored in. + forge.study table in the database. EFO information is stored as a list + under the key 'study_experimental_factor', the name of the table it is + stored in. """ # make sure not passing a study id in the info dict if "study_id" in info: @@ -174,7 +220,12 @@ def delete(cls, id_): # --- Attributes --- @property def title(self): - """Returns the title of the study""" + """Returns the title of the study + + Returns + ------- + str: title of study + """ conn_handler = SQLConnectionHandler() sql = ("SELECT study_title FROM qiita.{0} WHERE " "study_id = %s".format(self._table)) @@ -196,11 +247,16 @@ def title(self, title): @property def info(self): - """Dict with all information attached to the study""" + """Dict with all information attached to the study + + Returns + ------- + dict: info of study keyed to column names + """ conn_handler = SQLConnectionHandler() sql = "SELECT * FROM qiita.{0} WHERE study_id = %s".format(self._table) info = dict(conn_handler.execute_fetchone(sql, (self._id, ))) - efo = clean_sql_result(conn_handler.execute_fetchall(sql, (self._id,))) + efo = [x[0] for x in conn_handler.execute_fetchall(sql, (self._id,))] info["study_experimental_factor"] = efo # Convert everything from ids to objects @@ -225,6 +281,7 @@ def info(self, info): Parameters ---------- info : dict + information to change/update for the study, keyed to column name """ conn_handler = SQLConnectionHandler() @@ -260,7 +317,12 @@ def info(self, info): @property def status(self): - """Returns the study_status_id for the study""" + """Returns the study_status_id for the study + + Returns + ------- + int: status of study + """ conn_handler = SQLConnectionHandler() sql = ("SELECT study_status_id FROM qiita.{0} WHERE " "study_id = %s".format(self._table)) @@ -268,7 +330,13 @@ def status(self): @status.setter def status(self, status_id): - """Sets the study_status_id for the study""" + """Sets the study_status_id for the study + + Parameters + ---------- + status_id: int + ID for the new status + """ conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET study_status_id = %s WHERE " "study_id = %s".format(self._table)) @@ -278,88 +346,117 @@ def status(self, status_id): def sample_ids(self): """Returns the IDs of all samples in study - The sample IDs are returned as a list of strings in alphabetical order. + Returns + ------- + list of str + The sample IDs in alphabetical order. """ conn_handler = SQLConnectionHandler() sql = ("SELECT sample_id FROM qiita.required_sample_info WHERE " "study_id = %s ORDER BY sample_id".format(self._table)) - return clean_sql_result(conn_handler.execute_fetchall(sql, - (self._id, ))) + return [x[0] for x in conn_handler.execute_fetchall(sql, (self._id, ))] @property def shared_with(self): - """list of users the study is shared with""" + """list of users the study is shared with + + Returns + ------- + list of User objects + Users the study is shared with + """ conn_handler = SQLConnectionHandler() sql = ("SELECT email FROM qiita.{0}_users WHERE " "study_id = %s".format(self._table)) - return clean_sql_result(conn_handler.execute_fetchall(sql, - (self._id, ))) + users = [x[0] for x in conn_handler.execute_fetchall(sql, (self._id,))] + return [User(email) for email in users] @property def pmids(self): - """ Returns list of paper PMIDs from this study """ + """ Returns list of paper PMIDs from this study + + Returns + ------- + list of str + list of all the PMIDs + """ conn_handler = SQLConnectionHandler() sql = ("SELECT pmid FROM qiita.{0}_pmid WHERE " "study_id = %s".format(self._table)) - return clean_sql_result(conn_handler.execute_fetchall(sql, - (self._id, ))) + return [x[0] for x in conn_handler.execute_fetchall(sql, (self._id, ))] @property def investigations(self): - """ Returns list of investigation_id this study is part of """ + """ Returns list of investigations this study is part of + + Returns + ------- + list of Investigation objects + """ conn_handler = SQLConnectionHandler() sql = ("SELECT investigation_id FROM qiita.investigation_study WHERE " "study_id = %s") - return clean_sql_result(conn_handler.execute_fetchall(sql, - (self._id, ))) + invs = conn_handler.execute_fetchall(sql, (self._id, )) + return [Investigation(inv[0]) for inv in invs] @property def metadata(self): - """ Returns list of metadata columns """ + """ Returns list of metadata columns + + Returns + ------- + SampleTemplate object + """ return SampleTemplate(self._id) @property def raw_data(self): - """ Returns list of data objects with raw data info """ + """ Returns list of data objects with raw data info + + Returns + ------- + list of RawData objects + """ conn_handler = SQLConnectionHandler() sql = ("SELECT raw_data_id FROM qiita.study_raw_data WHERE " "study_id = %s") - raw_ids = clean_sql_result(conn_handler.execute_fetchall(sql, - (self._id, ))) - raw_data = [] - for rid in raw_ids: - raw_data.append(RawData(rid)) - return raw_data + raw_ids = [x[0] for x in conn_handler.execute_fetchall(sql, + (self._id, ))] + return [RawData(rid) for rid in raw_ids] @property def preprocessed_data(self): - """ Returns list of data objects with preprocessed data info """ + """ Returns list of data objects with preprocessed data info + + Returns + ------- + list of PreprocessedData objects + """ conn_handler = SQLConnectionHandler() sql = ("SELECT preprocessed_data_id FROM qiita.study_preprocessed_data" " WHERE study_id = %s") - pre_ids = clean_sql_result(conn_handler.execute_fetchall(sql, - (self._id, ))) - preprocessed_data = [] - for pid in pre_ids: - preprocessed_data.append(PreprocessedData(pid)) - return preprocessed_data + pre_ids = [x[0] for x in conn_handler.execute_fetchall(sql, + (self._id,))] + return [PreprocessedData(pid) for pid in pre_ids] @property def processed_data(self): - """ Returns list of data objects with processed data info """ + """ Returns list of data objects with processed data info + + Returns + ------- + list of ProcessedData objects + """ conn_handler = SQLConnectionHandler() sql = ("SELECT processed_data_id FROM qiita.processed_data WHERE " "preprocessed_data_id IN (SELECT preprocessed_data_id FROM " "qiita.study_preprocessed_data where study_id = %s)") - pro_ids = clean_sql_result(conn_handler.execute_fetchall(sql, - (self._id, ))) - processed_data = [] - for pid in pro_ids: - processed_data.append(ProcessedData(pid)) - return processed_data + pro_ids = [x[0] for x in conn_handler.execute_fetchall(sql, + (self._id,))] + return [ProcessedData(pid) for pid in pro_ids] # --- methods --- - def share_with(self, email): + def share_with(self, user): """Shares the study with given user Parameters @@ -370,7 +467,7 @@ def share_with(self, email): conn_handler = SQLConnectionHandler() sql = ("INSERT INTO qiita.study_users (study_id, email) VALUES " "(%s, %s)") - conn_handler.execute(sql, (self._id, email)) + conn_handler.execute(sql, (self._id, user.id)) def add_pmid(self, pmid): """Adds PMID to study @@ -404,6 +501,23 @@ class StudyPerson(QiitaObject): @classmethod def create(cls, name, email, address=None, phone=None): + """Create a StudyPerson object, checking if person already exists. + + Parameters + ---------- + name: str + name of person + email: str + email of person + address: str, optional + address of person + phone: str, optional + phone number of person + + Returns + ------- + New StudyPerson object + """ # Make sure person doesn't already exist conn_handler = SQLConnectionHandler() sql = ("SELECT study_person_id FROM qiita.{0} WHERE name = %s AND " @@ -421,6 +535,7 @@ def create(cls, name, email, address=None, phone=None): # Properties @property def name(self): + """Returns the name of the person""" conn_handler = SQLConnectionHandler() sql = ("SELECT name FROM qiita.{0} WHERE " "study_person_id = %s".format(self._table)) @@ -428,6 +543,7 @@ def name(self): @name.setter def name(self, value): + """Changes the name of the person""" conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET name = %s WHERE " "study_person_id = %s".format(self._table)) @@ -435,6 +551,7 @@ def name(self, value): @property def email(self): + """Returns the email of the person""" conn_handler = SQLConnectionHandler() sql = ("SELECT email FROM qiita.{0} WHERE " "study_person_id = %s".format(self._table)) @@ -442,6 +559,7 @@ def email(self): @email.setter def email(self, value): + """Changes the name of the person""" conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET email = %s WHERE " "study_person_id = %s".format(self._table)) @@ -449,13 +567,22 @@ def email(self, value): @property def address(self): + """Returns the address of the person + + Returns: str or None + address or None if no address in database + """ conn_handler = SQLConnectionHandler() sql = ("SELECT address FROM qiita.{0} WHERE study_person_id =" " %s".format(self._table)) - return conn_handler.execute_fetchone(sql, (self._id, ))[0] + address = conn_handler.execute_fetchone(sql, (self._id, )) + if address is not None: + return address[0] + return None @address.setter def address(self, value): + """Set/update the address of the person""" conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET address = %s WHERE " "study_person_id = %s".format(self._table)) @@ -463,6 +590,11 @@ def address(self, value): @property def phone(self): + """Returns the phone number of the person + + Returns: str or None + phone or None if no address in database + """ conn_handler = SQLConnectionHandler() sql = ("SELECT phone FROM qiita.{0} WHERE " "study_person_id = %s".format(self._table)) @@ -470,6 +602,7 @@ def phone(self): @phone.setter def phone(self, value): + """Set/update the address of the person""" conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET phone = %s WHERE " "study_person_id = %s".format(self._table)) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 14de7a4b4..edfcd167d 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -314,14 +314,14 @@ def test_retrieve_sample_ids(self): self.assertEqual(obs, exp) def test_retrieve_shared_with(self): - self.assertEqual(self.study.shared_with, ['shared@foo.bar']) + self.assertEqual(self.study.shared_with, [User('shared@foo.bar')]) def test_retrieve_pmids(self): exp = ['123456', '7891011'] self.assertEqual(self.study.pmids, exp) def test_retrieve_investigations(self): - self.assertEqual(self.study.investigations, [1]) + self.assertEqual(self.study.investigations, [Investigation(1)]) def test_retrieve_metadata(self): self.assertEqual(self.study.metadata, SampleTemplate(1)) @@ -336,9 +336,9 @@ def test_retrieve_processed_data(self): self.assertEqual(self.study.processed_data, [ProcessedData(1)]) def test_share_with(self): - self.study.share_with('admin@foo.bar') - self.assertEqual(self.study.shared_with, ['shared@foo.bar', - 'admin@foo.bar']) + self.study.share_with(User('admin@foo.bar')) + self.assertEqual(self.study.shared_with, [User('shared@foo.bar'), + User('admin@foo.bar')]) def test_add_pmid(self): self.study.add_pmid("4544444") diff --git a/qiita_db/util.py b/qiita_db/util.py index e0b74b1c9..2a68edc51 100644 --- a/qiita_db/util.py +++ b/qiita_db/util.py @@ -53,22 +53,6 @@ def scrub_data(s): return ret -def clean_sql_result(results): - """Parses single value list of lists from psycopg2 and returns list of - items - - Parameters - ---------- - results: list of lists - list in the form [[item1], [item2], [item3], ...] - - Returns - ------- - list: [item1, item2, item3, ...] - """ - return [i[0] for i in results] - - def check_required(keys, required): """Makes sure all required columns are in a list @@ -110,7 +94,7 @@ def check_table_cols(conn_handler, keys, table): """ sql = ("SELECT column_name FROM information_schema.columns WHERE " "table_name = %s") - cols = clean_sql_result(conn_handler.execute_fetchall(sql, (table, ))) + cols = [x[0] for x in conn_handler.execute_fetchall(sql, (table, ))] if len(cols) == 0: raise RuntimeError("Unable to fetch column names for table %s" % table) if len(set(keys).difference(cols)) > 0: From ac49f38dc7c80c0ab9766f29c53f403251d2b6f1 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 2 Jun 2014 17:29:48 -0600 Subject: [PATCH 28/57] EVEN MORE DOCSTRINGS --- qiita_db/study.py | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 33fcde352..b008e6bd3 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -535,7 +535,12 @@ def create(cls, name, email, address=None, phone=None): # Properties @property def name(self): - """Returns the name of the person""" + """Returns the name of the person + + Returns + ------- + str: name of person + """ conn_handler = SQLConnectionHandler() sql = ("SELECT name FROM qiita.{0} WHERE " "study_person_id = %s".format(self._table)) @@ -543,7 +548,13 @@ def name(self): @name.setter def name(self, value): - """Changes the name of the person""" + """Changes the name of the person + + Parameters + ---------- + value: str + New name for person + """ conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET name = %s WHERE " "study_person_id = %s".format(self._table)) @@ -551,7 +562,12 @@ def name(self, value): @property def email(self): - """Returns the email of the person""" + """Returns the email of the person + + Returns + ------- + str: email of person + """ conn_handler = SQLConnectionHandler() sql = ("SELECT email FROM qiita.{0} WHERE " "study_person_id = %s".format(self._table)) @@ -559,7 +575,13 @@ def email(self): @email.setter def email(self, value): - """Changes the name of the person""" + """Changes the name of the person + + Parameters + ---------- + value: str + New email for person + """ conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET email = %s WHERE " "study_person_id = %s".format(self._table)) @@ -582,7 +604,13 @@ def address(self): @address.setter def address(self, value): - """Set/update the address of the person""" + """Set/update the address of the person + + Parameters + ---------- + value: str + New address for person + """ conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET address = %s WHERE " "study_person_id = %s".format(self._table)) @@ -602,7 +630,13 @@ def phone(self): @phone.setter def phone(self, value): - """Set/update the address of the person""" + """Set/update the phone number of the person + + Parameters + ---------- + value: str + New phone number for person + """ conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET phone = %s WHERE " "study_person_id = %s".format(self._table)) From db5eaff359ea915dad6606bbac7ef9b79579f60b Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 2 Jun 2014 17:37:55 -0600 Subject: [PATCH 29/57] slight fix --- qiita_db/study.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index b008e6bd3..0b0faa548 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -92,8 +92,7 @@ class Study(QiitaStatusObject): - """ - Study object to access to the Qiita Study information + r"""Study object to access to the Qiita Study information Attributes ---------- @@ -484,7 +483,7 @@ def add_pmid(self, pmid): class StudyPerson(QiitaObject): - """Object handling information pertaining to people involved in a study + r"""Object handling information pertaining to people involved in a study Attributes ---------- @@ -591,7 +590,9 @@ def email(self, value): def address(self): """Returns the address of the person - Returns: str or None + Returns + ------- + str or None address or None if no address in database """ conn_handler = SQLConnectionHandler() @@ -620,7 +621,9 @@ def address(self, value): def phone(self): """Returns the phone number of the person - Returns: str or None + Returns + ------- + str or None phone or None if no address in database """ conn_handler = SQLConnectionHandler() From 17e8220b2ffbde3b1546dde559af16e82c3fe136 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 2 Jun 2014 17:47:33 -0600 Subject: [PATCH 30/57] fix copy/paste issue --- qiita_db/study.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 0b0faa548..86ed5d5bc 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -4,7 +4,7 @@ Study and StudyPerson objects (:mod:`qiita_db.study`) ================================================================ -.. currentmodule:: skbio.core.distance +.. currentmodule:: qiita_db.study This module provides the implementation of the Study class. It allows access to all basic information including name and pmids associated with the study, as From 3c24b1d74b241fcd3e4169ec32305c42ee8611b4 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 3 Jun 2014 16:50:40 -0600 Subject: [PATCH 31/57] Take care of @josenavas suggestions --- qiita_db/exceptions.py | 4 + qiita_db/study.py | 260 ++++++++++---------- qiita_db/support_files/populate_test_db.sql | 2 +- qiita_db/test/test_study.py | 219 ++++++++--------- qiita_db/test/test_util.py | 49 ++++ qiita_db/util.py | 57 +++-- 6 files changed, 327 insertions(+), 264 deletions(-) create mode 100644 qiita_db/test/test_util.py diff --git a/qiita_db/exceptions.py b/qiita_db/exceptions.py index 54b65607d..eab23f0a6 100644 --- a/qiita_db/exceptions.py +++ b/qiita_db/exceptions.py @@ -35,3 +35,7 @@ class QiitaDBStatusError(QiitaDBError): class QiitaDBConnectionError(QiitaDBError): """Exception for error when connecting to the db""" pass + +class QiitaDBColumnError(QiitaDBError): + """Exception when missing table information or excess information passed""" + pass diff --git a/qiita_db/study.py b/qiita_db/study.py index 86ed5d5bc..d3e59f336 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -2,7 +2,7 @@ r""" Study and StudyPerson objects (:mod:`qiita_db.study`) -================================================================ +===================================================== .. currentmodule:: qiita_db.study @@ -24,11 +24,14 @@ Examples -------- -Studdies are attached to people. These people have names, emails, addresses, -and phone numbers. The email and name are the minimum required information. +Studies contain contact people (PIs, Lab members, and EBI contacts). These +people have names, emails, addresses, and phone numbers. The email and name are +the minimum required information. + +>>> from qiita_db.study import StudyPerson # doctest: +SKIP >>> person = StudyPerson.create('Some Dude', 'somedude@foo.bar', - address='111 fake street', - phone='111-121-1313') # doctest: +SKIP +... address='111 fake street', +... phone='111-121-1313') # doctest: +SKIP >>> person.name # doctest: +SKIP Some dude >>> person.email # doctest: +SKIP @@ -40,6 +43,10 @@ A study requres a minimum of information to be created. Note that the people must be passed as StudyPerson objects and the owner as a User object. + +>>> from qiita_db.study import Study # doctest: +SKIP +>>> from qiita_db.user import User # doctest: +SKIP +>>> from qiita_db.study import Investigation # doctest: +SKIP >>> info = { ... "timeseries_type_id": 1, ... "study_experimental_factor": 1, @@ -60,6 +67,7 @@ You can also add a study to an investigation by passing the investigation object while creating the study. + >>> investigation = Investigation(1) # doctest: +SKIP >>> Study(owner, info, investigation) # doctest: +SKIP """ @@ -72,23 +80,20 @@ # The full license is in the file LICENSE, distributed with this software. # ----------------------------------------------------------------------------- +from future_builtins import zip from datetime import date +from qiita_core.exceptions import (QiitaStudyError, + IncompetentQiitaDeveloperError) from .base import QiitaStatusObject, QiitaObject +from .exceptions import (QiitaDBColumnError, QiitaDBNotImplementedError, + QiitaDBExecutionError) from .data import RawData, PreprocessedData, ProcessedData from .user import User from .investigation import Investigation -from .util import check_required, check_table_cols +from .util import check_required_columns, check_table_cols from .metadata_template import SampleTemplate from .sql_connection import SQLConnectionHandler -from qiita_core.exceptions import QiitaStudyError - - -REQUIRED_KEYS = {"timeseries_type_id", "lab_person_id", "mixs_compliant", - "metadata_complete", "number_samples_collected", - "number_samples_promised", "portal_type_id", - "principal_investigator_id", "study_title", "study_alias", - "study_description", "study_abstract"} class Study(QiitaStatusObject): @@ -102,14 +107,12 @@ class Study(QiitaStatusObject): Major information about the study, keyed by db column name status: int Status of the study - sample_ids: list of str - All sample_ids associated with the study shared_with: list of User objects Emails of users the study is shared with pmids: list of str PMIDs assiciated with the study - investigations: list of Investigation objects - All investigations study is part of + investigation: Investigation object + Investigation the study is part of metadata: Metadata object Metadata object tied to this study raw_data: list of RawData objects @@ -121,11 +124,8 @@ class Study(QiitaStatusObject): Methods ------- - share_with(User_obj) - Shares the study with given user - - add_pmid(self, pmid): - Adds PMID to study + share_with + add_pmid """ _table = "study" @@ -144,8 +144,13 @@ def create(cls, owner, info, investigation=None): Raises ------ - QiitaDBExecutionError - All required keys not passed or non-db columns in info dictionary + QiitaDBColumnError + Non-db columns in info dictionary + All required keys not passed + QiitaStudyError + Study already exists + IncompetentQiitaDeveloperError + study_id or status passed as a key Notes ----- @@ -156,10 +161,11 @@ def create(cls, owner, info, investigation=None): """ # make sure not passing a study id in the info dict if "study_id" in info: - raise QiitaStudyError("Can't pass study_id in info dict!") - - # make sure required keys are in the info dict - check_required(info, REQUIRED_KEYS) + raise IncompetentQiitaDeveloperError("Can't pass study_id in info " + "dict!") + if "study_status_id" in info: + raise IncompetentQiitaDeveloperError("Can't pass status in info " + "dict!") # Save study_experimental_factor data for insertion efo = None @@ -169,30 +175,38 @@ def create(cls, owner, info, investigation=None): efo = [efo] info.pop("study_experimental_factor") else: - raise QiitaStudyError("EFO information is required!") + raise QiitaDBColumnError("EFO info not passed!") + + # add default values to info + info['email'] = owner.id + info['reprocess'] = False + info['first_contact'] = date.today().strftime("%B %d, %Y") + info['study_status_id'] = 1 conn_handler = SQLConnectionHandler() # make sure dictionary only has keys for available columns in db check_table_cols(conn_handler, info, "study") + # make sure reqired columns in dictionary + check_required_columns(conn_handler, info, "study") # Insert study into database - sql = ("INSERT INTO qiita.{0} (email,study_status_id,first_contact," - "reprocess, %s) VALUES (%s) RETURNING " - "study_id".format(cls._table) % - (','.join(info.keys()), ','.join(['%s'] * (len(info)+4)))) + keys = info.keys() + sql = ("INSERT INTO qiita.{0} ({1}) VALUES ({2}) RETURNING " + "study_id".format(cls._table, ','.join(keys), + ','.join(['%s'] * len(info)))) # make sure data in same order as sql column names, and ids are used - data = [owner.id, 1, date.today().strftime("%B %d, %Y"), 'FALSE'] - for col, val in info.items(): - if isinstance(val, QiitaObject): - data.append(val.id) + data = [] + for col in keys: + if isinstance(info[col], QiitaObject): + data.append(info[col].id) else: - data.append(val) + data.append(info[col]) study_id = conn_handler.execute_fetchone(sql, data)[0] # insert efo information into database sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)".format(cls._table)) - conn_handler.executemany(sql, zip([study_id] * len(efo), efo)) + conn_handler.executemany(sql, list(zip([study_id] * len(efo), efo))) # add study to investigation if necessary if investigation: @@ -214,7 +228,7 @@ def delete(cls, id_): # delete raw data # drop sample_x dynamic table # delete study row from study table (and cascade to satelite tables) - raise NotImplementedError() + raise QiitaDBNotImplementedError() # --- Attributes --- @property @@ -223,7 +237,8 @@ def title(self): Returns ------- - str: title of study + str + Title of study """ conn_handler = SQLConnectionHandler() sql = ("SELECT study_title FROM qiita.{0} WHERE " @@ -250,12 +265,15 @@ def info(self): Returns ------- - dict: info of study keyed to column names + dict + info of study keyed to column names """ conn_handler = SQLConnectionHandler() sql = "SELECT * FROM qiita.{0} WHERE study_id = %s".format(self._table) info = dict(conn_handler.execute_fetchone(sql, (self._id, ))) - efo = [x[0] for x in conn_handler.execute_fetchall(sql, (self._id,))] + sql = ("SELECT efo_id FROM qiita.{0}_experimental_factor WHERE " + "study_id = %s".format(self._table)) + efo = [x[0] for x in conn_handler.execute_fetchall(sql, (self._id, ))] info["study_experimental_factor"] = efo # Convert everything from ids to objects @@ -269,8 +287,9 @@ def info(self): if info['emp_person_id'] is not None: info['emp_person_id'] = StudyPerson( info['emp_person_id']) - # remove id since not needed + # remove id and status since not needed info.pop("study_id") + info.pop("study_status_id") return info @info.setter @@ -281,7 +300,17 @@ def info(self, info): ---------- info : dict information to change/update for the study, keyed to column name + + Raises + ------ + IncompetentQiitaDeveloperError + Empty dict passed + QiitaDBColumnError + Unknown column names passed """ + if len(info) < 1: + raise IncompetentQiitaDeveloperError("Need entries in info dict!") + conn_handler = SQLConnectionHandler() # Save study_experimental_factor data for insertion @@ -294,25 +323,28 @@ def info(self, info): check_table_cols(conn_handler, info, "study") + sql_vals = [] data = [] - sql = "UPDATE qiita.{0} SET ".format(self._table) # items() used for py3 compatability # build query with data values in correct order for SQL statement for key, val in info.items(): - sql = ' '.join((sql, key, "=", "%s,")) + sql_vals.extend((key, "=", "%s,")) if isinstance(val, QiitaObject): data.append(val.id) else: data.append(val) - sql = ' '.join((sql[:-1], "WHERE study_id = %s")) data.append(self._id) + + sql = ("UPDATE qiita.{0} SET {1} WHERE " + "study_id = %s".format(self._table, ' '.join(sql_vals)[:-1])) conn_handler.execute(sql, data) if efo: # insert efo information into database sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)".format(self._table)) - conn_handler.executemany(sql, zip([self._id] * len(efo), efo)) + conn_handler.executemany(sql, + list(zip([self._id] * len(efo), efo))) @property def status(self): @@ -341,20 +373,6 @@ def status(self, status_id): "study_id = %s".format(self._table)) conn_handler.execute(sql, (status_id, self._id)) - @property - def sample_ids(self): - """Returns the IDs of all samples in study - - Returns - ------- - list of str - The sample IDs in alphabetical order. - """ - conn_handler = SQLConnectionHandler() - sql = ("SELECT sample_id FROM qiita.required_sample_info WHERE " - "study_id = %s ORDER BY sample_id".format(self._table)) - return [x[0] for x in conn_handler.execute_fetchall(sql, (self._id, ))] - @property def shared_with(self): """list of users the study is shared with @@ -385,18 +403,20 @@ def pmids(self): return [x[0] for x in conn_handler.execute_fetchall(sql, (self._id, ))] @property - def investigations(self): - """ Returns list of investigations this study is part of + def investigation(self): + """ Returns Investigation this study is part of Returns ------- - list of Investigation objects + Investigation object """ conn_handler = SQLConnectionHandler() sql = ("SELECT investigation_id FROM qiita.investigation_study WHERE " "study_id = %s") - invs = conn_handler.execute_fetchall(sql, (self._id, )) - return [Investigation(inv[0]) for inv in invs] + inv = conn_handler.execute_fetchone(sql, (self._id, )) + if inv is None: + return None + return Investigation(inv[0]) @property def metadata(self): @@ -419,9 +439,8 @@ def raw_data(self): conn_handler = SQLConnectionHandler() sql = ("SELECT raw_data_id FROM qiita.study_raw_data WHERE " "study_id = %s") - raw_ids = [x[0] for x in conn_handler.execute_fetchall(sql, - (self._id, ))] - return [RawData(rid) for rid in raw_ids] + return [RawData(x[0]) for x in + conn_handler.execute_fetchall(sql, (self._id,))] @property def preprocessed_data(self): @@ -434,9 +453,8 @@ def preprocessed_data(self): conn_handler = SQLConnectionHandler() sql = ("SELECT preprocessed_data_id FROM qiita.study_preprocessed_data" " WHERE study_id = %s") - pre_ids = [x[0] for x in conn_handler.execute_fetchall(sql, - (self._id,))] - return [PreprocessedData(pid) for pid in pre_ids] + return [PreprocessedData(x[0]) for x in + conn_handler.execute_fetchall(sql, (self._id,))] @property def processed_data(self): @@ -450,9 +468,8 @@ def processed_data(self): sql = ("SELECT processed_data_id FROM qiita.processed_data WHERE " "preprocessed_data_id IN (SELECT preprocessed_data_id FROM " "qiita.study_preprocessed_data where study_id = %s)") - pro_ids = [x[0] for x in conn_handler.execute_fetchall(sql, - (self._id,))] - return [ProcessedData(pid) for pid in pro_ids] + return [ProcessedData(x[0]) for x in + conn_handler.execute_fetchall(sql, (self._id,))] # --- methods --- def share_with(self, user): @@ -460,8 +477,8 @@ def share_with(self, user): Parameters ---------- - email: str - email of the user to share with + email: User object + The user to share with """ conn_handler = SQLConnectionHandler() sql = ("INSERT INTO qiita.study_users (study_id, email) VALUES " @@ -473,7 +490,7 @@ def add_pmid(self, pmid): Parameters ---------- - pmid: int + pmid: str pmid to associate with study """ conn_handler = SQLConnectionHandler() @@ -491,13 +508,34 @@ class StudyPerson(QiitaObject): name of the person email: str email of the person - address: str, optional + address: str or None address of the person - phone: str, optional + phone: str or None phone number of the person """ _table = "study_person" + @classmethod + def exists(cls, name, email): + """Checks if a person exists + + Parameters + ---------- + name: str + Name of the person + email: str + Email of the person + + Returns + ------- + bool + True if person exists else false + """ + conn_handler = SQLConnectionHandler() + sql = ("SELECT count(1) FROM qiita.{0} WHERE name = %s AND " + "email = %s".format(cls._table)) + return bool(conn_handler.execute_fetchone(sql, (name, email))[0]) + @classmethod def create(cls, name, email, address=None, phone=None): """Create a StudyPerson object, checking if person already exists. @@ -516,19 +554,22 @@ def create(cls, name, email, address=None, phone=None): Returns ------- New StudyPerson object + + Raises + ------ + QiitaDBExecutionError + Person already exists """ - # Make sure person doesn't already exist + if cls.exists(name, email): + raise QiitaDBExecutionError("StudyPerson already exists!") + + # Doesn't exist so insert new person + sql = ("INSERT INTO qiita.{0} (name, email, address, phone) VALUES" + " (%s, %s, %s, %s) RETURNING " + "study_person_id".format(cls._table)) conn_handler = SQLConnectionHandler() - sql = ("SELECT study_person_id FROM qiita.{0} WHERE name = %s AND " - "email = %s".format(cls._table)) - spid = conn_handler.execute_fetchone(sql, (name, email)) - if spid is None: - # Doesn't exist so insert new person - sql = ("INSERT INTO qiita.{0} (name, email, address, phone) VALUES" - " (%s, %s, %s, %s) RETURNING " - "study_person_id".format(cls._table)) - spid = conn_handler.execute_fetchone(sql, (name, email, address, - phone)) + spid = conn_handler.execute_fetchone(sql, (name, email, address, + phone)) return cls(spid[0]) # Properties @@ -545,20 +586,6 @@ def name(self): "study_person_id = %s".format(self._table)) return conn_handler.execute_fetchone(sql, (self._id, ))[0] - @name.setter - def name(self, value): - """Changes the name of the person - - Parameters - ---------- - value: str - New name for person - """ - conn_handler = SQLConnectionHandler() - sql = ("UPDATE qiita.{0} SET name = %s WHERE " - "study_person_id = %s".format(self._table)) - conn_handler.execute(sql, (value, self._id)) - @property def email(self): """Returns the email of the person @@ -572,20 +599,6 @@ def email(self): "study_person_id = %s".format(self._table)) return conn_handler.execute_fetchone(sql, (self._id, ))[0] - @email.setter - def email(self, value): - """Changes the name of the person - - Parameters - ---------- - value: str - New email for person - """ - conn_handler = SQLConnectionHandler() - sql = ("UPDATE qiita.{0} SET email = %s WHERE " - "study_person_id = %s".format(self._table)) - conn_handler.execute(sql, (value, self._id)) - @property def address(self): """Returns the address of the person @@ -598,10 +611,7 @@ def address(self): conn_handler = SQLConnectionHandler() sql = ("SELECT address FROM qiita.{0} WHERE study_person_id =" " %s".format(self._table)) - address = conn_handler.execute_fetchone(sql, (self._id, )) - if address is not None: - return address[0] - return None + return conn_handler.execute_fetchone(sql, (self._id, ))[0] @address.setter def address(self, value): diff --git a/qiita_db/support_files/populate_test_db.sql b/qiita_db/support_files/populate_test_db.sql index 9d5a8162d..6c042eee0 100644 --- a/qiita_db/support_files/populate_test_db.sql +++ b/qiita_db/support_files/populate_test_db.sql @@ -17,7 +17,7 @@ INSERT INTO qiita.qiita_user (email, user_level_id, password, name, -- Insert some study persons INSERT INTO qiita.study_person (name, email, address, phone) VALUES ('LabDude', 'lab_dude@foo.bar', '123 lab street', '121-222-3333'), - ('empDude', 'emp_dude@foo.bar', '123 emp street', NULL), + ('empDude', 'emp_dude@foo.bar', NULL, '444-222-3333'), ('PIDude', 'PI_dude@foo.bar', '123 PI street', NULL); -- Insert a study: EMP 1001 diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index edfcd167d..9566cd88f 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -1,15 +1,16 @@ from unittest import TestCase, main from datetime import date +from qiita_core.exceptions import (QiitaStudyError, + IncompetentQiitaDeveloperError) +from qiita_core.util import qiita_test_checker from qiita_db.study import Study, StudyPerson from qiita_db.investigation import Investigation from qiita_db.data import PreprocessedData, RawData, ProcessedData from qiita_db.metadata_template import SampleTemplate from qiita_db.user import User -from qiita_core.util import qiita_test_checker -from qiita_db.exceptions import QiitaDBExecutionError +from qiita_db.exceptions import QiitaDBExecutionError, QiitaDBColumnError from qiita_db.sql_connection import SQLConnectionHandler -from qiita_core.exceptions import QiitaStudyError # ----------------------------------------------------------------------------- # Copyright (c) 2014--, The Qiita Development Team. @@ -19,6 +20,7 @@ # The full license is in the file LICENSE, distributed with this software. # ----------------------------------------------------------------------------- + @qiita_test_checker() class TestStudyPerson(TestCase): def setUp(self): @@ -38,34 +40,22 @@ def test_create_studyperson(self): self.assertEqual(obs["phone"], '111-121-1313') def test_create_studyperson_already_exists(self): - new = StudyPerson.create('LabDude', 'lab_dude@foo.bar') - self.assertEqual(new.id, 1) - conn = SQLConnectionHandler() - obs = conn.execute_fetchone("SELECT * FROM qiita.study_person WHERE " - "study_person_id = 1") - self.assertEqual(obs["study_person_id"], 1) - self.assertEqual(obs["email"], 'lab_dude@foo.bar') - self.assertEqual(obs["name"], 'LabDude') - self.assertEqual(obs["address"], '123 lab street') - self.assertEqual(obs["phone"], '121-222-3333') + with self.assertRaises(QiitaDBExecutionError): + new = StudyPerson.create('LabDude', 'lab_dude@foo.bar') def test_retrieve_name(self): self.assertEqual(self.studyperson.name, 'LabDude') - def test_set_name(self): - self.studyperson.name = 'NewDude' - self.assertEqual(self.studyperson.name, 'NewDude') - def test_retrieve_email(self): self.assertEqual(self.studyperson.email, 'lab_dude@foo.bar') - def test_set_email(self): - self.studyperson.email = 'new@foo.bar' - self.assertEqual(self.studyperson.email, 'new@foo.bar') - def test_retrieve_address(self): self.assertEqual(self.studyperson.address, '123 lab street') + def test_retrieve_address_null(self): + person = StudyPerson(2) + self.assertEqual(person.address, None) + def test_set_address(self): self.studyperson.address = '123 nonsense road' self.assertEqual(self.studyperson.address, '123 nonsense road') @@ -73,9 +63,13 @@ def test_set_address(self): def test_retrieve_phone(self): self.assertEqual(self.studyperson.phone, '121-222-3333') + def test_retrieve_phone_null(self): + person = StudyPerson(3) + self.assertEqual(person.phone, None) + def test_set_phone(self): - self.studyperson.phone = '111111111111111111111' - self.assertEqual(self.studyperson.phone, '111111111111111111111') + self.studyperson.phone = '111111111111111111121' + self.assertEqual(self.studyperson.phone, '111111111111111111121') @qiita_test_checker() @@ -102,6 +96,41 @@ def setUp(self): "lab_person_id": StudyPerson(1) } + self.weedexp = { + 'mixs_compliant': True, + 'metadata_complete': True, + 'reprocess': False, + 'number_samples_promised': 27, + 'emp_person_id': StudyPerson(2), + 'funding': None, + 'vamps_id': None, + 'first_contact': '2014-05-19 16:10', + 'principal_investigator_id': StudyPerson(3), + 'timeseries_type_id': 1, + 'study_abstract': ("This is a preliminary study to examine the " + "microbiota associated with the Cannabis plant." + " Soils samples from the bulk soil, soil " + "associated with the roots, and the rhizosphere" + " were extracted and the DNA sequenced. Roots " + "from three independent plants of different " + "strains were examined. These roots were " + "obtained November 11, 2011 from plants that " + "had been harvested in the summer. Future " + "studies will attempt to analyze the soils and " + "rhizospheres from the same location at diff" + "erent time points in the plant lifecycle."), + 'email': User('test@foo.bar'), + 'spatial_series': False, + 'study_description': ('Analysis of the Cannabis Plant Microbiome'), + 'study_experimental_factor': [1], + 'portal_type_id': 2, + 'study_alias': 'Cannabis Soils', + 'most_recent_contact': '2014-05-19 16:11', + 'lab_person_id': StudyPerson(1), + 'study_title': ('Identification of the Microbiomes for Cannabis ' + 'Soils'), + 'number_samples_collected': 27} + def test_create_study_min_data(self): """Insert a study into the database""" obs = Study.create(User('test@foo.bar'), self.info) @@ -121,6 +150,7 @@ def test_create_study_min_data(self): 'most_recent_contact': None, 'lab_person_id': 1, 'study_title': 'Fried chicken microbiome', 'number_samples_collected': 25} + conn = SQLConnectionHandler() obsins = dict(conn.execute_fetchone("SELECT * FROM qiita.study WHERE " "study_id = 2")) @@ -130,8 +160,7 @@ def test_create_study_min_data(self): efo = conn.execute_fetchall("SELECT efo_id FROM " "qiita.study_experimental_factor WHERE " "study_id = 2") - obsefo = [x[0] for x in efo] - self.assertEqual(obsefo, [1]) + self.assertEqual(efo, [[1]]) def test_create_study_with_investigation(self): """Insert a study into the database with an investigation""" @@ -146,7 +175,7 @@ def test_create_study_with_investigation(self): def test_create_study_all_data(self): """Insert a study into the database with every info field""" self.info.update({ - 'vamps_id': 1111111, + 'vamps_id': 'MBE_1111111', 'funding': 'FundAgency', 'spatial_series': True, 'metadata_complete': False, @@ -156,7 +185,7 @@ def test_create_study_all_data(self): exp = {'mixs_compliant': True, 'metadata_complete': False, 'reprocess': False, 'study_status_id': 1, 'number_samples_promised': 28, 'emp_person_id': 2, - 'funding': 'FundAgency', 'vamps_id': '1111111', + 'funding': 'FundAgency', 'vamps_id': 'MBE_1111111', 'first_contact': date.today().strftime("%B %d, %Y"), 'principal_investigator_id': 3, 'timeseries_type_id': 1, 'study_abstract': ('We wanted to see if we could get funding ' @@ -186,114 +215,81 @@ def test_create_study_all_data(self): def test_create_missing_requred(self): """ Insert a study that is missing a required info key""" self.info.pop("study_title") - self.assertRaises(RuntimeError, Study.create, 'test@foo.bar', - self.info) + self.assertRaises(QiitaDBColumnError, Study.create, + User('test@foo.bar'), self.info) def test_create_study_id(self): """Insert a study with study_id present""" self.info.update({"study_id": 1}) - self.assertRaises(QiitaStudyError, Study.create, 'test@foo.bar', - self.info) + self.assertRaises(IncompetentQiitaDeveloperError, Study.create, + User('test@foo.bar'), self.info) + + def test_create_study_status(self): + """Insert a study with status present""" + self.info.update({"study_status_id": 1}) + self.assertRaises(IncompetentQiitaDeveloperError, Study.create, + User('test@foo.bar'), self.info) def test_create_no_efo(self): """Insert a study no experimental factors passed""" self.info.pop("study_experimental_factor") - self.assertRaises(QiitaStudyError, Study.create, 'test@foo.bar', + self.assertRaises(QiitaDBColumnError, Study.create, 'test@foo.bar', self.info) def test_create_unknown_db_col(self): """ Insert a study with an info key not in the database""" self.info["SHOULDNOTBEHERE"] = "BWAHAHAHAHAHA" - self.assertRaises(QiitaDBExecutionError, Study.create, 'test@foo.bar', - self.info) + self.assertRaises(QiitaDBColumnError, Study.create, + User('test@foo.bar'), self.info) def test_retrieve_title(self): - self.assertEqual(self.study.title, ('Identification of the Microbiomes' - ' for Cannabis Soils')) + self.assertEqual(self.study.title, 'Identification of the Microbiomes' + ' for Cannabis Soils') def test_set_title(self): self.study.title = "Weed Soils" self.assertEqual(self.study.title, "Weed Soils") def test_retrieve_info(self): - exp = { - 'mixs_compliant': True, 'metadata_complete': True, - 'reprocess': False, 'study_status_id': 2, - 'number_samples_promised': 27, 'emp_person_id': StudyPerson(2), - 'funding': None, 'vamps_id': None, - 'first_contact': '2014-05-19 16:10', - 'principal_investigator_id': StudyPerson(3), - 'timeseries_type_id': 1, - 'study_abstract': ("This is a preliminary study to examine the " - "microbiota associated with the Cannabis plant." - " Soils samples from the bulk soil, soil " - "associated with the roots, and the rhizosphere" - " were extracted and the DNA sequenced. Roots " - "from three independent plants of different " - "strains were examined. These roots were " - "obtained November 11, 2011 from plants that " - "had been harvested in the summer. Future " - "studies will attempt to analyze the soils and " - "rhizospheres from the same location at diff" - "erent time points in the plant lifecycle."), - 'email': User('test@foo.bar'), 'spatial_series': False, - 'study_description': ('Analysis of the Cannabis Plant Microbiome'), - 'study_experimental_factor': [1], 'portal_type_id': 2, - 'study_alias': 'Cannabis Soils', - 'most_recent_contact': '2014-05-19 16:11', - 'lab_person_id': StudyPerson(1), - 'study_title': ('Identification of the Microbiomes for Cannabis ' - 'Soils'), - 'number_samples_collected': 27} - self.assertEqual(self.study.info, exp) + self.assertEqual(self.study.info, self.weedexp) def test_set_info(self): + """Set info with integer efo_id""" newinfo = { "timeseries_type_id": 2, - "study_experimental_factor": [3, 4], + "study_experimental_factor": 3, "metadata_complete": False, "number_samples_collected": 28, "lab_person_id": StudyPerson(2), - "vamps_id": 111222 + "vamps_id": 'MBE_111222' } - exp = { - 'mixs_compliant': True, - 'metadata_complete': False, - 'reprocess': False, - 'study_status_id': 2, - 'number_samples_promised': 27, - 'emp_person_id': StudyPerson(2), - 'funding': None, - 'vamps_id': '111222', - 'first_contact': '2014-05-19 16:10', - 'principal_investigator_id': StudyPerson(3), - 'timeseries_type_id': 2, - 'study_abstract': ('This is a preliminary study to examine the ' - 'microbiota associated with the Cannabis plant. ' - 'Soils samples from the bulk soil, soil ' - 'associated with the roots, and the rhizosphere ' - 'were extracted and the DNA sequenced. Roots from' - ' three independent plants of different strains ' - 'were examined. These roots were obtained ' - 'November 11, 2011 from plants that had been ' - 'harvested in the summer. Future studies will ' - 'attempt to analyze the soils and rhizospheres ' - 'from the same location at different time points ' - 'in the plant lifecycle.'), - 'email': User('test@foo.bar'), - 'spatial_series': False, - 'study_description': 'Analysis of the Cannabis Plant Microbiome', - 'study_experimental_factor': [1], - 'portal_type_id': 2, - 'study_alias': 'Cannabis Soils', - 'most_recent_contact': '2014-05-19 16:11', - 'lab_person_id': StudyPerson(2), - 'study_title':'Identification of the Microbiomes for Cannabis Soils', - 'number_samples_collected': 28} + self.weedexp.update(newinfo) + # Fix study_experimental_factor since update wipes out the 1 + self.weedexp['study_experimental_factor'] = [1, 3] + self.study.info = newinfo + self.assertEqual(self.study.info, self.weedexp) + + def test_set_info_efo_list(self): + """Set info with list efo_id""" + newinfo = { + "timeseries_type_id": 2, + "study_experimental_factor": [3, 4], + "metadata_complete": False, + "number_samples_collected": 28, + "lab_person_id": StudyPerson(2), + "vamps_id": 'MBE_111222' + } + self.weedexp.update(newinfo) + # Fix study_experimental_factor since update wipes out the 1 + self.weedexp['study_experimental_factor'] = [1, 3, 4] self.study.info = newinfo - self.assertEqual(self.study.info, exp) + self.assertEqual(self.study.info, self.weedexp) + + def test_info_empty(self): + with self.assertRaises(IncompetentQiitaDeveloperError): + self.study.info = {} def test_retrieve_status(self): self.assertEqual(self.study.status, 2) @@ -302,17 +298,6 @@ def test_set_status(self): self.study.status = 1 self.assertEqual(self.study.status, 1) - def test_retrieve_sample_ids(self): - exp = set(['SKB8.640193', 'SKD8.640184', 'SKB7.640196', 'SKM9.640192', - 'SKM4.640180', 'SKM5.640177', 'SKB5.640181', 'SKD6.640190', - 'SKB2.640194', 'SKD2.640178', 'SKM7.640188', 'SKB1.640202', - 'SKD1.640179', 'SKD3.640198', 'SKM8.640201', 'SKM2.640199', - 'SKB9.640200', 'SKD5.640186', 'SKM3.640197', 'SKD9.640182', - 'SKB4.640189', 'SKD7.640191', 'SKM6.640187', 'SKD4.640185', - 'SKB3.640195', 'SKB6.640176', 'SKM1.640183']) - obs = set(self.study.sample_ids) - self.assertEqual(obs, exp) - def test_retrieve_shared_with(self): self.assertEqual(self.study.shared_with, [User('shared@foo.bar')]) @@ -320,8 +305,8 @@ def test_retrieve_pmids(self): exp = ['123456', '7891011'] self.assertEqual(self.study.pmids, exp) - def test_retrieve_investigations(self): - self.assertEqual(self.study.investigations, [Investigation(1)]) + def test_retrieve_investigation(self): + self.assertEqual(self.study.investigation, Investigation(1)) def test_retrieve_metadata(self): self.assertEqual(self.study.metadata, SampleTemplate(1)) @@ -341,7 +326,7 @@ def test_share_with(self): User('admin@foo.bar')]) def test_add_pmid(self): - self.study.add_pmid("4544444") + self.study.add_pmid('4544444') exp = ['123456', '7891011', '4544444'] self.assertEqual(self.study.pmids, exp) diff --git a/qiita_db/test/test_util.py b/qiita_db/test/test_util.py new file mode 100644 index 000000000..2b1cfe011 --- /dev/null +++ b/qiita_db/test/test_util.py @@ -0,0 +1,49 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2014--, The Qiita Development Team. +# +# Distributed under the terms of the BSD 3-clause License. +# +# The full license is in the file LICENSE, distributed with this software. +# ----------------------------------------------------------------------------- + +from unittest import TestCase, main + +from qiita_core.util import qiita_test_checker +from qiita_db.util import check_table_cols, check_required_columns +from qiita_db.sql_connection import SQLConnectionHandler +from qiita_db.exceptions import QiitaDBColumnError + + +@qiita_test_checker() +class DBUtilTests(TestCase): + + def setUp(self): + self.conn_handler = SQLConnectionHandler() + self.table = 'study' + self.required = [ + 'number_samples_promised', 'study_title', 'mixs_compliant', + 'metadata_complete', 'study_description', 'first_contact', + 'reprocess', 'study_status_id', 'portal_type_id', + 'timeseries_type_id', 'study_alias', 'study_abstract', + 'principal_investigator_id', 'email', 'number_samples_collected'] + + def test_check_required_columns(self): + check_required_columns(self.conn_handler, self.required, self.table) + + def test_check_required_columns_fail(self): + self.required.remove('study_title') + with self.assertRaises(QiitaDBColumnError): + check_required_columns(self.conn_handler, self.required, + self.table) + + def test_check_table_cols(self): + check_table_cols(self.conn_handler, self.required, self.table) + + def test_check_table_cols_fail(self): + self.required.append('BADTHINGNOINHERE') + with self.assertRaises(QiitaDBColumnError): + check_table_cols(self.conn_handler, self.required, + self.table) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/qiita_db/util.py b/qiita_db/util.py index 2a68edc51..432f11a90 100644 --- a/qiita_db/util.py +++ b/qiita_db/util.py @@ -1,8 +1,7 @@ #!/usr/bin/env python from __future__ import division -from os.path import abspath, dirname, join -from .exceptions import QiitaDBExecutionError +from .exceptions import QiitaDBColumnError # ----------------------------------------------------------------------------- # Copyright (c) 2014--, The Qiita Development Team. @@ -53,50 +52,66 @@ def scrub_data(s): return ret -def check_required(keys, required): - """Makes sure all required columns are in a list +def check_required_columns(conn_handler, keys, table): + """Makes sure all required columns in database table are in keys Parameters ---------- + conn_handler: SQLConnectionHandler object + Previously opened connection to the database keys: iterable - list, set, or other iterable holding the keys in the dictionary - required: set - set of column names required for a table + Holds the keys in the dictionary + table: str + name of the table to check required columns Raises ------ - QiitaDBExecutionError - If not all required keys are in keys + QiitaDBColumnError + If keys exist that are not in the table + RuntimeError + Unable to get columns from database """ - if not isinstance(required, set): - raise ValueError("required keys list must be set type object") - if len(required.difference(set(keys))) > 0: - raise RuntimeError("Required keys missing: %s" % - required.difference(set(keys))) + sql = ("SELECT is_nullable, column_name FROM information_schema.columns " + "WHERE table_name = %s") + cols = conn_handler.execute_fetchall(sql, (table, )) + # Test needed because a user with certain permissions can query without + # error but be unable to get the column names + if len(cols) == 0: + raise RuntimeError("Unable to fetch column names for table %s" % table) + required = set(x[1] for x in cols if x[0] == 'NO') + # remove the table id column as required + required.remove("%s_id" % table) + if len(required.difference(keys)) > 0: + raise QiitaDBColumnError("Required keys missing: %s" % + required.difference(keys)) def check_table_cols(conn_handler, keys, table): - """Makes sure all keys correspond to coumn headers in a table + """Makes sure all keys correspond to column headers in a table Parameters ---------- conn_handler: SQLConnectionHandler object - Previously opened conection to the database + Previously opened connection to the database keys: iterable - list, set, or other iterable holding the keys in the dictionary + Holds the keys in the dictionary table: str name of the table to check column names Raises ------ - QiitaDBExecutionError - If keys exist that are not in the table + QiitaDBColumnError + If a key is found that is not in table columns + RuntimeError + Unable to get columns from database """ sql = ("SELECT column_name FROM information_schema.columns WHERE " "table_name = %s") cols = [x[0] for x in conn_handler.execute_fetchall(sql, (table, ))] + # Test needed because a user with certain permissions can query without + # error but be unable to get the column names if len(cols) == 0: raise RuntimeError("Unable to fetch column names for table %s" % table) if len(set(keys).difference(cols)) > 0: - raise QiitaDBExecutionError("Non-database keys found: %s" % - set(keys).difference(cols)) + raise QiitaDBColumnError("Non-database keys found: %s" % + set(keys).difference(cols)) From 10c4c04db0a40b446660ef2a576f111f01cbd6e6 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 3 Jun 2014 17:13:54 -0600 Subject: [PATCH 32/57] A bit more docstring --- qiita_db/study.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index d3e59f336..a5adf6588 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -6,12 +6,15 @@ .. currentmodule:: qiita_db.study -This module provides the implementation of the Study class. It allows access to -all basic information including name and pmids associated with the study, as -well as returning objects for the data, metadata, owner, and shared users. It -is the central hub for creating, deleting, and accessing a study in the -database. +This module provides the implementation of the Study and StudyPerson classes. +The study class allows access to all basic information including name and +pmids associated with the study, as well as returning objects for the data, +metadata, owner, and shared users. It is the central hub for creating, +deleting, and accessing a study in the database. +Contacts are taken care of by the StudyPerson class. This holds the contact's +name, email, address, and phone of the various persons in a study, e.g. The PI +or lab contact. Classes ------- @@ -46,7 +49,6 @@ >>> from qiita_db.study import Study # doctest: +SKIP >>> from qiita_db.user import User # doctest: +SKIP ->>> from qiita_db.study import Investigation # doctest: +SKIP >>> info = { ... "timeseries_type_id": 1, ... "study_experimental_factor": 1, @@ -68,6 +70,25 @@ You can also add a study to an investigation by passing the investigation object while creating the study. +>>> from qiita_db.study import Study # doctest: +SKIP +>>> from qiita_db.user import User # doctest: +SKIP +>>> from qiita_db.study import Investigation # doctest: +SKIP +>>> info = { +... "timeseries_type_id": 1, +... "study_experimental_factor": 1, +... "metadata_complete": True, +... "mixs_compliant": True, +... "number_samples_collected": 25, +... "number_samples_promised": 28, +... "portal_type_id": 3, +... "study_title": "Study Title", +... "study_alias": "TST", +... "study_description": ("Some description of the study goes here"), +... "study_abstract": ("Some abstract goes here"), +... "emp_person_id": StudyPerson(2), +... "principal_investigator_id": StudyPerson(3), +... "lab_person_id": StudyPerson(1)} # doctest: +SKIP +>>> owner = User('owner@foo.bar') # doctest: +SKIP >>> investigation = Investigation(1) # doctest: +SKIP >>> Study(owner, info, investigation) # doctest: +SKIP """ From b061b2e4e50e6df605a4e7364fc15315bfd1da26 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 3 Jun 2014 17:25:25 -0600 Subject: [PATCH 33/57] pep8 fixes --- qiita_db/study.py | 9 ++++----- qiita_db/test/test_study.py | 3 +-- qiita_db/test/test_util.py | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index a5adf6588..149e50e85 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -104,8 +104,7 @@ from future_builtins import zip from datetime import date -from qiita_core.exceptions import (QiitaStudyError, - IncompetentQiitaDeveloperError) +from qiita_core.exceptions import IncompetentQiitaDeveloperError from .base import QiitaStatusObject, QiitaObject from .exceptions import (QiitaDBColumnError, QiitaDBNotImplementedError, QiitaDBExecutionError) @@ -451,7 +450,7 @@ def metadata(self): @property def raw_data(self): - """ Returns list of data objects with raw data info + """ Returns list of data objects with raw data info Returns ------- @@ -465,7 +464,7 @@ def raw_data(self): @property def preprocessed_data(self): - """ Returns list of data objects with preprocessed data info + """ Returns list of data objects with preprocessed data info Returns ------- @@ -583,7 +582,7 @@ def create(cls, name, email, address=None, phone=None): """ if cls.exists(name, email): raise QiitaDBExecutionError("StudyPerson already exists!") - + # Doesn't exist so insert new person sql = ("INSERT INTO qiita.{0} (name, email, address, phone) VALUES" " (%s, %s, %s, %s) RETURNING " diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 9566cd88f..58bcfe6c8 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -1,8 +1,7 @@ from unittest import TestCase, main from datetime import date -from qiita_core.exceptions import (QiitaStudyError, - IncompetentQiitaDeveloperError) +from qiita_core.exceptions import IncompetentQiitaDeveloperError from qiita_core.util import qiita_test_checker from qiita_db.study import Study, StudyPerson from qiita_db.investigation import Investigation diff --git a/qiita_db/test/test_util.py b/qiita_db/test/test_util.py index 2b1cfe011..17d124eb6 100644 --- a/qiita_db/test/test_util.py +++ b/qiita_db/test/test_util.py @@ -43,7 +43,7 @@ def test_check_table_cols_fail(self): self.required.append('BADTHINGNOINHERE') with self.assertRaises(QiitaDBColumnError): check_table_cols(self.conn_handler, self.required, - self.table) + self.table) if __name__ == "__main__": main() \ No newline at end of file From 51da6412dcb34754845a049be4cb01756f8b7c2a Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 3 Jun 2014 18:07:25 -0600 Subject: [PATCH 34/57] import THE FUTURE --- qiita_db/study.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 149e50e85..215714427 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -100,8 +100,10 @@ # # The full license is in the file LICENSE, distributed with this software. # ----------------------------------------------------------------------------- - -from future_builtins import zip +try: + from itertools import izip as zip +except ImportError: # Python 3 so ignore + pass from datetime import date from qiita_core.exceptions import IncompetentQiitaDeveloperError From 0d755d035bba56337932578540b28119e7d62631 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 3 Jun 2014 18:08:25 -0600 Subject: [PATCH 35/57] import THE FUTURE AGAIN --- qiita_db/study.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 215714427..6b438b974 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -100,10 +100,8 @@ # # The full license is in the file LICENSE, distributed with this software. # ----------------------------------------------------------------------------- -try: - from itertools import izip as zip -except ImportError: # Python 3 so ignore - pass + +from future.builtins import zip from datetime import date from qiita_core.exceptions import IncompetentQiitaDeveloperError From 7bfa663e5a9b6fdd182e81aefbb7934cbd0a2567 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 3 Jun 2014 18:20:59 -0600 Subject: [PATCH 36/57] moving the future --- qiita_db/study.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 6b438b974..3b6ac98c8 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -1,5 +1,3 @@ -from __future__ import division - r""" Study and StudyPerson objects (:mod:`qiita_db.study`) ===================================================== @@ -101,6 +99,7 @@ # The full license is in the file LICENSE, distributed with this software. # ----------------------------------------------------------------------------- +from __future__ import division from future.builtins import zip from datetime import date From e506ccfa6938ea537f1cacb7c6396ceb751a72a3 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 3 Jun 2014 22:18:59 -0600 Subject: [PATCH 37/57] addressing @wasade comments --- qiita_db/study.py | 14 +++------- qiita_db/test/test_study.py | 52 ++++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 3b6ac98c8..d406aab70 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -297,15 +297,9 @@ def info(self): # Convert everything from ids to objects info['email'] = User(info['email']) - if info['principal_investigator_id'] is not None: - info['principal_investigator_id'] = StudyPerson( - info['principal_investigator_id']) - if info['lab_person_id'] is not None: - info['lab_person_id'] = StudyPerson( - info['lab_person_id']) - if info['emp_person_id'] is not None: - info['emp_person_id'] = StudyPerson( - info['emp_person_id']) + info.update({k: StudyPerson(info[k]) for k in + ['principal_investigator_id', 'lab_person_id', + 'emp_person_id'] if info[k] is not None}) # remove id and status since not needed info.pop("study_id") info.pop("study_status_id") @@ -358,7 +352,7 @@ def info(self, info): "study_id = %s".format(self._table, ' '.join(sql_vals)[:-1])) conn_handler.execute(sql, data) - if efo: + if efo is not None: # insert efo information into database sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)".format(self._table)) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 58bcfe6c8..61cc5ef7a 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -45,9 +45,17 @@ def test_create_studyperson_already_exists(self): def test_retrieve_name(self): self.assertEqual(self.studyperson.name, 'LabDude') + def test_set_name_fail(self): + with self.assertRaises(AttributeError): + self.studyperson.name = 'Fail Dude' + def test_retrieve_email(self): self.assertEqual(self.studyperson.email, 'lab_dude@foo.bar') + def test_set_email_fail(self): + with self.assertRaises(AttributeError): + self.studyperson.email = 'faildude@foo.bar' + def test_retrieve_address(self): self.assertEqual(self.studyperson.address, '123 lab street') @@ -88,8 +96,8 @@ def setUp(self): "study_alias": "FCM", "study_description": ("Microbiome of people who eat nothing but " "fried chicken"), - "study_abstract": ("We wanted to see if we could get funding for " - "giving people heart attacks"), + "study_abstract": ("Exploring how a high fat diet changes the " + "gut microbiome"), "emp_person_id": StudyPerson(2), "principal_investigator_id": StudyPerson(3), "lab_person_id": StudyPerson(1) @@ -140,8 +148,8 @@ def test_create_study_min_data(self): 'funding': None, 'vamps_id': None, 'first_contact': date.today().strftime("%B %d, %Y"), 'principal_investigator_id': 3, 'timeseries_type_id': 1, - 'study_abstract': ('We wanted to see if we could get funding ' - 'for giving people heart attacks'), + 'study_abstract': ('Exploring how a high fat diet changes the ' + 'gut microbiome'), 'email': 'test@foo.bar', 'spatial_series': None, 'study_description': ('Microbiome of people who eat nothing but' ' fried chicken'), @@ -187,8 +195,8 @@ def test_create_study_all_data(self): 'funding': 'FundAgency', 'vamps_id': 'MBE_1111111', 'first_contact': date.today().strftime("%B %d, %Y"), 'principal_investigator_id': 3, 'timeseries_type_id': 1, - 'study_abstract': ('We wanted to see if we could get funding ' - 'for giving people heart attacks'), + 'study_abstract': ('Exploring how a high fat diet changes the ' + 'gut microbiome'), 'email': 'test@foo.bar', 'spatial_series': True, 'study_description': ('Microbiome of people who eat nothing ' 'but fried chicken'), @@ -199,47 +207,43 @@ def test_create_study_all_data(self): conn = SQLConnectionHandler() obsins = dict(conn.execute_fetchone("SELECT * FROM qiita.study WHERE " "study_id = 2")) - for thing, val in exp.iteritems(): - if val != obsins[thing]: - print thing, val, obsins[thing] self.assertEqual(obsins, exp) # make sure EFO went in to table correctly - efo = conn.execute_fetchall("SELECT efo_id FROM " - "qiita.study_experimental_factor WHERE " - "study_id = 2") - obsefo = [x[0] for x in efo] - self.assertEqual(obsefo, [1]) + obsefo = conn.execute_fetchall("SELECT efo_id FROM " + "qiita.study_experimental_factor WHERE " + "study_id = 2") + self.assertEqual(obsefo, [[1]]) def test_create_missing_requred(self): """ Insert a study that is missing a required info key""" self.info.pop("study_title") - self.assertRaises(QiitaDBColumnError, Study.create, - User('test@foo.bar'), self.info) + with self.assertRaises(QiitaDBColumnError): + Study.create(User('test@foo.bar'), self.info) def test_create_study_id(self): """Insert a study with study_id present""" self.info.update({"study_id": 1}) - self.assertRaises(IncompetentQiitaDeveloperError, Study.create, - User('test@foo.bar'), self.info) + with self.assertRaises(IncompetentQiitaDeveloperError): + Study.create(User('test@foo.bar'), self.info) def test_create_study_status(self): """Insert a study with status present""" self.info.update({"study_status_id": 1}) - self.assertRaises(IncompetentQiitaDeveloperError, Study.create, - User('test@foo.bar'), self.info) + with self.assertRaises(IncompetentQiitaDeveloperError): + Study.create(User('test@foo.bar'), self.info) def test_create_no_efo(self): """Insert a study no experimental factors passed""" self.info.pop("study_experimental_factor") - self.assertRaises(QiitaDBColumnError, Study.create, 'test@foo.bar', - self.info) + with self.assertRaises(QiitaDBColumnError): + Study.create('test@foo.bar', self.info) def test_create_unknown_db_col(self): """ Insert a study with an info key not in the database""" self.info["SHOULDNOTBEHERE"] = "BWAHAHAHAHAHA" - self.assertRaises(QiitaDBColumnError, Study.create, - User('test@foo.bar'), self.info) + with self.assertRaises(QiitaDBColumnError): + Study.create(User('test@foo.bar'), self.info) def test_retrieve_title(self): self.assertEqual(self.study.title, 'Identification of the Microbiomes' From 0e691b4f6f00bf0d84e446fca046d455a6d78c2a Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 3 Jun 2014 22:30:41 -0600 Subject: [PATCH 38/57] cleaner join in info.setter --- qiita_db/study.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index d406aab70..141a4b459 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -341,7 +341,7 @@ def info(self, info): # items() used for py3 compatability # build query with data values in correct order for SQL statement for key, val in info.items(): - sql_vals.extend((key, "=", "%s,")) + sql_vals.append("{0} = %s".format(key)) if isinstance(val, QiitaObject): data.append(val.id) else: @@ -349,7 +349,7 @@ def info(self, info): data.append(self._id) sql = ("UPDATE qiita.{0} SET {1} WHERE " - "study_id = %s".format(self._table, ' '.join(sql_vals)[:-1])) + "study_id = %s".format(self._table, ','.join(sql_vals))) conn_handler.execute(sql, data) if efo is not None: From 678df7a757d7979b8839bb464b18225a0aa44691 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 3 Jun 2014 22:39:49 -0600 Subject: [PATCH 39/57] pep8 test_util --- qiita_db/test/test_util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiita_db/test/test_util.py b/qiita_db/test/test_util.py index 17d124eb6..447e1fbf2 100644 --- a/qiita_db/test/test_util.py +++ b/qiita_db/test/test_util.py @@ -16,7 +16,6 @@ @qiita_test_checker() class DBUtilTests(TestCase): - def setUp(self): self.conn_handler = SQLConnectionHandler() self.table = 'study' @@ -46,4 +45,4 @@ def test_check_table_cols_fail(self): self.table) if __name__ == "__main__": - main() \ No newline at end of file + main() From 352dbda404dd9899c527bfbdd33d734585a4a031 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 4 Jun 2014 00:25:37 -0600 Subject: [PATCH 40/57] add lock on setters for public study --- qiita_db/study.py | 28 +++++++++++++++++++++++++--- qiita_db/test/test_study.py | 17 ++++++++++++++++- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 141a4b459..60340ebc0 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -106,7 +106,7 @@ from qiita_core.exceptions import IncompetentQiitaDeveloperError from .base import QiitaStatusObject, QiitaObject from .exceptions import (QiitaDBColumnError, QiitaDBNotImplementedError, - QiitaDBExecutionError) + QiitaDBExecutionError, QiitaDBStatusError) from .data import RawData, PreprocessedData, ProcessedData from .user import User from .investigation import Investigation @@ -145,6 +145,11 @@ class Study(QiitaStatusObject): ------- share_with add_pmid + + Notes + ----- + All setters raise QiitaDBStatusError if trying to change a public study. You + should not be doing that. """ _table = "study" @@ -250,6 +255,17 @@ def delete(cls, id_): raise QiitaDBNotImplementedError() # --- Attributes --- + def _lock_public(self): + """Locks a study if it is public + + Raises + ------ + QiitaDBStatusError + study is public + """ + if self.status == 2: + raise QiitaDBStatusError("Can't edit public study!") + @property def title(self): """Returns the title of the study @@ -273,6 +289,7 @@ def title(self, title): title : str The new study title """ + self._lock_public() conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET study_title = %s WHERE " "study_id = %s".format(self._table)) @@ -321,6 +338,7 @@ def info(self, info): QiitaDBColumnError Unknown column names passed """ + self._lock_public() if len(info) < 1: raise IncompetentQiitaDeveloperError("Need entries in info dict!") @@ -380,6 +398,10 @@ def status(self, status_id): ---------- status_id: int ID for the new status + + Notes + ----- + You can still change this even when a study is public. BE CAREFUL! """ conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET study_status_id = %s WHERE " @@ -398,8 +420,8 @@ def shared_with(self): conn_handler = SQLConnectionHandler() sql = ("SELECT email FROM qiita.{0}_users WHERE " "study_id = %s".format(self._table)) - users = [x[0] for x in conn_handler.execute_fetchall(sql, (self._id,))] - return [User(email) for email in users] + return [User(x[0]) for x in conn_handler.execute_fetchall(sql, + (self._id,))] @property def pmids(self): diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 61cc5ef7a..45097d37b 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -8,7 +8,8 @@ from qiita_db.data import PreprocessedData, RawData, ProcessedData from qiita_db.metadata_template import SampleTemplate from qiita_db.user import User -from qiita_db.exceptions import QiitaDBExecutionError, QiitaDBColumnError +from qiita_db.exceptions import (QiitaDBExecutionError, QiitaDBColumnError, + QiitaDBStatusError) from qiita_db.sql_connection import SQLConnectionHandler # ----------------------------------------------------------------------------- @@ -250,9 +251,15 @@ def test_retrieve_title(self): ' for Cannabis Soils') def test_set_title(self): + self.study.status = 1 self.study.title = "Weed Soils" self.assertEqual(self.study.title, "Weed Soils") + def test_set_title_public(self): + """Tests for fail if editing title of a public study""" + with self.assertRaises(QiitaDBStatusError): + self.study.title = "Weed Soils" + def test_retrieve_info(self): self.assertEqual(self.study.info, self.weedexp) @@ -270,9 +277,15 @@ def test_set_info(self): self.weedexp.update(newinfo) # Fix study_experimental_factor since update wipes out the 1 self.weedexp['study_experimental_factor'] = [1, 3] + self.study.status = 1 self.study.info = newinfo self.assertEqual(self.study.info, self.weedexp) + def test_set_tinfo_public(self): + """Tests for fail if editing info of a public study""" + with self.assertRaises(QiitaDBStatusError): + self.study.title = "Weed Soils" + def test_set_info_efo_list(self): """Set info with list efo_id""" newinfo = { @@ -287,10 +300,12 @@ def test_set_info_efo_list(self): self.weedexp.update(newinfo) # Fix study_experimental_factor since update wipes out the 1 self.weedexp['study_experimental_factor'] = [1, 3, 4] + self.study.status = 1 self.study.info = newinfo self.assertEqual(self.study.info, self.weedexp) def test_info_empty(self): + self.study.status = 1 with self.assertRaises(IncompetentQiitaDeveloperError): self.study.info = {} From e29947c883beb53be6e0bfccf3929d93b0cc71f5 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 4 Jun 2014 08:25:27 -0600 Subject: [PATCH 41/57] fix docstring --- qiita_db/study.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 60340ebc0..04cafb986 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -163,7 +163,7 @@ def create(cls, owner, info, investigation=None): the user id of the study' owner info: dict the information attached to the study. - investigation_id: Investigation object + investigation: Investigation object if the study is part of an investigation, the id to associate with Raises From 136007deec090d75337392a2bfbfb3f20fa16124 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 4 Jun 2014 08:38:40 -0600 Subject: [PATCH 42/57] fix tests for new test data --- qiita_db/test/test_study.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 45097d37b..faf5f0a0c 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -330,10 +330,11 @@ def test_retrieve_metadata(self): self.assertEqual(self.study.metadata, SampleTemplate(1)) def test_retrieve_raw_data(self): - self.assertEqual(self.study.raw_data, [RawData(1)]) + self.assertEqual(self.study.raw_data, [RawData(1), RawData(2)]) def test_retrieve_preprocessed_data(self): - self.assertEqual(self.study.preprocessed_data, [PreprocessedData(1)]) + self.assertEqual(self.study.preprocessed_data, [PreprocessedData(1), + PreprocessedData(2)]) def test_retrieve_processed_data(self): self.assertEqual(self.study.processed_data, [ProcessedData(1)]) From f5c333e1cc4763b35217eba22ed6934341a142e6 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 4 Jun 2014 08:42:04 -0600 Subject: [PATCH 43/57] fix pep8 --- qiita_db/exceptions.py | 1 + qiita_db/study.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qiita_db/exceptions.py b/qiita_db/exceptions.py index eab23f0a6..a69b2cff4 100644 --- a/qiita_db/exceptions.py +++ b/qiita_db/exceptions.py @@ -36,6 +36,7 @@ class QiitaDBConnectionError(QiitaDBError): """Exception for error when connecting to the db""" pass + class QiitaDBColumnError(QiitaDBError): """Exception when missing table information or excess information passed""" pass diff --git a/qiita_db/study.py b/qiita_db/study.py index 04cafb986..0414846bb 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -148,8 +148,8 @@ class Study(QiitaStatusObject): Notes ----- - All setters raise QiitaDBStatusError if trying to change a public study. You - should not be doing that. + All setters raise QiitaDBStatusError if trying to change a public study. + You should not be doing that. """ _table = "study" From 69af985cb5417d19db808f3950513480263bdd82 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 9 Jun 2014 12:22:34 -0600 Subject: [PATCH 44/57] fixing more code --- qiita_db/exceptions.py | 5 ++ qiita_db/study.py | 127 ++++++++++++++++---------------- qiita_db/test/test_study.py | 139 +++++++++++++++++++----------------- qiita_db/test/test_util.py | 2 + 4 files changed, 145 insertions(+), 128 deletions(-) diff --git a/qiita_db/exceptions.py b/qiita_db/exceptions.py index a69b2cff4..6b9a37226 100644 --- a/qiita_db/exceptions.py +++ b/qiita_db/exceptions.py @@ -40,3 +40,8 @@ class QiitaDBConnectionError(QiitaDBError): class QiitaDBColumnError(QiitaDBError): """Exception when missing table information or excess information passed""" pass + + +class QiitaDBDuplicateError(QiitaDBError): + """Exception when duplicating something in the database""" + pass diff --git a/qiita_db/study.py b/qiita_db/study.py index 0414846bb..f3fc5c98a 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -49,7 +49,6 @@ >>> from qiita_db.user import User # doctest: +SKIP >>> info = { ... "timeseries_type_id": 1, -... "study_experimental_factor": 1, ... "metadata_complete": True, ... "mixs_compliant": True, ... "number_samples_collected": 25, @@ -57,13 +56,13 @@ ... "portal_type_id": 3, ... "study_title": "Study Title", ... "study_alias": "TST", -... "study_description": ("Some description of the study goes here"), -... "study_abstract": ("Some abstract goes here"), +... "study_description": "Some description of the study goes here", +... "study_abstract": "Some abstract goes here", ... "emp_person_id": StudyPerson(2), ... "principal_investigator_id": StudyPerson(3), ... "lab_person_id": StudyPerson(1)} # doctest: +SKIP >>> owner = User('owner@foo.bar') # doctest: +SKIP ->>> Study(owner, info) # doctest: +SKIP +>>> Study(owner, 1, info) # doctest: +SKIP You can also add a study to an investigation by passing the investigation object while creating the study. @@ -73,7 +72,6 @@ >>> from qiita_db.study import Investigation # doctest: +SKIP >>> info = { ... "timeseries_type_id": 1, -... "study_experimental_factor": 1, ... "metadata_complete": True, ... "mixs_compliant": True, ... "number_samples_collected": 25, @@ -81,14 +79,14 @@ ... "portal_type_id": 3, ... "study_title": "Study Title", ... "study_alias": "TST", -... "study_description": ("Some description of the study goes here"), -... "study_abstract": ("Some abstract goes here"), +... "study_description": "Some description of the study goes here", +... "study_abstract": "Some abstract goes here", ... "emp_person_id": StudyPerson(2), ... "principal_investigator_id": StudyPerson(3), ... "lab_person_id": StudyPerson(1)} # doctest: +SKIP >>> owner = User('owner@foo.bar') # doctest: +SKIP >>> investigation = Investigation(1) # doctest: +SKIP ->>> Study(owner, info, investigation) # doctest: +SKIP +>>> Study(owner, 1, info, investigation) # doctest: +SKIP """ # ----------------------------------------------------------------------------- @@ -105,8 +103,8 @@ from qiita_core.exceptions import IncompetentQiitaDeveloperError from .base import QiitaStatusObject, QiitaObject -from .exceptions import (QiitaDBColumnError, QiitaDBNotImplementedError, - QiitaDBExecutionError, QiitaDBStatusError) +from .exceptions import (QiitaDBDuplicateError, QiitaDBNotImplementedError, + QiitaDBExecutionError, QiitaDBStatusError,) from .data import RawData, PreprocessedData, ProcessedData from .user import User from .investigation import Investigation @@ -126,6 +124,8 @@ class Study(QiitaStatusObject): Major information about the study, keyed by db column name status: int Status of the study + efo: list of int + list of Experimental Factor Ontology ids for the study shared_with: list of User objects Emails of users the study is shared with pmids: list of str @@ -143,7 +143,7 @@ class Study(QiitaStatusObject): Methods ------- - share_with + share add_pmid Notes @@ -154,7 +154,7 @@ class Study(QiitaStatusObject): _table = "study" @classmethod - def create(cls, owner, info, investigation=None): + def create(cls, owner, efo, info, investigation=None): """Creates a new study on the database Parameters @@ -163,6 +163,8 @@ def create(cls, owner, info, investigation=None): the user id of the study' owner info: dict the information attached to the study. + efo: int or list + Experimental Factor Ontology or -ies for the study investigation: Investigation object if the study is part of an investigation, the id to associate with @@ -171,15 +173,13 @@ def create(cls, owner, info, investigation=None): QiitaDBColumnError Non-db columns in info dictionary All required keys not passed - QiitaStudyError - Study already exists IncompetentQiitaDeveloperError - study_id or status passed as a key + study_id or study_status_id passed as a key Notes ----- - All keys in info, except the efo, must be equal to columns in the - forge.study table in the database. EFO information is stored as a list + All keys in info, except the efo, must be equal to columns in + qiita.study table in the database. EFO information is stored as a list under the key 'study_experimental_factor', the name of the table it is stored in. """ @@ -191,27 +191,18 @@ def create(cls, owner, info, investigation=None): raise IncompetentQiitaDeveloperError("Can't pass status in info " "dict!") - # Save study_experimental_factor data for insertion - efo = None - if "study_experimental_factor" in info: - efo = info["study_experimental_factor"] - if isinstance(efo, int): - efo = [efo] - info.pop("study_experimental_factor") - else: - raise QiitaDBColumnError("EFO info not passed!") - # add default values to info info['email'] = owner.id info['reprocess'] = False info['first_contact'] = date.today().strftime("%B %d, %Y") + # default to waiting_approval status info['study_status_id'] = 1 conn_handler = SQLConnectionHandler() # make sure dictionary only has keys for available columns in db - check_table_cols(conn_handler, info, "study") + check_table_cols(conn_handler, info, cls._table) # make sure reqired columns in dictionary - check_required_columns(conn_handler, info, "study") + check_required_columns(conn_handler, info, cls._table) # Insert study into database keys = info.keys() @@ -228,6 +219,8 @@ def create(cls, owner, info, investigation=None): study_id = conn_handler.execute_fetchone(sql, data)[0] # insert efo information into database + if isinstance(efo, int): + efo = [efo] sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)".format(cls._table)) conn_handler.executemany(sql, list(zip([study_id] * len(efo), efo))) @@ -307,10 +300,6 @@ def info(self): conn_handler = SQLConnectionHandler() sql = "SELECT * FROM qiita.{0} WHERE study_id = %s".format(self._table) info = dict(conn_handler.execute_fetchone(sql, (self._id, ))) - sql = ("SELECT efo_id FROM qiita.{0}_experimental_factor WHERE " - "study_id = %s".format(self._table)) - efo = [x[0] for x in conn_handler.execute_fetchall(sql, (self._id, ))] - info["study_experimental_factor"] = efo # Convert everything from ids to objects info['email'] = User(info['email']) @@ -339,20 +328,12 @@ def info(self, info): Unknown column names passed """ self._lock_public() - if len(info) < 1: + if not info: raise IncompetentQiitaDeveloperError("Need entries in info dict!") conn_handler = SQLConnectionHandler() - - # Save study_experimental_factor data for insertion - efo = None - if "study_experimental_factor" in info: - efo = info["study_experimental_factor"] - if isinstance(efo, int): - efo = [efo] - info.pop("study_experimental_factor") - - check_table_cols(conn_handler, info, "study") + # make sure dictionary only has keys for available columns in db + check_table_cols(conn_handler, info, self._table) sql_vals = [] data = [] @@ -370,13 +351,6 @@ def info(self, info): "study_id = %s".format(self._table, ','.join(sql_vals))) conn_handler.execute(sql, data) - if efo is not None: - # insert efo information into database - sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " - "efo_id) VALUES (%s, %s)".format(self._table)) - conn_handler.executemany(sql, - list(zip([self._id] * len(efo), efo))) - @property def status(self): """Returns the study_status_id for the study @@ -398,16 +372,43 @@ def status(self, status_id): ---------- status_id: int ID for the new status - - Notes - ----- - You can still change this even when a study is public. BE CAREFUL! """ + self._lock_public() conn_handler = SQLConnectionHandler() sql = ("UPDATE qiita.{0} SET study_status_id = %s WHERE " "study_id = %s".format(self._table)) conn_handler.execute(sql, (status_id, self._id)) + @property + def efo(self): + conn_handler = SQLConnectionHandler() + sql = ("SELECT efo_id FROM qiita.{0}_experimental_factor WHERE " + "study_id = %s".format(self._table)) + return [x[0] for x in conn_handler.execute_fetchall(sql, (self._id, ))] + + @efo.setter + def efo(self, efo_vals): + """Sets the efo for the study + + Parameters + ---------- + status_id: int + ID for the new status + """ + self._lock_public() + if isinstance(efo_vals, int): + efo_vals = [efo_vals] + # wipe out any EFOs currently attached to study + conn_handler = SQLConnectionHandler() + sql = ("DELETE FROM qiita.{0}_experimental_factor WHERE " + "study_id = %s".format(self._table)) + conn_handler.execute(sql, (self._id, )) + # insert new EFO information into database + sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " + "efo_id) VALUES (%s, %s)".format(self._table)) + conn_handler.executemany(sql, list(zip([self._id] * len(efo_vals), + efo_vals))) + @property def shared_with(self): """list of users the study is shared with @@ -449,9 +450,7 @@ def investigation(self): sql = ("SELECT investigation_id FROM qiita.investigation_study WHERE " "study_id = %s") inv = conn_handler.execute_fetchone(sql, (self._id, )) - if inv is None: - return None - return Investigation(inv[0]) + return Investigation(inv[0]) if inv is not None else inv @property def metadata(self): @@ -507,12 +506,12 @@ def processed_data(self): conn_handler.execute_fetchall(sql, (self._id,))] # --- methods --- - def share_with(self, user): + def share(self, user): """Shares the study with given user Parameters ---------- - email: User object + user: User object The user to share with """ conn_handler = SQLConnectionHandler() @@ -567,9 +566,9 @@ def exists(cls, name, email): True if person exists else false """ conn_handler = SQLConnectionHandler() - sql = ("SELECT count(1) FROM qiita.{0} WHERE name = %s AND " - "email = %s".format(cls._table)) - return bool(conn_handler.execute_fetchone(sql, (name, email))[0]) + sql = ("SELECT exists(SELECT * FROM qiita.{0} WHERE " + "name = %s AND email = %s)".format(cls._table)) + return conn_handler.execute_fetchone(sql, (name, email))[0] @classmethod def create(cls, name, email, address=None, phone=None): @@ -596,7 +595,7 @@ def create(cls, name, email, address=None, phone=None): Person already exists """ if cls.exists(name, email): - raise QiitaDBExecutionError("StudyPerson already exists!") + raise QiitaDBDuplicateError("StudyPerson already exists!") # Doesn't exist so insert new person sql = ("INSERT INTO qiita.{0} (name, email, address, phone) VALUES" diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index faf5f0a0c..f2596e3b6 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -8,7 +8,7 @@ from qiita_db.data import PreprocessedData, RawData, ProcessedData from qiita_db.metadata_template import SampleTemplate from qiita_db.user import User -from qiita_db.exceptions import (QiitaDBExecutionError, QiitaDBColumnError, +from qiita_db.exceptions import (QiitaDBDuplicateError, QiitaDBColumnError, QiitaDBStatusError) from qiita_db.sql_connection import SQLConnectionHandler @@ -40,7 +40,7 @@ def test_create_studyperson(self): self.assertEqual(obs["phone"], '111-121-1313') def test_create_studyperson_already_exists(self): - with self.assertRaises(QiitaDBExecutionError): + with self.assertRaises(QiitaDBDuplicateError): new = StudyPerson.create('LabDude', 'lab_dude@foo.bar') def test_retrieve_name(self): @@ -87,7 +87,6 @@ def setUp(self): self.info = { "timeseries_type_id": 1, - "study_experimental_factor": 1, "metadata_complete": True, "mixs_compliant": True, "number_samples_collected": 25, @@ -104,7 +103,7 @@ def setUp(self): "lab_person_id": StudyPerson(1) } - self.weedexp = { + self.existingexp = { 'mixs_compliant': True, 'metadata_complete': True, 'reprocess': False, @@ -129,8 +128,7 @@ def setUp(self): "erent time points in the plant lifecycle."), 'email': User('test@foo.bar'), 'spatial_series': False, - 'study_description': ('Analysis of the Cannabis Plant Microbiome'), - 'study_experimental_factor': [1], + 'study_description': 'Analysis of the Cannabis Plant Microbiome', 'portal_type_id': 2, 'study_alias': 'Cannabis Soils', 'most_recent_contact': '2014-05-19 16:11', @@ -141,14 +139,15 @@ def setUp(self): def test_create_study_min_data(self): """Insert a study into the database""" - obs = Study.create(User('test@foo.bar'), self.info) + obs = Study.create(User('test@foo.bar'), 1, self.info) self.assertEqual(obs.id, 2) exp = {'mixs_compliant': True, 'metadata_complete': True, 'reprocess': False, 'study_status_id': 1, 'number_samples_promised': 28, 'emp_person_id': 2, 'funding': None, 'vamps_id': None, 'first_contact': date.today().strftime("%B %d, %Y"), - 'principal_investigator_id': 3, 'timeseries_type_id': 1, + 'principal_investigator_id': 3, + 'timeseries_type_id': 1, 'study_abstract': ('Exploring how a high fat diet changes the ' 'gut microbiome'), 'email': 'test@foo.bar', 'spatial_series': None, @@ -172,13 +171,14 @@ def test_create_study_min_data(self): def test_create_study_with_investigation(self): """Insert a study into the database with an investigation""" - obs = Study.create(User('test@foo.bar'), self.info, Investigation(1)) + obs = Study.create(User('test@foo.bar'), 1, self.info, + Investigation(1)) self.assertEqual(obs.id, 2) # check the investigation was assigned conn = SQLConnectionHandler() - obs3 = conn.execute_fetchall("SELECT * from qiita.investigation_study " + obs = conn.execute_fetchall("SELECT * from qiita.investigation_study " "WHERE study_id = 2") - self.assertEqual(obs3, [[1, 2]]) + self.assertEqual(obs, [[1, 2]]) def test_create_study_all_data(self): """Insert a study into the database with every info field""" @@ -188,7 +188,7 @@ def test_create_study_all_data(self): 'spatial_series': True, 'metadata_complete': False, }) - obs = Study.create(User('test@foo.bar'), self.info) + obs = Study.create(User('test@foo.bar'), 1, self.info) self.assertEqual(obs.id, 2) exp = {'mixs_compliant': True, 'metadata_complete': False, 'reprocess': False, 'study_status_id': 1, @@ -216,105 +216,104 @@ def test_create_study_all_data(self): "study_id = 2") self.assertEqual(obsefo, [[1]]) - def test_create_missing_requred(self): + def test_create_missing_required(self): """ Insert a study that is missing a required info key""" self.info.pop("study_title") with self.assertRaises(QiitaDBColumnError): - Study.create(User('test@foo.bar'), self.info) + Study.create(User('test@foo.bar'), 1, self.info) def test_create_study_id(self): """Insert a study with study_id present""" self.info.update({"study_id": 1}) with self.assertRaises(IncompetentQiitaDeveloperError): - Study.create(User('test@foo.bar'), self.info) + Study.create(User('test@foo.bar'), 1, self.info) def test_create_study_status(self): """Insert a study with status present""" self.info.update({"study_status_id": 1}) with self.assertRaises(IncompetentQiitaDeveloperError): - Study.create(User('test@foo.bar'), self.info) - - def test_create_no_efo(self): - """Insert a study no experimental factors passed""" - self.info.pop("study_experimental_factor") - with self.assertRaises(QiitaDBColumnError): - Study.create('test@foo.bar', self.info) + Study.create(User('test@foo.bar'), 1, self.info) def test_create_unknown_db_col(self): """ Insert a study with an info key not in the database""" self.info["SHOULDNOTBEHERE"] = "BWAHAHAHAHAHA" with self.assertRaises(QiitaDBColumnError): - Study.create(User('test@foo.bar'), self.info) + Study.create(User('test@foo.bar'), 1, self.info) def test_retrieve_title(self): self.assertEqual(self.study.title, 'Identification of the Microbiomes' ' for Cannabis Soils') def test_set_title(self): - self.study.status = 1 - self.study.title = "Weed Soils" - self.assertEqual(self.study.title, "Weed Soils") + new = Study.create(User('test@foo.bar'), 1, self.info) + new.title = "Cannabis soils" + self.assertEqual(new.title, "Cannabis soils") def test_set_title_public(self): """Tests for fail if editing title of a public study""" with self.assertRaises(QiitaDBStatusError): - self.study.title = "Weed Soils" + self.study.title = "FAILBOAT" - def test_retrieve_info(self): - self.assertEqual(self.study.info, self.weedexp) + def test_get_efo(self): + self.assertEqual(self.study.efo, [1]) - def test_set_info(self): - """Set info with integer efo_id""" - newinfo = { - "timeseries_type_id": 2, - "study_experimental_factor": 3, - "metadata_complete": False, - "number_samples_collected": 28, - "lab_person_id": StudyPerson(2), - "vamps_id": 'MBE_111222' - } + def test_set_efo_list(self): + """Set efo with list efo_id""" + new = Study.create(User("test@foo.bar"), 1, self.info) + new.efo = [3, 4] + self.assertEqual(new.efo, [3, 4]) - self.weedexp.update(newinfo) - # Fix study_experimental_factor since update wipes out the 1 - self.weedexp['study_experimental_factor'] = [1, 3] - self.study.status = 1 - self.study.info = newinfo - self.assertEqual(self.study.info, self.weedexp) + def test_set_efo_int(self): + """Set efo with int efo_id""" + new = Study.create(User("test@foo.bar"), 1, self.info) + new.efo = 5 + self.assertEqual(new.efo, [5]) - def test_set_tinfo_public(self): - """Tests for fail if editing info of a public study""" + def test_set_efo_public(self): + """Set efo on a public study""" with self.assertRaises(QiitaDBStatusError): - self.study.title = "Weed Soils" + self.study.efo = 6 + + def test_retrieve_info(self): + self.assertEqual(self.study.info, self.existingexp) - def test_set_info_efo_list(self): - """Set info with list efo_id""" + def test_set_info(self): + """Set info in a study""" newinfo = { "timeseries_type_id": 2, - "study_experimental_factor": [3, 4], "metadata_complete": False, "number_samples_collected": 28, "lab_person_id": StudyPerson(2), "vamps_id": 'MBE_111222' } - - self.weedexp.update(newinfo) - # Fix study_experimental_factor since update wipes out the 1 - self.weedexp['study_experimental_factor'] = [1, 3, 4] - self.study.status = 1 - self.study.info = newinfo - self.assertEqual(self.study.info, self.weedexp) + new = Study.create(User('test@foo.bar'), 1, self.info) + self.info.update(newinfo) + new.info = newinfo + # add missing table cols + self.info["funding"] = None + self.info["spatial_series"] = False + for key, val in new.info.iteritems(): + if val != self.info[key]: + print key, val, self.info[key] + self.assertEqual(new.info, self.info) + + def test_set_info_public(self): + """Tests for fail if editing info of a public study""" + with self.assertRaises(QiitaDBStatusError): + self.study.info = {"vamps_id": "12321312"} def test_info_empty(self): - self.study.status = 1 + new = Study.create(User('test@foo.bar'), 1, self.info) with self.assertRaises(IncompetentQiitaDeveloperError): - self.study.info = {} + new.info = {} def test_retrieve_status(self): self.assertEqual(self.study.status, 2) def test_set_status(self): - self.study.status = 1 - self.assertEqual(self.study.status, 1) + new = Study.create(User('test@foo.bar'), 1, self.info) + new.status = 3 + self.assertEqual(new.status, 3) def test_retrieve_shared_with(self): self.assertEqual(self.study.shared_with, [User('shared@foo.bar')]) @@ -332,15 +331,27 @@ def test_retrieve_metadata(self): def test_retrieve_raw_data(self): self.assertEqual(self.study.raw_data, [RawData(1), RawData(2)]) + def test_retrieve_raw_data_none(self): + new = Study.create(User('test@foo.bar'), 1, self.info) + self.assertEqual(new.raw_data, []) + def test_retrieve_preprocessed_data(self): self.assertEqual(self.study.preprocessed_data, [PreprocessedData(1), PreprocessedData(2)]) + def test_retrieve_preprocessed_data_none(self): + new = Study.create(User('test@foo.bar'), 1, self.info) + self.assertEqual(new.preprocessed_data, []) + def test_retrieve_processed_data(self): self.assertEqual(self.study.processed_data, [ProcessedData(1)]) - def test_share_with(self): - self.study.share_with(User('admin@foo.bar')) + def test_retrieve_processed_data_none(self): + new = Study.create(User('test@foo.bar'), 1, self.info) + self.assertEqual(new.processed_data, []) + + def test_share(self): + self.study.share(User('admin@foo.bar')) self.assertEqual(self.study.shared_with, [User('shared@foo.bar'), User('admin@foo.bar')]) diff --git a/qiita_db/test/test_util.py b/qiita_db/test/test_util.py index 447e1fbf2..c74de4207 100644 --- a/qiita_db/test/test_util.py +++ b/qiita_db/test/test_util.py @@ -27,6 +27,7 @@ def setUp(self): 'principal_investigator_id', 'email', 'number_samples_collected'] def test_check_required_columns(self): + # Doesn't do anything if correct info passed, only errors if wrong info check_required_columns(self.conn_handler, self.required, self.table) def test_check_required_columns_fail(self): @@ -36,6 +37,7 @@ def test_check_required_columns_fail(self): self.table) def test_check_table_cols(self): + # Doesn't do anything if correct info passed, only errors if wrong info check_table_cols(self.conn_handler, self.required, self.table) def test_check_table_cols_fail(self): From 95c46293710dc2a3d9cb16cc8fbcfa75df3b2bf0 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 9 Jun 2014 13:24:42 -0600 Subject: [PATCH 45/57] fix some travis failures --- qiita_db/exceptions.py | 4 ++++ qiita_db/test/test_study.py | 2 +- qiita_db/user.py | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/qiita_db/exceptions.py b/qiita_db/exceptions.py index 887055244..c339697d5 100644 --- a/qiita_db/exceptions.py +++ b/qiita_db/exceptions.py @@ -42,6 +42,10 @@ class QiitaDBDuplicateError(QiitaDBError): pass +class QiitaDBStatusError(QiitaDBError): + """Exception when editing is done with an unallowed status""" + pass + class QiitaDBUnknownIDError(QiitaDBError): """Exception for error when an object does not exists in the DB""" def __init__(self, missing_id, table): diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index f2596e3b6..c63a5534a 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -177,7 +177,7 @@ def test_create_study_with_investigation(self): # check the investigation was assigned conn = SQLConnectionHandler() obs = conn.execute_fetchall("SELECT * from qiita.investigation_study " - "WHERE study_id = 2") + "WHERE study_id = 2") self.assertEqual(obs, [[1, 2]]) def test_create_study_all_data(self): diff --git a/qiita_db/user.py b/qiita_db/user.py index 2a0a05165..010b099d2 100644 --- a/qiita_db/user.py +++ b/qiita_db/user.py @@ -20,6 +20,7 @@ from .base import QiitaObject from .exceptions import QiitaDBNotImplementedError +from .sql_connection import SQLConnectionHandler LEVELS = {'admin', 'dev', 'superuser', 'user', 'guest'} @@ -65,7 +66,34 @@ class User(QiitaObject): Removes a shared analysis from the user """ - @staticmethod + _table = "qiita_user" + + def _check_id(self, id_, conn_handler=None): + r"""Check that the provided ID actually exists on the database + + Parameters + ---------- + id_ : object + The ID to test + conn_handler : SQLConnectionHandler + The connection handler object connected to the DB + + Notes + ----- + This functionoverwrites the base function, as sql layout doesn't follow + the same conventions done in the other classes. + """ + self._check_subclass() + + conn_handler = (conn_handler if conn_handler is not None + else SQLConnectionHandler()) + print ("SELECT EXISTS(SELECT * FROM qiita.qiita_user WHERE " + "email = %s)" % id_) + return conn_handler.execute_fetchone( + "SELECT EXISTS(SELECT * FROM qiita.qiita_user WHERE " + "email = %s)", (id_, ))[0] + + @classmethod def create(email, password): """Creates a new user on the database @@ -78,7 +106,7 @@ def create(email, password): """ raise QiitaDBNotImplementedError() - @staticmethod + @classmethod def delete(id_): """Deletes the user `id` from the database From ea5973aedb46f7fab82c49feb499ebd3bf189847 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 11 Jun 2014 14:23:29 -0600 Subject: [PATCH 46/57] some more changes --- qiita_db/base.py | 6 ++-- qiita_db/data.py | 3 ++ qiita_db/study.py | 65 ++++++++++++------------------------- qiita_db/test/test_study.py | 30 ++++++++--------- qiita_db/user.py | 2 -- 5 files changed, 40 insertions(+), 66 deletions(-) diff --git a/qiita_db/base.py b/qiita_db/base.py index b27c8fef4..b365538c5 100644 --- a/qiita_db/base.py +++ b/qiita_db/base.py @@ -199,7 +199,7 @@ def status(self): "{0}_id = %s)".format(self._table), (self._id, ))[0] - def _status_setter_checks(self): + def _status_setter_checks(self, conn_handler): r"""Perform any extra checks that needed to be done before setting the object status on the database. Should be overwritten by the subclasses """ @@ -218,10 +218,10 @@ def status(self, status): self._check_subclass() # Perform any extra checks needed before we update the status in the DB - self._status_setter_checks() + conn_handler = SQLConnectionHandler() + self._status_setter_checks(conn_handler) # Update the status of the object - conn_handler = SQLConnectionHandler() conn_handler.execute( "UPDATE qiita.{0} SET {0}_status_id = " "(SELECT {0}_status_id FROM qiita.{0}_status WHERE status = %s) " diff --git a/qiita_db/data.py b/qiita_db/data.py index 235dc63b7..bc17293ec 100644 --- a/qiita_db/data.py +++ b/qiita_db/data.py @@ -3,11 +3,14 @@ class RawData(QiitaStatusObject): _table = "raw_data" + pass class PreprocessedData(QiitaStatusObject): + _table = "preprocessed_data" pass class ProcessedData(QiitaStatusObject): + _table = "processed_data" pass \ No newline at end of file diff --git a/qiita_db/study.py b/qiita_db/study.py index f3fc5c98a..a72fd9348 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -104,7 +104,7 @@ from qiita_core.exceptions import IncompetentQiitaDeveloperError from .base import QiitaStatusObject, QiitaObject from .exceptions import (QiitaDBDuplicateError, QiitaDBNotImplementedError, - QiitaDBExecutionError, QiitaDBStatusError,) + QiitaDBStatusError) from .data import RawData, PreprocessedData, ProcessedData from .user import User from .investigation import Investigation @@ -144,6 +144,7 @@ class Study(QiitaStatusObject): Methods ------- share + unshare add_pmid Notes @@ -153,6 +154,19 @@ class Study(QiitaStatusObject): """ _table = "study" + def _lock_public(self, conn_handler): + """Raises QiitaDBStatusError if study is public""" + sql = ("SELECT study_status_id FROM qiita.{0} WHERE " + "{0}_id = %s".format(self._table)) + if conn_handler.execute_fetchone(sql, (self._id, ))[0] == 2: + raise QiitaDBStatusError("Can't change status of public study!") + + def _status_setter_checks(self, conn_handler): + r"""Perform any extra checks that needed to be done before setting the + object status on the database. Should be overwritten by the subclasses + """ + self._lock_public(conn_handler) + @classmethod def create(cls, owner, efo, info, investigation=None): """Creates a new study on the database @@ -248,17 +262,6 @@ def delete(cls, id_): raise QiitaDBNotImplementedError() # --- Attributes --- - def _lock_public(self): - """Locks a study if it is public - - Raises - ------ - QiitaDBStatusError - study is public - """ - if self.status == 2: - raise QiitaDBStatusError("Can't edit public study!") - @property def title(self): """Returns the title of the study @@ -282,8 +285,8 @@ def title(self, title): title : str The new study title """ - self._lock_public() conn_handler = SQLConnectionHandler() + self._lock_public(conn_handler) sql = ("UPDATE qiita.{0} SET study_title = %s WHERE " "study_id = %s".format(self._table)) return conn_handler.execute(sql, (title, self._id)) @@ -327,11 +330,11 @@ def info(self, info): QiitaDBColumnError Unknown column names passed """ - self._lock_public() + conn_handler = SQLConnectionHandler() + self._lock_public(conn_handler) if not info: raise IncompetentQiitaDeveloperError("Need entries in info dict!") - conn_handler = SQLConnectionHandler() # make sure dictionary only has keys for available columns in db check_table_cols(conn_handler, info, self._table) @@ -351,34 +354,6 @@ def info(self, info): "study_id = %s".format(self._table, ','.join(sql_vals))) conn_handler.execute(sql, data) - @property - def status(self): - """Returns the study_status_id for the study - - Returns - ------- - int: status of study - """ - conn_handler = SQLConnectionHandler() - sql = ("SELECT study_status_id FROM qiita.{0} WHERE " - "study_id = %s".format(self._table)) - return conn_handler.execute_fetchone(sql, (self._id, ))[0] - - @status.setter - def status(self, status_id): - """Sets the study_status_id for the study - - Parameters - ---------- - status_id: int - ID for the new status - """ - self._lock_public() - conn_handler = SQLConnectionHandler() - sql = ("UPDATE qiita.{0} SET study_status_id = %s WHERE " - "study_id = %s".format(self._table)) - conn_handler.execute(sql, (status_id, self._id)) - @property def efo(self): conn_handler = SQLConnectionHandler() @@ -395,11 +370,11 @@ def efo(self, efo_vals): status_id: int ID for the new status """ - self._lock_public() + conn_handler = SQLConnectionHandler() + self._lock_public(conn_handler) if isinstance(efo_vals, int): efo_vals = [efo_vals] # wipe out any EFOs currently attached to study - conn_handler = SQLConnectionHandler() sql = ("DELETE FROM qiita.{0}_experimental_factor WHERE " "study_id = %s".format(self._table)) conn_handler.execute(sql, (self._id, )) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index c63a5534a..0f1d23295 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -31,17 +31,14 @@ def test_create_studyperson(self): '111 fake street', '111-121-1313') self.assertEqual(new.id, 4) conn = SQLConnectionHandler() - obs = conn.execute_fetchone("SELECT * FROM qiita.study_person WHERE " + obs = conn.execute_fetchall("SELECT * FROM qiita.study_person WHERE " "study_person_id = 4") - self.assertEqual(obs["study_person_id"], 4) - self.assertEqual(obs["email"], 'somedude@foo.bar') - self.assertEqual(obs["name"], 'SomeDude') - self.assertEqual(obs["address"], '111 fake street') - self.assertEqual(obs["phone"], '111-121-1313') + self.assertEqual(obs, [[4, 'SomeDude', 'somedude@foo.bar', + '111 fake street', '111-121-1313']]) def test_create_studyperson_already_exists(self): with self.assertRaises(QiitaDBDuplicateError): - new = StudyPerson.create('LabDude', 'lab_dude@foo.bar') + StudyPerson.create('LabDude', 'lab_dude@foo.bar') def test_retrieve_name(self): self.assertEqual(self.studyperson.name, 'LabDude') @@ -159,8 +156,10 @@ def test_create_study_min_data(self): 'number_samples_collected': 25} conn = SQLConnectionHandler() - obsins = dict(conn.execute_fetchone("SELECT * FROM qiita.study WHERE " + obsins = dict(conn.execute_fetchall("SELECT * FROM qiita.study WHERE " "study_id = 2")) + self.assertEqual(len(obsins), 1) + obsins = obsins[0] self.assertEqual(obsins, exp) # make sure EFO went in to table correctly @@ -206,8 +205,10 @@ def test_create_study_all_data(self): 'study_title': 'Fried chicken microbiome', 'number_samples_collected': 25} conn = SQLConnectionHandler() - obsins = dict(conn.execute_fetchone("SELECT * FROM qiita.study WHERE " - "study_id = 2")) + obsins = conn.execute_fetchall("SELECT * FROM qiita.study WHERE " + "study_id = 2") + self.assertEqual(len(obsins), 1) + obsins = dict(obsins[0]) self.assertEqual(obsins, exp) # make sure EFO went in to table correctly @@ -292,9 +293,6 @@ def test_set_info(self): # add missing table cols self.info["funding"] = None self.info["spatial_series"] = False - for key, val in new.info.iteritems(): - if val != self.info[key]: - print key, val, self.info[key] self.assertEqual(new.info, self.info) def test_set_info_public(self): @@ -308,12 +306,12 @@ def test_info_empty(self): new.info = {} def test_retrieve_status(self): - self.assertEqual(self.study.status, 2) + self.assertEqual(self.study.status, "public") def test_set_status(self): new = Study.create(User('test@foo.bar'), 1, self.info) - new.status = 3 - self.assertEqual(new.status, 3) + new.status = "private" + self.assertEqual(new.status, "private") def test_retrieve_shared_with(self): self.assertEqual(self.study.shared_with, [User('shared@foo.bar')]) diff --git a/qiita_db/user.py b/qiita_db/user.py index 010b099d2..d8226f938 100644 --- a/qiita_db/user.py +++ b/qiita_db/user.py @@ -87,8 +87,6 @@ def _check_id(self, id_, conn_handler=None): conn_handler = (conn_handler if conn_handler is not None else SQLConnectionHandler()) - print ("SELECT EXISTS(SELECT * FROM qiita.qiita_user WHERE " - "email = %s)" % id_) return conn_handler.execute_fetchone( "SELECT EXISTS(SELECT * FROM qiita.qiita_user WHERE " "email = %s)", (id_, ))[0] From 5a09f9e25ddb93c87849d9a8df3f876850daf55e Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 11 Jun 2014 15:47:12 -0600 Subject: [PATCH 47/57] calm before the storm --- qiita_db/metadata_template.py | 14 +++++ qiita_db/study.py | 103 +++++++++++++++++----------------- qiita_db/test/test_study.py | 76 ++++++++++++++----------- 3 files changed, 107 insertions(+), 86 deletions(-) diff --git a/qiita_db/metadata_template.py b/qiita_db/metadata_template.py index 195519c68..1cd371fbf 100644 --- a/qiita_db/metadata_template.py +++ b/qiita_db/metadata_template.py @@ -66,6 +66,18 @@ class MetadataTemplate(QiitaStatusObject): # instantiate this base class _table_prefix = None _column_table = None + _id_column = None + + def _check_id(self, id_, conn_handler=None): + # PLACEHOLDER SO TESTS PASS. Jose will rewrite for metadata pr + r"""""" + self._check_subclass() + conn_handler = (conn_handler if conn_handler is not None + else SQLConnectionHandler()) + return conn_handler.execute_fetchone( + "SELECT EXISTS(SELECT * FROM qiita.{0} WHERE " + "{1}=%s)".format(self._table, self._id_column), + (id_, ))[0] @classmethod def _get_table_name(cls, study_id): @@ -262,8 +274,10 @@ def has_single_category_values(self, category): class SampleTemplate(MetadataTemplate): """""" + _table = "required_sample_info" _table_prefix = "sample_" _column_table = "study_sample_columns" + _id_column = "study_id" class PrepTemplate(MetadataTemplate): diff --git a/qiita_db/study.py b/qiita_db/study.py index a72fd9348..70986bb50 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -54,7 +54,6 @@ ... "number_samples_collected": 25, ... "number_samples_promised": 28, ... "portal_type_id": 3, -... "study_title": "Study Title", ... "study_alias": "TST", ... "study_description": "Some description of the study goes here", ... "study_abstract": "Some abstract goes here", @@ -62,7 +61,7 @@ ... "principal_investigator_id": StudyPerson(3), ... "lab_person_id": StudyPerson(1)} # doctest: +SKIP >>> owner = User('owner@foo.bar') # doctest: +SKIP ->>> Study(owner, 1, info) # doctest: +SKIP +>>> Study(owner, "New Study Title", 1, info) # doctest: +SKIP You can also add a study to an investigation by passing the investigation object while creating the study. @@ -77,7 +76,6 @@ ... "number_samples_collected": 25, ... "number_samples_promised": 28, ... "portal_type_id": 3, -... "study_title": "Study Title", ... "study_alias": "TST", ... "study_description": "Some description of the study goes here", ... "study_abstract": "Some abstract goes here", @@ -86,7 +84,7 @@ ... "lab_person_id": StudyPerson(1)} # doctest: +SKIP >>> owner = User('owner@foo.bar') # doctest: +SKIP >>> investigation = Investigation(1) # doctest: +SKIP ->>> Study(owner, 1, info, investigation) # doctest: +SKIP +>>> Study(owner, "New Study Title", 1, info, investigation) # doctest: +SKIP """ # ----------------------------------------------------------------------------- @@ -100,11 +98,12 @@ from __future__ import division from future.builtins import zip from datetime import date +from copy import deepcopy from qiita_core.exceptions import IncompetentQiitaDeveloperError from .base import QiitaStatusObject, QiitaObject from .exceptions import (QiitaDBDuplicateError, QiitaDBNotImplementedError, - QiitaDBStatusError) + QiitaDBStatusError, QiitaDBColumnError) from .data import RawData, PreprocessedData, ProcessedData from .user import User from .investigation import Investigation @@ -118,28 +117,17 @@ class Study(QiitaStatusObject): Attributes ---------- - name: str - name of the study - info: dict - Major information about the study, keyed by db column name - status: int - Status of the study - efo: list of int - list of Experimental Factor Ontology ids for the study - shared_with: list of User objects - Emails of users the study is shared with - pmids: list of str - PMIDs assiciated with the study - investigation: Investigation object - Investigation the study is part of - metadata: Metadata object - Metadata object tied to this study - raw_data: list of RawData objects - All raw data attached to the study - preprocessed_data: list of PreprocessedData objects - All preprocessed data attached to the study - processed_data: list of ProcessedData objects - All processed data attached to the study + name + info + status + efo + shared_with + pmids + investigation + metadata + raw_data + preprocessed_data + processed_data Methods ------- @@ -153,6 +141,8 @@ class Study(QiitaStatusObject): You should not be doing that. """ _table = "study" + # The following columns are considered not part of the user info + _non_info = {"study_id", "study_status_id", "study_title"} def _lock_public(self, conn_handler): """Raises QiitaDBStatusError if study is public""" @@ -168,17 +158,19 @@ def _status_setter_checks(self, conn_handler): self._lock_public(conn_handler) @classmethod - def create(cls, owner, efo, info, investigation=None): + def create(cls, owner, title, efo, info, investigation=None): """Creates a new study on the database Parameters ---------- owner : User object the user id of the study' owner - info: dict - the information attached to the study. + title: str + Title of the study efo: int or list Experimental Factor Ontology or -ies for the study + info: dict + the information attached to the study. investigation: Investigation object if the study is part of an investigation, the id to associate with @@ -197,39 +189,39 @@ def create(cls, owner, efo, info, investigation=None): under the key 'study_experimental_factor', the name of the table it is stored in. """ - # make sure not passing a study id in the info dict - if "study_id" in info: - raise IncompetentQiitaDeveloperError("Can't pass study_id in info " - "dict!") - if "study_status_id" in info: - raise IncompetentQiitaDeveloperError("Can't pass status in info " - "dict!") + # make sure not passing non-info columns in the info dict + if cls._non_info.intersection(info): + raise QiitaDBColumnError("non info keys passed: %s" % + cls._non_info.intersection(info)) # add default values to info - info['email'] = owner.id - info['reprocess'] = False - info['first_contact'] = date.today().strftime("%B %d, %Y") + insertdict = deepcopy(info) + if "first_contact" not in insertdict: + insertdict['first_contact'] = date.today().strftime("%B %d, %Y") + insertdict['email'] = owner.id + insertdict['study_title'] = title + insertdict['reprocess'] = False # default to waiting_approval status - info['study_status_id'] = 1 + insertdict['study_status_id'] = 1 conn_handler = SQLConnectionHandler() # make sure dictionary only has keys for available columns in db - check_table_cols(conn_handler, info, cls._table) + check_table_cols(conn_handler, insertdict, cls._table) # make sure reqired columns in dictionary - check_required_columns(conn_handler, info, cls._table) + check_required_columns(conn_handler, insertdict, cls._table) # Insert study into database - keys = info.keys() + keys = insertdict.keys() sql = ("INSERT INTO qiita.{0} ({1}) VALUES ({2}) RETURNING " "study_id".format(cls._table, ','.join(keys), - ','.join(['%s'] * len(info)))) + ','.join(['%s'] * len(insertdict)))) # make sure data in same order as sql column names, and ids are used data = [] for col in keys: - if isinstance(info[col], QiitaObject): - data.append(info[col].id) + if isinstance(insertdict[col], QiitaObject): + data.append(insertdict[col].id) else: - data.append(info[col]) + data.append(insertdict[col]) study_id = conn_handler.execute_fetchone(sql, data)[0] # insert efo information into database @@ -309,9 +301,9 @@ def info(self): info.update({k: StudyPerson(info[k]) for k in ['principal_investigator_id', 'lab_person_id', 'emp_person_id'] if info[k] is not None}) - # remove id and status since not needed - info.pop("study_id") - info.pop("study_status_id") + # remove items from info + for item in self._non_info: + info.pop(item) return info @info.setter @@ -330,11 +322,16 @@ def info(self, info): QiitaDBColumnError Unknown column names passed """ - conn_handler = SQLConnectionHandler() - self._lock_public(conn_handler) if not info: raise IncompetentQiitaDeveloperError("Need entries in info dict!") + if self._non_info.intersection(info): + raise QiitaDBColumnError("non info keys passed: %s" % + self._non_info.intersection(info)) + + conn_handler = SQLConnectionHandler() + self._lock_public(conn_handler) + # make sure dictionary only has keys for available columns in db check_table_cols(conn_handler, info, self._table) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 0f1d23295..3a8e1c1e7 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -86,10 +86,10 @@ def setUp(self): "timeseries_type_id": 1, "metadata_complete": True, "mixs_compliant": True, + "email": User('test@foo.bar'), "number_samples_collected": 25, "number_samples_promised": 28, "portal_type_id": 3, - "study_title": "Fried chicken microbiome", "study_alias": "FCM", "study_description": ("Microbiome of people who eat nothing but " "fried chicken"), @@ -130,13 +130,12 @@ def setUp(self): 'study_alias': 'Cannabis Soils', 'most_recent_contact': '2014-05-19 16:11', 'lab_person_id': StudyPerson(1), - 'study_title': ('Identification of the Microbiomes for Cannabis ' - 'Soils'), 'number_samples_collected': 27} def test_create_study_min_data(self): """Insert a study into the database""" - obs = Study.create(User('test@foo.bar'), 1, self.info) + obs = Study.create(User('test@foo.bar'), "Fried chicken microbiome", + 1, self.info) self.assertEqual(obs.id, 2) exp = {'mixs_compliant': True, 'metadata_complete': True, 'reprocess': False, 'study_status_id': 1, @@ -156,10 +155,10 @@ def test_create_study_min_data(self): 'number_samples_collected': 25} conn = SQLConnectionHandler() - obsins = dict(conn.execute_fetchall("SELECT * FROM qiita.study WHERE " - "study_id = 2")) + obsins = conn.execute_fetchall("SELECT * FROM qiita.study WHERE " + "study_id = 2") self.assertEqual(len(obsins), 1) - obsins = obsins[0] + obsins = dict(obsins[0]) self.assertEqual(obsins, exp) # make sure EFO went in to table correctly @@ -170,7 +169,8 @@ def test_create_study_min_data(self): def test_create_study_with_investigation(self): """Insert a study into the database with an investigation""" - obs = Study.create(User('test@foo.bar'), 1, self.info, + obs = Study.create(User('test@foo.bar'), "Fried chicken microbiome", + 1, self.info, Investigation(1)) self.assertEqual(obs.id, 2) # check the investigation was assigned @@ -187,7 +187,8 @@ def test_create_study_all_data(self): 'spatial_series': True, 'metadata_complete': False, }) - obs = Study.create(User('test@foo.bar'), 1, self.info) + obs = Study.create(User('test@foo.bar'), "Fried chicken microbiome", + 1, self.info) self.assertEqual(obs.id, 2) exp = {'mixs_compliant': True, 'metadata_complete': False, 'reprocess': False, 'study_status_id': 1, @@ -219,34 +220,32 @@ def test_create_study_all_data(self): def test_create_missing_required(self): """ Insert a study that is missing a required info key""" - self.info.pop("study_title") + self.info.pop("study_alias") with self.assertRaises(QiitaDBColumnError): - Study.create(User('test@foo.bar'), 1, self.info) + Study.create(User('test@foo.bar'), "Fried Chicken Microbiome", + 1, self.info) - def test_create_study_id(self): - """Insert a study with study_id present""" + def test_create_study_with_not_allowed_key(self): + """Insert a study with key from _non_info present""" self.info.update({"study_id": 1}) - with self.assertRaises(IncompetentQiitaDeveloperError): - Study.create(User('test@foo.bar'), 1, self.info) - - def test_create_study_status(self): - """Insert a study with status present""" - self.info.update({"study_status_id": 1}) - with self.assertRaises(IncompetentQiitaDeveloperError): - Study.create(User('test@foo.bar'), 1, self.info) + with self.assertRaises(QiitaDBColumnError): + Study.create(User('test@foo.bar'), "Fried Chicken Microbiome", + 1, self.info) def test_create_unknown_db_col(self): """ Insert a study with an info key not in the database""" self.info["SHOULDNOTBEHERE"] = "BWAHAHAHAHAHA" with self.assertRaises(QiitaDBColumnError): - Study.create(User('test@foo.bar'), 1, self.info) + Study.create(User('test@foo.bar'), "Fried Chicken Microbiome", + 1, self.info) def test_retrieve_title(self): self.assertEqual(self.study.title, 'Identification of the Microbiomes' ' for Cannabis Soils') def test_set_title(self): - new = Study.create(User('test@foo.bar'), 1, self.info) + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) new.title = "Cannabis soils" self.assertEqual(new.title, "Cannabis soils") @@ -260,13 +259,15 @@ def test_get_efo(self): def test_set_efo_list(self): """Set efo with list efo_id""" - new = Study.create(User("test@foo.bar"), 1, self.info) + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) new.efo = [3, 4] self.assertEqual(new.efo, [3, 4]) def test_set_efo_int(self): """Set efo with int efo_id""" - new = Study.create(User("test@foo.bar"), 1, self.info) + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) new.efo = 5 self.assertEqual(new.efo, [5]) @@ -285,14 +286,18 @@ def test_set_info(self): "metadata_complete": False, "number_samples_collected": 28, "lab_person_id": StudyPerson(2), - "vamps_id": 'MBE_111222' + "vamps_id": 'MBE_111222', + "first_contact": "June 11, 2014" } - new = Study.create(User('test@foo.bar'), 1, self.info) + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) self.info.update(newinfo) new.info = newinfo # add missing table cols self.info["funding"] = None - self.info["spatial_series"] = False + self.info["spatial_series"] = None + self.info["most_recent_contact"] = None + self.info["reprocess"] = False self.assertEqual(new.info, self.info) def test_set_info_public(self): @@ -301,7 +306,8 @@ def test_set_info_public(self): self.study.info = {"vamps_id": "12321312"} def test_info_empty(self): - new = Study.create(User('test@foo.bar'), 1, self.info) + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) with self.assertRaises(IncompetentQiitaDeveloperError): new.info = {} @@ -309,7 +315,8 @@ def test_retrieve_status(self): self.assertEqual(self.study.status, "public") def test_set_status(self): - new = Study.create(User('test@foo.bar'), 1, self.info) + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) new.status = "private" self.assertEqual(new.status, "private") @@ -330,7 +337,8 @@ def test_retrieve_raw_data(self): self.assertEqual(self.study.raw_data, [RawData(1), RawData(2)]) def test_retrieve_raw_data_none(self): - new = Study.create(User('test@foo.bar'), 1, self.info) + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) self.assertEqual(new.raw_data, []) def test_retrieve_preprocessed_data(self): @@ -338,14 +346,16 @@ def test_retrieve_preprocessed_data(self): PreprocessedData(2)]) def test_retrieve_preprocessed_data_none(self): - new = Study.create(User('test@foo.bar'), 1, self.info) + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) self.assertEqual(new.preprocessed_data, []) def test_retrieve_processed_data(self): self.assertEqual(self.study.processed_data, [ProcessedData(1)]) def test_retrieve_processed_data_none(self): - new = Study.create(User('test@foo.bar'), 1, self.info) + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) self.assertEqual(new.processed_data, []) def test_share(self): From fadcaa7f76fbc126437f39fd4117ed57ae5476a4 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 11 Jun 2014 17:07:58 -0600 Subject: [PATCH 48/57] remote testcode --- qiita_db/test/test_study.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index fd9c68280..ed4505f43 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -319,9 +319,6 @@ def test_set_info(self): self.infoexp["most_recent_contact"] = None self.infoexp["reprocess"] = False self.infoexp["lab_person_id"] = 2 - for key, val in new.info.iteritems(): - if val != self.infoexp[key]: - print key, val, self.infoexp[key] self.assertEqual(new.info, self.infoexp) def test_set_info_public(self): From ddb99722b2edd7208f26ca66208648d797518446 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 11 Jun 2014 17:31:19 -0600 Subject: [PATCH 49/57] fixing tests --- qiita_db/exceptions.py | 1 + qiita_db/study.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qiita_db/exceptions.py b/qiita_db/exceptions.py index c339697d5..66a0cf2df 100644 --- a/qiita_db/exceptions.py +++ b/qiita_db/exceptions.py @@ -46,6 +46,7 @@ class QiitaDBStatusError(QiitaDBError): """Exception when editing is done with an unallowed status""" pass + class QiitaDBUnknownIDError(QiitaDBError): """Exception for error when an object does not exists in the DB""" def __init__(self, missing_id, table): diff --git a/qiita_db/study.py b/qiita_db/study.py index 7ebb19587..7fc7844af 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -159,7 +159,7 @@ def create(cls, owner, title, efo, info, investigation=None): Parameters ---------- - owner : User object + owner : User object the study' owner title: str Title of the study @@ -182,7 +182,7 @@ def create(cls, owner, title, efo, info, investigation=None): Notes ----- All keys in info, except the efo, must be equal to columns in - qiita.study table in the database. + qiita.study table in the database. """ # make sure not passing non-info columns in the info dict if cls._non_info.intersection(info): From 3a1df461de22e6590c6d97a04c205502d931fc43 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 11 Jun 2014 18:36:08 -0600 Subject: [PATCH 50/57] fix _non_info bug, comments --- qiita_db/exceptions.py | 3 +- qiita_db/study.py | 71 +++++++------------------------ qiita_db/test/test_study.py | 85 +++++++++++++++++++------------------ qiita_db/util.py | 4 -- 4 files changed, 59 insertions(+), 104 deletions(-) diff --git a/qiita_db/exceptions.py b/qiita_db/exceptions.py index 66a0cf2df..3886b6724 100644 --- a/qiita_db/exceptions.py +++ b/qiita_db/exceptions.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python from __future__ import division # ----------------------------------------------------------------------------- @@ -52,4 +51,4 @@ class QiitaDBUnknownIDError(QiitaDBError): def __init__(self, missing_id, table): super(QiitaDBUnknownIDError, self).__init__() self.args = ("The object with ID '%s' does not exists in table '%s" - % (missing_id, table)) + % (missing_id, table),) diff --git a/qiita_db/study.py b/qiita_db/study.py index 7fc7844af..e91bdfca0 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -6,7 +6,7 @@ This module provides the implementation of the Study and StudyPerson classes. The study class allows access to all basic information including name and -pmids associated with the study, as well as returning objects for the data, +pmids associated with the study, as well as returning ids for the data, metadata, owner, and shared users. It is the central hub for creating, deleting, and accessing a study in the database. @@ -102,8 +102,8 @@ from qiita_core.exceptions import IncompetentQiitaDeveloperError from .base import QiitaStatusObject, QiitaObject -from .exceptions import (QiitaDBDuplicateError, QiitaDBNotImplementedError, - QiitaDBStatusError, QiitaDBColumnError) +from .exceptions import (QiitaDBDuplicateError, QiitaDBStatusError, + QiitaDBColumnError) from .util import check_required_columns, check_table_cols from .sql_connection import SQLConnectionHandler @@ -127,8 +127,6 @@ class Study(QiitaStatusObject): Methods ------- - share - unshare add_pmid Notes @@ -137,14 +135,12 @@ class Study(QiitaStatusObject): You should not be doing that. """ _table = "study" - # The following columns are considered not part of the user info - _non_info = {"study_id", "study_status_id", "study_title"} + # The following columns are considered not part of the study info + _non_info = {"email", "study_id", "study_status_id", "study_title"} def _lock_public(self, conn_handler): """Raises QiitaDBStatusError if study is public""" - sql = ("SELECT study_status_id FROM qiita.{0} WHERE " - "{0}_id = %s".format(self._table)) - if conn_handler.execute_fetchone(sql, (self._id, ))[0] == 2: + if self.check_status(("public", )): raise QiitaDBStatusError("Can't change status of public study!") def _status_setter_checks(self, conn_handler): @@ -177,7 +173,7 @@ def create(cls, owner, title, efo, info, investigation=None): Non-db columns in info dictionary All required keys not passed IncompetentQiitaDeveloperError - study_id or study_status_id passed as a key + study_id, study_status_id, or study_title passed as a key Notes ----- @@ -195,7 +191,8 @@ def create(cls, owner, title, efo, info, investigation=None): insertdict['first_contact'] = date.today().strftime("%B %d, %Y") insertdict['email'] = owner.id insertdict['study_title'] = title - insertdict['reprocess'] = False + if "reprocess" not in insertdict: + insertdict['reprocess'] = False # default to waiting_approval status insertdict['study_status_id'] = 1 @@ -234,20 +231,6 @@ def create(cls, owner, title, efo, info, investigation=None): return cls(study_id) - @classmethod - def delete(cls, id_): - """Deletes the study `id_` from the database - - Parameters - ---------- - id_ : - The object identifier - """ - # delete raw data - # drop sample_x dynamic table - # delete study row from study table (and cascade to satelite tables) - raise QiitaDBNotImplementedError() - # --- Attributes --- @property def title(self): @@ -315,8 +298,8 @@ def info(self, info): raise IncompetentQiitaDeveloperError("Need entries in info dict!") if self._non_info.intersection(info): - raise QiitaDBColumnError("non info keys passed: %s" % - self._non_info.intersection(info)) + raise QiitaDBColumnError("non info keys passed: %s" % + self._non_info.intersection(info)) conn_handler = SQLConnectionHandler() self._lock_public(conn_handler) @@ -463,32 +446,6 @@ def processed_data(self): return [x[0] for x in conn_handler.execute_fetchall(sql, (self._id,))] # --- methods --- - def share(self, user): - """Shares the study with given user - - Parameters - ---------- - user: User object - The user to share with - """ - conn_handler = SQLConnectionHandler() - sql = ("INSERT INTO qiita.study_users (study_id, email) VALUES " - "(%s, %s)") - conn_handler.execute(sql, (self._id, user.id)) - - def unshare(self, user): - """Unshares the study with given user - - Parameters - ---------- - user: User object - The user to unshare with - """ - conn_handler = SQLConnectionHandler() - sql = ("DELETE FROM qiita.study_users WHERE study_id = %s AND " - "email = %s") - conn_handler.execute(sql, (self._id, user.id)) - def add_pmid(self, pmid): """Adds PMID to study @@ -583,7 +540,8 @@ def name(self): Returns ------- - str: name of person + str + Name of person """ conn_handler = SQLConnectionHandler() sql = ("SELECT name FROM qiita.{0} WHERE " @@ -596,7 +554,8 @@ def email(self): Returns ------- - str: email of person + str + Email of person """ conn_handler = SQLConnectionHandler() sql = ("SELECT email FROM qiita.{0} WHERE " diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index ed4505f43..88baf5266 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -85,15 +85,14 @@ def setUp(self): "timeseries_type_id": 1, "metadata_complete": True, "mixs_compliant": True, - "email": User('test@foo.bar'), "number_samples_collected": 25, "number_samples_promised": 28, "portal_type_id": 3, "study_alias": "FCM", - "study_description": ("Microbiome of people who eat nothing but " - "fried chicken"), - "study_abstract": ("Exploring how a high fat diet changes the " - "gut microbiome"), + "study_description": "Microbiome of people who eat nothing but " + "fried chicken", + "study_abstract": "Exploring how a high fat diet changes the " + "gut microbiome", "emp_person_id": StudyPerson(2), "principal_investigator_id": StudyPerson(3), "lab_person_id": StudyPerson(1) @@ -103,15 +102,14 @@ def setUp(self): "timeseries_type_id": 1, "metadata_complete": True, "mixs_compliant": True, - "email": 'test@foo.bar', "number_samples_collected": 25, "number_samples_promised": 28, "portal_type_id": 3, "study_alias": "FCM", - "study_description": ("Microbiome of people who eat nothing but " - "fried chicken"), - "study_abstract": ("Exploring how a high fat diet changes the " - "gut microbiome"), + "study_description": "Microbiome of people who eat nothing but " + "fried chicken", + "study_abstract": "Exploring how a high fat diet changes the " + "gut microbiome", "emp_person_id": 2, "principal_investigator_id": 3, "lab_person_id": 1 @@ -128,19 +126,17 @@ def setUp(self): 'first_contact': '2014-05-19 16:10', 'principal_investigator_id': StudyPerson(3), 'timeseries_type_id': 1, - 'study_abstract': ("This is a preliminary study to examine the " - "microbiota associated with the Cannabis plant." - " Soils samples from the bulk soil, soil " - "associated with the roots, and the rhizosphere" - " were extracted and the DNA sequenced. Roots " - "from three independent plants of different " - "strains were examined. These roots were " - "obtained November 11, 2011 from plants that " - "had been harvested in the summer. Future " - "studies will attempt to analyze the soils and " - "rhizospheres from the same location at diff" - "erent time points in the plant lifecycle."), - 'email': User('test@foo.bar'), + 'study_abstract': + "This is a preliminary study to examine the " + "microbiota associated with the Cannabis plant. Soils samples " + "from the bulk soil, soil associated with the roots, and the " + "rhizosphere were extracted and the DNA sequenced. Roots " + "from three independent plants of different strains were " + "examined. These roots were obtained November 11, 2011 from " + "plants that had been harvested in the summer. Future " + "studies will attempt to analyze the soils and rhizospheres " + "from the same location at different time points in the plant " + "lifecycle.", 'spatial_series': False, 'study_description': 'Analysis of the Cannabis Plant Microbiome', 'portal_type_id': 2, @@ -161,11 +157,11 @@ def test_create_study_min_data(self): 'first_contact': date.today().strftime("%B %d, %Y"), 'principal_investigator_id': 3, 'timeseries_type_id': 1, - 'study_abstract': ('Exploring how a high fat diet changes the ' - 'gut microbiome'), + 'study_abstract': 'Exploring how a high fat diet changes the ' + 'gut microbiome', 'email': 'test@foo.bar', 'spatial_series': None, - 'study_description': ('Microbiome of people who eat nothing but' - ' fried chicken'), + 'study_description': 'Microbiome of people who eat nothing but' + ' fried chicken', 'portal_type_id': 3, 'study_alias': 'FCM', 'study_id': 2, 'most_recent_contact': None, 'lab_person_id': 1, 'study_title': 'Fried chicken microbiome', @@ -203,21 +199,23 @@ def test_create_study_all_data(self): 'funding': 'FundAgency', 'spatial_series': True, 'metadata_complete': False, + 'reprocess': True, + 'first_contact': "Today" }) obs = Study.create(User('test@foo.bar'), "Fried chicken microbiome", 1, self.info) self.assertEqual(obs.id, 2) exp = {'mixs_compliant': True, 'metadata_complete': False, - 'reprocess': False, 'study_status_id': 1, + 'reprocess': True, 'study_status_id': 1, 'number_samples_promised': 28, 'emp_person_id': 2, 'funding': 'FundAgency', 'vamps_id': 'MBE_1111111', - 'first_contact': date.today().strftime("%B %d, %Y"), + 'first_contact': "Today", 'principal_investigator_id': 3, 'timeseries_type_id': 1, - 'study_abstract': ('Exploring how a high fat diet changes the ' - 'gut microbiome'), + 'study_abstract': 'Exploring how a high fat diet changes the ' + 'gut microbiome', 'email': 'test@foo.bar', 'spatial_series': True, - 'study_description': ('Microbiome of people who eat nothing ' - 'but fried chicken'), + 'study_description': 'Microbiome of people who eat nothing ' + 'but fried chicken', 'portal_type_id': 3, 'study_alias': 'FCM', 'study_id': 2, 'most_recent_contact': None, 'lab_person_id': 1, 'study_title': 'Fried chicken microbiome', @@ -326,6 +324,13 @@ def test_set_info_public(self): with self.assertRaises(QiitaDBStatusError): self.study.info = {"vamps_id": "12321312"} + def test_set_info_disallowed_keys(self): + """Tests for fail if sending non-info keys in info dict""" + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) + with self.assertRaises(QiitaDBColumnError): + new.info = {"email": "fail@fail.com"} + def test_info_empty(self): new = Study.create(User('test@foo.bar'), 'Identification of the ' 'Microbiomes for Cannabis Soils', 1, self.info) @@ -351,6 +356,11 @@ def test_retrieve_pmids(self): def test_retrieve_investigation(self): self.assertEqual(self.study.investigation, 1) + def test_retrieve_investigation_empty(self): + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) + self.assertEqual(new.investigation, None) + def test_retrieve_metadata(self): self.assertEqual(self.study.metadata, 1) @@ -378,15 +388,6 @@ def test_retrieve_processed_data_none(self): 'Microbiomes for Cannabis Soils', 1, self.info) self.assertEqual(new.processed_data, []) - def test_share(self): - self.study.share(User('admin@foo.bar')) - self.assertEqual(self.study.shared_with, ['shared@foo.bar', - 'admin@foo.bar']) - - def test_unshare(self): - self.study.unshare(User('shared@foo.bar')) - self.assertEqual(self.study.shared_with, []) - def test_add_pmid(self): self.study.add_pmid('4544444') exp = ['123456', '7891011', '4544444'] diff --git a/qiita_db/util.py b/qiita_db/util.py index bc774c2c2..df35d19d9 100644 --- a/qiita_db/util.py +++ b/qiita_db/util.py @@ -9,10 +9,6 @@ Methods ------- -<<<<<<< HEAD -from .exceptions import QiitaDBColumnError - -======= ..autosummary:: :toctree: generated/ From 6503a78d37ff031f6bdbdcb0a30a54fdd536fa5e Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 11 Jun 2014 19:00:35 -0600 Subject: [PATCH 51/57] fix the last bits....hopefully --- qiita_db/study.py | 10 +++++----- qiita_db/test/test_study.py | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index e91bdfca0..4893f99b9 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -7,7 +7,7 @@ This module provides the implementation of the Study and StudyPerson classes. The study class allows access to all basic information including name and pmids associated with the study, as well as returning ids for the data, -metadata, owner, and shared users. It is the central hub for creating, +sample template, owner, and shared users. It is the central hub for creating, deleting, and accessing a study in the database. Contacts are taken care of by the StudyPerson class. This holds the contact's @@ -120,7 +120,7 @@ class Study(QiitaStatusObject): shared_with pmids investigation - metadata + sample_template raw_data preprocessed_data processed_data @@ -173,7 +173,7 @@ def create(cls, owner, title, efo, info, investigation=None): Non-db columns in info dictionary All required keys not passed IncompetentQiitaDeveloperError - study_id, study_status_id, or study_title passed as a key + email, study_id, study_status_id, or study_title passed as a key Notes ----- @@ -396,8 +396,8 @@ def investigation(self): return inv[0] if inv is not None else inv @property - def metadata(self): - """ Returns metadata information id + def sample_template(self): + """ Returns sample_template information id Returns ------- diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 88baf5266..6c3c33c96 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -183,8 +183,7 @@ def test_create_study_min_data(self): def test_create_study_with_investigation(self): """Insert a study into the database with an investigation""" obs = Study.create(User('test@foo.bar'), "Fried chicken microbiome", - 1, self.info, - Investigation(1)) + 1, self.info, Investigation(1)) self.assertEqual(obs.id, 2) # check the investigation was assigned conn = SQLConnectionHandler() From 9007a91e29eaeba8772d7c43101bbd65506aa763 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 11 Jun 2014 19:44:22 -0600 Subject: [PATCH 52/57] 100% --- qiita_db/test/test_study.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 6d658807c..c4ba1e6bf 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -352,6 +352,11 @@ def test_retrieve_pmids(self): exp = ['123456', '7891011'] self.assertEqual(self.study.pmids, exp) + def test_retrieve_pmids_empty(self): + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', 1, self.info) + self.assertEqual(new.pmids, []) + def test_retrieve_investigation(self): self.assertEqual(self.study.investigation, 1) From 18c2edf71c0a3152757f1deec8c632dc201e19e8 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 11 Jun 2014 20:09:01 -0600 Subject: [PATCH 53/57] fix some merge conflict issues --- qiita_db/test/test_user.py | 13 ++++++++++--- qiita_db/user.py | 21 --------------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/qiita_db/test/test_user.py b/qiita_db/test/test_user.py index 323bb8329..a30612e09 100644 --- a/qiita_db/test/test_user.py +++ b/qiita_db/test/test_user.py @@ -8,12 +8,12 @@ from unittest import TestCase, main -from qiita_core.exceptions import (IncompetentQiitaDeveloperError, - IncorrectEmailError, IncorrectPasswordError) +from qiita_core.exceptions import IncorrectEmailError, IncorrectPasswordError from qiita_core.util import qiita_test_checker from qiita_db.user import User from qiita_db.sql_connection import SQLConnectionHandler -from qiita_db.exceptions import QiitaDBDuplicateError, QiitaDBColumnError +from qiita_db.exceptions import (QiitaDBDuplicateError, QiitaDBColumnError, + QiitaDBUnknownIDError) @qiita_test_checker() @@ -31,6 +31,13 @@ def setUp(self): 'phone': '111-222-3344' } + def test_instantiate_user(self): + User('admin@foo.bar') + + def test_instantiate_unknown_user(self): + with self.assertRaises(QiitaDBUnknownIDError): + User('FAIL@OMG.bar') + def _check_correct_info(self, obs, exp): self.assertEqual(set(exp.keys()), set(obs.keys())) for key in exp: diff --git a/qiita_db/user.py b/qiita_db/user.py index 4c0a1462e..975e0afa7 100644 --- a/qiita_db/user.py +++ b/qiita_db/user.py @@ -216,27 +216,6 @@ def create(cls, email, password, info=None): conn_handler.execute(sql, values) return cls(email) - def _check_id(self, id_, conn_handler=None): - r"""Check that the provided ID actually exists on the database - - Parameters - ---------- - id_ : str - The ID to test - conn_handler - The connection handler object connected to the DB - - Notes - ----- - This function overwrites the base function, as sql layout doesn't - follow the same conventions done in the other tables. - """ - conn_handler = (conn_handler if conn_handler is not None - else SQLConnectionHandler()) - return conn_handler.execute_fetchone( - "SELECT EXISTS(SELECT * FROM qiita.qiita_user WHERE " - "email = %s)", (id_, ))[0] - # ---properties--- @property From c4a2234acae8f29ebb3db381aaafb3d664b0597b Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Thu, 12 Jun 2014 09:29:44 -0600 Subject: [PATCH 54/57] Taking care of @adamrp suggestions --- qiita_db/study.py | 55 ++++++++++++++++++------------------- qiita_db/test/test_study.py | 6 ++-- qiita_db/user.py | 6 ++-- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 4893f99b9..1d331097f 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -97,6 +97,7 @@ from __future__ import division from future.builtins import zip +from future.utils import viewitems from datetime import date from copy import deepcopy @@ -157,15 +158,15 @@ def create(cls, owner, title, efo, info, investigation=None): ---------- owner : User object the study' owner - title: str + title : str Title of the study - efo: int or list + efo : int or list Experimental Factor Ontology or -ies for the study - info: dict + info : dict the information attached to the study. All "*_id" keys must pass the objects associated with them. - investigation: Investigation object - if the study is part of an investigation, the id to associate with + investigation : Investigation object, optional + If passed, the investigation to associate with. Defaults to None. Raises ------ @@ -182,13 +183,13 @@ def create(cls, owner, title, efo, info, investigation=None): """ # make sure not passing non-info columns in the info dict if cls._non_info.intersection(info): - raise QiitaDBColumnError("non info keys passed: %s" % - cls._non_info.intersection(info)) + raise QiitaDBColumnError("non info keys passed: %s" % + cls._non_info.intersection(info)) # add default values to info insertdict = deepcopy(info) if "first_contact" not in insertdict: - insertdict['first_contact'] = date.today().strftime("%B %d, %Y") + insertdict['first_contact'] = date.today().isoformat() insertdict['email'] = owner.id insertdict['study_title'] = title if "reprocess" not in insertdict: @@ -203,13 +204,12 @@ def create(cls, owner, title, efo, info, investigation=None): check_required_columns(conn_handler, insertdict, cls._table) # Insert study into database - keys = insertdict.keys() sql = ("INSERT INTO qiita.{0} ({1}) VALUES ({2}) RETURNING " - "study_id".format(cls._table, ','.join(keys), + "study_id".format(cls._table, ','.join(insertdict), ','.join(['%s'] * len(insertdict)))) # make sure data in same order as sql column names, and ids are used data = [] - for col in keys: + for col in insertdict: if isinstance(insertdict[col], QiitaObject): data.append(insertdict[col].id) else: @@ -218,7 +218,7 @@ def create(cls, owner, title, efo, info, investigation=None): # insert efo information into database if isinstance(efo, int): - efo = [efo] + efo = [efo] sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)".format(cls._table)) conn_handler.executemany(sql, list(zip([study_id] * len(efo), efo))) @@ -309,9 +309,8 @@ def info(self, info): sql_vals = [] data = [] - # items() used for py3 compatability # build query with data values in correct order for SQL statement - for key, val in info.items(): + for key, val in viewitems(info): sql_vals.append("{0} = %s".format(key)) if isinstance(val, QiitaObject): data.append(val.id) @@ -336,13 +335,13 @@ def efo(self, efo_vals): Parameters ---------- - status_id: int + status_id : int ID for the new status """ conn_handler = SQLConnectionHandler() self._lock_public(conn_handler) if isinstance(efo_vals, int): - efo_vals = [efo_vals] + efo_vals = [efo_vals] # wipe out any EFOs currently attached to study sql = ("DELETE FROM qiita.{0}_experimental_factor WHERE " "study_id = %s".format(self._table)) @@ -351,7 +350,7 @@ def efo(self, efo_vals): sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)".format(self._table)) conn_handler.executemany(sql, list(zip([self._id] * len(efo_vals), - efo_vals))) + efo_vals))) @property def shared_with(self): @@ -451,7 +450,7 @@ def add_pmid(self, pmid): Parameters ---------- - pmid: str + pmid : str pmid to associate with study """ conn_handler = SQLConnectionHandler() @@ -465,13 +464,13 @@ class StudyPerson(QiitaObject): Attributes ---------- - name: str + name : str name of the person - email: str + email : str email of the person - address: str or None + address : str or None address of the person - phone: str or None + phone : str or None phone number of the person """ _table = "study_person" @@ -503,13 +502,13 @@ def create(cls, name, email, address=None, phone=None): Parameters ---------- - name: str + name : str name of person - email: str + email : str email of person - address: str, optional + address : str, optional address of person - phone: str, optional + phone : str, optional phone number of person Returns @@ -582,7 +581,7 @@ def address(self, value): Parameters ---------- - value: str + value : str New address for person """ conn_handler = SQLConnectionHandler() @@ -610,7 +609,7 @@ def phone(self, value): Parameters ---------- - value: str + value : str New phone number for person """ conn_handler = SQLConnectionHandler() diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index c4ba1e6bf..354ef17fb 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -1,6 +1,8 @@ from unittest import TestCase, main from datetime import date +from future.utils import viewitems + from qiita_core.exceptions import IncompetentQiitaDeveloperError from qiita_core.util import qiita_test_checker from qiita_db.base import QiitaObject @@ -154,7 +156,7 @@ def test_create_study_min_data(self): 'reprocess': False, 'study_status_id': 1, 'number_samples_promised': 28, 'emp_person_id': 2, 'funding': None, 'vamps_id': None, - 'first_contact': date.today().strftime("%B %d, %Y"), + 'first_contact': date.today().isoformat(), 'principal_investigator_id': 3, 'timeseries_type_id': 1, 'study_abstract': 'Exploring how a high fat diet changes the ' @@ -291,7 +293,7 @@ def test_set_efo_public(self): self.study.efo = 6 def test_retrieve_info(self): - for key, val in self.existingexp.items(): + for key, val in viewitems(self.existingexp): if isinstance(val, QiitaObject): self.existingexp[key] = val.id self.assertEqual(self.study.info, self.existingexp) diff --git a/qiita_db/user.py b/qiita_db/user.py index 975e0afa7..1383168b3 100644 --- a/qiita_db/user.py +++ b/qiita_db/user.py @@ -73,7 +73,7 @@ class User(QiitaObject): "pass_reset_code", "pass_reset_timestamp"} def _check_id(self, id_, conn_handler=None): - r"""Check that the provided ID actually exists on the database + r"""Check that the provided ID actually exists in the database Parameters ---------- @@ -84,8 +84,8 @@ def _check_id(self, id_, conn_handler=None): Notes ----- - This functionoverwrites the base function, as sql layout doesn't follow - the same conventions done in the other classes. + This function overwrites the base function, as sql layout doesn't + follow the same conventions done in the other classes. """ self._check_subclass() From e03e6124c395257d2719485ffb305302d8023e1d Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Thu, 12 Jun 2014 10:24:32 -0600 Subject: [PATCH 55/57] applying @josenavas sugestions --- qiita_db/study.py | 24 +++++++++--------------- qiita_db/test/test_study.py | 37 +++++++++++++++---------------------- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 1d331097f..b93c5cb46 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -145,8 +145,7 @@ def _lock_public(self, conn_handler): raise QiitaDBStatusError("Can't change status of public study!") def _status_setter_checks(self, conn_handler): - r"""Perform any extra checks that needed to be done before setting the - object status on the database. Should be overwritten by the subclasses + r"""Perform a check to make sure not setting status away from public """ self._lock_public(conn_handler) @@ -157,11 +156,11 @@ def create(cls, owner, title, efo, info, investigation=None): Parameters ---------- owner : User object - the study' owner + the study's owner title : str Title of the study - efo : int or list - Experimental Factor Ontology or -ies for the study + efo : list + Experimental Factor Ontology id(s) for the study info : dict the information attached to the study. All "*_id" keys must pass the objects associated with them. @@ -217,11 +216,9 @@ def create(cls, owner, title, efo, info, investigation=None): study_id = conn_handler.execute_fetchone(sql, data)[0] # insert efo information into database - if isinstance(efo, int): - efo = [efo] sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)".format(cls._table)) - conn_handler.executemany(sql, list(zip([study_id] * len(efo), efo))) + conn_handler.executemany(sql, [(study_id, e) for e in efo]) # add study to investigation if necessary if investigation: @@ -335,13 +332,11 @@ def efo(self, efo_vals): Parameters ---------- - status_id : int - ID for the new status + efo_vals : list + Id(s) for the new efo values """ conn_handler = SQLConnectionHandler() self._lock_public(conn_handler) - if isinstance(efo_vals, int): - efo_vals = [efo_vals] # wipe out any EFOs currently attached to study sql = ("DELETE FROM qiita.{0}_experimental_factor WHERE " "study_id = %s".format(self._table)) @@ -349,8 +344,7 @@ def efo(self, efo_vals): # insert new EFO information into database sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)".format(self._table)) - conn_handler.executemany(sql, list(zip([self._id] * len(efo_vals), - efo_vals))) + conn_handler.executemany(sql, [(self._id, efo) for efo in efo_vals]) @property def shared_with(self): @@ -517,7 +511,7 @@ def create(cls, name, email, address=None, phone=None): Raises ------ - QiitaDBExecutionError + QiitaDBDuplicateError Person already exists """ if cls.exists(name, email): diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 354ef17fb..3754a2030 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -150,7 +150,7 @@ def setUp(self): def test_create_study_min_data(self): """Insert a study into the database""" obs = Study.create(User('test@foo.bar'), "Fried chicken microbiome", - 1, self.info) + [1], self.info) self.assertEqual(obs.id, 2) exp = {'mixs_compliant': True, 'metadata_complete': True, 'reprocess': False, 'study_status_id': 1, @@ -185,7 +185,7 @@ def test_create_study_min_data(self): def test_create_study_with_investigation(self): """Insert a study into the database with an investigation""" obs = Study.create(User('test@foo.bar'), "Fried chicken microbiome", - 1, self.info, Investigation(1)) + [1], self.info, Investigation(1)) self.assertEqual(obs.id, 2) # check the investigation was assigned conn = SQLConnectionHandler() @@ -204,7 +204,7 @@ def test_create_study_all_data(self): 'first_contact': "Today" }) obs = Study.create(User('test@foo.bar'), "Fried chicken microbiome", - 1, self.info) + [1], self.info) self.assertEqual(obs.id, 2) exp = {'mixs_compliant': True, 'metadata_complete': False, 'reprocess': True, 'study_status_id': 1, @@ -261,7 +261,7 @@ def test_retrieve_title(self): def test_set_title(self): new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) + 'Microbiomes for Cannabis Soils', [1], self.info) new.title = "Cannabis soils" self.assertEqual(new.title, "Cannabis soils") @@ -273,20 +273,13 @@ def test_set_title_public(self): def test_get_efo(self): self.assertEqual(self.study.efo, [1]) - def test_set_efo_list(self): + def test_set_efo(self): """Set efo with list efo_id""" new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) + 'Microbiomes for Cannabis Soils', [1], self.info) new.efo = [3, 4] self.assertEqual(new.efo, [3, 4]) - def test_set_efo_int(self): - """Set efo with int efo_id""" - new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) - new.efo = 5 - self.assertEqual(new.efo, [5]) - def test_set_efo_public(self): """Set efo on a public study""" with self.assertRaises(QiitaDBStatusError): @@ -309,7 +302,7 @@ def test_set_info(self): "first_contact": "June 11, 2014" } new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) + 'Microbiomes for Cannabis Soils', [1], self.info) self.infoexp.update(newinfo) new.info = newinfo # add missing table cols @@ -328,13 +321,13 @@ def test_set_info_public(self): def test_set_info_disallowed_keys(self): """Tests for fail if sending non-info keys in info dict""" new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) + 'Microbiomes for Cannabis Soils', [1], self.info) with self.assertRaises(QiitaDBColumnError): new.info = {"email": "fail@fail.com"} def test_info_empty(self): new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) + 'Microbiomes for Cannabis Soils', [1], self.info) with self.assertRaises(IncompetentQiitaDeveloperError): new.info = {} @@ -343,7 +336,7 @@ def test_retrieve_status(self): def test_set_status(self): new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) + 'Microbiomes for Cannabis Soils', [1], self.info) new.status = "private" self.assertEqual(new.status, "private") @@ -356,7 +349,7 @@ def test_retrieve_pmids(self): def test_retrieve_pmids_empty(self): new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) + 'Microbiomes for Cannabis Soils', [1], self.info) self.assertEqual(new.pmids, []) def test_retrieve_investigation(self): @@ -364,7 +357,7 @@ def test_retrieve_investigation(self): def test_retrieve_investigation_empty(self): new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) + 'Microbiomes for Cannabis Soils', [1], self.info) self.assertEqual(new.investigation, None) def test_retrieve_sample_template(self): @@ -375,7 +368,7 @@ def test_retrieve_raw_data(self): def test_retrieve_raw_data_none(self): new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) + 'Microbiomes for Cannabis Soils', [1], self.info) self.assertEqual(new.raw_data, []) def test_retrieve_preprocessed_data(self): @@ -383,7 +376,7 @@ def test_retrieve_preprocessed_data(self): def test_retrieve_preprocessed_data_none(self): new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) + 'Microbiomes for Cannabis Soils', [1], self.info) self.assertEqual(new.preprocessed_data, []) def test_retrieve_processed_data(self): @@ -391,7 +384,7 @@ def test_retrieve_processed_data(self): def test_retrieve_processed_data_none(self): new = Study.create(User('test@foo.bar'), 'Identification of the ' - 'Microbiomes for Cannabis Soils', 1, self.info) + 'Microbiomes for Cannabis Soils', [1], self.info) self.assertEqual(new.processed_data, []) def test_add_pmid(self): From e06472bf34e1b05833c9fc8177cdba06d536652b Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Thu, 12 Jun 2014 11:44:29 -0600 Subject: [PATCH 56/57] empty efo fix --- qiita_db/study.py | 10 ++++++++++ qiita_db/test/test_study.py | 19 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index b93c5cb46..7bdd4aa61 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -174,6 +174,7 @@ def create(cls, owner, title, efo, info, investigation=None): All required keys not passed IncompetentQiitaDeveloperError email, study_id, study_status_id, or study_title passed as a key + empty efo list passed Notes ----- @@ -216,6 +217,8 @@ def create(cls, owner, title, efo, info, investigation=None): study_id = conn_handler.execute_fetchone(sql, data)[0] # insert efo information into database + if efo == []: + raise IncompetentQiitaDeveloperError("Need EFO information!") sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)".format(cls._table)) conn_handler.executemany(sql, [(study_id, e) for e in efo]) @@ -334,7 +337,14 @@ def efo(self, efo_vals): ---------- efo_vals : list Id(s) for the new efo values + + Raises + ------ + IncompetentQiitaDeveloperError + Empty efo list passed """ + if efo_vals == []: + raise IncompetentQiitaDeveloperError("Need EFO information!") conn_handler = SQLConnectionHandler() self._lock_public(conn_handler) # wipe out any EFOs currently attached to study diff --git a/qiita_db/test/test_study.py b/qiita_db/test/test_study.py index 3754a2030..9657e7b37 100644 --- a/qiita_db/test/test_study.py +++ b/qiita_db/test/test_study.py @@ -239,21 +239,27 @@ def test_create_missing_required(self): self.info.pop("study_alias") with self.assertRaises(QiitaDBColumnError): Study.create(User('test@foo.bar'), "Fried Chicken Microbiome", - 1, self.info) + [1], self.info) + + def test_create_empty_efo(self): + """ Insert a study that is missing a required info key""" + with self.assertRaises(IncompetentQiitaDeveloperError): + Study.create(User('test@foo.bar'), "Fried Chicken Microbiome", + [], self.info) def test_create_study_with_not_allowed_key(self): """Insert a study with key from _non_info present""" self.info.update({"study_id": 1}) with self.assertRaises(QiitaDBColumnError): Study.create(User('test@foo.bar'), "Fried Chicken Microbiome", - 1, self.info) + [1], self.info) def test_create_unknown_db_col(self): """ Insert a study with an info key not in the database""" self.info["SHOULDNOTBEHERE"] = "BWAHAHAHAHAHA" with self.assertRaises(QiitaDBColumnError): Study.create(User('test@foo.bar'), "Fried Chicken Microbiome", - 1, self.info) + [1], self.info) def test_retrieve_title(self): self.assertEqual(self.study.title, 'Identification of the Microbiomes' @@ -280,6 +286,13 @@ def test_set_efo(self): new.efo = [3, 4] self.assertEqual(new.efo, [3, 4]) + def test_set_efo_empty(self): + """Set efo with list efo_id""" + new = Study.create(User('test@foo.bar'), 'Identification of the ' + 'Microbiomes for Cannabis Soils', [1], self.info) + with self.assertRaises(IncompetentQiitaDeveloperError): + new.efo = [] + def test_set_efo_public(self): """Set efo on a public study""" with self.assertRaises(QiitaDBStatusError): From 4d745341e44abe4a5e7f25894bf050b63b6b3c30 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Thu, 12 Jun 2014 11:53:09 -0600 Subject: [PATCH 57/57] move efo checks --- qiita_db/study.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qiita_db/study.py b/qiita_db/study.py index 7bdd4aa61..d2e51cb05 100644 --- a/qiita_db/study.py +++ b/qiita_db/study.py @@ -186,6 +186,10 @@ def create(cls, owner, title, efo, info, investigation=None): raise QiitaDBColumnError("non info keys passed: %s" % cls._non_info.intersection(info)) + # make sure efo info passed + if not efo: + raise IncompetentQiitaDeveloperError("Need EFO information!") + # add default values to info insertdict = deepcopy(info) if "first_contact" not in insertdict: @@ -217,8 +221,6 @@ def create(cls, owner, title, efo, info, investigation=None): study_id = conn_handler.execute_fetchone(sql, data)[0] # insert efo information into database - if efo == []: - raise IncompetentQiitaDeveloperError("Need EFO information!") sql = ("INSERT INTO qiita.{0}_experimental_factor (study_id, " "efo_id) VALUES (%s, %s)".format(cls._table)) conn_handler.executemany(sql, [(study_id, e) for e in efo]) @@ -343,7 +345,7 @@ def efo(self, efo_vals): IncompetentQiitaDeveloperError Empty efo list passed """ - if efo_vals == []: + if not efo_vals: raise IncompetentQiitaDeveloperError("Need EFO information!") conn_handler = SQLConnectionHandler() self._lock_public(conn_handler)