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.
Files changed (140) hide show
  1. pyworkflow/__init__.py +33 -0
  2. pyworkflow/apps/__init__.py +29 -0
  3. pyworkflow/apps/pw_manager.py +37 -0
  4. pyworkflow/apps/pw_plot.py +51 -0
  5. pyworkflow/apps/pw_project.py +113 -0
  6. pyworkflow/apps/pw_protocol_list.py +143 -0
  7. pyworkflow/apps/pw_protocol_run.py +51 -0
  8. pyworkflow/apps/pw_run_tests.py +267 -0
  9. pyworkflow/apps/pw_schedule_run.py +322 -0
  10. pyworkflow/apps/pw_sleep.py +37 -0
  11. pyworkflow/apps/pw_sync_data.py +439 -0
  12. pyworkflow/apps/pw_viewer.py +78 -0
  13. pyworkflow/config.py +536 -0
  14. pyworkflow/constants.py +212 -0
  15. pyworkflow/exceptions.py +18 -0
  16. pyworkflow/gui/__init__.py +36 -0
  17. pyworkflow/gui/browser.py +726 -0
  18. pyworkflow/gui/canvas.py +1190 -0
  19. pyworkflow/gui/dialog.py +976 -0
  20. pyworkflow/gui/form.py +2627 -0
  21. pyworkflow/gui/graph.py +247 -0
  22. pyworkflow/gui/graph_layout.py +271 -0
  23. pyworkflow/gui/gui.py +566 -0
  24. pyworkflow/gui/matplotlib_image.py +233 -0
  25. pyworkflow/gui/plotter.py +247 -0
  26. pyworkflow/gui/project/__init__.py +25 -0
  27. pyworkflow/gui/project/base.py +192 -0
  28. pyworkflow/gui/project/constants.py +139 -0
  29. pyworkflow/gui/project/labels.py +205 -0
  30. pyworkflow/gui/project/project.py +484 -0
  31. pyworkflow/gui/project/searchprotocol.py +154 -0
  32. pyworkflow/gui/project/searchrun.py +181 -0
  33. pyworkflow/gui/project/steps.py +166 -0
  34. pyworkflow/gui/project/utils.py +332 -0
  35. pyworkflow/gui/project/variables.py +179 -0
  36. pyworkflow/gui/project/viewdata.py +472 -0
  37. pyworkflow/gui/project/viewprojects.py +510 -0
  38. pyworkflow/gui/project/viewprotocols.py +2093 -0
  39. pyworkflow/gui/project/viewprotocols_extra.py +560 -0
  40. pyworkflow/gui/text.py +771 -0
  41. pyworkflow/gui/tooltip.py +185 -0
  42. pyworkflow/gui/tree.py +684 -0
  43. pyworkflow/gui/widgets.py +307 -0
  44. pyworkflow/mapper/__init__.py +26 -0
  45. pyworkflow/mapper/mapper.py +222 -0
  46. pyworkflow/mapper/sqlite.py +1578 -0
  47. pyworkflow/mapper/sqlite_db.py +145 -0
  48. pyworkflow/object.py +1512 -0
  49. pyworkflow/plugin.py +712 -0
  50. pyworkflow/project/__init__.py +31 -0
  51. pyworkflow/project/config.py +451 -0
  52. pyworkflow/project/manager.py +179 -0
  53. pyworkflow/project/project.py +1990 -0
  54. pyworkflow/project/scripts/clean_projects.py +77 -0
  55. pyworkflow/project/scripts/config.py +92 -0
  56. pyworkflow/project/scripts/create.py +77 -0
  57. pyworkflow/project/scripts/edit_workflow.py +90 -0
  58. pyworkflow/project/scripts/fix_links.py +39 -0
  59. pyworkflow/project/scripts/load.py +87 -0
  60. pyworkflow/project/scripts/refresh.py +83 -0
  61. pyworkflow/project/scripts/schedule.py +111 -0
  62. pyworkflow/project/scripts/stack2volume.py +41 -0
  63. pyworkflow/project/scripts/stop.py +81 -0
  64. pyworkflow/protocol/__init__.py +38 -0
  65. pyworkflow/protocol/bibtex.py +48 -0
  66. pyworkflow/protocol/constants.py +86 -0
  67. pyworkflow/protocol/executor.py +334 -0
  68. pyworkflow/protocol/hosts.py +313 -0
  69. pyworkflow/protocol/launch.py +270 -0
  70. pyworkflow/protocol/package.py +42 -0
  71. pyworkflow/protocol/params.py +744 -0
  72. pyworkflow/protocol/protocol.py +2554 -0
  73. pyworkflow/resources/Imagej.png +0 -0
  74. pyworkflow/resources/chimera.png +0 -0
  75. pyworkflow/resources/fa-exclamation-triangle_alert.png +0 -0
  76. pyworkflow/resources/fa-info-circle_alert.png +0 -0
  77. pyworkflow/resources/fa-search.png +0 -0
  78. pyworkflow/resources/fa-times-circle_alert.png +0 -0
  79. pyworkflow/resources/file_vol.png +0 -0
  80. pyworkflow/resources/loading.gif +0 -0
  81. pyworkflow/resources/no-image128.png +0 -0
  82. pyworkflow/resources/scipion_bn.png +0 -0
  83. pyworkflow/resources/scipion_icon.png +0 -0
  84. pyworkflow/resources/scipion_icon.svg +397 -0
  85. pyworkflow/resources/scipion_icon_proj.png +0 -0
  86. pyworkflow/resources/scipion_icon_projs.png +0 -0
  87. pyworkflow/resources/scipion_icon_prot.png +0 -0
  88. pyworkflow/resources/scipion_logo.png +0 -0
  89. pyworkflow/resources/scipion_logo_normal.png +0 -0
  90. pyworkflow/resources/scipion_logo_small.png +0 -0
  91. pyworkflow/resources/sprites.png +0 -0
  92. pyworkflow/resources/sprites.xcf +0 -0
  93. pyworkflow/resources/wait.gif +0 -0
  94. pyworkflow/template.py +322 -0
  95. pyworkflow/tests/__init__.py +29 -0
  96. pyworkflow/tests/test_utils.py +25 -0
  97. pyworkflow/tests/tests.py +341 -0
  98. pyworkflow/utils/__init__.py +38 -0
  99. pyworkflow/utils/dataset.py +414 -0
  100. pyworkflow/utils/echo.py +104 -0
  101. pyworkflow/utils/graph.py +196 -0
  102. pyworkflow/utils/log.py +284 -0
  103. pyworkflow/utils/path.py +527 -0
  104. pyworkflow/utils/process.py +132 -0
  105. pyworkflow/utils/profiler.py +92 -0
  106. pyworkflow/utils/progressbar.py +154 -0
  107. pyworkflow/utils/properties.py +627 -0
  108. pyworkflow/utils/reflection.py +129 -0
  109. pyworkflow/utils/utils.py +877 -0
  110. pyworkflow/utils/which.py +229 -0
  111. pyworkflow/viewer.py +328 -0
  112. pyworkflow/webservices/__init__.py +8 -0
  113. pyworkflow/webservices/config.py +11 -0
  114. pyworkflow/webservices/notifier.py +162 -0
  115. pyworkflow/webservices/repository.py +59 -0
  116. pyworkflow/webservices/workflowhub.py +74 -0
  117. pyworkflow/wizard.py +64 -0
  118. pyworkflowtests/__init__.py +51 -0
  119. pyworkflowtests/bibtex.py +51 -0
  120. pyworkflowtests/objects.py +830 -0
  121. pyworkflowtests/protocols.py +154 -0
  122. pyworkflowtests/tests/__init__.py +0 -0
  123. pyworkflowtests/tests/test_canvas.py +72 -0
  124. pyworkflowtests/tests/test_domain.py +45 -0
  125. pyworkflowtests/tests/test_logs.py +74 -0
  126. pyworkflowtests/tests/test_mappers.py +392 -0
  127. pyworkflowtests/tests/test_object.py +507 -0
  128. pyworkflowtests/tests/test_project.py +42 -0
  129. pyworkflowtests/tests/test_protocol_execution.py +72 -0
  130. pyworkflowtests/tests/test_protocol_export.py +78 -0
  131. pyworkflowtests/tests/test_protocol_output.py +158 -0
  132. pyworkflowtests/tests/test_streaming.py +47 -0
  133. pyworkflowtests/tests/test_utils.py +210 -0
  134. scipion_pyworkflow-3.7.0.dist-info/LICENSE.txt +674 -0
  135. scipion_pyworkflow-3.7.0.dist-info/METADATA +107 -0
  136. scipion_pyworkflow-3.7.0.dist-info/RECORD +140 -0
  137. scipion_pyworkflow-3.7.0.dist-info/WHEEL +5 -0
  138. scipion_pyworkflow-3.7.0.dist-info/dependency_links.txt +1 -0
  139. scipion_pyworkflow-3.7.0.dist-info/entry_points.txt +5 -0
  140. scipion_pyworkflow-3.7.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,744 @@
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
+
26
+ import re
27
+ import collections
28
+
29
+ from pyworkflow.object import *
30
+ from .constants import *
31
+
32
+
33
+ class FormElement(Object):
34
+ """Base for any element on the form"""
35
+ ATTRIBUTES = ['label', 'expertLevel', 'condition', 'important', 'help',
36
+ 'default', 'paramClass']
37
+
38
+ def __init__(self, **args):
39
+ super().__init__(**args)
40
+ self.label = String(args.get('label', None))
41
+ self.expertLevel = Integer(args.get('expertLevel', LEVEL_NORMAL))
42
+ self.condition = String(args.get('condition', None))
43
+ self._isImportant = Boolean(args.get('important', False))
44
+ self.help = String(args.get('help', None))
45
+ # This two list will be filled by the Form
46
+ # which have a global-view of all parameters
47
+ # All param names in which condition appears this param
48
+ self._dependants = []
49
+ # All param names that appears in the condition
50
+ self._conditionParams = []
51
+
52
+ def isExpert(self):
53
+ return self.expertLevel > LEVEL_NORMAL
54
+
55
+ def setExpert(self):
56
+ self.expertLevel.set(LEVEL_ADVANCED)
57
+
58
+ def isImportant(self):
59
+ return self._isImportant.get()
60
+
61
+ def setImportant(self, value):
62
+ self._isImportant.set(value)
63
+
64
+ def hasCondition(self):
65
+ return self.condition.hasValue()
66
+
67
+ def getLabel(self):
68
+ return self.label.get()
69
+
70
+ def getHelp(self):
71
+ return self.help.get()
72
+
73
+ def config(self, **kwargs):
74
+ """ Configure the object and set attributes
75
+ coming in the keyword-arguments, the
76
+ same as in the __init__
77
+ """
78
+ for key in self.ATTRIBUTES:
79
+ if key in kwargs:
80
+ self.setAttributeValue(key, kwargs.get(key))
81
+
82
+
83
+ class Param(FormElement):
84
+ """Definition of a protocol parameter"""
85
+ def __init__(self, **args):
86
+ FormElement.__init__(self, **args)
87
+ # This should be defined in subclasses
88
+ self.paramClass = args.get('paramClass', None)
89
+ self.default = String(args.get('default', None))
90
+
91
+ # Allow pointers (used for scalars)
92
+ self.allowsPointers = args.get('allowsPointers', False)
93
+ self.validators = args.get('validators', [])
94
+ self.readOnly = args.get("readOnly", False)
95
+
96
+ def __str__(self):
97
+ return " label: %s" % self.label.get()
98
+
99
+ def addValidator(self, validator):
100
+ """ Validators should be callables that
101
+ receive a value and return a list of errors if so.
102
+ If everything is ok, the result should be an empty list.
103
+ """
104
+ self.validators.append(validator)
105
+
106
+ def validate(self, value):
107
+ errors = []
108
+ for val in self.validators:
109
+ errors += val(value)
110
+ return errors
111
+
112
+ def getDefault(self):
113
+ return self.default.get()
114
+
115
+ def setDefault(self, newDefault):
116
+ self.default.set(newDefault)
117
+
118
+
119
+ class ElementGroup(FormElement):
120
+ """ Class to group some params in the form.
121
+ Such as: Labeled group or params in the same line.
122
+ """
123
+ def __init__(self, form=None, **args):
124
+ FormElement.__init__(self, **args)
125
+ self._form = form
126
+ self._paramList = []
127
+
128
+ def iterParams(self):
129
+ """ Return key and param for every child param. """
130
+ for name in self._paramList:
131
+ yield name, self._form.getParam(name)
132
+
133
+ def addParam(self, paramName, ParamClass, **kwargs):
134
+ """Add a new param to the group"""
135
+ param = ParamClass(**kwargs)
136
+ self._paramList.append(paramName)
137
+ self._form.registerParam(paramName, param)
138
+ return param
139
+
140
+ def addHidden(self, paramName, ParamClass, **kwargs):
141
+ """Add a hidden parameter to be used in conditions. """
142
+ kwargs.update({'label': '', 'condition': 'False'})
143
+ self.addParam(paramName, ParamClass, **kwargs)
144
+
145
+ def addLine(self, lineName, **kwargs):
146
+
147
+ labelName = lineName
148
+ for symbol in ' ()':
149
+ labelName = labelName.replace(symbol, '_')
150
+
151
+ return self.addParam(labelName, Line, form=self._form,
152
+ label=lineName, **kwargs)
153
+
154
+
155
+ # ----------- Some type of ElementGroup --------------------------
156
+
157
+ class Line(ElementGroup):
158
+ """ Group to put some parameters in the same line. """
159
+ pass
160
+
161
+
162
+ class Group(ElementGroup):
163
+ """ Group some parameters with a labeled frame. """
164
+ pass
165
+
166
+
167
+ class Section(ElementGroup):
168
+ """Definition of a section to hold other params"""
169
+ def __init__(self, form, **args):
170
+ ElementGroup.__init__(self, form, **args)
171
+ self.questionParam = String(args.get('questionParam', ''))
172
+
173
+ def hasQuestion(self):
174
+ """Return True if a question param was set"""
175
+ return self.questionParam.get() in self._paramList
176
+
177
+ def getQuestionName(self):
178
+ """ Return the name of the question param. """
179
+ return self.questionParam.get()
180
+
181
+ def getQuestion(self):
182
+ """ Return the question param"""
183
+ return self._form.getParam(self.questionParam.get())
184
+
185
+ def addGroup(self, groupName, **kwargs):
186
+ labelName = groupName
187
+ for symbol in ' ()':
188
+ labelName = labelName.replace(symbol, '_')
189
+
190
+ return self.addParam(labelName, Group, form=self._form,
191
+ label=groupName, **kwargs)
192
+
193
+
194
+ class Form(object):
195
+ """Store all sections and parameters"""
196
+ def __init__(self, protocol):
197
+ """ Build a Form from a given protocol. """
198
+ object.__init__(self)
199
+ self._sectionList = [] # Store list of sections
200
+ # Dictionary to store all params, grouped by sections
201
+ self._paramsDict = collections.OrderedDict()
202
+ self._lastSection = None
203
+ self._protocol = protocol
204
+ self.addGeneralSection()
205
+
206
+ def getClass(self):
207
+ return type(self)
208
+
209
+ def addSection(self, label='', **kwargs):
210
+ """Add a new section"""
211
+ self.lastSection = Section(self, label=label, **kwargs)
212
+ self._sectionList.append(self.lastSection)
213
+ return self.lastSection
214
+
215
+ def getSection(self, label):
216
+ """ get section by label from _sectionList"""
217
+ for s in self._sectionList:
218
+ if s.label == label:
219
+ return s
220
+ return
221
+
222
+ def addGroup(self, *args, **kwargs):
223
+ return self.lastSection.addGroup(*args, **kwargs)
224
+
225
+ def addLine(self, *args, **kwargs):
226
+ return self.lastSection.addLine(*args, **kwargs)
227
+
228
+ def registerParam(self, paramName, param):
229
+ """ Register a given param in the form. """
230
+ self._paramsDict[paramName] = param
231
+ self._analizeCondition(paramName, param)
232
+
233
+ def addParam(self, *args, **kwargs):
234
+ """Add a new param to last section"""
235
+ return self.lastSection.addParam(*args, **kwargs)
236
+
237
+ # Adhoc method for specific params
238
+ def addBooleanParam(self, name, label, help, default=True, **kwargs):
239
+ return self.addParam(name, BooleanParam, label=label, help=help, default=default, **kwargs)
240
+
241
+ def addHidden(self, *args, **kwargs):
242
+ return self.lastSection.addHidden(*args, **kwargs)
243
+
244
+ def _analizeCondition(self, paramName, param):
245
+ if param.hasCondition():
246
+ param._conditionParams = []
247
+ tokens = re.split(r'\W+', param.condition.get())
248
+ for t in tokens:
249
+ if self.hasParam(t):
250
+ self.getParam(t)._dependants.append(paramName)
251
+ param._conditionParams.append(t)
252
+ if self._protocol.hasAttribute(t):
253
+ param._conditionParams.append(t)
254
+
255
+ def evalParamCondition(self, paramName):
256
+ """Evaluate if a condition is True for a give param
257
+ with the values of a particular Protocol"""
258
+ param = self.getParam(paramName)
259
+
260
+ if not param.hasCondition():
261
+ return True
262
+ condStr = param.condition.get()
263
+ localDict = {}
264
+ globalDict = dict(globals())
265
+ # FIXME: Check why this import is here
266
+ from pyworkflow import Config
267
+ globalDict.update(Config.getDomain().getObjects())
268
+
269
+ for t in param._conditionParams:
270
+ if self.hasParam(t) or self._protocol.hasAttribute(t):
271
+ localDict[t] = self._protocol.getAttributeValue(t)
272
+
273
+ return eval(condStr, globalDict, localDict)
274
+
275
+ def validateParams(self, protocol):
276
+ """ Check that all validations of the params in the form
277
+ are met for the protocol param values.
278
+ It will return a list with errors, just in the same
279
+ way of the Protocol.validate function
280
+ """
281
+ errors = []
282
+
283
+ for name, param in self.iterParams():
284
+ value = protocol.getAttributeValue(name)
285
+ errors += param.validate(value)
286
+
287
+ return errors
288
+
289
+ def getParam(self, paramName):
290
+ """Retrieve a param given a the param name
291
+ None is returned if not found
292
+ """
293
+ return self._paramsDict.get(paramName, None)
294
+
295
+ def hasParam(self, paramName):
296
+ return paramName in self._paramsDict
297
+
298
+ def __str__(self):
299
+ s = "Form: \n"
300
+ for section in self.iterSections():
301
+ s += str(section)
302
+ return s
303
+
304
+ def iterSections(self):
305
+ return self._sectionList
306
+
307
+ def iterAllParams(self):
308
+ """ Iter all parameters, including ElementGroups. """
309
+ return self._paramsDict.items()
310
+
311
+ def iterParams(self):
312
+ """ Iter parameters disregarding the ElementGroups. """
313
+ for k, v in self._paramsDict.items():
314
+ if not isinstance(v, ElementGroup):
315
+ yield k, v
316
+
317
+ def iterPointerParams(self):
318
+ for paramName, param in self._paramsDict.items():
319
+ if isinstance(param, PointerParam):
320
+ yield paramName, param
321
+
322
+ def addGeneralSection(self):
323
+ self.addSection(label='General')
324
+ self.addParam('runName', StringParam, label="Run name:", important=True,
325
+ help='Select run name label to identify this run.')
326
+ self.addParam('runMode', EnumParam, choices=['resume', 'restart'],
327
+ label="Run mode", display=EnumParam.DISPLAY_COMBO, default=0,
328
+ help='The <resume> mode will try to start the execution'
329
+ 'from the last successfully finished step if possible.'
330
+ 'On the contrary, <restart> will delete all previous results'
331
+ 'of this particular run and start from the beginning. This option'
332
+ 'should be used carefully.'
333
+ )
334
+
335
+ def addParallelSection(self, threads=1, mpi=8, condition="",
336
+ hours=72, jobsize=0):
337
+
338
+ """ Adds the parallelization section to the form
339
+ pass threads=0 to disable threads parameter and mpi=0 to disable mpi params
340
+
341
+ :param threads: default value for of threads, defaults to 1
342
+ :param mpi: default value for mpi, defaults to 8"""
343
+
344
+ self.addSection(label='Parallelization')
345
+ self.addParam('hostName', StringParam, default="localhost",
346
+ label='Execution host',
347
+ help='Select in which of the available do you want to launch this protocol.')
348
+ if threads > 0:
349
+ self.addParam('numberOfThreads', IntParam, default=threads,
350
+ label='Threads',
351
+ help='This option provides shared-memory parallelization on multi-core machines.'
352
+ 'It does not require any additional software.')
353
+ if mpi > 0:
354
+ self.addParam('numberOfMpi', IntParam, default=mpi,
355
+ label='MPI processes',
356
+ help='This option provides the number of independent processes spawned'
357
+ 'in parallel by <mpirun> command in a cluster, usually through'
358
+ 'a queue system. This will require that you have compile this software '
359
+ 'with <mpi> support.')
360
+ if jobsize > 0:
361
+ self.addParam('mpiJobSize', IntParam, default=jobsize,
362
+ label='MPI job size', condition="numberOfMpi>1",
363
+ help='Minimum size of jobs in mpi processes.'
364
+ 'Set to 1 for large images (e.g. 500x500)'
365
+ 'and to 10 for small images (e.g. 100x100)')
366
+
367
+
368
+ class StringParam(Param):
369
+ """Param with underlying String value"""
370
+ def __init__(self, **args):
371
+ Param.__init__(self, paramClass=String, **args)
372
+
373
+
374
+ class TextParam(StringParam):
375
+ """Long string params"""
376
+ def __init__(self, **args):
377
+ StringParam.__init__(self, **args)
378
+ self.height = args.get('height', 5)
379
+ self.width = args.get('width', 30)
380
+
381
+
382
+ class RegexParam(StringParam):
383
+ """Regex based string param"""
384
+ pass
385
+
386
+
387
+ class PathParam(StringParam):
388
+ """Param for path strings"""
389
+ pass
390
+
391
+
392
+ # TODO: Handle filter pattern
393
+ class FileParam(PathParam):
394
+ """Filename path"""
395
+ pass
396
+
397
+
398
+ class FolderParam(PathParam):
399
+ """Folder path"""
400
+ pass
401
+
402
+
403
+ class LabelParam(StringParam):
404
+ """ Just the same as StringParam, but to be rendered
405
+ as a label and can not be directly edited by the user
406
+ in the Protocol Form.
407
+ """
408
+ pass
409
+
410
+
411
+ class IntParam(Param):
412
+ def __init__(self, **args):
413
+ Param.__init__(self, paramClass=Integer, **args)
414
+ self.addValidator(Format(int, error="should be an integer",
415
+ allowsNull=args.get('allowsNull', False)))
416
+
417
+
418
+ class EnumParam(IntParam):
419
+ """Select from a list of values, separated by comma"""
420
+ # Possible values for display
421
+ DISPLAY_LIST = 0
422
+ DISPLAY_COMBO = 1
423
+ DISPLAY_HLIST = 2 # horizontal list, save space
424
+
425
+ def __init__(self, **args):
426
+ IntParam.__init__(self, **args)
427
+ self.choices = args.get('choices', [])
428
+ self.display = Integer(args.get('display', EnumParam.DISPLAY_COMBO))
429
+
430
+
431
+ class FloatParam(Param):
432
+ def __init__(self, **args):
433
+ Param.__init__(self, paramClass=Float, **args)
434
+ self.addValidator(Format(float, error="should be a float",
435
+ allowsNull=args.get('allowsNull', False)))
436
+
437
+
438
+ class BooleanParam(Param):
439
+ """ Param to store boolean values. By default it will be displayed as 2 radio buttons with Yes/no labels.
440
+ Alternatively, if you pass checkbox it will be displayed as a checkbox.
441
+
442
+ :param display: (Optional) default DISPLAY_YES_NO. (Yes /no)
443
+ Alternatively use BooleanParam.DISPLAY_CHECKBOX to use checkboxes """
444
+ DISPLAY_YES_NO = 1
445
+ DISPLAY_CHECKBOX = 2
446
+
447
+ def __init__(self, display=DISPLAY_YES_NO, **args):
448
+ Param.__init__(self, paramClass=Boolean, **args)
449
+ self.display = display
450
+ self.addValidator(NonEmptyBool)
451
+
452
+
453
+ class HiddenBooleanParam(BooleanParam):
454
+ def __init__(self, **args):
455
+ Param.__init__(self, paramClass=Boolean, **args)
456
+
457
+
458
+ class PointerParam(Param):
459
+ """ This type of Param will serve to select existing objects
460
+ in the database that will be input for some protocol.
461
+ """
462
+ def __init__(self, paramClass=Pointer, **args):
463
+ Param.__init__(self, paramClass=paramClass, **args)
464
+ # This will be the class to be pointed
465
+ self.setPointerClass(args['pointerClass'])
466
+ # Some conditions on the pointed candidates
467
+ self.pointerCondition = String(args.get('pointerCondition', None))
468
+ self.allowsNull = Boolean(args.get('allowsNull', False))
469
+
470
+ def setPointerClass(self, newPointerClass):
471
+
472
+ # Tolerate passing classes instead of their names
473
+ if isinstance(newPointerClass, list):
474
+ self.pointerClass = CsvList()
475
+ self.pointerClass.set(",". join([clazz.__name__ for clazz in newPointerClass]))
476
+
477
+ elif(isinstance(newPointerClass, str)):
478
+ if ',' in newPointerClass:
479
+ self.pointerClass = CsvList()
480
+ self.pointerClass.set(newPointerClass)
481
+ else:
482
+ self.pointerClass = String(newPointerClass)
483
+
484
+ # Single class item, not the string
485
+ else:
486
+ self.pointerClass = String(newPointerClass.__name__)
487
+
488
+
489
+ class MultiPointerParam(PointerParam):
490
+ """ This type of Param will serve to select objects
491
+ with DIFFERENT types from the database to be input for some protocol.
492
+ """
493
+ def __init__(self, **args):
494
+ PointerParam.__init__(self, paramClass=PointerList, **args)
495
+ self.maxNumObjects = Integer(args.get('maxNumObjects', 100))
496
+ self.minNumObjects = Integer(args.get('minNumObjects', 2))
497
+
498
+
499
+ class RelationParam(Param):
500
+ """ This type of Param is very similar to PointerParam, since it will
501
+ hold a pointer to another object. But, in the PointerParam, we search
502
+ for objects of some Class (maybe with some conditions).
503
+ Here, we search for objects related to a given attribute of a protocol
504
+ by a given relation.
505
+ """
506
+ def __init__(self, **args):
507
+ Param.__init__(self, paramClass=Pointer, **args)
508
+ # This will be the name of the relation
509
+ self._relationName = String(args.get('relationName'))
510
+ # We will store the attribute name in the protocol to be
511
+ # used as the object for which relations will be search
512
+ self._attributeName = String(args.get('attributeName'))
513
+ # This specify if we want to search for childs or parents
514
+ # of the given attribute of the protocol
515
+ self._direction = Integer(args.get('direction', RELATION_CHILDS))
516
+ self.allowsNull = Boolean(args.get('allowsNull', False))
517
+
518
+ def getName(self):
519
+ return self._relationName.get()
520
+
521
+ def getAttributeName(self):
522
+ return self._attributeName.get()
523
+
524
+ def getDirection(self):
525
+ return self._direction.get()
526
+
527
+
528
+ class ProtocolClassParam(StringParam):
529
+ def __init__(self, **args):
530
+ StringParam.__init__(self, **args)
531
+ self.protocolClassName = String(args.get('protocolClassName'))
532
+ self.allowSubclasses = Boolean(args.get('allowSubclasses', False))
533
+
534
+
535
+ class DigFreqParam(FloatParam):
536
+ """ Digital frequency param. """
537
+ def __init__(self, **args):
538
+ FloatParam.__init__(self, **args)
539
+ self.addValidator(FreqValidator)
540
+
541
+
542
+ class NumericListParam(StringParam):
543
+ """ This class will serve to have list representations as strings.
544
+ Possible notation are:
545
+ 1000 10 1 1 -> to define a list with 4 values [1000, 10, 1, 1], or
546
+ 10x2 5x3 -> to define a list with 5 values [10, 10, 5, 5, 5]
547
+ If you ask for more elements than in the list, the last one is repeated
548
+ """
549
+ def __init__(self, **args):
550
+ StringParam.__init__(self, **args)
551
+ self.addValidator(NumericListValidator())
552
+
553
+
554
+ class NumericRangeParam(StringParam):
555
+ """ This class will serve to specify range of numbers with a string representation.
556
+ Possible notation are::
557
+
558
+ "1,5-8,10" -> [1,5,6,7,8,10]
559
+ "2,6,9-11" -> [2,6,9,10,11]
560
+ "2 5, 6-8" -> [2,5,6,7,8]
561
+
562
+ """
563
+ def __init__(self, **args):
564
+ StringParam.__init__(self, **args)
565
+ self.addValidator(NumericRangeValidator())
566
+
567
+
568
+ class TupleParam(Param):
569
+ """ This class will condense a tuple of several
570
+ other params of the same type and related concept.
571
+ For example: min and max, low and high.
572
+ """
573
+ def __init__(self, **args):
574
+ Param.__init__(self, **args)
575
+
576
+
577
+ class DeprecatedParam:
578
+ """ Deprecated param. To be used when you want to rename an existing param
579
+ and still be able to recover old param value. It acts like a redirector, passing the
580
+ value received when its value is set to the new renamed parameter
581
+
582
+ usage: In defineParams method, before the renamed param definition line add the following:
583
+
584
+ self.oldName = DeprecatedParam("newName", self)
585
+ form.addParam('newName', ...)
586
+
587
+ """
588
+ def __init__(self, newParamName, prot):
589
+ """
590
+
591
+ :param newParamName: Name of the renamed param
592
+ :param prot: Protocol hosting this and the renamed param
593
+
594
+ """
595
+ self._newParamName = newParamName
596
+ self.prot = prot
597
+ # Need to fake being a Object at loading time
598
+ self._objId = None
599
+ self._objIsPointer = False
600
+
601
+ def set(self, value, cleanExtended=False):
602
+ if hasattr(self.prot, self._newParamName):
603
+ newParam = self._getNewParam()
604
+ if newParam.isPointer():
605
+ newParam.set(value, cleanExtended)
606
+ self._extended = newParam._extended
607
+ else:
608
+ newParam.set(value)
609
+
610
+ def isPointer(self):
611
+ return self._getNewParam().isPointer()
612
+
613
+ def getObjValue(self):
614
+ return None
615
+
616
+ def _getNewParam(self):
617
+ return getattr(self.prot, self._newParamName)
618
+
619
+ # -----------------------------------------------------------------------------
620
+ # Validators
621
+ # -----------------------------------------------------------------------------
622
+ class Validator(object):
623
+ pass
624
+
625
+
626
+ class Conditional(Validator):
627
+ """ Simple validation based on a condition.
628
+ If the value doesn't meet the condition,
629
+ the error will be returned.
630
+ """
631
+ def __init__(self, error, allowsNull=False):
632
+ self.error = error
633
+ self._allowsNull = allowsNull
634
+
635
+ def __call__(self, value):
636
+ errors = []
637
+ if value is not None or not self._allowsNull:
638
+ if not self._condition(value):
639
+ errors.append(self.error)
640
+ return errors
641
+
642
+
643
+ class Format(Conditional):
644
+ """ Check if the format is right. """
645
+ def __init__(self, valueType, error='Value have not a correct format',
646
+ allowsNull=False):
647
+ Conditional.__init__(self, error, allowsNull)
648
+ self.valueType = valueType
649
+
650
+ def _condition(self, value):
651
+ try:
652
+ self.valueType(value)
653
+ return True
654
+ except Exception:
655
+ return False
656
+
657
+
658
+ class NonEmptyCondition(Conditional):
659
+ def __init__(self, error='Value cannot be empty'):
660
+ Conditional.__init__(self, error)
661
+ self._condition = lambda value: len(value) > 0
662
+
663
+
664
+ class LT(Conditional):
665
+ def __init__(self, threshold,
666
+ error='Value should be less than the threshold'):
667
+ Conditional.__init__(self, error)
668
+ self._condition = lambda value: value < threshold
669
+
670
+
671
+ class LE(Conditional):
672
+ def __init__(self, threshold,
673
+ error='Value should be less or equal than the threshold'):
674
+ Conditional.__init__(self, error)
675
+ self._condition = lambda value: value <= threshold
676
+
677
+
678
+ class GT(Conditional):
679
+ def __init__(self, threshold,
680
+ error='Value should be greater than the threshold'):
681
+ Conditional.__init__(self, error)
682
+ self._condition = lambda value: value > threshold
683
+
684
+
685
+ class GE(Conditional):
686
+ def __init__(self, thresold, error='Value should be greater or equal than the threshold'):
687
+ Conditional.__init__(self, error)
688
+ self._condition = lambda value: value is not None and value >= thresold
689
+
690
+
691
+ class Range(Conditional):
692
+ def __init__(self, minValue, maxValue, error='Value is outside range'):
693
+ Conditional.__init__(self, error)
694
+ self._condition = lambda value: minValue <= value <= maxValue
695
+
696
+
697
+ class NumericListValidator(Conditional):
698
+ """ Validator for ListParam. See ListParam. """
699
+ def __init__(self, error='Incorrect format for numeric list param'):
700
+ Conditional.__init__(self, error)
701
+
702
+ def _condition(self, value):
703
+ try:
704
+ parts = re.split(r"[x\s]", value)
705
+ parts = list(filter(None, parts))
706
+ for p in parts:
707
+ float(p)
708
+ return True
709
+ except Exception:
710
+ return False
711
+
712
+
713
+ class NumericRangeValidator(Conditional):
714
+ """ Validator for RangeParam. See RangeParam. """
715
+
716
+ def __init__(self, error='Incorrect format for numeric range param'):
717
+ Conditional.__init__(self, error)
718
+
719
+ def _condition(self, value):
720
+ try:
721
+ parts = re.split(r"[-,\s]", value)
722
+ parts = list(filter(None, parts))
723
+ for p in parts:
724
+ float(p)
725
+ return True
726
+ except Exception:
727
+ return False
728
+
729
+
730
+ class NonEmptyBoolCondition(Conditional):
731
+ def __init__(self, error='Boolean param needs to be set.'):
732
+ Conditional.__init__(self, error)
733
+ self._condition = lambda value: value is not None
734
+
735
+
736
+ # --------- Some constants validators ---------------------
737
+
738
+ Positive = GT(0.0, error='Value should be greater than zero')
739
+
740
+ FreqValidator = Range(0.001, 0.5,
741
+ error="Digital frequencies should be between 0.001 and 0.5")
742
+
743
+ NonEmpty = NonEmptyCondition()
744
+ NonEmptyBool = NonEmptyBoolCondition()