Skip to content

Commit

Permalink
Merge pull request #73 from josenavas/init_fix
Browse files Browse the repository at this point in the history
More fixes on the Qiita(Status)Object classes
  • Loading branch information
antgonza committed Jun 9, 2014
2 parents 162cb0f + bac600d commit a120cce
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 138 deletions.
235 changes: 167 additions & 68 deletions qiita_db/base.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
#!/usr/bin/env python
from __future__ import division
r"""
Base objects (:mod: `qiita_db.base`)
====================================
from .sql_connection import SQLConnectionHandler

"""
Objects for dealing with qiita_db objects
..currentmodule:: qiita_db.base
This module provides base objects for dealing with any qiita_db object that
needs to be stored.
needs to be stored on the database.
Classes
-------
- `QiitaObject` -- A Qiita object class with a storage id
- `QiitaStatusObject` -- A Qiita object class with a storage id and status
..autosummary::
:toctree: generated/
QiitaObject
QiitaStatusObject
"""

# -----------------------------------------------------------------------------
Expand All @@ -23,11 +25,14 @@
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------

from .exceptions import QiitaDBNotImplementedError, QiitaDBStatusError
from __future__ import division
from qiita_core.exceptions import IncompetentQiitaDeveloperError
from .sql_connection import SQLConnectionHandler
from .exceptions import QiitaDBNotImplementedError, QiitaDBUnknownIDError


class QiitaObject(object):
"""Base class for any qiita_db object
r"""Base class for any qiita_db object
Parameters
----------
Expand All @@ -36,100 +41,210 @@ class QiitaObject(object):
Attributes
----------
id_
id
Methods
-------
create()
Creates a new object with a new id on the storage system
delete(id_)
Deletes the object `id_` from the storage system
create
delete
exists
_check_subclass
_check_id
__eq__
__neq__
Raises
------
IncompetentQiitaDeveloperError
If trying to instantiate the base class directly
"""

_table = None

@classmethod
def create(cls):
"""Creates a new object with a new id on the storage system"""
r"""Creates a new object with a new id on the storage system
Raises
------
QiitaDBNotImplementedError
If the method is not overwritten by a subclass
"""
raise QiitaDBNotImplementedError()

def delete(id_):
"""Deletes the object `id_` from the storage system
r"""Deletes the object `id_` from the storage system
Parameters
----------
id_ :
id_ : object
The object identifier
Raises
------
QiitaDBNotImplementedError
If the method is not overwritten by a subclass
"""
raise QiitaDBNotImplementedError()

@classmethod
def exists(cls):
"""Checks if a given object info is already present on the DB"""
r"""Checks if a given object info is already present on the DB
Raises
------
QiitaDBNotImplementedError
If the method is not overwritten by a subclass
"""
raise QiitaDBNotImplementedError()

def _check_subclass(self):
r"""Check that we are not calling a function that needs to access the
database from the base class
Raises
------
IncompetentQiitaDeveloperError
If its called directly from a base class
"""
if self._table is None:
raise IncompetentQiitaDeveloperError(
"Could not instantiate an object of the base class")

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 function does not work for the User class. The problem is
that the User sql layout doesn't follow the same conventions done in
the other classes. However, still defining here as there is only one
subclass that doesn't follow this convention and it can override this.
"""
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 "
"{0}_id=%s)".format(self._table), (id_, ))[0]

def __init__(self, id_):
"""Initializes the object
r"""Initializes the object
Parameters
----------
id_: the object identifier
Raises
------
QiitaDBUnknownIDError
If `id_` does not correspond to any object
"""
if not self._check_id(id_):
raise QiitaDBUnknownIDError(id_, self._table)

self._id = id_

def __eq__(self, other):
r"""Self and other are equal based on type and database id"""
if type(self) != type(other):
return False
if other._id != self._id:
return False
return True

def __ne__(self, other):
r"""Self and other are not equal based on type and database id"""
return not self.__eq__(other)

@property
def id(self):
"""The object id on the storage system"""
r"""The object id on the storage system"""
return self._id


class QiitaStatusObject(QiitaObject):
"""Base class for any qiita_db object with a status property
r"""Base class for any qiita_db object with a status property
Attributes
----------
status :
The current status of the object
status
Methods
-------
check_status
_status_setter_checks
"""

@property
def status(self):
"""String with the current status of the analysis"""
r"""String with the current status of the analysis"""
# Check that self._table is actually defined
self._check_subclass()

