|
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 |