scipion-pyworkflow 3.10.6__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 (123) hide show
  1. pyworkflow/config.py +131 -67
  2. pyworkflow/constants.py +2 -1
  3. pyworkflow/plugin.py +93 -44
  4. pyworkflow/resources/showj/arrowDown.png +0 -0
  5. pyworkflow/resources/showj/arrowUp.png +0 -0
  6. pyworkflow/resources/showj/background_section.png +0 -0
  7. pyworkflow/resources/showj/colRowModeOff.png +0 -0
  8. pyworkflow/resources/showj/colRowModeOn.png +0 -0
  9. pyworkflow/resources/showj/delete.png +0 -0
  10. pyworkflow/resources/showj/doc_icon.png +0 -0
  11. pyworkflow/resources/showj/download_icon.png +0 -0
  12. pyworkflow/resources/showj/enabled_gallery.png +0 -0
  13. pyworkflow/resources/showj/galleryViewOff.png +0 -0
  14. pyworkflow/resources/showj/galleryViewOn.png +0 -0
  15. pyworkflow/resources/showj/goto.png +0 -0
  16. pyworkflow/resources/showj/menu.png +0 -0
  17. pyworkflow/resources/showj/separator.png +0 -0
  18. pyworkflow/resources/showj/tableViewOff.png +0 -0
  19. pyworkflow/resources/showj/tableViewOn.png +0 -0
  20. pyworkflow/resources/showj/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  21. pyworkflow/resources/showj/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  22. pyworkflow/resources/showj/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  23. pyworkflow/resources/showj/volumeOff.png +0 -0
  24. pyworkflow/resources/showj/volumeOn.png +0 -0
  25. pyworkflow/viewer.py +23 -1
  26. pyworkflowtests/protocols.py +1 -3
  27. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/METADATA +13 -27
  28. scipion_pyworkflow-3.11.0.dist-info/RECORD +71 -0
  29. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/WHEEL +1 -1
  30. pyworkflow/apps/__init__.py +0 -29
  31. pyworkflow/apps/pw_manager.py +0 -37
  32. pyworkflow/apps/pw_plot.py +0 -51
  33. pyworkflow/apps/pw_project.py +0 -113
  34. pyworkflow/apps/pw_protocol_list.py +0 -143
  35. pyworkflow/apps/pw_protocol_run.py +0 -51
  36. pyworkflow/apps/pw_run_tests.py +0 -267
  37. pyworkflow/apps/pw_schedule_run.py +0 -322
  38. pyworkflow/apps/pw_sleep.py +0 -37
  39. pyworkflow/apps/pw_sync_data.py +0 -439
  40. pyworkflow/apps/pw_viewer.py +0 -78
  41. pyworkflow/gui/__init__.py +0 -36
  42. pyworkflow/gui/browser.py +0 -726
  43. pyworkflow/gui/canvas.py +0 -1190
  44. pyworkflow/gui/dialog.py +0 -977
  45. pyworkflow/gui/form.py +0 -2637
  46. pyworkflow/gui/graph.py +0 -247
  47. pyworkflow/gui/graph_layout.py +0 -271
  48. pyworkflow/gui/gui.py +0 -566
  49. pyworkflow/gui/matplotlib_image.py +0 -233
  50. pyworkflow/gui/plotter.py +0 -247
  51. pyworkflow/gui/project/__init__.py +0 -25
  52. pyworkflow/gui/project/base.py +0 -192
  53. pyworkflow/gui/project/constants.py +0 -139
  54. pyworkflow/gui/project/labels.py +0 -205
  55. pyworkflow/gui/project/project.py +0 -492
  56. pyworkflow/gui/project/searchprotocol.py +0 -154
  57. pyworkflow/gui/project/searchrun.py +0 -181
  58. pyworkflow/gui/project/steps.py +0 -171
  59. pyworkflow/gui/project/utils.py +0 -332
  60. pyworkflow/gui/project/variables.py +0 -179
  61. pyworkflow/gui/project/viewdata.py +0 -472
  62. pyworkflow/gui/project/viewprojects.py +0 -510
  63. pyworkflow/gui/project/viewprotocols.py +0 -2093
  64. pyworkflow/gui/project/viewprotocols_extra.py +0 -559
  65. pyworkflow/gui/text.py +0 -771
  66. pyworkflow/gui/tooltip.py +0 -185
  67. pyworkflow/gui/tree.py +0 -684
  68. pyworkflow/gui/widgets.py +0 -307
  69. pyworkflow/mapper/__init__.py +0 -26
  70. pyworkflow/mapper/mapper.py +0 -222
  71. pyworkflow/mapper/sqlite.py +0 -1581
  72. pyworkflow/mapper/sqlite_db.py +0 -145
  73. pyworkflow/project/__init__.py +0 -31
  74. pyworkflow/project/config.py +0 -454
  75. pyworkflow/project/manager.py +0 -180
  76. pyworkflow/project/project.py +0 -2007
  77. pyworkflow/protocol/__init__.py +0 -38
  78. pyworkflow/protocol/bibtex.py +0 -48
  79. pyworkflow/protocol/constants.py +0 -87
  80. pyworkflow/protocol/executor.py +0 -471
  81. pyworkflow/protocol/hosts.py +0 -314
  82. pyworkflow/protocol/launch.py +0 -270
  83. pyworkflow/protocol/package.py +0 -42
  84. pyworkflow/protocol/params.py +0 -741
  85. pyworkflow/protocol/protocol.py +0 -2641
  86. pyworkflow/tests/__init__.py +0 -29
  87. pyworkflow/tests/test_utils.py +0 -25
  88. pyworkflow/tests/tests.py +0 -341
  89. pyworkflow/utils/__init__.py +0 -38
  90. pyworkflow/utils/dataset.py +0 -414
  91. pyworkflow/utils/echo.py +0 -104
  92. pyworkflow/utils/graph.py +0 -169
  93. pyworkflow/utils/log.py +0 -284
  94. pyworkflow/utils/path.py +0 -528
  95. pyworkflow/utils/process.py +0 -153
  96. pyworkflow/utils/profiler.py +0 -92
  97. pyworkflow/utils/progressbar.py +0 -154
  98. pyworkflow/utils/properties.py +0 -631
  99. pyworkflow/utils/reflection.py +0 -129
  100. pyworkflow/utils/utils.py +0 -879
  101. pyworkflow/utils/which.py +0 -229
  102. pyworkflow/webservices/__init__.py +0 -8
  103. pyworkflow/webservices/config.py +0 -11
  104. pyworkflow/webservices/notifier.py +0 -162
  105. pyworkflow/webservices/repository.py +0 -59
  106. pyworkflow/webservices/workflowhub.py +0 -74
  107. pyworkflowtests/tests/__init__.py +0 -0
  108. pyworkflowtests/tests/test_canvas.py +0 -72
  109. pyworkflowtests/tests/test_domain.py +0 -45
  110. pyworkflowtests/tests/test_logs.py +0 -74
  111. pyworkflowtests/tests/test_mappers.py +0 -392
  112. pyworkflowtests/tests/test_object.py +0 -507
  113. pyworkflowtests/tests/test_project.py +0 -42
  114. pyworkflowtests/tests/test_protocol_execution.py +0 -142
  115. pyworkflowtests/tests/test_protocol_export.py +0 -78
  116. pyworkflowtests/tests/test_protocol_output.py +0 -158
  117. pyworkflowtests/tests/test_streaming.py +0 -47
  118. pyworkflowtests/tests/test_utils.py +0 -210
  119. scipion_pyworkflow-3.10.6.dist-info/RECORD +0 -140
  120. scipion_pyworkflow-3.10.6.dist-info/dependency_links.txt +0 -1
  121. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/entry_points.txt +0 -0
  122. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/licenses/LICENSE.txt +0 -0
  123. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/top_level.txt +0 -0
