scipion-pyworkflow 3.7.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.
- pyworkflow/__init__.py +33 -0
- pyworkflow/apps/__init__.py +29 -0
- pyworkflow/apps/pw_manager.py +37 -0
- pyworkflow/apps/pw_plot.py +51 -0
- pyworkflow/apps/pw_project.py +113 -0
- pyworkflow/apps/pw_protocol_list.py +143 -0
- pyworkflow/apps/pw_protocol_run.py +51 -0
- pyworkflow/apps/pw_run_tests.py +267 -0
- pyworkflow/apps/pw_schedule_run.py +322 -0
- pyworkflow/apps/pw_sleep.py +37 -0
- pyworkflow/apps/pw_sync_data.py +439 -0
- pyworkflow/apps/pw_viewer.py +78 -0
- pyworkflow/config.py +536 -0
- pyworkflow/constants.py +212 -0
- pyworkflow/exceptions.py +18 -0
- pyworkflow/gui/__init__.py +36 -0
- pyworkflow/gui/browser.py +726 -0
- pyworkflow/gui/canvas.py +1190 -0
- pyworkflow/gui/dialog.py +976 -0
- pyworkflow/gui/form.py +2627 -0
- pyworkflow/gui/graph.py +247 -0
- pyworkflow/gui/graph_layout.py +271 -0
- pyworkflow/gui/gui.py +566 -0
- pyworkflow/gui/matplotlib_image.py +233 -0
- pyworkflow/gui/plotter.py +247 -0
- pyworkflow/gui/project/__init__.py +25 -0
- pyworkflow/gui/project/base.py +192 -0
- pyworkflow/gui/project/constants.py +139 -0
- pyworkflow/gui/project/labels.py +205 -0
- pyworkflow/gui/project/project.py +484 -0
- pyworkflow/gui/project/searchprotocol.py +154 -0
- pyworkflow/gui/project/searchrun.py +181 -0
- pyworkflow/gui/project/steps.py +166 -0
- pyworkflow/gui/project/utils.py +332 -0
- pyworkflow/gui/project/variables.py +179 -0
- pyworkflow/gui/project/viewdata.py +472 -0
- pyworkflow/gui/project/viewprojects.py +510 -0
- pyworkflow/gui/project/viewprotocols.py +2093 -0
- pyworkflow/gui/project/viewprotocols_extra.py +560 -0
- pyworkflow/gui/text.py +771 -0
- pyworkflow/gui/tooltip.py +185 -0
- pyworkflow/gui/tree.py +684 -0
- pyworkflow/gui/widgets.py +307 -0
- pyworkflow/mapper/__init__.py +26 -0
- pyworkflow/mapper/mapper.py +222 -0
- pyworkflow/mapper/sqlite.py +1578 -0
- pyworkflow/mapper/sqlite_db.py +145 -0
- pyworkflow/object.py +1512 -0
- pyworkflow/plugin.py +712 -0
- pyworkflow/project/__init__.py +31 -0
- pyworkflow/project/config.py +451 -0
- pyworkflow/project/manager.py +179 -0
- pyworkflow/project/project.py +1990 -0
- pyworkflow/project/scripts/clean_projects.py +77 -0
- pyworkflow/project/scripts/config.py +92 -0
- pyworkflow/project/scripts/create.py +77 -0
- pyworkflow/project/scripts/edit_workflow.py +90 -0
- pyworkflow/project/scripts/fix_links.py +39 -0
- pyworkflow/project/scripts/load.py +87 -0
- pyworkflow/project/scripts/refresh.py +83 -0
- pyworkflow/project/scripts/schedule.py +111 -0
- pyworkflow/project/scripts/stack2volume.py +41 -0
- pyworkflow/project/scripts/stop.py +81 -0
- pyworkflow/protocol/__init__.py +38 -0
- pyworkflow/protocol/bibtex.py +48 -0
- pyworkflow/protocol/constants.py +86 -0
- pyworkflow/protocol/executor.py +334 -0
- pyworkflow/protocol/hosts.py +313 -0
- pyworkflow/protocol/launch.py +270 -0
- pyworkflow/protocol/package.py +42 -0
- pyworkflow/protocol/params.py +744 -0
- pyworkflow/protocol/protocol.py +2554 -0
- pyworkflow/resources/Imagej.png +0 -0
- pyworkflow/resources/chimera.png +0 -0
- pyworkflow/resources/fa-exclamation-triangle_alert.png +0 -0
- pyworkflow/resources/fa-info-circle_alert.png +0 -0
- pyworkflow/resources/fa-search.png +0 -0
- pyworkflow/resources/fa-times-circle_alert.png +0 -0
- pyworkflow/resources/file_vol.png +0 -0
- pyworkflow/resources/loading.gif +0 -0
- pyworkflow/resources/no-image128.png +0 -0
- pyworkflow/resources/scipion_bn.png +0 -0
- pyworkflow/resources/scipion_icon.png +0 -0
- pyworkflow/resources/scipion_icon.svg +397 -0
- pyworkflow/resources/scipion_icon_proj.png +0 -0
- pyworkflow/resources/scipion_icon_projs.png +0 -0
- pyworkflow/resources/scipion_icon_prot.png +0 -0
- pyworkflow/resources/scipion_logo.png +0 -0
- pyworkflow/resources/scipion_logo_normal.png +0 -0
- pyworkflow/resources/scipion_logo_small.png +0 -0
- pyworkflow/resources/sprites.png +0 -0
- pyworkflow/resources/sprites.xcf +0 -0
- pyworkflow/resources/wait.gif +0 -0
- pyworkflow/template.py +322 -0
- pyworkflow/tests/__init__.py +29 -0
- pyworkflow/tests/test_utils.py +25 -0
- pyworkflow/tests/tests.py +341 -0
- pyworkflow/utils/__init__.py +38 -0
- pyworkflow/utils/dataset.py +414 -0
- pyworkflow/utils/echo.py +104 -0
- pyworkflow/utils/graph.py +196 -0
- pyworkflow/utils/log.py +284 -0
- pyworkflow/utils/path.py +527 -0
- pyworkflow/utils/process.py +132 -0
- pyworkflow/utils/profiler.py +92 -0
- pyworkflow/utils/progressbar.py +154 -0
- pyworkflow/utils/properties.py +627 -0
- pyworkflow/utils/reflection.py +129 -0
- pyworkflow/utils/utils.py +877 -0
- pyworkflow/utils/which.py +229 -0
- pyworkflow/viewer.py +328 -0
- pyworkflow/webservices/__init__.py +8 -0
- pyworkflow/webservices/config.py +11 -0
- pyworkflow/webservices/notifier.py +162 -0
- pyworkflow/webservices/repository.py +59 -0
- pyworkflow/webservices/workflowhub.py +74 -0
- pyworkflow/wizard.py +64 -0
- pyworkflowtests/__init__.py +51 -0
- pyworkflowtests/bibtex.py +51 -0
- pyworkflowtests/objects.py +830 -0
- pyworkflowtests/protocols.py +154 -0
- pyworkflowtests/tests/__init__.py +0 -0
- pyworkflowtests/tests/test_canvas.py +72 -0
- pyworkflowtests/tests/test_domain.py +45 -0
- pyworkflowtests/tests/test_logs.py +74 -0
- pyworkflowtests/tests/test_mappers.py +392 -0
- pyworkflowtests/tests/test_object.py +507 -0
- pyworkflowtests/tests/test_project.py +42 -0
- pyworkflowtests/tests/test_protocol_execution.py +72 -0
- pyworkflowtests/tests/test_protocol_export.py +78 -0
- pyworkflowtests/tests/test_protocol_output.py +158 -0
- pyworkflowtests/tests/test_streaming.py +47 -0
- pyworkflowtests/tests/test_utils.py +210 -0
- scipion_pyworkflow-3.7.0.dist-info/LICENSE.txt +674 -0
- scipion_pyworkflow-3.7.0.dist-info/METADATA +107 -0
- scipion_pyworkflow-3.7.0.dist-info/RECORD +140 -0
- scipion_pyworkflow-3.7.0.dist-info/WHEEL +5 -0
- scipion_pyworkflow-3.7.0.dist-info/dependency_links.txt +1 -0
- scipion_pyworkflow-3.7.0.dist-info/entry_points.txt +5 -0
- scipion_pyworkflow-3.7.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,307 @@
|
|
1
|
+
# **************************************************************************
|
2
|
+
# *
|
3
|
+
# * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
|
4
|
+
# *
|
5
|
+
# * [1] SciLifeLab, Stockholm University
|
6
|
+
# *
|
7
|
+
# * This program is free software: you can redistribute it and/or modify
|
8
|
+
# * it under the terms of the GNU General Public License as published by
|
9
|
+
# * the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# * (at your option) any later version.
|
11
|
+
# *
|
12
|
+
# * This program is distributed in the hope that it will be useful,
|
13
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# * GNU General Public License for more details.
|
16
|
+
# *
|
17
|
+
# * You should have received a copy of the GNU General Public License
|
18
|
+
# * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
# *
|
20
|
+
# * All comments concerning this program package may be sent to the
|
21
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
22
|
+
# *
|
23
|
+
# **************************************************************************
|
24
|
+
"""
|
25
|
+
Some basic GUI widgets are implemented in this module.
|
26
|
+
The widgets here are suppose to be used to build more complex
|
27
|
+
elements.
|
28
|
+
"""
|
29
|
+
|
30
|
+
import tkinter as tk
|
31
|
+
import tkinter.ttk as ttk
|
32
|
+
|
33
|
+
from math import ceil
|
34
|
+
|
35
|
+
import pyworkflow
|
36
|
+
from pyworkflow import TK_GRAY_DEFAULT, Config
|
37
|
+
|
38
|
+
from . import gui
|
39
|
+
from .tooltip import ToolTip
|
40
|
+
|
41
|
+
|
42
|
+
class Button(tk.Button):
|
43
|
+
_images = {}
|
44
|
+
|
45
|
+
def __init__(self, master, text, imagePath=None, tooltip=None, **opts):
|
46
|
+
defaults = {}
|
47
|
+
# Used to be {'font': gui.fontButton} but there is no such
|
48
|
+
# definition in pyworkflow/gui.
|
49
|
+
defaults.update(opts)
|
50
|
+
|
51
|
+
if 'bg' in defaults and defaults['bg'] is None:
|
52
|
+
del defaults['bg']
|
53
|
+
|
54
|
+
if imagePath is not None:
|
55
|
+
btnImage = gui.getImage(imagePath)
|
56
|
+
else:
|
57
|
+
btnImage = None
|
58
|
+
|
59
|
+
if btnImage is not None:
|
60
|
+
if 'compound' not in defaults:
|
61
|
+
defaults['compound'] = tk.LEFT
|
62
|
+
tk.Button.__init__(self, master, text=text, image=btnImage, **defaults)
|
63
|
+
self.image = btnImage
|
64
|
+
else:
|
65
|
+
tk.Button.__init__(self, master, text=text, **defaults)
|
66
|
+
|
67
|
+
if tooltip:
|
68
|
+
ToolTip(self, tooltip, 500)
|
69
|
+
|
70
|
+
def setImage(self, imagePath):
|
71
|
+
self.image = gui.getImage(imagePath)
|
72
|
+
self.config(image=self.image)
|
73
|
+
|
74
|
+
|
75
|
+
class HotButton(Button):
|
76
|
+
""" Button having the firebrick color and some other settings. """
|
77
|
+
|
78
|
+
def __init__(self, master, text, imagePath=None, tooltip=None, **opts):
|
79
|
+
defaults = {'activebackground': gui.cfgButtonActiveBgColor, 'bg': gui.cfgButtonBgColor,
|
80
|
+
'fg': gui.cfgButtonFgColor, 'activeforeground': gui.cfgButtonActiveFgColor,
|
81
|
+
'compound': tk.LEFT}
|
82
|
+
defaults.update(opts)
|
83
|
+
Button.__init__(self, master, text, imagePath, tooltip, **defaults)
|
84
|
+
|
85
|
+
|
86
|
+
class IconButton(HotButton):
|
87
|
+
""" Hot button, but only with image and no border """
|
88
|
+
|
89
|
+
def __init__(self, master, text, imagePath, tooltip=None, **opts):
|
90
|
+
defaults = {'bd': 0, 'bg': Config.SCIPION_BG_COLOR, 'compound': tk.NONE}
|
91
|
+
defaults.update(opts)
|
92
|
+
HotButton.__init__(self, master, text, imagePath, tooltip, **defaults)
|
93
|
+
|
94
|
+
|
95
|
+
class AutoScrollbar(tk.Scrollbar):
|
96
|
+
""""A scrollbar that hides itself if it's not needed."""
|
97
|
+
|
98
|
+
def set(self, lo, hi):
|
99
|
+
if float(lo) <= 0.0 and float(hi) >= 1.0:
|
100
|
+
self.grid_remove()
|
101
|
+
# self.tk.call("grid", "remove", self)
|
102
|
+
else:
|
103
|
+
self.grid()
|
104
|
+
tk.Scrollbar.set(self, lo, hi)
|
105
|
+
|
106
|
+
|
107
|
+
class Scrollable(object):
|
108
|
+
"""This is a base class for all scrollable widgets.
|
109
|
+
If it is enabled, it will wrap the widget with a frame
|
110
|
+
and will add vertical and horizontal AutoScrollbar"""
|
111
|
+
|
112
|
+
def __init__(self, master, WidgetClass, frame=True, **opts):
|
113
|
+
if frame:
|
114
|
+
self.frame = tk.Frame(master)
|
115
|
+
self.frame.rowconfigure(0, weight=1)
|
116
|
+
self.frame.columnconfigure(0, weight=1)
|
117
|
+
self.vscroll = AutoScrollbar(self.frame)
|
118
|
+
self.vscroll.grid(row=0, column=1, sticky='ns')
|
119
|
+
self.hscroll = AutoScrollbar(self.frame, orient=tk.HORIZONTAL)
|
120
|
+
self.hscroll.grid(row=1, column=0, sticky='ew')
|
121
|
+
WidgetClass.__init__(self, self.frame,
|
122
|
+
yscrollcommand=self.vscroll.set,
|
123
|
+
xscrollcommand=self.hscroll.set, **opts)
|
124
|
+
self.vscroll.config(command=self.yview)
|
125
|
+
self.hscroll.config(command=self.xview)
|
126
|
+
self.grid(row=0, column=0, sticky='news')
|
127
|
+
self.grid = self.frame.grid
|
128
|
+
self.grid_remove = self.frame.grid_remove
|
129
|
+
else:
|
130
|
+
WidgetClass.__init__(self, master, **opts)
|
131
|
+
|
132
|
+
# Bind ourselves
|
133
|
+
self.bindWidget(self)
|
134
|
+
|
135
|
+
|
136
|
+
def scroll(self, event):
|
137
|
+
print("Deprecated, use scrollV")
|
138
|
+
self.scrollV(event)
|
139
|
+
|
140
|
+
def scrollV(self, event):
|
141
|
+
self._basescroll(event, self.yview)
|
142
|
+
|
143
|
+
def scrollH(self, event):
|
144
|
+
self._basescroll(event, self.xview)
|
145
|
+
|
146
|
+
def _basescroll(self, event, view):
|
147
|
+
|
148
|
+
if event.num == 5 or event.delta < 0:
|
149
|
+
count = 1
|
150
|
+
if event.num == 4 or event.delta > 0:
|
151
|
+
count = -1
|
152
|
+
view("scroll", count, "units")
|
153
|
+
|
154
|
+
def bindWidget(self, widget):
|
155
|
+
""" Make the scroll in the widget, respond to this.
|
156
|
+
Useful for child widgets.
|
157
|
+
"""
|
158
|
+
# with Windows OS
|
159
|
+
widget.bind("<MouseWheel>", self.scrollV)
|
160
|
+
# with Linux OS
|
161
|
+
widget.bind("<Button-4>", self.scrollV)
|
162
|
+
widget.bind("<Button-5>", self.scrollV)
|
163
|
+
|
164
|
+
widget.bind("<Control-Button-4>", self.scrollH)
|
165
|
+
widget.bind("<Control-Button-5>", self.scrollH)
|
166
|
+
|
167
|
+
|
168
|
+
def getVScroll(self):
|
169
|
+
return self.vscroll.get()
|
170
|
+
|
171
|
+
def getHScroll(self):
|
172
|
+
return self.hscroll.get()
|
173
|
+
|
174
|
+
|
175
|
+
class ExplanationText(tk.Text):
|
176
|
+
"""Create an explanation text box"""
|
177
|
+
|
178
|
+
def __init__(self, frame, text='', bg=TK_GRAY_DEFAULT, border=0, wrap='word'):
|
179
|
+
self.text = tk.Text(frame, bg=bg, wrap=wrap, border=border)
|
180
|
+
self.updateExpText(text)
|
181
|
+
|
182
|
+
def updateExpText(self, text, width=50):
|
183
|
+
# Adapt textbox height to text length (width is in characters)
|
184
|
+
n_lines = ceil(len(text) / width)
|
185
|
+
|
186
|
+
self.text.config(state='normal', height=n_lines) # Make it editable
|
187
|
+
self.text.insert(tk.END, text)
|
188
|
+
self.text.config(state='disabled') # Disable text edit
|
189
|
+
|
190
|
+
|
191
|
+
class LabelSlider(ttk.Frame):
|
192
|
+
""" Create a personalized frame that contains label, slider and label value
|
193
|
+
it also keeps a variable with the value """
|
194
|
+
|
195
|
+
def __init__(self, master, label, from_=0, to=100, value=50, callback=None, step=0.01, length=None,
|
196
|
+
labelWidth=None, tickinterval=None, showvalue=None):
|
197
|
+
self.selectedLabelText = '=> {}'.format(label)
|
198
|
+
self.labelText = ' {}'.format(label)
|
199
|
+
self.var = tk.DoubleVar()
|
200
|
+
self.var.set(float(value))
|
201
|
+
ttk.Frame.__init__(self, master)
|
202
|
+
self.labelWidget = ttk.Label(self, text=self.labelText, width=labelWidth)
|
203
|
+
self.slider = tk.Scale(self, from_=from_, to=to, variable=self.var,
|
204
|
+
bigincrement=step, resolution=step, orient=tk.HORIZONTAL, length=length,
|
205
|
+
tickinterval=tickinterval, showvalue=showvalue)
|
206
|
+
if callback:
|
207
|
+
self.var.trace('w', callback)
|
208
|
+
|
209
|
+
self.labelWidget.grid(row=0, column=0, sticky='nes', padx=5, pady=5)
|
210
|
+
self.slider.grid(row=0, column=1, sticky='news', padx=5, pady=5)
|
211
|
+
self.columnconfigure(1, weight=3)
|
212
|
+
|
213
|
+
def get(self):
|
214
|
+
return self.var.get()
|
215
|
+
|
216
|
+
def highlightLabel(self):
|
217
|
+
self.labelWidget.config(text=self.selectedLabelText)
|
218
|
+
|
219
|
+
def removeHighlightFromLabel(self):
|
220
|
+
self.labelWidget.config(text=self.labelText)
|
221
|
+
|
222
|
+
|
223
|
+
class ComboBox(ttk.Combobox):
|
224
|
+
""" Extension of ttk.ComboBox to allow having different display text and values.
|
225
|
+
Also adding some utils to getSelected index and value (same for set)
|
226
|
+
"""
|
227
|
+
|
228
|
+
def __init__(self, parent, choices, values=None, initial=None, onChange=None, **kwargs):
|
229
|
+
""" Create a combobox from a list of choices.
|
230
|
+
Params:
|
231
|
+
parent: the parent widget (required by Tkinter)
|
232
|
+
choices: a list with the options to be shown.
|
233
|
+
values: if None, will enumerate from 0 to len(choices)-1
|
234
|
+
if a list is provided, should have the same length as choices.
|
235
|
+
initial: if None, take the first choice
|
236
|
+
onChange: provide a callback function to be used when change the selected value
|
237
|
+
**kwargs: extra arguments passed to ttk.Combobox constructor.
|
238
|
+
"""
|
239
|
+
indexes = range(len(choices))
|
240
|
+
if values is None:
|
241
|
+
values = indexes
|
242
|
+
choices = [str(c) for c in choices] # Convert to a list of strings
|
243
|
+
|
244
|
+
if initial is None:
|
245
|
+
initial = choices[0]
|
246
|
+
self._valuesDict = dict(zip(choices, values))
|
247
|
+
self._indexDict = dict(zip(choices, indexes))
|
248
|
+
|
249
|
+
self._var = tk.StringVar()
|
250
|
+
self._var.set(initial)
|
251
|
+
self._changeCallback = onChange
|
252
|
+
self._var.trace('w', self._onChanged)
|
253
|
+
ttk.Combobox.__init__(self, parent, textvariable=self._var, state='readonly', **kwargs)
|
254
|
+
self['values'] = choices
|
255
|
+
|
256
|
+
def getValue(self):
|
257
|
+
""" Return the selected value. """
|
258
|
+
return self._valuesDict[self._var.get()]
|
259
|
+
|
260
|
+
def getIndex(self):
|
261
|
+
""" Return the selected value. """
|
262
|
+
return self._indexDict[self._var.get()]
|
263
|
+
|
264
|
+
def getText(self):
|
265
|
+
""" Return the selected option text. """
|
266
|
+
return self._var.get()
|
267
|
+
|
268
|
+
def setChangeCallback(self, callback):
|
269
|
+
self._changeCallback = callback
|
270
|
+
|
271
|
+
def _onChanged(self, *args):
|
272
|
+
if self._changeCallback:
|
273
|
+
self._changeCallback(self)
|
274
|
+
|
275
|
+
|
276
|
+
class GradientFrame(tk.Canvas):
|
277
|
+
"""A gradient frame which uses a canvas to draw the background
|
278
|
+
Taken from:
|
279
|
+
http://stackoverflow.com/questions/11892521/tkinter-custom-window
|
280
|
+
"""
|
281
|
+
|
282
|
+
def __init__(self, parent, **args):
|
283
|
+
tk.Canvas.__init__(self, parent, **args)
|
284
|
+
self._color1 = Config.SCIPION_BG_COLOR #"#d2a7a7"
|
285
|
+
self._color2 = Config.SCIPION_MAIN_COLOR #"#820808"
|
286
|
+
self.bind("<Configure>", self._draw_gradient)
|
287
|
+
|
288
|
+
def _draw_gradient(self, event=None):
|
289
|
+
self.delete("gradient")
|
290
|
+
width = self.winfo_width()
|
291
|
+
height = self.winfo_height()
|
292
|
+
limit = width // 2
|
293
|
+
r1, g1, b1 = self.winfo_rgb(self._color1)
|
294
|
+
r2, g2, b2 = self.winfo_rgb(self._color2)
|
295
|
+
r_ratio = (r2 - r1) / limit
|
296
|
+
g_ratio = (g2 - g1) / limit
|
297
|
+
b_ratio = (b2 - b1) / limit
|
298
|
+
|
299
|
+
for i in range(limit + 1):
|
300
|
+
nr = int(r1 + (r_ratio * i))
|
301
|
+
ng = int(g1 + (g_ratio * i))
|
302
|
+
nb = int(b1 + (b_ratio * i))
|
303
|
+
color = "#%4.4x%4.4x%4.4x" % (nr, ng, nb)
|
304
|
+
self.create_line(i, 0, i, height, tags=("gradient",), fill=color)
|
305
|
+
self.create_line(width - i, 0, width - i, height,
|
306
|
+
tags=("gradient",), fill=color)
|
307
|
+
self.lower("gradient")
|
@@ -0,0 +1,26 @@
|
|
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
|
+
from .mapper import Mapper
|
26
|
+
from .sqlite import SqliteMapper, SqliteDb, SqliteFlatMapper, SqliteFlatDb
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# **************************************************************************
|
2
|
+
# *
|
3
|
+
# * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
|
4
|
+
# *
|
5
|
+
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
|
6
|
+
# *
|
7
|
+
# * This program is free software; you can redistribute it and/or modify
|
8
|
+
# * it under the terms of the GNU General Public License as published by
|
9
|
+
# * the Free Software Foundation; either version 3 of the License, or
|
10
|
+
# * (at your option) any later version.
|
11
|
+
# *
|
12
|
+
# * This program is distributed in the hope that it will be useful,
|
13
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# * GNU General Public License for more details.
|
16
|
+
# *
|
17
|
+
# * You should have received a copy of the GNU General Public License
|
18
|
+
# * along with this program; if not, write to the Free Software
|
19
|
+
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
20
|
+
# * 02111-1307 USA
|
21
|
+
# *
|
22
|
+
# * All comments concerning this program package may be sent to the
|
23
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
24
|
+
# *
|
25
|
+
# **************************************************************************
|
26
|
+
import logging
|
27
|
+
logger = logging.getLogger(__name__)
|
28
|
+
|
29
|
+
import pyworkflow.object as obj
|
30
|
+
|
31
|
+
|
32
|
+
class Mapper:
|
33
|
+
"""This class will serves as a Data Mapper pattern.
|
34
|
+
It will store/retrieve objects from some storage environment.
|
35
|
+
(like SQL, XML or others)
|
36
|
+
The mapper should have access to class dictionary
|
37
|
+
in order to build any give class by name"""
|
38
|
+
|
39
|
+
# Just to avoid print several times the same warning
|
40
|
+
__warnings = set()
|
41
|
+
|
42
|
+
ORIGINAL_CLASS_NAME_ATTRIBUTE = "oldClassName"
|
43
|
+
|
44
|
+
@staticmethod
|
45
|
+
def annotateClassName(instance, oldClassName):
|
46
|
+
""" Annotate an object with the original class name"""
|
47
|
+
setattr(instance, Mapper.ORIGINAL_CLASS_NAME_ATTRIBUTE, oldClassName)
|
48
|
+
|
49
|
+
@staticmethod
|
50
|
+
def getObjectPersistingClassName(instance):
|
51
|
+
if hasattr(instance, Mapper.ORIGINAL_CLASS_NAME_ATTRIBUTE):
|
52
|
+
return getattr(instance, Mapper.ORIGINAL_CLASS_NAME_ATTRIBUTE)
|
53
|
+
else:
|
54
|
+
return instance.getClassName()
|
55
|
+
|
56
|
+
def __init__(self, dictClasses=None):
|
57
|
+
if dictClasses:
|
58
|
+
self.dictClasses = dictClasses
|
59
|
+
else:
|
60
|
+
self.dictClasses = dir(obj)
|
61
|
+
|
62
|
+
def warning(self, msg):
|
63
|
+
if msg not in self.__warnings:
|
64
|
+
print("WARNING: %s" % msg)
|
65
|
+
self.__warnings.add(msg)
|
66
|
+
|
67
|
+
def _buildObjectFromClass(self, className):
|
68
|
+
""" Build an instance of an object
|
69
|
+
given the class name, it should be in
|
70
|
+
the classes' dictionary.
|
71
|
+
|
72
|
+
If class is missing, then the default class is used ("LegacyProtocol") and warns about it
|
73
|
+
"""
|
74
|
+
if className not in self.dictClasses:
|
75
|
+
self.warning("Class '%s' not found in mapper dict. Ignored. "
|
76
|
+
% className)
|
77
|
+
return None
|
78
|
+
|
79
|
+
objClass = self.dictClasses[className]
|
80
|
+
|
81
|
+
if not issubclass(objClass, obj.Object):
|
82
|
+
logger.warning("WARNING: Class '%s' is not a subclass of Object. Ignored. " % className)
|
83
|
+
return None
|
84
|
+
|
85
|
+
try:
|
86
|
+
instance = self.dictClasses[className]()
|
87
|
+
except Exception as e:
|
88
|
+
clazz = self.dictClasses._default
|
89
|
+
logger.error('Class %s could not be created. Replacing it with %s ' % (className, clazz.__name__), exc_info=e)
|
90
|
+
instance = clazz()
|
91
|
+
|
92
|
+
# If it's the default class in the dictionary
|
93
|
+
if objClass.__name__ != className:
|
94
|
+
# ... annotate original class name
|
95
|
+
self.annotateClassName(instance, className)
|
96
|
+
|
97
|
+
return instance
|
98
|
+
|
99
|
+
def _getStrValue(self, value):
|
100
|
+
""" Return empty string if value is None or empty. """
|
101
|
+
if not value:
|
102
|
+
return ''
|
103
|
+
return value
|
104
|
+
|
105
|
+
def commit(self):
|
106
|
+
"""Commit changes made to the storage"""
|
107
|
+
pass
|
108
|
+
|
109
|
+
def insert(self, obj):
|
110
|
+
"""Insert a new object into the system, the id will be set"""
|
111
|
+
pass
|
112
|
+
|
113
|
+
def update(self, obj, direction='to'):
|
114
|
+
"""Update an existing object, the id should not be None
|
115
|
+
direction can be "to" or "from" which indicates the
|
116
|
+
priority of the update.
|
117
|
+
If "to" is used, object data is put in storage.
|
118
|
+
If "from", object data is retrieved from storage"""
|
119
|
+
if direction == 'to': # Update db with object changes
|
120
|
+
self.updateTo(obj)
|
121
|
+
elif direction == 'from': # Update object with db info
|
122
|
+
self.updateFrom(obj)
|
123
|
+
else:
|
124
|
+
raise Exception('Invalid option %s for Sqlite.updateObject' % direction)
|
125
|
+
|
126
|
+
def updateFrom(self, obj):
|
127
|
+
"""Update object data with storage info"""
|
128
|
+
pass
|
129
|
+
|
130
|
+
def updateTo(self, obj):
|
131
|
+
"""Update storage with object info"""
|
132
|
+
pass
|
133
|
+
|
134
|
+
def store(self, obj):
|
135
|
+
"""Stores an object, it can be inserted or updated"""
|
136
|
+
if obj._objId is None:
|
137
|
+
self.insert(obj)
|
138
|
+
else:
|
139
|
+
self.updateTo(obj)
|
140
|
+
|
141
|
+
def selectById(self, objId):
|
142
|
+
"""Return the object which id is objId"""
|
143
|
+
pass
|
144
|
+
|
145
|
+
def exists(self, objId):
|
146
|
+
"""Return True if the id is in the database"""
|
147
|
+
pass
|
148
|
+
|
149
|
+
def selectAll(self, iterate=False):
|
150
|
+
"""Return all object from storage"""
|
151
|
+
pass
|
152
|
+
|
153
|
+
def selectFirst(self):
|
154
|
+
"""Return only the first element"""
|
155
|
+
for obj in self.selectAll(iterate=True):
|
156
|
+
return obj
|
157
|
+
return None
|
158
|
+
|
159
|
+
def selectBy(self, **args):
|
160
|
+
"""Select object meetings some criteria"""
|
161
|
+
pass
|
162
|
+
|
163
|
+
def selectByClass(self, className, includeSubclasses=True):
|
164
|
+
"""Select all object of a give class.
|
165
|
+
By default object of subclasses will be retrieved also.
|
166
|
+
This behaviour can be changed by passing includeSubclass=False"""
|
167
|
+
return self.selectBy(classname=className)
|
168
|
+
|
169
|
+
def getParent(self, obj):
|
170
|
+
""" Retrieve the parent object of another. """
|
171
|
+
pass
|
172
|
+
|
173
|
+
def getFullName(self, obj):
|
174
|
+
""" Return the full object name such as:
|
175
|
+
grandparent.parent.objName
|
176
|
+
"""
|
177
|
+
nameParts = []
|
178
|
+
parent = obj
|
179
|
+
|
180
|
+
while parent:
|
181
|
+
nameParts.insert(0, parent.getLastName())
|
182
|
+
parent = self.getParent(parent)
|
183
|
+
|
184
|
+
return '.'.join(nameParts)
|
185
|
+
|
186
|
+
def insertRelation(self, relName, creatorObj, parentObj, childObj):
|
187
|
+
""" This function will add a new relation between two objects.
|
188
|
+
Params:
|
189
|
+
relName: the name of the relation to be added.
|
190
|
+
creatorObj: this object will be the one who register the relation.
|
191
|
+
parentObj: this is "parent" in the relation
|
192
|
+
childObj: this is "child" in the relation
|
193
|
+
"""
|
194
|
+
pass
|
195
|
+
|
196
|
+
def getRelationChilds(self, relName, parentObj):
|
197
|
+
""" Return all "child" objects for a given relation.
|
198
|
+
Params:
|
199
|
+
relName: the name of the relation.
|
200
|
+
parentObj: this is "parent" in the relation
|
201
|
+
Returns:
|
202
|
+
a list of "child" objects.
|
203
|
+
"""
|
204
|
+
pass
|
205
|
+
|
206
|
+
def getRelationParents(self, relName, childObj):
|
207
|
+
""" Return all "parent" objects for a given relation.
|
208
|
+
Params:
|
209
|
+
relName: the name of the relation.
|
210
|
+
childObj: this is "child" in the relation
|
211
|
+
Returns:
|
212
|
+
a list of "parent" objects.
|
213
|
+
"""
|
214
|
+
pass
|
215
|
+
|
216
|
+
def getRelationsByCreator(self, creatorObj):
|
217
|
+
""" Return all relations created by creatorObj. """
|
218
|
+
pass
|
219
|
+
|
220
|
+
def deleteRelations(self, creatorObj):
|
221
|
+
""" Delete all relations created by object creatorObj """
|
222
|
+
pass
|