# Get the DB status of the object
conn_handler = SQLConnectionHandler()
return conn_handler.execute_fetchone(
"SELECT status FROM qiita.{0}_status WHERE {0}_status_id = "
"(SELECT {0}_status_id FROM qiita.{0} WHERE "
"{0}_id = %s)".format(self._table),
(self._id, ))[0]

def _status_setter_checks(self):
r"""Perform any extra checks that needed to be done before setting the
object status on the database. Should be overwritten by the subclasses
"""
raise QiitaDBNotImplementedError()

@status.setter
def status(self, status):
"""Change the status of the analysis
r"""Change the status of the analysis
Parameters
----------
status: str
The new object status
"""
raise QiitaDBNotImplementedError()
# Check that self._table is actually defined
self._check_subclass()

# Perform any extra checks needed before we update the status in the DB
self._status_setter_checks()

def check_status(self, status, exclude=False):
"""Decorator: checks status of object, allowing function to run if
conditions met.
# 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) "
"WHERE {0}_id = %s".format(self._table), (status, self._id))

def check_status(self, status, exclude=False, conn_handler=None):
r"""Checks status of object.
Parameters
----------
status: str or iterable
Single status or iterable of statuses to check against.
status: iterable
Iterable of statuses to check against.
exclude: bool, optional
If True, will check that database status is NOT one of the statuses
passed. Default False.
conn_handler: SQLConnectionHandler, optional
The connection handler object connected to the DB
Returns
-------
bool
True if the object status is in the desired set of statuses. False
otherwise.
Notes
-----
Expand All @@ -144,37 +259,21 @@ def check_status(self, status, exclude=False):
Table setup:
foo: foo_status_id ----> foo_status: foo_status_id, status
"""
if isinstance(status, str):
status = [status]

# get the DB status of the object
sql = ("SELECT status FROM qiita.{0}_status WHERE {0}_status_id = "
"(SELECT {0}_status_id FROM qiita.{0} WHERE "
"{0}_id = %s)").format(self._table)
conn = SQLConnectionHandler()
dbstatus = conn.execute_fetchone(sql, (self._id, ))[0]

# get all available statuses
sql = "SELECT DISTINCT status FROM qiita.%s_status" % self._table
statuses = [x[0] for x in conn.execute_fetchall(sql, (self._id, ))]

def wrap(f):
# Wrap needed to get function to wrap with this decorator
def wrapped_f(*args):
# Wrapped_f function needed to get func args
for s in status:
if s not in statuses:
raise ValueError("%s is not a valid status" % status)
if exclude:
if dbstatus not in status:
return f(*args)
else:
raise QiitaDBStatusError(("DB status %s in %s" %
(dbstatus, str(status))))
elif dbstatus in status:
return f(*args)
else:
raise QiitaDBStatusError(("DB status %s not in %s" %
(dbstatus, str(status))))
return wrapped_f
return wrap
# Check that self._table is actually defined
self._check_subclass()

# Get all available statuses
conn_handler = (conn_handler if conn_handler is not None
else SQLConnectionHandler())
statuses = [x[0] for x in conn_handler.execute_fetchall(
"SELECT DISTINCT status FROM qiita.{0}_status".format(self._table),
(self._id, ))]

# Check that all the provided statuses are valid statuses
if set(status).difference(statuses):
raise ValueError("%s are not valid status values"
% set(status).difference(statuses))

# Get the DB status of the object
dbstatus = self.status
return dbstatus not in status if exclude else dbstatus in status
2 changes: 1 addition & 1 deletion qiita_db/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


class RawData(QiitaStatusObject):
pass
_table = "raw_data"


class PreprocessedData(QiitaStatusObject):
Expand Down
13 changes: 8 additions & 5 deletions qiita_db/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ class QiitaDBExecutionError(QiitaDBError):
pass


class QiitaDBStatusError(QiitaDBError):
"""Exception for error when trying to run dissalowed functions"""
pass


class QiitaDBConnectionError(QiitaDBError):
"""Exception for error when connecting to the db"""
pass


class QiitaDBUnknownIDError(QiitaDBError):
"""Exception for error when an object does not exists in the DB"""
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))
1 change: 1 addition & 0 deletions qiita_db/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Study(QiitaStatusObject):
remove_samples(samples)
Removes the samples listed in `samples` from the study
"""
_table = "study"

@staticmethod
def create(owner):
Expand Down
Loading

0 comments on commit a120cce

Please sign in to comment.