--- a
+++ b/qiita_db/base.py
@@ -0,0 +1,223 @@
+r"""
+Base objects (:mod: `qiita_db.base`)
+====================================
+
+..currentmodule:: qiita_db.base
+
+This module provides base objects for dealing with any qiita_db object that
+needs to be stored on the database.
+
+Classes
+-------
+
+..autosummary::
+    :toctree: generated/
+
+    QiitaObject
+"""
+
+# -----------------------------------------------------------------------------
+# 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 qiita_core.exceptions import IncompetentQiitaDeveloperError
+from qiita_core.qiita_settings import qiita_config
+import qiita_db as qdb
+
+
+class QiitaObject(object):
+    r"""Base class for any qiita_db object
+
+    Parameters
+    ----------
+    id_: int, long, str, or unicode
+        The object id on the storage system
+
+    Attributes
+    ----------
+    id
+
+    Methods
+    -------
+    create
+    delete
+    exists
+    _check_subclass
+    _check_id
+    __eq__
+    __neq__
+
+    Raises
+    ------
+    IncompetentQiitaDeveloperError
+        If trying to instantiate the base class directly
+    """
+
+    _table = None
+    _portal_table = None
+
+    @classmethod
+    def create(cls):
+        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 qdb.exceptions.QiitaDBNotImplementedError()
+
+    @classmethod
+    def delete(cls, id_):
+        r"""Deletes the object `id_` from the storage system
+
+        Parameters
+        ----------
+        id_ : object
+            The object identifier
+
+        Raises
+        ------
+        QiitaDBNotImplementedError
+            If the method is not overwritten by a subclass
+        """
+        raise qdb.exceptions.QiitaDBNotImplementedError()
+
+    @classmethod
+    def exists(cls):
+        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 qdb.exceptions.QiitaDBNotImplementedError()
+
+    @classmethod
+    def _check_subclass(cls):
+        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 cls._table is None:
+            raise IncompetentQiitaDeveloperError(
+                "Could not instantiate an object of the base class")
+
+    def _check_id(self, id_):
+        r"""Check that the provided ID actually exists on the database
+
+        Parameters
+        ----------
+        id_ : object
+            The ID to test
+
+        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.
+        """
+        with qdb.sql_connection.TRN:
+            sql = """SELECT EXISTS(
+                        SELECT * FROM qiita.{0}
+                        WHERE {0}_id=%s)""".format(self._table)
+            qdb.sql_connection.TRN.add(sql, [id_])
+            return qdb.sql_connection.TRN.execute_fetchlast()
+
+    def _check_portal(self, id_):
+        """Checks that object is accessible in current portal
+
+        Parameters
+        ----------
+        id_ : object
+            The ID to test
+        """
+        if self._portal_table is None:
+            # assume not portal limited object
+            return True
+
+        with qdb.sql_connection.TRN:
+            sql = """SELECT EXISTS(
+                        SELECT *
+                        FROM qiita.{0}
+                            JOIN qiita.portal_type USING (portal_type_id)
+                        WHERE {1}_id = %s AND portal = %s
+                    )""".format(self._portal_table, self._table)
+            qdb.sql_connection.TRN.add(sql, [id_, qiita_config.portal])
+            return qdb.sql_connection.TRN.execute_fetchlast()
+
+    def __init__(self, id_):
+        r"""Initializes the object
+
+        Parameters
+        ----------
+        id_: int, long, str, or unicode
+            the object identifier
+
+        Raises
+        ------
+        QiitaDBUnknownIDError
+            If `id_` does not correspond to any object
+        """
+        # Most IDs in the database are numerical, but some (e.g., IDs used for
+        # the User object) are strings. Moreover, some integer IDs are passed
+        # as strings (e.g., '5'). Therefore, explicit type-checking is needed
+        # here to accommodate these possibilities.
+        if not isinstance(id_, (int, str)):
+            raise TypeError("id_ must be a numerical or text type (not %s) "
+                            "when instantiating "
+                            "%s" % (id_.__class__.__name__,
+                                    self.__class__.__name__))
+
+        if isinstance(id_, (str)):
+            if id_.isdigit():
+                id_ = int(id_)
+
+        with qdb.sql_connection.TRN:
+            self._check_subclass()
+            try:
+                _id = self._check_id(id_)
+            except ValueError as error:
+                if 'INVALID_TEXT_REPRESENTATION' not in str(error):
+                    raise error
+                _id = False
+
+            if not _id:
+                raise qdb.exceptions.QiitaDBUnknownIDError(id_, self._table)
+
+            if not self._check_portal(id_):
+                raise qdb.exceptions.QiitaDBError(
+                    "%s with id %d inaccessible in current portal: %s"
+                    % (self.__class__.__name__, id_, qiita_config.portal))
+
+        self._id = id_
+
+    def __eq__(self, other):
+        r"""Self and other are equal based on type and database id"""
+        if type(self) is not 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)
+
+    def __hash__(self):
+        r"""The hash of an object is based on the id"""
+        return hash(str(self.id))
+
+    @property
+    def id(self):
+        r"""The object id on the storage system"""
+        return self._id