pyworkflow/utils/utils.py DELETED
@@ -1,879 +0,0 @@
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
- import logging
25
- logger = logging.getLogger(__name__)
26
-
27
- import contextlib
28
- import sys
29
- import platform
30
- import os
31
- import re
32
- from datetime import datetime, timezone
33
- import traceback
34
- import sysconfig
35
-
36
- import bibtexparser
37
- import numpy as np
38
- import math
39
- from pyworkflow.constants import StrColors
40
- from pyworkflow import Config
41
-
42
-
43
- def prettyDate(time=False):
44
- """
45
- Get a datetime object or a int() Epoch timestamp and return a
46
- pretty string like 'an hour ago', 'Yesterday', '3 months ago',
47
- 'just now', etc
48
- """
49
- now = datetime.now()
50
- if type(time) is int:
51
- diff = now - datetime.fromtimestamp(time)
52
- elif type(time) is float:
53
- diff = now - datetime.fromtimestamp(int(time))
54
- elif isinstance(time, datetime):
55
- diff = now - time
56
- elif not time:
57
- # Avoid now - now (sonar cloud bug)
58
- copy = now
59
- diff = now - copy
60
- second_diff = diff.seconds
61
- day_diff = diff.days
62
-
63
- if day_diff < 0:
64
- return ''
65
-
66
- if day_diff == 0:
67
- if second_diff < 10:
68
- return "just now"
69
- if second_diff < 60:
70
- return str(second_diff) + " seconds ago"
71
- if second_diff < 120:
72
- return "a minute ago"
73
- if second_diff < 3600:
74
- return str(int(second_diff / 60)) + " minutes ago"
75
- if second_diff < 7200:
76
- return "an hour ago"
77
- if second_diff < 86400:
78
- return str(int(second_diff / 3600)) + " hours ago"
79
- if day_diff == 1:
80
- return "Yesterday"
81
- if day_diff < 7:
82
- return str(day_diff) + " days ago"
83
- if day_diff < 31:
84
- return str(int(day_diff / 7)) + " weeks ago"
85
- if day_diff < 365:
86
- return str(int(day_diff / 30)) + " months ago"
87
- return str(int(day_diff / 365)) + " years ago"
88
-
89
-
90
- def dateStr(dt=None, time=True, secs=False, dateFormat=None):
91
- """ Get a normal string representation of datetime.
92
- If dt is None, use NOW.
93
- """
94
- if dt is None:
95
- dt = datetime.now()
96
- elif isinstance(dt, float) or isinstance(dt, int):
97
- dt = datetime.fromtimestamp(dt)
98
-
99
- if dateFormat is None:
100
- dateFormat = '%d-%m-%Y'
101
- if time:
102
- dateFormat += ' %H:%M'
103
- if secs:
104
- dateFormat += ':%S'
105
-
106
- return dt.strftime(dateFormat)
107
-
108
-
109
- prettyTime = dateStr
110
-
111
-
112
- def prettyTimestamp(dt=None, format='%Y-%m-%d_%H%M%S'):
113
- if dt is None:
114
- dt = datetime.now()
115
-
116
- return dt.strftime(format)
117
-
118
-
119
- def prettySize(size):
120
- """ Human friendly file size. """
121
- unit_list = list(zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'],
122
- [0, 0, 1, 2, 2, 2]))
123
- if size > 1:
124
- exponent = min(int(math.log(size, 1024)), len(unit_list) - 1)
125
- quotient = float(size) / 1024 ** exponent
126
- unit, num_decimals = unit_list[exponent]
127
- format_string = '{:.%sf} {}' % num_decimals
128
- return format_string.format(quotient, unit)
129
- if size == 0:
130
- return '0 bytes'
131
- if size == 1:
132
- return '1 byte'
133
-
134
-
135
- def prettyDelta(timedelta):
136
- """ Remove the milliseconds of the timedelta. """
137
- return str(timedelta).split('.')[0]
138
-
139
-
140
- def to_utc(t):
141
- """ Make date conversions to utc"""
142
- return datetime.fromtimestamp(t, tz=timezone.utc)
143
-
144
-
145
- def prettyLog(msg):
146
- logger.info(cyanStr(msg))
147
-
148
-
149
- class Timer(object):
150
- """ Simple Timer base in datetime.now and timedelta. """
151
-
152
- def __init__(self, message=""):
153
- self._message = message
154
-
155
- def tic(self):
156
- self._dt = datetime.now()
157
-
158
- def getElapsedTime(self):
159
- return datetime.now() - self._dt
160
-
161
- def toc(self, message='Elapsed:'):
162
- logger.info(message + str(self.getElapsedTime()))
163
-
164
- def getToc(self):
165
- return prettyDelta(self.getElapsedTime())
166
-
167
- def __enter__(self):
168
- self.tic()
169
-
170
- def __exit__(self, type, value, traceback):
171
- self.toc(self._message)
172
-
173
-
174
- def timeit(func):
175
- """
176
- Decorator function to have a simple measurement of the execution time of a given function.
177
- To use it ::
178
-
179
- @timeit
180
- def func(...)
181
- ...
182
-
183
- """
184
-
185
- def timedFunc(*args, **kwargs):
186
- t = Timer()
187
- t.tic()
188
- result = func(*args, **kwargs)
189
- t.toc("Function '%s' took" % func)
190
-
191
- return result
192
-
193
- return timedFunc
194
-
195
-
196
- def trace(nlevels, separator=' --> ', stream=sys.stdout):
197
- # Example:
198
- # @trace(3)
199
- # def doRefresh(...
200
- # gives as output whenever doRefresh is called lines like:
201
- # text.py:486 _addFileTab --> text.py:330 __init__ --> doRefresh
202
-
203
- def realTrace(f):
204
- """ Decorator function to print stack call in a human-readable way.
205
- """
206
-
207
- def tracedFunc(*args, **kwargs):
208
- stack = traceback.extract_stack()[-nlevels - 1:-1]
209
- fmt = lambda x: '%s:%d %s' % (os.path.basename(x[0]), x[1], x[2])
210
- stList = list(map(fmt, stack))
211
- stream.write(separator.join(stList + [f.__name__]) + '\n')
212
- return f(*args, **kwargs)
213
-
214
- return tracedFunc
215
-
216
- return realTrace
217
-
218
-
219
- def prettyDict(d):
220
- print("{")
221
- for k, v in d.items():
222
- print(" %s: %s" % (k, v))
223
- print("}")
224
-
225
-
226
- def prettyXml(elem, level=0):
227
- """ Add indentation for XML elements for more human readable text. """
228
- i = "\n" + level * " "
229
- if len(elem):
230
- if not elem.text or not elem.text.strip():
231
- elem.text = i + " "
232
- if not elem.tail or not elem.tail.strip():
233
- elem.tail = i
234
- for _elem in elem:
235
- prettyXml(_elem, level + 1)
236
- if not _elem.tail or not _elem.tail.strip():
237
- _elem.tail = i
238
-
239
-
240
- def getUniqueItems(originalList):
241
- """ Method to remove repeated items from one list
242
- originalList -- Original list with repeated items, or not.
243
- returns -- New list with the content of original list without repeated items
244
- """
245
- auxDict = {}
246
- resultList = [auxDict.setdefault(x, x) for x in originalList if x not in auxDict]
247
- return resultList
248
-
249
- def sortListByList(inList, priorityList):
250
- """ Returns a list sorted by some elements in a second priorityList"""
251
- if priorityList:
252
- sortedList = priorityList + [item for item in inList
253
- if item not in priorityList]
254
- return sortedList
255
- else:
256
- return inList
257
-
258
-
259
- def getLocalUserName():
260
- """ Recover local machine user name.
261
- returns: Local machine user name.
262
- """
263
- import getpass
264
- return getpass.getuser()
265
-
266
-
267
- def getLocalHostName():
268
- return getHostName()
269
-
270
-
271
- def getHostName():
272
- """ Return the name of the local machine. """
273
- import socket
274
- return socket.gethostname()
275
-
276
-
277
- def getHostFullName():
278
- """ Return the fully-qualified name of the local machine. """
279
- import socket
280
- return socket.getfqdn()
281
-
282
- def getPython():
283
- return sys.executable
284
-
285
- def getPythonPackagesFolder():
286
- # This does not work on MAC virtual envs
287
- # import site
288
- # return site.getsitepackages()[0]
289
-
290
- return sysconfig.get_path("platlib")
291
-
292
-
293
- # ******************************File utils *******************************
294
-
295
- def isInFile(text, filePath):
296
- """
297
- Checks if given text is in the given file.
298
-
299
- :param text: Text to check.
300
- :param filePath: File path to check.
301
-
302
- :returns True if the given text is in the given file,
303
- False if it is not in the file.
304
-
305
- """
306
- return any(text in line for line in open(filePath))
307
-
308
-
309
- def getLineInFile(text, fileName):
310
- """ Find the line where the given text is located in the given file.
311
-
312
- :param text: Text to check.
313
- :param filePath: File path to check.
314
-
315
- :return line number where the text was located.
316
-
317
- """
318
- with open(fileName) as f:
319
- for i, line in enumerate(f):
320
- if text in line:
321
- return i + 1
322
- return None
323
-
324
- def hasAnyFileChanged(files, time):
325
- """ Returns true if any of the files in files list has been changed after 'time'"""
326
- for file in files:
327
- if hasFileChangedSince(file, time):
328
- return True
329
-
330
- return False
331
-
332
- def hasFileChangedSince(file, time):
333
- """ Returns true if the file has changed after 'time'"""
334
- modTime = datetime.fromtimestamp(os.path.getmtime(file))
335
- return time < modTime
336
-
337
-
338
- # ------------- Colored message strings -----------------------------
339
-
340
-
341
- def getColorStr(text, color, bold=False):
342
- """ Add ANSI color codes to the string if there is a terminal sys.stdout.
343
-
344
- :param text: text to be colored
345
- :param color: red or green
346
- :param bold: bold the text
347
- """
348
- if not Config.colorsInTerminal():
349
- return text
350
-
351
- attr = [str(color.value)]
352
-
353
- if bold:
354
- attr.append('1')
355
- return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), text)
356
-
357
-
358
- def grayStr(text):
359
- return getColorStr(text, color=StrColors.gray)
360
-
361
-
362
- def redStr(text):
363
- return getColorStr(text, color=StrColors.red)
364
-
365
-
366
- def greenStr(text):
367
- return getColorStr(text, color=StrColors.green)
368
-
369
-
370
- def yellowStr(text):
371
- return getColorStr(text, color=StrColors.yellow)
372
-
373
-
374
- def blueStr(text):
375
- return getColorStr(text, color=StrColors.blue)
376
-
377
-
378
- def magentaStr(text):
379
- return getColorStr(text, color=StrColors.magenta)
380
-
381
-
382
- def cyanStr(text):
383
- return getColorStr(text, color=StrColors.cyan)
384
-
385
-
386
- def ansi(n, bold=False):
387
- """Return function that escapes text with ANSI color n."""
388
- return lambda txt: '\x1b[%d%sm%s\x1b[0m' % (n, ';1' if bold else '', txt)
389
-
390
-
391
- black, red, green, yellow, blue, magenta, cyan, white = map(ansi, range(30, 38))
392
- blackB, redB, greenB, yellowB, blueB, magentaB, cyanB, whiteB = [
393
- ansi(i, bold=True) for i in range(30, 38)]
394
-
395
- # -------------- Hyper text highlighting ----------------------------
396
- #
397
- # We use a subset of TWiki hyper text conventions.
398
- # In particular:
399
- # *some_text* will display some_text in bold
400
- # _some_text_ will display some_text in italic
401
- # Links:
402
- # http://www.link-page.com -> hyperlink using the url as label
403
- # [[http://www.link-page.com][Link page]] -> hyperlink using "Link page" as label
404
-
405
- # Types of recognized styles
406
- HYPER_BOLD = 'bold'
407
- HYPER_ITALIC = 'italic'
408
- HYPER_LINK1 = 'link1'
409
- HYPER_SCIPION_OPEN = 'sci-open'
410
- HYPER_LINK2 = 'link2'
411
- HYPER_ALL = 'all'
412
-
413
- # Associated regular expressions
414
- PATTERN_BOLD = r"(^|[\s])[*](?P<bold>[^\s*][^*]*[^\s*]|[^\s*])[*]"
415
- # PATTERN_BOLD = r"[\s]+[*]([^\s][^*]+[^\s])[*][\s]+"
416
- PATTERN_ITALIC = r"(^|[\s])[_](?P<italic>[^\s_][^_]*[^\s_]|[^\s_])[_]"
417
- # PATTERN_ITALIC = r"[\s]+[_]([^\s][^_]+[^\s])[_][\s]+"
418
- PATTERN_LINK1 = r'(?P<link1>http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+#]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)'
419
- PATTERN_LINK2 = r"[\[]{2}(?P<link2>[^\s][^\]]+[^\s])[\]][\[](?P<link2_label>[^\s][^\]]+[^\s])[\]]{2}"
420
- # __PATTERN_LINK2 should be first since it could contain __PATTERN_LINK1
421
- PATTERN_ALL = '|'.join([PATTERN_BOLD, PATTERN_ITALIC, PATTERN_LINK2, PATTERN_LINK1])
422
-
423
- # Compiled regex
424
- # Not need now, each pattern compiled separately
425
- # HYPER_REGEX = {
426
- # HYPER_BOLD: re.compile(PATTERN_BOLD),
427
- # HYPER_ITALIC: re.compile(PATTERN_ITALIC),
428
- # HYPER_LINK1: re.compile(PATTERN_LINK1),
429
- # HYPER_LINK2: re.compile(PATTERN_LINK1),
430
- # }
431
- HYPER_ALL_RE = re.compile(PATTERN_ALL)
432
-
433
-
434
- def parseHyperText(text, matchCallback):
435
- """ Parse the text recognizing Hyper definitions below.
436
-
437
- :param matchCallback: a callback function to processing each matching,
438
- it should accept the type of match (HYPER_BOLD, ITALIC or LINK)
439
-
440
- :return The input text with the replacements made by matchCallback
441
- """
442
-
443
- def _match(match):
444
- """ Call the proper matchCallback with some extra info. """
445
- m = match.group().strip()
446
- if m.startswith('*'):
447
- tag = HYPER_BOLD
448
- elif m.startswith('_'):
449
- tag = HYPER_ITALIC
450
- elif m.startswith('http'):
451
- tag = HYPER_LINK1
452
- elif m.startswith('[['):
453
- tag = HYPER_LINK2
454
- else:
455
- raise Exception("Bad prefix for HyperText match")
456
- return matchCallback(match, tag)
457
-
458
- return HYPER_ALL_RE.sub(_match, text)
459
-
460
-
461
- # for hyperMode, hyperRegex in HYPER_REGEX.iteritems():
462
- # text = hyperRegex.sub(lambda match: matchCallback(match, hyperMode), text)
463
- #
464
- # return text
465
-
466
- class LazyDict(object):
467
- """ Dictionary to be initialized at the moment it is accessed for the first time.
468
- Initialization is done by a callback passed at instantiation"""
469
- def __init__(self, callback=dict):
470
- """ :param callback: method to initialize the dictionary. Should return a dictionary"""
471
- self.data = None
472
- self.callback = callback
473
-
474
- def evaluate_callback(self):
475
- self.data = self.callback()
476
-
477
- def __getitem__(self, name):
478
- if self.data is None:
479
- self.evaluate_callback()
480
- return self.data.__getitem__(name)
481
-
482
- def __setitem__(self, name, value):
483
- if self.data is None:
484
- self.evaluate_callback()
485
- return self.data.__setitem__(name, value)
486
-
487
- def __getattr__(self, name):
488
- if self.data is None:
489
- self.evaluate_callback()
490
- return getattr(self.data, name)
491
-
492
- def __iter__(self):
493
- if self.data is None:
494
- self.evaluate_callback()
495
- return self.data.__iter__()
496
-
497
-
498
- def parseBibTex(bibtexStr):
499
- """ Parse a bibtex file and return a dictionary. """
500
-
501
- return bibtexparser.loads(bibtexStr,
502
- parser=bibtexparser.bparser.BibTexParser(common_strings=True)
503
- ).entries_dict
504
-
505
-
506
-
507
- def isPower2(num):
508
- """ Return True if 'num' is a power of 2. """
509
- return num != 0 and ((num & (num - 1)) == 0)
510
-
511
-
512
- # ---------------------------------------------------------------------------
513
- # Parsing of arguments
514
- # ---------------------------------------------------------------------------
515
-
516
- def getListFromRangeString(rangeStr):
517
- """ Create a list of integers from a string with range definitions.
518
- Examples:
519
- "1,5-8,10" -> [1,5,6,7,8,10]
520
- "2,6,9-11" -> [2,6,9,10,11]
521
- "2 5, 6-8" -> [2,5,6,7,8]
522
- """
523
- # Split elements by command or space
524
- elements = re.split(r',|\s', rangeStr)
525
- values = []
526
- for e in elements:
527
- if '-' in e:
528
- limits = e.split('-')
529
- values += range(int(limits[0]), int(limits[1]) + 1)
530
- else:
531
- # If values are separated by comma also splitted
532
- values += map(int, e.split())
533
- return values
534
-
535
-
536
- def getRangeStringFromList(list):
537
- left = None
538
- right = None
539
- ranges = []
540
-
541
- def addRange():
542
- if left == right: # Single element
543
- ranges.append("%d" % right)
544
- else:
545
- ranges.append("%(left)d-%(right)d" % locals())
546
-
547
- for item in list:
548
- if right is None:
549
- left = right = item
550
- else:
551
- if item == right + 1:
552
- right += 1
553
- else:
554
- addRange()
555
- left = right = item
556
- addRange()
557
- return ','.join(ranges)
558
-
559
-
560
- def getListFromValues(valuesStr, length=None, caster=str):
561
- """ Convert a string representing list items into a list.
562
- The items should be separated by spaces or commas and a multiplier 'x' can be used.
563
- If length is not None, then the last element will be repeated
564
- until the desired length is reached.
565
-
566
- Examples:
567
- '1 1 2x2 4 4' -> ['1', '1', '2', '2', '4', '4']
568
- '2x3, 3x4, 1' -> ['3', '3', '4', '4', '4', '1']
569
-
570
- """
571
- result = []
572
- valuesStr = valuesStr.replace(","," ")
573
- for chunk in valuesStr.split():
574
- if caster != str:
575
- values = chunk.split('x')
576
- else:
577
- values=[chunk]
578
-
579
- n = len(values)
580
- if n == 1: # 'x' is not present in the chunk, single value
581
- result += [caster(values[0])]
582
- elif n == 2: # multiple the values by the number after 'x'
583
- result += [caster(values[1])] * int(values[0])
584
- else:
585
- raise Exception("More than one 'x' is not allowed in list string value.")
586
-
587
- # If length is passed, we fill the list with
588
- # the last element until length is reached
589
- if length is not None and length > len(result):
590
- item = result[-1]
591
- result += [caster(item)] * (length - len(result))
592
-
593
- return result
594
-
595
-
596
- def getFloatListFromValues(valuesStr, length=None):
597
- """ Convert a string to a list of floats"""
598
- return [v for v in getListFromValues(valuesStr, length, caster=float)]
599
-
600
-
601
- def getBoolListFromValues(valuesStr, length=None):
602
- """ Convert a string to a list of booleans"""
603
- from pyworkflow.object import Boolean
604
- return [v.get() for v in getListFromValues(valuesStr, length, caster=Boolean)]
605
-
606
-
607
- def getStringListFromValues(valuesStr, length=None):
608
- """ Convert a string to a list of booleans"""
609
- from pyworkflow.object import String
610
- return [String(value=v).get() for v in getListFromValues(valuesStr, length)]
611
-
612
-
613
- class Environ(dict):
614
- """ Some utilities to handle environment settings. """
615
- REPLACE = 0
616
- BEGIN = 1
617
- END = 2
618
-
619
- def getFirst(self, keys, mandatory=False):
620
- """ Return the value of the first key present in the environment.
621
- If none is found, returns the 'defaultValue' parameter.
622
- """
623
- for k in keys:
624
- if k in self:
625
- return self.get(k)
626
-
627
- if mandatory:
628
- logger.info("None of the variables: %s found in the Environment. "
629
- "Please check scipion.conf files." % (str(keys)))
630
-
631
- return None
632
-
633
- def set(self, varName, varValue, position=REPLACE):
634
- """ Modify the value for some variable.
635
-
636
- :param varName: for example LD_LIBRARY_PATH
637
- :param varValue: the value to set, prefix or suffix.
638
- :param position: controls how the value will be changed.
639
- If REPLACE, it will overwrite the value of
640
- the var. BEGIN or END will preserve the current value
641
- and will add, at the beginning or end, the new value.
642
-
643
- """
644
- if varName in self and position != self.REPLACE:
645
- if position == self.BEGIN:
646
- self[varName] = varValue + os.pathsep + self[varName]
647
- elif position == self.END:
648
- self[varName] = self[varName] + os.pathsep + varValue
649
- else:
650
- self[varName] = varValue
651
-
652
- def update(self, valuesDict, position=REPLACE):
653
- """ Use set for each key, value pair in valuesDict. """
654
- for k, v in valuesDict.items():
655
- self.set(k, v, position)
656
-
657
- def addLibrary(self, libraryPath, position=BEGIN):
658
- """ Adds a path to LD_LIBRARY_PATH at the requested position
659
- if the provided paths exist. """
660
-
661
- if libraryPath is None:
662
- return
663
-
664
- if existsVariablePaths(libraryPath):
665
- self.update({'LD_LIBRARY_PATH': libraryPath}, position=position)
666
- else:
667
- logger.info("Some paths do not exist in: % s" % libraryPath)
668
-
669
- def setPrepend(self, prepend):
670
- """ Use this method to set a prepend string that will be added at
671
- the beginning of any command that will be run in this environment.
672
- This can be useful for example when 'modules' need to be loaded and
673
- a simple environment variables setup is not enough.
674
- """
675
- setattr(self, '__prepend', prepend)
676
-
677
- def getPrepend(self):
678
- """ Return if there is any prepend value. See setPrepend function. """
679
- return getattr(self, '__prepend', '')
680
-
681
-
682
- def existsVariablePaths(variableValue):
683
- """ Check if the path (or paths) in variableValue exists.
684
- Multiple paths are allowed if separated by os."""
685
- return all(os.path.exists(p)
686
- for p in variableValue.split(os.pathsep) if p.split())
687
-
688
-
689
- def environAdd(varName, newValue, valueFirst=False):
690
- """ Add a new value to some environ variable.
691
- If valueFirst is true, the new value will be at the beginning.
692
- """
693
- varList = [os.environ[varName]]
694
- i = 1
695
- if valueFirst:
696
- i = 0
697
- varList.insert(i, newValue)
698
- os.environ[varName] = os.pathsep.join(varList)
699
-
700
-
701
- def envVarOn(varName, env=None):
702
- """ Is variable set to True in the environment? """
703
- v = env.get(varName) if env else os.environ.get(varName)
704
- return strToBoolean(v)
705
-
706
- def strToBoolean(string):
707
- """ Converts a string into a Boolean if the string is on of true, yes, on, 1. Case insensitive."""
708
- return string is not None and string.lower() in ['true', 'yes', 'on', '1']
709
-
710
- def strToDuration(durationStr):
711
- """ Converts a string representing an elapsed time to seconds
712
- E.g.: for "1m 10s" it'll return 70 """
713
-
714
- toEval = durationStr.replace("d", "*3600*24")\
715
- .replace("h", "*3600")\
716
- .replace("m", "*60") \
717
- .replace("s", "") \
718
- .replace(" ", "+")
719
- return eval(toEval)
720
-
721
- def getMemoryAvailable():
722
- """ Return the total memory of the system in MB """
723
- from psutil import virtual_memory
724
- return virtual_memory().total // 1024 ** 2
725
-
726
-
727
- def getFreePort(basePort=0, host=''):
728
- import socket
729
- try:
730
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
731
- s.bind((host, basePort))
732
- ipaddr, port = s.getsockname()
733
- s.close()
734
- except Exception as e:
735
- logger.error("Can't get a free port", exc_info=e)
736
- return 0
737
- return port
738
-
739
-
740
- def readProperties(propsFile):
741
- myprops = {}
742
- with open(propsFile, 'r') as f:
743
- for line in f:
744
- line = line.rstrip() # removes trailing whitespace and '\n' chars
745
-
746
- if "=" not in line:
747
- continue # skips blanks and comments w/o =
748
- if line.startswith("#"):
749
- continue # skips comments which contain =
750
-
751
- k, v = line.split("=", 1)
752
- myprops[k] = v
753
- return myprops
754
-
755
-
756
- # ---------------------Color utils --------------------------
757
- def hex_to_rgb(value):
758
- value = value.lstrip('#')
759
- lv = len(value)
760
- return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
761
-
762
-
763
- def rgb_to_hex(rgb):
764
- return '#%02x%02x%02x' % (int(rgb[0]), int(rgb[1]), int(rgb[2]))
765
-
766
-
767
- def lighter(color, percent):
768
- """assumes color is rgb between (0, 0, 0) and (255, 255, 255)"""
769
- color = np.array(color)
770
- white = np.array([255, 255, 255])
771
- vector = white - color
772
- return tuple(np.around(color + vector * percent))
773
-
774
-
775
- def formatExceptionInfo(level=6):
776
- error_type, error_value, trbk = sys.exc_info()
777
- tb_list = traceback.format_tb(trbk, level)
778
- s = "Error: %s \nDescription: %s \nTraceback:" % (error_type.__name__,
779
- error_value)
780
- for i in tb_list:
781
- s += "\n" + i
782
- return s
783
-
784
-
785
- def printTraceBack():
786
- traceback.print_stack()
787
-
788
-
789
- def getEnvVariable(variableName, default=None, exceptionMsg=None):
790
- """ Returns the value of an environment variable or raise an exception message.
791
- Useful when adding variable to the config file and report accurate messages"""
792
- value = os.getenv(variableName)
793
-
794
- if exceptionMsg is None:
795
- exceptionMsg = ("Environment variable %s not found. "
796
- "Please check scipion configuration. "
797
- "Try running : scipion config." % variableName)
798
-
799
- if value is None:
800
- if default is None:
801
- raise Exception(exceptionMsg)
802
- else:
803
- return default
804
- else:
805
- return value
806
-
807
-
808
- @contextlib.contextmanager
809
- def weakImport(package, msg=None):
810
- """
811
- This method can be used to tolerate imports that may fail.
812
-
813
- e.g::
814
-
815
- from .protocol_ctffind import CistemProtCTFFind
816
- with weakImport('tomo'):
817
- from .protocol_ts_ctffind import CistemProtTsCtffind
818
-
819
- In this case CistemProtTsCtffind should fail if tomo package is missing,
820
- but exception is captured and all the imports above should be available
821
-
822
- :param package: name of the package that is expected to fail
823
-
824
- """
825
- try:
826
- yield
827
- except ImportError as e:
828
- if "'%s'" % package not in str(e):
829
- raise e
830
- elif msg is not None:
831
- logger.warning(msg)
832
- # To be removed once developers have installed distro. 20-Nov-2023.
833
- with weakImport("distro", msg='You are missing distro package. '
834
- 'Did you "git pulled"?. Please run "scipion3 pip install distro==1.8".'):
835
- import distro
836
-
837
- class OS:
838
- @staticmethod
839
- def getPlatform():
840
- return platform.system()
841
-
842
- @classmethod
843
- def getDistro(cls):
844
- return distro
845
-
846
- @classmethod
847
- def isWSL(cls):
848
-
849
- # For now lets assume that if WSL_DISTRO_NAME exists is a WLS
850
- return cls.getWLSNAME() is not None
851
-
852
- @classmethod
853
- def getWLSNAME(cls):
854
- return os.environ.get("WSL_DISTRO_NAME", None)
855
-
856
- @classmethod
857
- def isUbuntu(cls):
858
- return distro.id() == "ubuntu"
859
-
860
- @classmethod
861
- def isCentos(cls):
862
- return distro.id() == "centos"
863
-
864
- @classmethod
865
- def WLSfile2Windows(cls, file):
866
- # Links in WSL are not valid in windows
867
- file = os.path.realpath(file).replace("/", "\\")
868
- file = ("\\\\wsl.localhost\\" + cls.getWLSNAME() + file)
869
- return file
870
-
871
-
872
- def valueToList(value):
873
- """ Returns a list containing value, unless value is already a list. If value is None returns an empty list"""
874
- if value is None:
875
- return []
876
- elif isinstance(value, list):
877
- return value
878
- else:
879
- return [value]