scipion-pyworkflow 3.10.5__py3-none-any.whl → 3.11.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. pyworkflow/config.py +131 -67
  2. pyworkflow/constants.py +12 -2
  3. pyworkflow/object.py +3 -2
  4. pyworkflow/plugin.py +93 -44
  5. pyworkflow/project/scripts/fix_links.py +4 -1
  6. pyworkflow/resources/showj/arrowDown.png +0 -0
  7. pyworkflow/resources/showj/arrowUp.png +0 -0
  8. pyworkflow/resources/showj/background_section.png +0 -0
  9. pyworkflow/resources/showj/colRowModeOff.png +0 -0
  10. pyworkflow/resources/showj/colRowModeOn.png +0 -0
  11. pyworkflow/resources/showj/delete.png +0 -0
  12. pyworkflow/resources/showj/doc_icon.png +0 -0
  13. pyworkflow/resources/showj/download_icon.png +0 -0
  14. pyworkflow/resources/showj/enabled_gallery.png +0 -0
  15. pyworkflow/resources/showj/galleryViewOff.png +0 -0
  16. pyworkflow/resources/showj/galleryViewOn.png +0 -0
  17. pyworkflow/resources/showj/goto.png +0 -0
  18. pyworkflow/resources/showj/menu.png +0 -0
  19. pyworkflow/resources/showj/separator.png +0 -0
  20. pyworkflow/resources/showj/tableViewOff.png +0 -0
  21. pyworkflow/resources/showj/tableViewOn.png +0 -0
  22. pyworkflow/resources/showj/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  23. pyworkflow/resources/showj/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  24. pyworkflow/resources/showj/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  25. pyworkflow/resources/showj/volumeOff.png +0 -0
  26. pyworkflow/resources/showj/volumeOn.png +0 -0
  27. pyworkflow/viewer.py +23 -1
  28. pyworkflowtests/objects.py +2 -2
  29. pyworkflowtests/protocols.py +1 -3
  30. {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info}/METADATA +21 -25
  31. scipion_pyworkflow-3.11.0.dist-info/RECORD +71 -0
  32. {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info}/WHEEL +1 -1
  33. scipion_pyworkflow-3.11.0.dist-info/entry_points.txt +2 -0
  34. pyworkflow/apps/__init__.py +0 -29
  35. pyworkflow/apps/pw_manager.py +0 -37
  36. pyworkflow/apps/pw_plot.py +0 -51
  37. pyworkflow/apps/pw_project.py +0 -113
  38. pyworkflow/apps/pw_protocol_list.py +0 -143
  39. pyworkflow/apps/pw_protocol_run.py +0 -51
  40. pyworkflow/apps/pw_run_tests.py +0 -267
  41. pyworkflow/apps/pw_schedule_run.py +0 -322
  42. pyworkflow/apps/pw_sleep.py +0 -37
  43. pyworkflow/apps/pw_sync_data.py +0 -439
  44. pyworkflow/apps/pw_viewer.py +0 -78
  45. pyworkflow/gui/__init__.py +0 -36
  46. pyworkflow/gui/browser.py +0 -726
  47. pyworkflow/gui/canvas.py +0 -1190
  48. pyworkflow/gui/dialog.py +0 -977
  49. pyworkflow/gui/form.py +0 -2637
  50. pyworkflow/gui/graph.py +0 -247
  51. pyworkflow/gui/graph_layout.py +0 -271
  52. pyworkflow/gui/gui.py +0 -566
  53. pyworkflow/gui/matplotlib_image.py +0 -233
  54. pyworkflow/gui/plotter.py +0 -247
  55. pyworkflow/gui/project/__init__.py +0 -25
  56. pyworkflow/gui/project/base.py +0 -192
  57. pyworkflow/gui/project/constants.py +0 -139
  58. pyworkflow/gui/project/labels.py +0 -205
  59. pyworkflow/gui/project/project.py +0 -492
  60. pyworkflow/gui/project/searchprotocol.py +0 -154
  61. pyworkflow/gui/project/searchrun.py +0 -181
  62. pyworkflow/gui/project/steps.py +0 -171
  63. pyworkflow/gui/project/utils.py +0 -332
  64. pyworkflow/gui/project/variables.py +0 -179
  65. pyworkflow/gui/project/viewdata.py +0 -472
  66. pyworkflow/gui/project/viewprojects.py +0 -510
  67. pyworkflow/gui/project/viewprotocols.py +0 -2093
  68. pyworkflow/gui/project/viewprotocols_extra.py +0 -560
  69. pyworkflow/gui/text.py +0 -771
  70. pyworkflow/gui/tooltip.py +0 -185
  71. pyworkflow/gui/tree.py +0 -684
  72. pyworkflow/gui/widgets.py +0 -307
  73. pyworkflow/mapper/__init__.py +0 -26
  74. pyworkflow/mapper/mapper.py +0 -222
  75. pyworkflow/mapper/sqlite.py +0 -1578
  76. pyworkflow/mapper/sqlite_db.py +0 -145
  77. pyworkflow/project/__init__.py +0 -31
  78. pyworkflow/project/config.py +0 -454
  79. pyworkflow/project/manager.py +0 -180
  80. pyworkflow/project/project.py +0 -2010
  81. pyworkflow/protocol/__init__.py +0 -38
  82. pyworkflow/protocol/bibtex.py +0 -48
  83. pyworkflow/protocol/constants.py +0 -87
  84. pyworkflow/protocol/executor.py +0 -455
  85. pyworkflow/protocol/hosts.py +0 -313
  86. pyworkflow/protocol/launch.py +0 -270
  87. pyworkflow/protocol/package.py +0 -42
  88. pyworkflow/protocol/params.py +0 -741
  89. pyworkflow/protocol/protocol.py +0 -2582
  90. pyworkflow/tests/__init__.py +0 -29
  91. pyworkflow/tests/test_utils.py +0 -25
  92. pyworkflow/tests/tests.py +0 -341
  93. pyworkflow/utils/__init__.py +0 -38
  94. pyworkflow/utils/dataset.py +0 -414
  95. pyworkflow/utils/echo.py +0 -104
  96. pyworkflow/utils/graph.py +0 -169
  97. pyworkflow/utils/log.py +0 -284
  98. pyworkflow/utils/path.py +0 -528
  99. pyworkflow/utils/process.py +0 -132
  100. pyworkflow/utils/profiler.py +0 -92
  101. pyworkflow/utils/progressbar.py +0 -154
  102. pyworkflow/utils/properties.py +0 -631
  103. pyworkflow/utils/reflection.py +0 -129
  104. pyworkflow/utils/utils.py +0 -879
  105. pyworkflow/utils/which.py +0 -229
  106. pyworkflow/webservices/__init__.py +0 -8
  107. pyworkflow/webservices/config.py +0 -11
  108. pyworkflow/webservices/notifier.py +0 -162
  109. pyworkflow/webservices/repository.py +0 -59
  110. pyworkflow/webservices/workflowhub.py +0 -74
  111. pyworkflowtests/tests/__init__.py +0 -0
  112. pyworkflowtests/tests/test_canvas.py +0 -72
  113. pyworkflowtests/tests/test_domain.py +0 -45
  114. pyworkflowtests/tests/test_logs.py +0 -74
  115. pyworkflowtests/tests/test_mappers.py +0 -392
  116. pyworkflowtests/tests/test_object.py +0 -507
  117. pyworkflowtests/tests/test_project.py +0 -42
  118. pyworkflowtests/tests/test_protocol_execution.py +0 -135
  119. pyworkflowtests/tests/test_protocol_export.py +0 -78
  120. pyworkflowtests/tests/test_protocol_output.py +0 -158
  121. pyworkflowtests/tests/test_streaming.py +0 -47
  122. pyworkflowtests/tests/test_utils.py +0 -210
  123. scipion_pyworkflow-3.10.5.dist-info/RECORD +0 -140
  124. scipion_pyworkflow-3.10.5.dist-info/dependency_links.txt +0 -1
  125. scipion_pyworkflow-3.10.5.dist-info/entry_points.txt +0 -5
  126. {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info/licenses}/LICENSE.txt +0 -0
  127. {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info}/top_level.txt +0 -0
