Diff of /qiita_db/base.py [000000] .. [879b32]

Switch to unified view

a b/qiita_db/base.py
1
r"""
2
Base objects (:mod: `qiita_db.base`)
3
====================================
4
5
..currentmodule:: qiita_db.base
6
7
This module provides base objects for dealing with any qiita_db object that
8
needs to be stored on the database.
9
10
Classes
11
-------
12
13
..autosummary::
14
    :toctree: generated/
15
16
    QiitaObject
17
"""
18
19
# -----------------------------------------------------------------------------
20
# Copyright (c) 2014--, The Qiita Development Team.
21
#
22
# Distributed under the terms of the BSD 3-clause License.
23
#
24
# The full license is in the file LICENSE, distributed with this software.
25
# -----------------------------------------------------------------------------
26
from qiita_core.exceptions import IncompetentQiitaDeveloperError
27
from qiita_core.qiita_settings import qiita_config
28
import qiita_db as qdb
29
30
31
class QiitaObject(object):
32
    r"""Base class for any qiita_db object
33
34
    Parameters
35
    ----------
36
    id_: int, long, str, or unicode
37
        The object id on the storage system
38
39
    Attributes
40
    ----------
41
    id
42
43
    Methods
44
    -------
45
    create
46
    delete
47
    exists
48
    _check_subclass
49
    _check_id
50
    __eq__
51
    __neq__
52
53
    Raises
54
    ------
55
    IncompetentQiitaDeveloperError
56
        If trying to instantiate the base class directly
57
    """
58
59
    _table = None
60
    _portal_table = None
61
62
    @classmethod
63
    def create(cls):
64
        r"""Creates a new object with a new id on the storage system
65
66
        Raises
67
        ------
68
        QiitaDBNotImplementedError
69
            If the method is not overwritten by a subclass
70
        """
71
        raise qdb.exceptions.QiitaDBNotImplementedError()
72
73
    @classmethod
74
    def delete(cls, id_):
75
        r"""Deletes the object `id_` from the storage system
76
77
        Parameters
78
        ----------
79
        id_ : object
80
            The object identifier
81
82
        Raises
83
        ------
84
        QiitaDBNotImplementedError
85
            If the method is not overwritten by a subclass
86
        """
87
        raise qdb.exceptions.QiitaDBNotImplementedError()
88
89
    @classmethod
90
    def exists(cls):
91
        r"""Checks if a given object info is already present on the DB
92
93
        Raises
94
        ------
95
        QiitaDBNotImplementedError
96
            If the method is not overwritten by a subclass
97
        """
98
        raise qdb.exceptions.QiitaDBNotImplementedError()
99
100
    @classmethod
101
    def _check_subclass(cls):
102
        r"""Check that we are not calling a function that needs to access the
103
        database from the base class
104
105
        Raises
106
        ------
107
        IncompetentQiitaDeveloperError
108
            If its called directly from a base class
109
        """
110
        if cls._table is None:
111
            raise IncompetentQiitaDeveloperError(
112
                "Could not instantiate an object of the base class")
113
114
    def _check_id(self, id_):
115
        r"""Check that the provided ID actually exists on the database
116
117
        Parameters
118
        ----------
119
        id_ : object
120
            The ID to test
121
122
        Notes
123
        -----
124
        This function does not work for the User class. The problem is
125
        that the User sql layout doesn't follow the same conventions done in
126
        the other classes. However, still defining here as there is only one
127
        subclass that doesn't follow this convention and it can override this.
128
        """
129
        with qdb.sql_connection.TRN:
130
            sql = """SELECT EXISTS(
131
                        SELECT * FROM qiita.{0}
132
                        WHERE {0}_id=%s)""".format(self._table)
133
            qdb.sql_connection.TRN.add(sql, [id_])
134
            return qdb.sql_connection.TRN.execute_fetchlast()
135
136
    def _check_portal(self, id_):
137
        """Checks that object is accessible in current portal
138
139
        Parameters
140
        ----------
141
        id_ : object
142
            The ID to test
143
        """
144
        if self._portal_table is None:
145
            # assume not portal limited object
146
            return True
147
148
        with qdb.sql_connection.TRN:
149
            sql = """SELECT EXISTS(
150
                        SELECT *
151
                        FROM qiita.{0}
152
                            JOIN qiita.portal_type USING (portal_type_id)
153
                        WHERE {1}_id = %s AND portal = %s
154
                    )""".format(self._portal_table, self._table)
155
            qdb.sql_connection.TRN.add(sql, [id_, qiita_config.portal])
156
            return qdb.sql_connection.TRN.execute_fetchlast()
157
158
    def __init__(self, id_):
159
        r"""Initializes the object
160
161
        Parameters
162
        ----------
163
        id_: int, long, str, or unicode
164
            the object identifier
165
166
        Raises
167
        ------
168
        QiitaDBUnknownIDError
169
            If `id_` does not correspond to any object
170
        """
171
        # Most IDs in the database are numerical, but some (e.g., IDs used for
172
        # the User object) are strings. Moreover, some integer IDs are passed
173
        # as strings (e.g., '5'). Therefore, explicit type-checking is needed
174
        # here to accommodate these possibilities.
175
        if not isinstance(id_, (int, str)):
176
            raise TypeError("id_ must be a numerical or text type (not %s) "
177
                            "when instantiating "
178
                            "%s" % (id_.__class__.__name__,
179
                                    self.__class__.__name__))
180
181
        if isinstance(id_, (str)):
182
            if id_.isdigit():
183
                id_ = int(id_)
184
185
        with qdb.sql_connection.TRN:
186
            self._check_subclass()
187
            try:
188
                _id = self._check_id(id_)
189
            except ValueError as error:
190
                if 'INVALID_TEXT_REPRESENTATION' not in str(error):
191
                    raise error
192
                _id = False
193
194
            if not _id:
195
                raise qdb.exceptions.QiitaDBUnknownIDError(id_, self._table)
196
197
            if not self._check_portal(id_):
198
                raise qdb.exceptions.QiitaDBError(
199
                    "%s with id %d inaccessible in current portal: %s"
200
                    % (self.__class__.__name__, id_, qiita_config.portal))
201
202
        self._id = id_
203
204
    def __eq__(self, other):
205
        r"""Self and other are equal based on type and database id"""
206
        if type(self) is not type(other):
207
            return False
208
        if other._id != self._id:
209
            return False
210
        return True
211
212
    def __ne__(self, other):
213
        r"""Self and other are not equal based on type and database id"""
214
        return not self.__eq__(other)
215
216
    def __hash__(self):
217
        r"""The hash of an object is based on the id"""
218
        return hash(str(self.id))
219
220
    @property
221
    def id(self):
222
        r"""The object id on the storage system"""
223
        return self._id