@@ -1,1578 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # **************************************************************************
3
- # *
4
- # * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
5
- # *
6
- # * [1] SciLifeLab, Stockholm University
7
- # *
8
- # * This program is free software: you can redistribute it and/or modify
9
- # * it under the terms of the GNU General Public License as published by
10
- # * the Free Software Foundation, either version 3 of the License, or
11
- # * (at your option) any later version.
12
- # *
13
- # * This program is distributed in the hope that it will be useful,
14
- # * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
- # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
- # * GNU General Public License for more details.
17
- # *
18
- # * You should have received a copy of the GNU General Public License
19
- # * along with this program. If not, see <https://www.gnu.org/licenses/>.
20
- # *
21
- # * All comments concerning this program package may be sent to the
22
- # * e-mail address 'scipion@cnb.csic.es'
23
- # *
24
- # **************************************************************************
25
- import logging
26
- logger = logging.getLogger(__name__)
27
- import re
28
- from collections import OrderedDict
29
-
30
- from pyworkflow import Config, ID_ATTRIBUTE, ID_COLUMN
31
- from pyworkflow.utils import replaceExt, joinExt, valueToList
32
- from .sqlite_db import SqliteDb, OperationalError
33
- from .mapper import Mapper
34
-
35
- ID = ID_COLUMN
36
- CREATION = 'creation'
37
- PARENT_ID = 'parent_id'
38
- CLASSNAME = 'classname'
39
- NAME = 'name'
40
-
41
-
42
- class SqliteMapper(Mapper):
43
- """Specific Mapper implementation using Sqlite database"""
44
- def __init__(self, dbName, dictClasses=None):
45
- Mapper.__init__(self, dictClasses)
46
- self.__initObjDict()
47
- self.__initUpdateDict()
48
- try:
49
- self.db = SqliteObjectsDb(dbName)
50
- except Exception as ex:
51
- raise Exception('Error creating SqliteMapper, dbName: %s'
52
- '\n error: %s' % (dbName, ex))
53
-
54
- def close(self):
55
- self.db.close()
56
-
57
- def commit(self):
58
- self.db.commit()
59
-
60
- def __getObjectValue(self, obj):
61
- """ Get the value of the object to be stored.
62
- We need to handle the special case of pointer, where we should
63
- store as value the object id of the pointed object.
64
- """
65
- value = obj.getObjValue()
66
-
67
- if obj.isPointer() and obj.hasValue():
68
- if value.hasObjId(): # Check the object has been stored previously
69
- value = value.strId() # For pointers store the id of referenced object
70
- else:
71
- self.updatePendingPointers.append(obj)
72
- value = "Pending update"
73
-
74
- return value
75
-
76
- def __insert(self, obj, namePrefix=None):
77
- if not hasattr(obj, '_objDoStore'):
78
- logger.info("MAPPER: object '%s' doesn't seem to be an Object subclass,"
79
- " it does not have attribute '_objDoStore'. "
80
- "Insert skipped." % obj)
81
- return
82
- obj._objId = self.db.insertObject(obj._objName, obj.getClassName(),
83
- self.__getObjectValue(obj),
84
- obj._objParentId,
85
- obj._objLabel, obj._objComment)
86
- self.updateDict[obj._objId] = obj
87
- sid = obj.strId()
88
- if namePrefix is None:
89
- namePrefix = sid
90
- else:
91
- namePrefix = joinExt(namePrefix, sid)
92
- self.insertChilds(obj, namePrefix)
93
-
94
- def insert(self, obj):
95
- """Insert a new object into the system, the id will be set"""
96
- self.__insert(obj)
97
-
98
- def insertChild(self, obj, key, attr, namePrefix=None):
99
- if not hasattr(attr, '_objDoStore'):
100
- logger.info("MAPPER: object '%s' doesn't seem to be an Object subclass,"
101
- " it does not have attribute '_objDoStore'. \n"
102
- "Insert skipped." % attr)
103
- return
104
-
105
- if namePrefix is None:
106
- namePrefix = self.__getNamePrefix(obj)
107
- attr._objName = joinExt(namePrefix, key)
108
- attr._objParentId = obj._objId
109
- self.__insert(attr, namePrefix)
110
-
111
- def insertChilds(self, obj, namePrefix=None):
112
- """ Insert childs of an object, if namePrefix is None,
113
- the it will be deduced from obj. """
114
- # This is also done in insertChild, but avoid
115
- # doing the same for every child element
116
- if namePrefix is None:
117
- namePrefix = self.__getNamePrefix(obj)
118
- for key, attr in obj.getAttributesToStore():
119
- self.insertChild(obj, key, attr, namePrefix)
120
-
121
- def deleteChilds(self, obj):
122
- namePrefix = self.__getNamePrefix(obj)
123
- self.db.deleteChildObjects(namePrefix)
124
-
125
- def deleteAll(self):
126
- """ Delete all objects stored """
127
- self.db.deleteAll()
128
-
129
- def delete(self, obj):
130
- """Delete an object and all its childs"""
131
- self.deleteChilds(obj)
132
- self.db.deleteObject(obj.getObjId())
133
-
134
- def __getNamePrefix(self, obj):
135
- if len(obj._objName) > 0 and '.' in obj._objName:
136
- return replaceExt(obj._objName, obj.strId())
137
- return obj.strId()
138
-
139
- def __printObj(self, obj):
140
- logger.info("obj._objId: %s" % obj._objId)
141
- logger.info("obj._objParentId: %s" % obj._objParentId)
142
- logger.info("obj._objName: %s"% obj._objName)
143
- logger.info("obj.getObjValue(): %s" % obj.getObjValue())
144
-
145
- def updateTo(self, obj, level=1):
146
- self.__initUpdateDict()
147
- self.__updateTo(obj, level)
148
- # Update pending pointers to objects
149
- for ptr in self.updatePendingPointers:
150
- self.db.updateObject(ptr._objId, ptr._objName,
151
- Mapper.getObjectPersistingClassName(ptr),
152
- self.__getObjectValue(obj), ptr._objParentId,
153
- ptr._objLabel, ptr._objComment)
154
-
155
- # Delete any child objects that have not been found.
156
- # This could be the case if some elements (such as inside List)
157
- # were stored in the database and were removed from the object
158
- self.db.deleteMissingObjectsByAncestor(self.__getNamePrefix(obj),
159
- self.updateDict.keys())
160
-
161
- def __updateTo(self, obj, level):
162
- self.db.updateObject(obj._objId, obj._objName,
163
- Mapper.getObjectPersistingClassName(obj),
164
- self.__getObjectValue(obj), obj._objParentId,
165
- obj._objLabel, obj._objComment)
166
-
167
- if obj.getObjId() in self.updateDict:
168
- raise Exception('Circular reference, object: %s found twice'
169
- % obj.getName())
170
-
171
- self.updateDict[obj._objId] = obj
172
-
173
- for key, attr in obj.getAttributesToStore():
174
- if attr._objId is None: # Insert new items from the previous state
175
- attr._objParentId = obj._objId
176
- namePrefix = self.__getNamePrefix(obj)
177
- attr._objName = joinExt(namePrefix, key)
178
- self.__insert(attr, namePrefix)
179
- else:
180
- self.__updateTo(attr, level + 2)
181
-
182
- def updateFrom(self, obj):
183
- objRow = self.db.selectObjectById(obj._objId)
184
- self.fillObject(obj, objRow)
185
-
186
- def selectById(self, objId):
187
- """Build the object which id is objId"""
188
- if objId in self.objDict:
189
- obj = self.objDict[objId]
190
- else:
191
- objRow = self.db.selectObjectById(objId)
192
- if objRow is None:
193
- obj = None
194
- else:
195
- obj = self._buildObjectFromClass(objRow['classname'])
196
- if obj is not None:
197
- self.fillObject(obj, objRow)
198
- return obj
199
-
200
- def exists(self, objId):
201
- return self.db.doesRowExist(objId)
202
-
203
- def getParent(self, obj):
204
- """ Retrieve the parent object of another. """
205
- return self.selectById(obj._objParentId)
206
-
207
- def fillObjectWithRow(self, obj, objRow):
208
- """ Fill the object with row data. """
209
-
210
- rowId = objRow[ID]
211
- rowName = self._getStrValue(objRow['name'])
212
-
213
- if not hasattr(obj, ID_ATTRIBUTE):
214
- raise Exception("Entry '%s' (id=%s) in the database, stored as '%s'"
215
- ", is being mapped to %s object. " %
216
- (rowName, rowId,
217
- objRow['classname'], type(obj)))
218
-
219
- obj._objId = rowId
220
-
221
- self.objDict[rowId] = obj
222
- obj._objName = rowName
223
- obj._objLabel = self._getStrValue(objRow['label'])
224
- obj._objComment = self._getStrValue(objRow['comment'])
225
- obj._objCreation = self._getStrValue(objRow[CREATION])
226
- objValue = objRow['value']
227
- obj._objParentId = objRow[PARENT_ID]
228
-
229
- if obj.isPointer():
230
- if objValue is not None:
231
- objValue = self.selectById(int(objValue))
232
- # This is necessary in some specific cases. E.g.:
233
- # CTF consensus creating:
234
- # A.- Micrographs
235
- # B.- CTFs --> pointing to micrographs in this same protocol(#1)
236
- # When loading this kind of protocol the sequence is as follows:
237
- # 1 Loading of protocol consuming consensus CTF output ..
238
- # ...
239
- # finds inputCtf (DIRECT pointer to B)
240
- # 2 loads set properties
241
- # ...
242
- # 2.4 pointer to micrographs (Pointer to Consensus + extended)
243
- # 2.4.1 pointee loads (Consensus protocol)
244
- # ...
245
- # ...
246
- # 2.4.n _extended of 2.4 is loaded since is a child of consensus
247
- # 2.4 obj.set() for 2.4 pointer --> will reset the extended to None.
248
- obj.set(objValue, cleanExtended=False)
249
- else:
250
- try:
251
- obj.set(objValue)
252
- except Exception as e:
253
- # Case for parameter type change. Loading the project tolerates type changes like Float to Int.
254
- # But when running a protocol loads happens differently (maybe something to look at) and comes here.
255
- logger.error("Can't set %s to %s. Maybe its type has changed!. Continues with default value %s." %
256
- (objValue, rowName, obj.get()))
257
-
258
- def fillObject(self, obj, objRow, includeChildren=True):
259
- """ Fills an already instantiated object the data in a row, including children
260
-
261
- NOTE: This, incase children are included, makes a query to the db with all its children 'like 2.*'.
262
- At some point it calls selectById triggering the loading of other protocols and ancestors.
263
-
264
- :param obj: Object to fill
265
- :param objRow: row with the values
266
- :param includeChildren: (True). If true children are also populated
267
-
268
- """
269
-
270
- self.fillObjectWithRow(obj, objRow)
271
- namePrefix = self.__getNamePrefix(obj)
272
-
273
- if includeChildren:
274
- childs = self.db.selectObjectsByAncestor(namePrefix)
275
-
276
- for childRow in childs:
277
-
278
- childParts = childRow[NAME].split('.')
279
- childName = childParts[-1]
280
- childId = childRow[ID]
281
- parentId = int(childParts[-2])
282
- # Here we are assuming that always the parent have
283
- # been processed first, so it will be in the dictionary
284
- parentObj = self.objDict.get(parentId, None)
285
- if parentObj is None: # Something went wrong
286
- continue
287
-
288
- # If id already in the objDict skip all this
289
- if childId in self.objDict.keys():
290
- setattr(parentObj, childName, self.objDict[childId])
291
- continue
292
-
293
- # Try to get the instance from the parent
294
- childObj = getattr(parentObj, childName, None)
295
-
296
- # If parent does not have that attribute...
297
- if childObj is None:
298
- # Instantiate it based on the class Name
299
- childObj = self._buildObjectFromClass(childRow[CLASSNAME])
300
-
301
- # If we have any problem building the object, just ignore it
302
- if childObj is None:
303
- # This is the case for deprecated types.
304
- continue
305
- setattr(parentObj, childName, childObj)
306
-
307
- self.fillObjectWithRow(childObj, childRow)
308
-
309
- def __buildObject(self, row):
310
- """ Builds and object, either based on the parent attribute, or a new
311
- one based on the class. """
312
- parentId = self._getParentIdFromRow(row)
313
- # If there is no parent...
314
- if parentId is None:
315
- # It must be a Root object, use the class
316
- return self._buildObjectFromClass(self._getClassFromRow(row))
317
- else:
318
- # Try to get the instance from the parent
319
- name = self._getNameFromRow(row)
320
- childParts = name.split('.')
321
- childName = childParts[-1]
322
-
323
- # Get the parent, we should have it cached
324
- parentObj = self.objDict.get(parentId, None)
325
- if parentObj is None: # Something went wrong
326
- logger.warning("Parent object (id=%d) was not found, "
327
- "object: %s. Ignored." % (parentId, name))
328
- return None
329
-
330
- childObj = getattr(parentObj, childName, None)
331
-
332
- # If parent object does not have that attribute
333
- if childObj is None:
334
- childObj = self._buildObjectFromClass(row[CLASSNAME])
335
- # If we have any problem building the object, just ignore it
336
- if childObj is None:
337
- return None
338
-
339
- setattr(parentObj, childName, childObj)
340
-
341
- return childObj
342
-
343
- def __objFromRow(self, objRow, includeChildren=True):
344
- objClassName = objRow['classname']
345
- obj = self._buildObjectFromClass(objClassName)
346
-
347
- if obj is not None:
348
- self.fillObject(obj, objRow, includeChildren)
349
-
350
- return obj
351
-
352
- def __iterObjectsFromRows(self, objRows, objectFilter=None):
353
- for objRow in objRows:
354
- obj = self.objDict.get(objRow['id'], None) or self.__objFromRow(objRow)
355
-
356
- if (obj is not None and
357
- objectFilter is None or objectFilter(obj)):
358
- yield obj
359
-
360
- def __objectsFromRows(self, objRows, iterate=False, objectFilter=None):
361
- """Create a set of object from a set of rows
362
- Params:
363
- objRows: rows result from a db select.
364
- iterate: if True, iterates over all elements, if False the whole
365
- list is returned
366
- objectFilter: function to filter some of the objects of the
367
- results.
368
- """
369
- if not iterate:
370
- return [obj for obj in self.__iterObjectsFromRows(objRows,
371
- objectFilter)]
372
- else:
373
- return self.__iterObjectsFromRows(objRows, objectFilter)
374
-
375
- def __initObjDict(self):
376
- """ Clear the objDict cache """
377
- self.objDict = {}
378
-
379
- def __initUpdateDict(self):
380
- """ Clear the updateDict cache """
381
- self.updateDict = {}
382
- # This is used to store pointers that pointed object are not stored yet
383
- self.updatePendingPointers = []
384
-
385
- def selectBy(self, iterate=False, objectFilter=None, **args):
386
- """Select object meetings some criteria"""
387
- self.__initObjDict()
388
- objRows = self.db.selectObjectsBy(**args)
389
- return self.__objectsFromRows(objRows, iterate, objectFilter)
390
-
391
- def selectByClass(self, className, includeSubclasses=True, iterate=False,
392
- objectFilter=None):
393
- self.__initObjDict()
394
-
395
- if includeSubclasses:
396
- from pyworkflow.utils.reflection import getSubclasses
397
- whereStr = "classname='%s'" % className
398
- base = self.dictClasses.get(className)
399
- subDict = getSubclasses(base, self.dictClasses)
400
- for k, v in subDict.items():
401
- if issubclass(v, base):
402
- whereStr += " OR classname='%s'" % k
403
- objRows = self.db.selectObjectsWhere(whereStr)
404
- return self.__objectsFromRows(objRows, iterate, objectFilter)
405
- else:
406
- return self.selectBy(iterate=iterate, classname=className)
407
-
408
- def selectAll(self, iterate=False, objectFilter=None):
409
- self.__initObjDict()
410
- objRows = self.db.selectObjectsByParent(parent_id=None)
411
- return self.__objectsFromRows(objRows, iterate, objectFilter)
412
-
413
- def selectAllBatch(self, objectFilter=None):
414
- """ Select all the row at once for all the project
415
-
416
- Returns:
417
- all the protocols populated with the data from the DB
418
-
419
- """
420
- self.__initObjDict()
421
-
422
- # Get all the data from Objects table sorted by Name
423
- objAll = self.db.selectAllObjects()
424
-
425
- # We should have first the protocol lines
426
- # then each of the protocol attributes
427
- # at then there is the creation time
428
-
429
- # Dictionary to store objects
430
- objs = []
431
-
432
- # For each row
433
- for row in objAll:
434
- obj = self._getObjectFromRow(row)
435
-
436
- if obj is not None and objectFilter is None or objectFilter(obj):
437
- objs.append(obj)
438
-
439
- return objs
440
-
441
- def _getObjectFromRow(self, row):
442
- """ Creates and fills and object described iin row
443
-
444
- :param row: A row with the Class to instantiate, and value to set."""
445
-
446
- # Get the ID first
447
- identifier = row[ID]
448
-
449
- # If already in the dictionary we skipp al this
450
- if identifier in self.objDict.keys():
451
- return self.objDict[identifier]
452
-
453
- # Build the object without children
454
- obj = self.__buildObject(row)
455
-
456
- # If an object was created
457
- if obj is not None:
458
- try:
459
- # Fill object attributes with row values
460
- self.fillObjectWithRow(obj, row)
461
-
462
- # Add it to the obj cache, we might need it later to assign
463
- # attributes
464
- self.objDict[obj._objId] = obj
465
-
466
- return obj
467
-
468
- except Exception as e:
469
- # Tolerate errors when loading attributes.
470
- # This could happen then a protocol has change a param to a new type not compatible
471
- # with previous values. Warn properly about it.
472
- logger.warning("Can't load the row (%s, %s, %s) form the database to memory. This could cause future error." %
473
- (identifier, row[NAME], row[PARENT_ID]))
474
-
475
- def _getObjectFromDictionary(self, objId):
476
- return self.objDict[objId]
477
-
478
- @staticmethod
479
- def _getIdFromRow(row):
480
- return SqliteMapper._getFieldFromRow(ID, row)
481
-
482
- @staticmethod
483
- def _getParentIdFromRow(row):
484
- return SqliteMapper._getFieldFromRow(PARENT_ID, row)
485
-
486
- @staticmethod
487
- def _getClassFromRow(row):
488
- return SqliteMapper._getFieldFromRow(CLASSNAME, row)
489
-
490
- @staticmethod
491
- def _getNameFromRow(row):
492
- return SqliteMapper._getFieldFromRow(NAME, row)
493
-
494
- @staticmethod
495
- def _getFieldFromRow(fieldName, row):
496
- return row[fieldName]
497
-
498
- def insertRelation(self, relName, creatorObj, parentObj, childObj,
499
- parentExt=None, childExt=None):
500
- """ This function will add a new relation between two objects.
501
- Params:
502
- relName: the name of the relation to be added.
503
- creatorObj: this object will be the one who register the relation.
504
- parentObj: this is "parent" in the relation
505
- childObj: this is "child" in the relation
506
- """
507
- for o in [creatorObj, parentObj, childObj]:
508
- if not o.hasObjId():
509
- raise Exception("Before adding a relation, the object should "
510
- "be stored in mapper")
511
- self.db.insertRelation(relName, creatorObj.getObjId(),
512
- parentObj.getObjId(), childObj.getObjId(),
513
- parentExt, childExt)
514
-
515
- def __objectsFromIds(self, objIds):
516
- """Return a list of objects, given a list of id's
517
- """
518
- return [self.selectById(rowId[ID]) for rowId in objIds]
519
-
520
- def getRelationChilds(self, relName, parentObj):
521
- """ Return all "child" objects for a given relation.
522
- Params:
523
- relName: the name of the relation.
524
- parentObj: this is "parent" in the relation
525
- Returns:
526
- a list of "child" objects.
527
- """
528
- childIds = self.db.selectRelationChilds(relName, parentObj.getObjId())
529
-
530
- return self.__objectsFromIds(childIds)
531
-
532
- def getRelationParents(self, relName, childObj):
533
- """ Return all "parent" objects for a given relation.
534
- Params:
535
- relName: the name of the relation.
536
- childObj: this is "child" in the relation
537
- Returns:
538
- a list of "parent" objects.
539
- """
540
- parentIds = self.db.selectRelationParents(relName, childObj.getObjId())
541
-
542
- return self.__objectsFromIds(parentIds)
543
-
544
- def getRelationsByCreator(self, creatorObj):
545
- """ Return all relations created by creatorObj. """
546
- return self.db.selectRelationsByCreator(creatorObj.getObjId())
547
-
548
- def getRelationsByName(self, relationName):
549
- """ Return all relations stored of a given type. """
550
- return self.db.selectRelationsByName(relationName)
551
-
552
- def deleteRelations(self, creatorObj):
553
- """ Delete all relations created by object creatorObj """
554
- self.db.deleteRelationsByCreator(creatorObj.getObjId())
555
-
556
- def insertRelationData(self, relName, creatorId, parentId, childId,
557
- parentExtended=None, childExtended=None):
558
- self.db.insertRelation(relName, creatorId, parentId, childId,
559
- parentExtended, childExtended)
560
-
561
-
562
- class SqliteObjectsDb(SqliteDb):
563
- """Class to handle a Sqlite database.
564
- It will create connection, execute queries and commands"""
565
- # Maintain the current version of the DB schema
566
- # useful for future updates and backward compatibility
567
- # version should be an integer number
568
- VERSION = 1
569
-
570
- SELECT = ("SELECT id, parent_id, name, classname, value, label, comment, "
571
- "datetime(creation, 'localtime') as creation FROM Objects")
572
- DELETE = "DELETE FROM Objects WHERE "
573
- DELETE_SEQUENCE = "DELETE FROM SQLITE_SEQUENCE WHERE name='Objects'"
574
-
575
- SELECT_RELATION = ("SELECT object_%s_id AS id FROM Relations "
576
- "WHERE name=? AND object_%s_id=?")
577
- SELECT_RELATIONS = "SELECT * FROM Relations WHERE "
578
- EXISTS = "SELECT EXISTS(SELECT 1 FROM Objects WHERE %s=? LIMIT 1)"
579
-
580
- def selectCmd(self, whereStr, orderByStr=' ORDER BY id'):
581
-
582
- whereStr = " WHERE " + whereStr if whereStr is not None else ''
583
- return self.SELECT + whereStr + orderByStr
584
-
585
- def __init__(self, dbName, timeout=1000, pragmas=None):
586
- SqliteDb.__init__(self)
587
- self._pragmas = pragmas or {}
588
- self._createConnection(dbName, timeout)
589
- self._initialize()
590
-
591
- def _initialize(self):
592
- """ Create the required tables if needed. """
593
- tables = self.getTables()
594
- # Check if the tables have been created or not
595
- if not tables:
596
- self.__createTables()
597
- else:
598
- self.__updateTables()
599
-
600
- def __createTables(self):
601
- """Create required tables if don't exists"""
602
- # Enable foreign keys
603
- self.setVersion(self.VERSION)
604
- self._pragmas['foreing_keys'] = "ON"
605
- for pragma in self._pragmas.items():
606
- self.executeCommand("PRAGMA %s=%s" % pragma)
607
- # Create the Objects table
608
- self.executeCommand("""CREATE TABLE IF NOT EXISTS Objects
609
- (id INTEGER PRIMARY KEY AUTOINCREMENT,
610
- parent_id INTEGER REFERENCES Objects(id),
611
- name TEXT, -- object name
612
- classname TEXT, -- object's class name
613
- value TEXT DEFAULT NULL, -- object value, used for Scalars
614
- label TEXT DEFAULT NULL, -- object label, text used for display
615
- comment TEXT DEFAULT NULL, -- object comment, text used for annotations
616
- creation DATE -- creation date and time of the object
617
- )""")
618
- # Create the Relations table
619
- self.executeCommand("""CREATE TABLE IF NOT EXISTS Relations
620
- (id INTEGER PRIMARY KEY AUTOINCREMENT,
621
- parent_id INTEGER REFERENCES Objects(id), -- object that created the relation
622
- name TEXT, -- relation name
623
- classname TEXT DEFAULT NULL, -- relation's class name
624
- value TEXT DEFAULT NULL, -- relation value
625
- label TEXT DEFAULT NULL, -- relation label, text used for display
626
- comment TEXT DEFAULT NULL, -- relation comment, text used for annotations
627
- object_parent_id INTEGER REFERENCES Objects(id) ON DELETE CASCADE,
628
- object_child_id INTEGER REFERENCES Objects(id) ON DELETE CASCADE,
629
- creation DATE, -- creation date and time of the object
630
- object_parent_extended TEXT DEFAULT NULL, -- extended property to consider internal objects
631
- object_child_extended TEXT DEFAULT NULL
632
- )""")
633
- self.commit()
634
-
635
- def __updateTables(self):
636
- """ This method is intended to update the table schema
637
- in the case of dealing with old database version.
638
- """
639
- if self.getVersion() < self.VERSION: # This applies for version 1
640
- # Add the extra column for pointer extended attribute in Relations table
641
- # from version 1 on, there is not needed since the table will
642
- # already contains this column
643
- columns = [c[1] for c in self.getTableColumns('Relations')]
644
- if 'object_parent_extended' not in columns:
645
- self.executeCommand("ALTER TABLE Relations "
646
- "ADD COLUMN object_parent_extended TEXT DEFAULT NULL")
647
- if 'object_child_extended' not in columns:
648
- self.executeCommand("ALTER TABLE Relations "
649
- "ADD COLUMN object_child_extended TEXT DEFAULT NULL")
650
- self.setVersion(self.VERSION)
651
-
652
- def insertObject(self, name, classname, value, parent_id, label, comment):
653
- """Execute command to insert a new object. Return the inserted object id"""
654
- try:
655
- self.executeCommand(
656
- 'INSERT INTO Objects (parent_id, name, classname, value, label, comment, creation)' +
657
- ' VALUES (?, ?, ?, ?, ?, ?, datetime(\'now\'))',
658
- (parent_id, name, classname, value, label, comment))
659
- return self.cursor.lastrowid
660
- except Exception as ex:
661
- logger.error("insertObject: ERROR")
662
- logger.error('INSERT INTO Objects (parent_id, name, classname, value, label, comment, creation)' +
663
- ' VALUES (?, ?, ?, ?, ?, ?, datetime(\'now\'))')
664
- logger.error((parent_id, name, classname, value, label, comment))
665
- raise ex
666
-
667
- def insertRelation(self, relName, parent_id, object_parent_id, object_child_id,
668
- object_parent_extended=None, object_child_extended=None, **kwargs):
669
- """Execute command to insert a new object. Return the inserted object id"""
670
- self.executeCommand("INSERT INTO Relations "
671
- "(parent_id, name, object_parent_id, object_child_id, creation, "
672
- "object_parent_extended, object_child_extended) "
673
- " VALUES (?, ?, ?, ?, datetime('now'), ?, ?)",
674
- (parent_id, relName, object_parent_id, object_child_id,
675
- object_parent_extended, object_child_extended))
676
- return self.cursor.lastrowid
677
-
678
- def updateObject(self, objId, name, classname, value, parent_id, label, comment):
679
- """Update object data """
680
- self.executeCommand("UPDATE Objects SET parent_id=?, name=?, "
681
- "classname=?, value=?, label=?, comment=? WHERE id=?",
682
- (parent_id, name, classname, value, label, comment, objId))
683
-
684
- def selectObjectById(self, objId):
685
- """Select an object give its id"""
686
- self.executeCommand(self.selectCmd(ID + "=?"), (objId,))
687
- return self.cursor.fetchone()
688
-
689
- def doesRowExist(self, objId):
690
- """Return True if a row with a given id exists"""
691
- self.executeCommand(self.EXISTS % ID, (objId,))
692
- one = self.cursor.fetchone()
693
- return one[0] == 1
694
-
695
- def selectAllObjects(self):
696
- """Select all data at once"""
697
- self.executeCommand(self.selectCmd(ID + ">0", ' ORDER BY parent_id'))
698
- return self.cursor.fetchall()
699
-
700
- def selectObjectsByParent(self, parent_id=None, iterate=False):
701
- """Select object with a given parent
702
- if the parent_id is None, all object with parent_id NULL
703
- will be returned"""
704
- if parent_id is None:
705
- self.executeCommand(self.selectCmd(PARENT_ID + " is NULL"))
706
- else:
707
- self.executeCommand(self.selectCmd(PARENT_ID + "=?"), (parent_id,))
708
- return self._results(iterate)
709
-
710
- def selectObjectsByAncestor(self, ancestor_namePrefix, iterate=False):
711
- """Select all objects in the hierarchy of ancestor_id"""
712
- self.executeCommand(self.selectCmd("name LIKE '%s.%%'"
713
- % ancestor_namePrefix))
714
- return self._results(iterate)
715
-
716
- def selectObjectsBy(self, iterate=False, **args):
717
- """More flexible select where the constrains can be passed
718
- as a dictionary, the concatenation is done by an AND"""
719
-
720
- if len(args) == 0:
721
- whereStr = '1=?'
722
- whereTuple = (1,)
723
- else:
724
- whereList = ['%s=?' % k for k in args.keys()]
725
- whereStr = ' AND '.join(whereList)
726
- whereTuple = tuple(args.values())
727
-
728
- self.executeCommand(self.selectCmd(whereStr), whereTuple)
729
- return self._results(iterate)
730
-
731
- def selectObjectsWhere(self, whereStr, iterate=False):
732
- self.executeCommand(self.selectCmd(whereStr))
733
- return self._results(iterate)
734
-
735
- def deleteObject(self, objId):
736
- """Delete an existing object"""
737
- self.executeCommand(self.DELETE + ID + "=?", (objId,))
738
-
739
- def deleteChildObjects(self, ancestor_namePrefix):
740
- """ Delete from db all objects that are childs
741
- of an ancestor, now them will have the same starting prefix"""
742
- self.executeCommand(self.DELETE + "name LIKE '%s.%%'"
743
- % ancestor_namePrefix)
744
-
745
- def selectMissingObjectsByAncestor(self, ancestor_namePrefix,
746
- idList):
747
- """Select all objects in the hierarchy of ancestor_id"""
748
- idStr = ','.join(str(i) for i in idList)
749
- cmd = self.selectCmd("name LIKE '%s.%%' AND id NOT IN (%s) "
750
- % (ancestor_namePrefix, idStr))
751
- self.executeCommand(cmd)
752
- return self._results(iterate=False)
753
-
754
- def deleteMissingObjectsByAncestor(self, ancestor_namePrefix, idList):
755
- """Select all objects in the hierarchy of ancestor_id"""
756
- idStr = ','.join(str(i) for i in idList)
757
- cmd = "%s name LIKE '%s.%%' AND id NOT IN (%s) " % (self.DELETE, ancestor_namePrefix, idStr)
758
- self.executeCommand(cmd)
759
-
760
- def deleteAll(self):
761
- """ Delete all objects from the db. """
762
- self.executeCommand(self.DELETE + "1")
763
- self.executeCommand(self.DELETE_SEQUENCE) # restart the count of ids
764
-
765
- def selectRelationChilds(self, relName, object_parent_id):
766
- self.executeCommand(self.SELECT_RELATION % ('child', 'parent'),
767
- (relName, object_parent_id))
768
- return self._results()
769
-
770
- def selectRelationParents(self, relName, object_child_id):
771
- self.executeCommand(self.SELECT_RELATION % ('parent', 'child'),
772
- (relName, object_child_id))
773
- return self._results()
774
-
775
- def selectRelationsByCreator(self, parent_id):
776
- self.executeCommand(self.SELECT_RELATIONS + PARENT_ID + "=?", (parent_id,))
777
- return self._results()
778
-
779
- def selectRelationsByName(self, relationName):
780
- self.executeCommand(self.SELECT_RELATIONS + "name=?", (relationName,))
781
- return self._results()
782
-
783
- def deleteRelationsByCreator(self, parent_id):
784
- self.executeCommand("DELETE FROM Relations where parent_id=?", (parent_id,))
785
-
786
-
787
- class SqliteFlatMapper(Mapper):
788
- """Specific Flat Mapper implementation using Sqlite database"""
789
- def __init__(self, dbName, dictClasses=None, tablePrefix='',
790
- indexes=None):
791
- Mapper.__init__(self, dictClasses)
792
- self._objTemplate = None
793
- self._attributesToStore = None
794
- try:
795
- # We (ROB and JMRT) are playing with different
796
- # PRAGMAS (see https://www.sqlite.org/pragma.html)
797
- # for the SqliteFlatMapper instances
798
- # We have been playing with the following
799
- # pragmas: {synchronous, journal_mode, temp_store and
800
- # cache_size} and inside scipion no improvement
801
- # has been observed. Outside scipion a careful
802
- # choosing of pragmas may duplicate the speed but
803
- # inside scipion, I think that the overhead due
804
- # to the manipulation of python classes is more
805
- # important that the data access.
806
-
807
- # uncommenting these pragmas increase the speed
808
- # by a factor of two if NO python object is manipulated.
809
- # Unfortunately, any interesting operation involve
810
- # creation and manipulation of python objects that take
811
- # longer than the access time to the database
812
- pragmas = {
813
- # 0 | OFF | 1 | NORMAL | 2 | FULL | 3 | EXTRA;
814
- # #'synchronous': 'OFF', # ON
815
- # DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF
816
- # #'journal_mode': 'OFF', # DELETE
817
- # FILE 0 | DEFAULT | 1 | FILE | 2 | MEMORY;
818
- # #'temp_store': 'MEMORY',
819
- # PRAGMA schema.cache_size = pages;
820
- # #'cache_size': '5000' # versus -2000
821
- # "temp_store_directory": "'.'",
822
- }
823
- self.db = SqliteFlatDb(dbName, tablePrefix,
824
- pragmas=pragmas, indexes=indexes)
825
- self.doCreateTables = self.db.missingTables()
826
-
827
- if not self.doCreateTables:
828
- self.__loadObjDict()
829
- except Exception as ex:
830
- raise SqliteFlatMapperException('Error creating SqliteFlatMapper, '
831
- 'dbName: %s, tablePrefix: %s\n error: %s' %
832
- (dbName, tablePrefix, ex))
833
-
834
- def commit(self):
835
- self.db.commit()
836
-
837
- def close(self):
838
- self.db.close()
839
-
840
- def insert(self, obj):
841
- if self.doCreateTables:
842
- self.db.createTables(obj.getObjDict(includeClass=True))
843
- self.doCreateTables = False
844
- """Insert a new object into the system, the id will be set"""
845
- self.db.insertObject(obj.getObjId(), obj.isEnabled(), obj.getObjLabel(), obj.getObjComment(),
846
- *self._getValuesFromObject(obj).values())
847
-
848
- def getAttributes2Store(self, item):
849
-
850
- if self._attributesToStore is None:
851
- self._attributesToStore = [key for key, value in item.getAttributesToStore()]
852
-
853
- return self._attributesToStore
854
-
855
- def _getValuesFromObject(self, item):
856
-
857
- valuesDict = OrderedDict()
858
-
859
- for attr in self.getAttributes2Store(item):
860
- item.fillObjDict('', valuesDict, False, attr, getattr(item, attr))
861
-
862
- return valuesDict
863
-
864
- def enableAppend(self):
865
- """ This will allow to append items to existing db.
866
- This is by default not allow, since most sets are not
867
- modified after creation.
868
- """
869
- if not self.doCreateTables:
870
- obj = self.selectFirst()
871
- if obj is not None:
872
- self.db.setupCommands(obj.getObjDict(includeClass=True))
873
-
874
- def clear(self):
875
- self.db.clear()
876
- self.doCreateTables = True
877
-
878
- def deleteAll(self):
879
- """ Delete all objects stored """
880
- self.db.deleteAll()
881
-
882
- def delete(self, obj):
883
- """Delete an object and all its childs"""
884
- self.db.deleteObject(obj.getObjId())
885
-
886
- def updateTo(self, obj, level=1):
887
- """ Update database entry with new object values. """
888
- if self.db.INSERT_OBJECT is None:
889
- self.db.setupCommands(obj.getObjDict(includeClass=True))
890
- args = list(obj.getObjDict().values())
891
- args.append(obj.getObjId())
892
- self.db.updateObject(obj.isEnabled(), obj.getObjLabel(), obj.getObjComment(), *args)
893
-
894
- def exists(self, objId):
895
- return self.db.doesRowExist(objId)
896
-
897
- def selectById(self, objId):
898
- """Build the object which id is objId"""
899
- objRow = self.db.selectObjectById(objId)
900
- if objRow is None:
901
- obj = None
902
- else:
903
- obj = self.__objFromRow(objRow)
904
- return obj
905
-
906
- def __loadObjDict(self):
907
- """ Load object properties and classes from db.
908
- Stores the _objTemplate for future reuse"""
909
- # Create a template object for retrieving stored ones
910
- columnList = []
911
- rows = self.db.getClassRows()
912
-
913
- # Adds common fields to the mapping
914
- # Schema definition in classes table
915
- self.db.addCommonFieldsToMap()
916
-
917
- attrClasses = {}
918
- self._objBuildList = []
919
-
920
- # For each row lin classes table (_samplinRate --> c01, Integer)
921
- for r in rows:
922
-
923
- # Something like: _acquisition._doseInitial
924
- label = r['label_property']
925
-
926
- # If the actual item class: "Particle" in a SetOfParticles
927
- # First loop.
928
- if label == SELF:
929
- objClassName = r['class_name']
930
-
931
- # Store the template to reuse it during iterations and avoid instantiation
932
- self._objTemplate = self._buildObjectFromClass(objClassName)
933
- self._objClass = self._objTemplate.__class__
934
- else:
935
- # Update the database column mapping: c01 <-> _samplingRate
936
- self.db._columnsMapping[label] = r['column_name']
937
-
938
- # List for the latter _objColumns [(5,"_smplingRate"), ...]
939
- # This latter will be used to take the value from the cursor's row using the index (row[5] => obj._samplingRate)
940
- columnList.append(label)
941
-
942
- # Annotate the class
943
- attrClasses[label] = r['class_name']
944
-
945
- # Split the label: _acquisition._doseInitial -> ["_acquisition", "_doseInitial"]
946
- # For the loop
947
- attrParts = label.split('.')
948
-
949
- # Like a breadcrumb in websites... partial path to the attribute
950
- attrJoin = ''
951
-
952
- # Start from the template (root)
953
- o = self._objTemplate
954
-
955
- # for each part: ["_acquisition", "_doseInitial"]
956
- for a in attrParts:
957
- attrJoin += a
958
-
959
- # Try to get the attribute. Case of attributes defined in the model (init)
960
- attr = getattr(o, a, None)
961
-
962
- # If the object does not have the attribute, then it might be an extra parameter or an optional like Transform.p+ç1
963
- if attr is None:
964
- className = attrClasses[attrJoin]
965
-
966
- # Instantiate the class.
967
- attr = self._buildObjectFromClass(className)
968
-
969
- # Get the class form the attr: it could come as a LegacyClass in case "className" is not found.
970
- self._objBuildList.append((attr.__class__, attrJoin.split('.')))
971
- setattr(o, a, attr)
972
- o = attr
973
- attrJoin += '.'
974
- basicRows = 5
975
- n = len(rows) + basicRows - 1
976
- self._objColumns = list(zip(range(basicRows, n), columnList))
977
-
978
- def __buildAndFillObj(self):
979
- """ Instantiates the set item base on the _objBuildList.
980
- _objBuildList has been populated when loading the classDictionary"""
981
-
982
- obj = self._objClass()
983
-
984
- for clazz, attrParts in self._objBuildList:
985
- o = obj
986
- for a in attrParts:
987
- attr = getattr(o, a, None)
988
- if not attr:
989
- setattr(o, a, clazz())
990
- break
991
- o = attr
992
- return obj
993
-
994
- def getInstance(self):
995
-
996
- if self._objTemplate is None:
997
- self.__loadObjDict()
998
-
999
- # Difference in performance using scipion3 tests pwperformance.tests.test_set_performance.TestSetPerformanceSteps.testSuperExtendedCoordinatesSet
1000
- # A test that creates a 10**6 set of extended coordinates (coordinates with 20 extra attributes)
1001
- # With the template iteration takes 10 secs
1002
- # Building the object each time take 25 secs (15 seconds more)
1003
- if Config.SCIPION_MAPPER_USE_TEMPLATE:
1004
- return self._objTemplate
1005
- else:
1006
- return self.__buildAndFillObj()
1007
-
1008
- def __objFromRow(self, objRow):
1009
-
1010
-
1011
- obj = self.getInstance()
1012
- obj.setObjId(objRow[ID])
1013
- obj.setObjLabel(self._getStrValue(objRow['label']))
1014
- obj.setObjComment(self._getStrValue(objRow['comment']))
1015
-
1016
- try:
1017
- obj.setEnabled(objRow['enabled'])
1018
- obj.setObjCreation(self._getStrValue(objRow[CREATION]))
1019
- except Exception:
1020
- # THIS SHOULD NOT HAPPEN
1021
- logger.warning("'creation' column not found in object: %s" % obj.getObjId())
1022
- logger.warning(" db: %s" % self.db.getDbName())
1023
- logger.warning(" objRow: %s." % dict(objRow))
1024
-
1025
- for c, attrName in self._objColumns:
1026
- obj.setAttributeValue(attrName, objRow[c])
1027
-
1028
- return obj
1029
-
1030
- def __iterObjectsFromRows(self, objRows, objectFilter=None):
1031
- for objRow in objRows:
1032
- obj = self.__objFromRow(objRow)
1033
- if objectFilter is None or objectFilter(obj):
1034
- yield obj
1035
-
1036
- def __objectsFromRows(self, objRows, iterate=False, objectFilter=None):
1037
- """Create a set of object from a set of rows
1038
- Params:
1039
- objRows: rows result from a db select.
1040
- iterate: if True, iterates over all elements, if False the whole
1041
- list is returned
1042
- objectFilter: function to filter some of the objects of the results.
1043
- """
1044
- if not iterate:
1045
- return [obj.clone()
1046
- for obj in self.__iterObjectsFromRows(objRows, objectFilter)]
1047
- else:
1048
- return self.__iterObjectsFromRows(objRows, objectFilter)
1049
-
1050
- def selectBy(self, iterate=False, objectFilter=None, **args):
1051
- """Select object meetings some criteria"""
1052
- objRows = self.db.selectObjectsBy(**args)
1053
- return self.__objectsFromRows(objRows, iterate, objectFilter)
1054
-
1055
- def selectAll(self, iterate=True, objectFilter=None, orderBy=ID,
1056
- direction='ASC', where='1', limit=None):
1057
- # Just a sanity check for emtpy sets, that doesn't contains
1058
- # 'Properties' table
1059
- if not self.db.hasTable('Properties'):
1060
- return iter([]) if iterate else []
1061
-
1062
- # Initialize the instance
1063
- self.getInstance()
1064
-
1065
- try:
1066
- objRows = self.db.selectAll(orderBy=orderBy,
1067
- direction=direction,
1068
- where=where,
1069
- limit=limit)
1070
- except OperationalError as e:
1071
- msg="""Error executing selectAll command: %s.
1072
- You may want to change the directory used by sqlite to create temporary files
1073
- to one that has enough free space. By default this directory is /tmp
1074
- You may achieve this goal by defining the SQLITE_TMPDIR environment variable
1075
- and restarting scipion. Export command:
1076
- export SQLITE_TMPDIR=. """ % str(e)
1077
- raise OperationalError(msg)
1078
-
1079
- return self.__objectsFromRows(objRows, iterate, objectFilter)
1080
-
1081
- def unique(self, labels, where=None):
1082
- """ Returns a list (for a single label) or a dictionary with unique values for the passed labels.
1083
- If more than one label is passed it will be unique rows similar ti SQL unique clause.
1084
-
1085
- :param labels (string or list) item attribute/s to retrieve unique row values
1086
- :param where (string) condition to filter the results"""
1087
-
1088
- if isinstance(labels, str):
1089
- labels = [labels]
1090
-
1091
- rows = self.db.unique(labels, where)
1092
- result = {label: [] for label in labels} # Initialize the results dictionary
1093
- for row in rows:
1094
- for label in labels:
1095
- result[label].append(row[label])
1096
-
1097
- # If there is only one label,
1098
- if len(labels) == 1:
1099
- return result[labels[0]]
1100
- else:
1101
- return result
1102
-
1103
- def aggregate(self, operations, operationLabel, groupByLabels=None):
1104
-
1105
- operations = valueToList(operations)
1106
- groupByLabels = valueToList(groupByLabels)
1107
-
1108
- rows = self.db.aggregate(operations, operationLabel, groupByLabels)
1109
-
1110
- # Transform the sql row into a disconnected list of dictionaries
1111
- results = []
1112
- for row in rows:
1113
- values = {}
1114
- for key in row.keys():
1115
- values[key] = row[key]
1116
- results.append(values)
1117
-
1118
- return results
1119
-
1120
- def count(self):
1121
- return 0 if self.doCreateTables else self.db.count()
1122
-
1123
- def maxId(self):
1124
- return 0 if self.doCreateTables else self.db.maxId()
1125
-
1126
- def __objectsFromIds(self, objIds):
1127
- """Return a list of objects, given a list of id's
1128
- """
1129
- return [self.selectById(rowId[ID]) for rowId in objIds]
1130
-
1131
- def hasProperty(self, key):
1132
- return self.db.hasProperty(key)
1133
-
1134
- def getProperty(self, key, defaultValue=None):
1135
- return self.db.getProperty(key, defaultValue)
1136
-
1137
- def setProperty(self, key, value):
1138
- return self.db.setProperty(key, value)
1139
-
1140
- def deleteProperty(self, key):
1141
- return self.db.deleteProperty(key)
1142
-
1143
- def getPropertyKeys(self):
1144
- return self.db.getPropertyKeys()
1145
-
1146
- @staticmethod
1147
- def fmtDate(date):
1148
- """ Formats a python date into a valid string to be used in a where term
1149
- Currently creation files is stored in utc time and is has no microseconds.
1150
-
1151
- :param date: python date un utc. use datetime.datetime.utcnow() instead of now()"""
1152
- return "datetime('%s')" % date.replace(microsecond=0)
1153
-
1154
- class SqliteFlatMapperException(Exception):
1155
- pass
1156
-
1157
-
1158
- SELF = 'self'
1159
-
1160
-
1161
- class SqliteFlatDb(SqliteDb):
1162
- """Class to handle a Sqlite database.
1163
- It will create connection, execute queries and commands"""
1164
- # Maintain the current version of the DB schema
1165
- # useful for future updates and backward compatibility
1166
- # version should be an integer number
1167
- VERSION = 1
1168
-
1169
- CLASS_MAP = {'Integer': 'INTEGER',
1170
- 'Float': 'REAL',
1171
- 'Boolean': 'INTEGER'
1172
- }
1173
-
1174
- def __init__(self, dbName, tablePrefix='', timeout=1000,
1175
- pragmas=None, indexes=None):
1176
- SqliteDb.__init__(self)
1177
- self._pragmas = pragmas or {}
1178
- self._indexes = indexes or []
1179
- tablePrefix = tablePrefix.strip()
1180
- # Avoid having _ for empty prefix
1181
- if tablePrefix and not tablePrefix.endswith('_'):
1182
- tablePrefix += '_'
1183
- # NOTE (Jose Miguel, 2014/01/02
1184
- # Reusing connections is a bit dangerous, since it have lead to
1185
- # unexpected and hard to trace errors due to using an out-of-date
1186
- # reused connection. That's why we are changing now the default to False
1187
- # and only setting to True when the tablePrefix is non-empty, which is
1188
- # the case for classes that are different tables in the same db and it
1189
- # logical to reuse the connection.
1190
- self._reuseConnections = bool(tablePrefix)
1191
-
1192
- self.CHECK_TABLES = ("SELECT name FROM sqlite_master WHERE type='table'"
1193
- " AND name='%sObjects';" % tablePrefix)
1194
- self.SELECT = "SELECT * FROM %sObjects" % tablePrefix
1195
- self.FROM = "FROM %sObjects" % tablePrefix
1196
- self.DELETE = "DELETE FROM %sObjects WHERE " % tablePrefix
1197
- self.INSERT_CLASS = ("INSERT INTO %sClasses (label_property, "
1198
- "column_name, class_name) VALUES (?, ?, ?)"
1199
- % tablePrefix)
1200
- self.SELECT_CLASS = "SELECT * FROM %sClasses;" % tablePrefix
1201
- self.EXISTS = "SELECT EXISTS(SELECT 1 FROM Objects WHERE %s=? LIMIT 1)"
1202
- self.tablePrefix = tablePrefix
1203
- self._createConnection(dbName, timeout)
1204
- self.INSERT_OBJECT = None
1205
- self.UPDATE_OBJECT = None
1206
- self._columnsMapping = {}
1207
-
1208
- self.INSERT_PROPERTY = "INSERT INTO Properties (key, value) VALUES (?, ?)"
1209
- self.DELETE_PROPERTY = "DELETE FROM Properties WHERE key=?"
1210
- self.UPDATE_PROPERTY = "UPDATE Properties SET value=? WHERE key=?"
1211
- self.SELECT_PROPERTY = "SELECT value FROM Properties WHERE key=?"
1212
- self.SELECT_PROPERTY_KEYS = "SELECT key FROM Properties"
1213
-
1214
- def hasProperty(self, key):
1215
- """ Return true if a property with this value is registered. """
1216
- # The database not will not have the 'Properties' table when
1217
- # there is not item inserted (ie an empty set)
1218
- if not self.hasTable('Properties'):
1219
- return False
1220
- self.executeCommand(self.SELECT_PROPERTY, (key,))
1221
- result = self.cursor.fetchone()
1222
- return result is not None
1223
-
1224
- def getProperty(self, key, defaultValue=None):
1225
- """ Return the value of a given property with this key.
1226
- If not found, the defaultValue will be returned.
1227
- """
1228
- # The database not will not have the 'Properties' table when
1229
- # there is not item inserted (ie an empty set)
1230
- if not self.hasTable('Properties'):
1231
- return defaultValue
1232
-
1233
- self.executeCommand(self.SELECT_PROPERTY, (key,))
1234
- result = self.cursor.fetchone()
1235
-
1236
- if result:
1237
- return result['value']
1238
- else:
1239
- return defaultValue
1240
-
1241
- def setProperty(self, key, value):
1242
- """ Insert or update the property with a value. """
1243
- # Just ignore the set property for empty sets
1244
- if not self.hasTable('Properties'):
1245
- return
1246
-
1247
- # All properties are stored as string, except for None type
1248
- value = str(value) if value is not None else None
1249
-
1250
- if self.hasProperty(key):
1251
- self.executeCommand(self.UPDATE_PROPERTY, (value, key))
1252
- else:
1253
- self.executeCommand(self.INSERT_PROPERTY, (key, value))
1254
-
1255
- def getPropertyKeys(self):
1256
- """ Return all properties stored of this object. """
1257
- self.executeCommand(self.SELECT_PROPERTY_KEYS)
1258
- keys = [r[0] for r in self.cursor.fetchall()]
1259
- return keys
1260
-
1261
- def deleteProperty(self, key):
1262
- self.executeCommand(self.DELETE_PROPERTY, (key,))
1263
-
1264
- def selectCmd(self, whereStr, orderByStr=' ORDER BY ' + ID):
1265
-
1266
- whereStr = "" if whereStr is None else " WHERE " + whereStr
1267
- return self.SELECT + whereStr + orderByStr
1268
-
1269
- def missingTables(self):
1270
- """ Return True is the needed Objects and Classes table are not
1271
- created yet. """
1272
- self.executeCommand(self.CHECK_TABLES)
1273
- result = self.cursor.fetchone()
1274
-
1275
- return result is None
1276
-
1277
- def clear(self):
1278
- self.executeCommand("DROP TABLE IF EXISTS Properties;")
1279
- self.executeCommand("DROP TABLE IF EXISTS %sClasses;"
1280
- % self.tablePrefix)
1281
- self.executeCommand("DROP TABLE IF EXISTS %sObjects;"
1282
- % self.tablePrefix)
1283
-
1284
- def createTables(self, objDict):
1285
- """Create the Classes and Object table to store items of a Set.
1286
- Each object will be stored in a single row.
1287
- Each nested property of the object will be stored as a column value.
1288
- """
1289
- self.setVersion(self.VERSION)
1290
- for pragma in self._pragmas.items():
1291
- logger.debug("Executing pragma: %s" % pragma)
1292
- self.executeCommand("PRAGMA %s = %s;" % pragma)
1293
- # Create a general Properties table to store some needed values
1294
- self.executeCommand("""CREATE TABLE IF NOT EXISTS Properties
1295
- (key TEXT UNIQUE, -- property key
1296
- value TEXT DEFAULT NULL -- property value
1297
- )""")
1298
- # Create the Classes table to store each column name and type
1299
- self.executeCommand("""CREATE TABLE IF NOT EXISTS %sClasses
1300
- (id INTEGER PRIMARY KEY AUTOINCREMENT,
1301
- label_property TEXT UNIQUE, --object label
1302
- column_name TEXT UNIQUE,
1303
- class_name TEXT DEFAULT NULL -- relation's class name
1304
- )""" % self.tablePrefix)
1305
- CREATE_OBJECT_TABLE = """CREATE TABLE IF NOT EXISTS %sObjects
1306
- (id INTEGER PRIMARY KEY,
1307
- enabled INTEGER DEFAULT 1, -- used to selected/deselect items from a set
1308
- label TEXT DEFAULT NULL, -- object label, text used for display
1309
- comment TEXT DEFAULT NULL, -- object comment, text used for annotations
1310
- creation DATE -- creation date and time of the object
1311
- """ % self.tablePrefix
1312
-
1313
- c = 0
1314
- colMap = {}
1315
- for k, v in objDict.items():
1316
- colName = 'c%02d' % c
1317
- className = v[0]
1318
- colMap[k] = colName
1319
- c += 1
1320
- self.executeCommand(self.INSERT_CLASS, (k, colName, className))
1321
- if k != SELF:
1322
- CREATE_OBJECT_TABLE += ',%s %s DEFAULT NULL' % (colName, self.CLASS_MAP.get(className, 'TEXT'))
1323
-
1324
- CREATE_OBJECT_TABLE += ')'
1325
- # Create the Objects table
1326
- self.executeCommand(CREATE_OBJECT_TABLE)
1327
-
1328
- for idx in self._indexes:
1329
- # first check if the attribute to be indexed exists
1330
- if idx in colMap:
1331
- self.executeCommand("CREATE INDEX index_%s ON Objects (%s);"
1332
- % (idx.replace('.', '_'), colMap[idx]))
1333
-
1334
- self.commit()
1335
- # Prepare the INSERT and UPDATE commands
1336
- self.setupCommands(objDict)
1337
-
1338
- def setupCommands(self, objDict):
1339
- """ Setup the INSERT and UPDATE commands base on the object dictionary. """
1340
- self.INSERT_OBJECT = "INSERT INTO %sObjects (id, enabled, label, comment, creation" % self.tablePrefix
1341
- self.UPDATE_OBJECT = "UPDATE %sObjects SET enabled=?, label=?, comment=?" % self.tablePrefix
1342
- c = 0
1343
- for k in objDict:
1344
- colName = 'c%02d' % c
1345
- self._columnsMapping[k] = colName
1346
- c += 1
1347
- if k != SELF:
1348
- self.INSERT_OBJECT += ',%s' % colName
1349
- self.UPDATE_OBJECT += ', %s=?' % colName
1350
-
1351
- self.addCommonFieldsToMap()
1352
-
1353
- self.INSERT_OBJECT += ") VALUES (?,?,?,?, datetime('now')" + ',?' * (c-1) + ')'
1354
- self.UPDATE_OBJECT += ' WHERE id=?'
1355
-
1356
- def addCommonFieldsToMap(self):
1357
-
1358
- # Add common fields to the mapping
1359
- self._columnsMapping[ID_COLUMN] = ID_COLUMN
1360
- self._columnsMapping[ID_ATTRIBUTE] = ID_COLUMN
1361
-
1362
- def getClassRows(self):
1363
- """ Create a dictionary with names of the attributes
1364
- of the columns. """
1365
- self.executeCommand(self.SELECT_CLASS)
1366
- return self._results(iterate=False)
1367
-
1368
- def getSelfClassName(self):
1369
- """ Return the class name of the attribute named 'self'.
1370
- This is the class of the items stored in a Set.
1371
- """
1372
- self.executeCommand(self.SELECT_CLASS)
1373
-
1374
- for classRow in self._iterResults():
1375
- if classRow['label_property'] == SELF:
1376
- return classRow['class_name']
1377
- raise Exception("Row '%s' was not found in Classes table. " % SELF)
1378
-
1379
- def insertObject(self, *args):
1380
- """Insert a new object as a row.
1381
- *args: id, label, comment, ...
1382
- where ... is the values of the objDict from which the tables
1383
- where created."""
1384
- self.executeCommand(self.INSERT_OBJECT, args)
1385
-
1386
- def updateObject(self, *args):
1387
- """Update object data """
1388
- self.executeCommand(self.UPDATE_OBJECT, args)
1389
-
1390
- def selectObjectById(self, objId):
1391
- """Select an object give its id"""
1392
- self.executeCommand(self.selectCmd(ID + "=?"), (objId,))
1393
- return self.cursor.fetchone()
1394
-
1395
- def doesRowExist(self, objId):
1396
- """Return True if a row with a given id exists"""
1397
- self.executeCommand(self.EXISTS % ID, (objId,))
1398
- one = self.cursor.fetchone()
1399
- return one[0] == 1
1400
-
1401
- def _getRealCol(self, colName):
1402
- """ Transform the column name taking into account
1403
- special columns such as: id or RANDOM(), and
1404
- getting the mapping translation otherwise.
1405
- """
1406
- if colName in [ID, 'RANDOM()', CREATION]:
1407
- return colName
1408
- elif colName in self._columnsMapping:
1409
- return self._columnsMapping[colName]
1410
- else:
1411
- return None
1412
- def selectAll(self, iterate=True, orderBy=ID, direction='ASC',
1413
- where=None, limit=None):
1414
- # Handle the specials orderBy values of 'id' and 'RANDOM()'
1415
- # other columns names should be mapped to table column
1416
- # such as: _micId -> c04
1417
-
1418
- if isinstance(orderBy, str):
1419
- orderByCol = self._getRealCol(orderBy)
1420
- elif isinstance(orderBy, list):
1421
- orderByCol = ','.join([self._getRealCol(c) for c in orderBy])
1422
- else:
1423
- raise Exception('Invalid type for orderBy: %s' % type(orderBy))
1424
-
1425
- whereStr = self._whereToWhereStr(where)
1426
-
1427
- cmd = self.selectCmd(whereStr,
1428
- orderByStr=' ORDER BY %s %s'
1429
- % (orderByCol, direction))
1430
- # If there is a limit
1431
- if limit:
1432
- # if it is a tuple
1433
- if isinstance(limit, tuple):
1434
- limit, skipRows = limit # Extract values from tuple
1435
- else:
1436
- skipRows = None
1437
- # If we need to skip rows
1438
- skipPart = "%s," % skipRows if skipRows else ""
1439
- cmd += " LIMIT %s %s" % (skipPart, limit)
1440
-
1441
- self.executeCommand(cmd)
1442
- return self._results(iterate)
1443
-
1444
- def _whereToWhereStr(self, where):
1445
- """ Parse the where string to replace the column name with
1446
- the real table column name ( for example: _micId -> c01 )
1447
- Right now we are assuming a simple where string in the form
1448
- colName=VALUE
1449
-
1450
- :param where string with pair of terms separated by "=" where left
1451
- element is an attribute of the item
1452
-
1453
- >>> Example:
1454
- >>> _micId=3 OR _micId=4
1455
- """
1456
- if where is None:
1457
- return
1458
-
1459
- whereStr = where
1460
- # Split by valid where operators: =, <, >
1461
- result = re.split('<=|>=|=|<|>|AND|OR', where)
1462
- # For each item
1463
- for term in result:
1464
- # trim it
1465
- term = term.strip()
1466
- whereRealCol = self._getRealCol(term)
1467
- if whereRealCol is not None:
1468
- whereStr = whereStr.replace(term, whereRealCol)
1469
-
1470
- return whereStr
1471
-
1472
- def unique(self, labels, where=None):
1473
- """ Returns the results of the execution of a UNIQUE query
1474
-
1475
- :param labels: list of attributes which you want unique values from
1476
- :param where: condition to match in the form: attrName=value
1477
- :return:
1478
- """
1479
- # let us count for testing
1480
- selectStr = 'SELECT DISTINCT '
1481
- separator = ' '
1482
- # This cannot be like the following line should be expressed in terms
1483
- # of c01, c02 etc (actual fields)....
1484
- for label in labels:
1485
- selectStr += "%s %s AS %s " % (separator, self._getRealCol(label), label)
1486
- separator = ', '
1487
-
1488
- sqlCommand = selectStr + self.FROM
1489
-
1490
- whereStr = self._whereToWhereStr(where)
1491
- if whereStr is not None:
1492
- sqlCommand += " WHERE " + whereStr
1493
-
1494
- self.executeCommand(sqlCommand)
1495
- return self._results(iterate=False)
1496
-
1497
- def aggregate(self, operations, operationLabel, groupByLabels=None):
1498
- """
1499
-
1500
- :param operations: string or LIST of operations: MIN, MAX, AVG, COUNT, SUM, TOTAL, GROUP_CONCAT. Any single argument function
1501
- defined for sqlite at https://www.sqlite.org/lang_aggfunc.html
1502
- :param operationLabel: string or LIST of attributes to apply the functions on
1503
- :param groupByLabels: (Optional) attribute or list of attributes to group by the data
1504
- :return:
1505
- """
1506
- # let us count for testing
1507
- selectStr = 'SELECT '
1508
- separator = ' '
1509
-
1510
- operations = valueToList(operations)
1511
- operationLabel = valueToList(operationLabel)
1512
- groupByLabels = valueToList(groupByLabels)
1513
-
1514
- # This cannot be like the following line should be expressed in terms
1515
- # of C1, C2 etc....
1516
- for index,label in enumerate(operationLabel):
1517
- for operation in operations:
1518
-
1519
- if index==0:
1520
- alias = operation
1521
- else:
1522
- alias = operation + label
1523
-
1524
- selectStr += "%s %s(%s) AS %s" % (separator, operation,
1525
- self._columnsMapping[label],
1526
- alias)
1527
- separator = ', '
1528
- if groupByLabels:
1529
- groupByStr = 'GROUP BY '
1530
- separator = ' '
1531
- for groupByLabel in groupByLabels:
1532
- groupByCol = self._columnsMapping[groupByLabel]
1533
- selectStr += ', %(groupByCol)s as "%(groupByLabel)s"' % locals()
1534
- groupByStr += "%s %s" % (separator, groupByCol)
1535
- separator = ', '
1536
- else:
1537
- groupByStr = ' '
1538
- sqlCommand = selectStr + "\n" + self.FROM + "\n" + groupByStr
1539
- self.executeCommand(sqlCommand)
1540
- return self._results(iterate=False)
1541
-
1542
- def count(self):
1543
- """ Return the number of element in the table. """
1544
- self.executeCommand(self.selectCmd('1').replace('*', 'COUNT(id)'))
1545
- return self.cursor.fetchone()[0]
1546
-
1547
- def maxId(self):
1548
- """ Return the maximum id from the Objects table. """
1549
- self.executeCommand(self.selectCmd('1').replace('*', 'MAX(id)'))
1550
- return self.cursor.fetchone()[0]
1551
-
1552
- # FIXME: Seems to be duplicated and a subset of selectAll
1553
- def selectObjectsBy(self, iterate=False, **args):
1554
- """More flexible select where the constrains can be passed
1555
- as a dictionary, the concatenation is done by an AND"""
1556
- whereList = ['%s=?' % k for k in args.keys()]
1557
- whereStr = ' AND '.join(whereList)
1558
- whereTuple = tuple(args.values())
1559
- whereStr = self._whereToWhereStr(whereStr)
1560
- self.executeCommand(self.selectCmd(whereStr), whereTuple)
1561
- return self._results(iterate)
1562
-
1563
- # FIXME: Seems to be duplicated and a subset of selectAll
1564
- # Moreover, it does not translate between "user columns" and
1565
- # "internal" Objects table columns
1566
- def selectObjectsWhere(self, whereStr, iterate=False):
1567
- self.executeCommand(self.selectCmd(whereStr))
1568
- return self._results(iterate)
1569
-
1570
- def deleteObject(self, objId):
1571
- """Delete an existing object"""
1572
- self.executeCommand(self.DELETE + "id=?", (objId,))
1573
-
1574
- def deleteAll(self):
1575
- """ Delete all objects from the db. """
1576
- if not self.missingTables():
1577
- self.executeCommand(self.DELETE + "1")
1578
-