scipion-pyworkflow 3.11.0__py3-none-any.whl → 3.11.1__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 (98) hide show
  1. pyworkflow/apps/__init__.py +29 -0
  2. pyworkflow/apps/pw_manager.py +37 -0
  3. pyworkflow/apps/pw_plot.py +51 -0
  4. pyworkflow/apps/pw_project.py +113 -0
  5. pyworkflow/apps/pw_protocol_list.py +143 -0
  6. pyworkflow/apps/pw_protocol_run.py +51 -0
  7. pyworkflow/apps/pw_run_tests.py +267 -0
  8. pyworkflow/apps/pw_schedule_run.py +322 -0
  9. pyworkflow/apps/pw_sleep.py +37 -0
  10. pyworkflow/apps/pw_sync_data.py +439 -0
  11. pyworkflow/apps/pw_viewer.py +78 -0
  12. pyworkflow/constants.py +1 -1
  13. pyworkflow/gui/__init__.py +36 -0
  14. pyworkflow/gui/browser.py +760 -0
  15. pyworkflow/gui/canvas.py +1190 -0
  16. pyworkflow/gui/dialog.py +979 -0
  17. pyworkflow/gui/form.py +2726 -0
  18. pyworkflow/gui/graph.py +247 -0
  19. pyworkflow/gui/graph_layout.py +271 -0
  20. pyworkflow/gui/gui.py +566 -0
  21. pyworkflow/gui/matplotlib_image.py +233 -0
  22. pyworkflow/gui/plotter.py +247 -0
  23. pyworkflow/gui/project/__init__.py +25 -0
  24. pyworkflow/gui/project/base.py +192 -0
  25. pyworkflow/gui/project/constants.py +139 -0
  26. pyworkflow/gui/project/labels.py +205 -0
  27. pyworkflow/gui/project/project.py +491 -0
  28. pyworkflow/gui/project/searchprotocol.py +238 -0
  29. pyworkflow/gui/project/searchrun.py +181 -0
  30. pyworkflow/gui/project/steps.py +171 -0
  31. pyworkflow/gui/project/utils.py +332 -0
  32. pyworkflow/gui/project/variables.py +179 -0
  33. pyworkflow/gui/project/viewdata.py +472 -0
  34. pyworkflow/gui/project/viewprojects.py +510 -0
  35. pyworkflow/gui/project/viewprotocols.py +2116 -0
  36. pyworkflow/gui/project/viewprotocols_extra.py +562 -0
  37. pyworkflow/gui/text.py +771 -0
  38. pyworkflow/gui/tooltip.py +185 -0
  39. pyworkflow/gui/tree.py +684 -0
  40. pyworkflow/gui/widgets.py +307 -0
  41. pyworkflow/mapper/__init__.py +26 -0
  42. pyworkflow/mapper/mapper.py +222 -0
  43. pyworkflow/mapper/sqlite.py +1581 -0
  44. pyworkflow/mapper/sqlite_db.py +145 -0
  45. pyworkflow/project/__init__.py +31 -0
  46. pyworkflow/project/config.py +454 -0
  47. pyworkflow/project/manager.py +180 -0
  48. pyworkflow/project/project.py +2095 -0
  49. pyworkflow/project/usage.py +165 -0
  50. pyworkflow/protocol/__init__.py +38 -0
  51. pyworkflow/protocol/bibtex.py +48 -0
  52. pyworkflow/protocol/constants.py +87 -0
  53. pyworkflow/protocol/executor.py +483 -0
  54. pyworkflow/protocol/hosts.py +317 -0
  55. pyworkflow/protocol/launch.py +277 -0
  56. pyworkflow/protocol/package.py +42 -0
  57. pyworkflow/protocol/params.py +781 -0
  58. pyworkflow/protocol/protocol.py +2707 -0
  59. pyworkflow/tests/__init__.py +29 -0
  60. pyworkflow/tests/test_utils.py +25 -0
  61. pyworkflow/tests/tests.py +341 -0
  62. pyworkflow/utils/__init__.py +38 -0
  63. pyworkflow/utils/dataset.py +414 -0
  64. pyworkflow/utils/echo.py +104 -0
  65. pyworkflow/utils/graph.py +169 -0
  66. pyworkflow/utils/log.py +293 -0
  67. pyworkflow/utils/path.py +528 -0
  68. pyworkflow/utils/process.py +153 -0
  69. pyworkflow/utils/profiler.py +92 -0
  70. pyworkflow/utils/progressbar.py +154 -0
  71. pyworkflow/utils/properties.py +617 -0
  72. pyworkflow/utils/reflection.py +129 -0
  73. pyworkflow/utils/utils.py +880 -0
  74. pyworkflow/utils/which.py +229 -0
  75. pyworkflow/webservices/__init__.py +8 -0
  76. pyworkflow/webservices/config.py +8 -0
  77. pyworkflow/webservices/notifier.py +152 -0
  78. pyworkflow/webservices/repository.py +59 -0
  79. pyworkflow/webservices/workflowhub.py +74 -0
  80. pyworkflowtests/tests/__init__.py +0 -0
  81. pyworkflowtests/tests/test_canvas.py +72 -0
  82. pyworkflowtests/tests/test_domain.py +45 -0
  83. pyworkflowtests/tests/test_logs.py +74 -0
  84. pyworkflowtests/tests/test_mappers.py +392 -0
  85. pyworkflowtests/tests/test_object.py +507 -0
  86. pyworkflowtests/tests/test_project.py +42 -0
  87. pyworkflowtests/tests/test_protocol_execution.py +146 -0
  88. pyworkflowtests/tests/test_protocol_export.py +78 -0
  89. pyworkflowtests/tests/test_protocol_output.py +158 -0
  90. pyworkflowtests/tests/test_streaming.py +47 -0
  91. pyworkflowtests/tests/test_utils.py +210 -0
  92. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/METADATA +2 -2
  93. scipion_pyworkflow-3.11.1.dist-info/RECORD +161 -0
  94. scipion_pyworkflow-3.11.0.dist-info/RECORD +0 -71
  95. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/WHEEL +0 -0
  96. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/entry_points.txt +0 -0
  97. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/licenses/LICENSE.txt +0 -0
  98. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,229 @@
1
+
2
+ # Copyright (c) 2002-2005 ActiveState Corp.
3
+ # See LICENSE.txt for license details.
4
+ # Author:
5
+ # Trent Mick (TrentM@ActiveState.com)
6
+ # Home:
7
+ # http://trentm.com/projects/which/
8
+
9
+ """Find the full path to commands.
10
+
11
+ which(command, path=None, verbose=0, exts=None)
12
+ Return the full path to the first match of the given command on the
13
+ path.
14
+
15
+ whichall(command, path=None, verbose=0, exts=None)
16
+ Return a list of full paths to all matches of the given command on
17
+ the path.
18
+
19
+ whichgen(command, path=None, verbose=0, exts=None)
20
+ Return a generator which will yield full paths to all matches of the
21
+ given command on the path.
22
+
23
+ """
24
+
25
+ import os
26
+ import sys
27
+ import stat
28
+ # ---- exceptions
29
+
30
+
31
+ class WhichError(Exception):
32
+ pass
33
+
34
+ # ---- internal support stuff
35
+
36
+
37
+ def _getRegisteredExecutable(exeName):
38
+ """Windows allow application paths to be registered in the registry."""
39
+ registered = None
40
+ if sys.platform.startswith('win'):
41
+ if os.path.splitext(exeName)[1].lower() != '.exe':
42
+ exeName += '.exe'
43
+ import winreg
44
+ try:
45
+ key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +\
46
+ exeName
47
+ value = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE, key)
48
+ registered = (value, "from HKLM\\"+key)
49
+ except winreg.error:
50
+ pass
51
+ if registered and not os.path.exists(registered[0]):
52
+ registered = None
53
+ return registered
54
+
55
+
56
+ def _samefile(fname1, fname2):
57
+ if sys.platform.startswith('win'):
58
+ return (os.path.normpath(os.path.normcase(fname1)) ==
59
+ os.path.normpath(os.path.normcase(fname2)))
60
+ else:
61
+ return os.path.samefile(fname1, fname2)
62
+
63
+
64
+ def _cull(potential, matches, verbose=0):
65
+ """Cull inappropriate matches. Possible reasons:
66
+ - a duplicate of a previous match
67
+ - not a disk file
68
+ - not executable (non-Windows)
69
+ If 'potential' is approved it is returned and added to 'matches'.
70
+ Otherwise, None is returned.
71
+ """
72
+ for match in matches: # don't yield duplicates
73
+ if _samefile(potential[0], match[0]):
74
+ if verbose:
75
+ sys.stderr.write("duplicate: %s (%s)\n" % potential)
76
+ return None
77
+ else:
78
+ if not stat.S_ISREG(os.stat(potential[0]).st_mode):
79
+ if verbose:
80
+ sys.stderr.write("not a regular file: %s (%s)\n" % potential)
81
+ elif not os.access(potential[0], os.X_OK):
82
+ if verbose:
83
+ sys.stderr.write("no executable access: %s (%s)\n"
84
+ % potential)
85
+ else:
86
+ matches.append(potential)
87
+ return potential
88
+
89
+
90
+ # ---- module API
91
+
92
+ def whichgen(command, path=None, verbose=0, exts=None):
93
+ """Return a generator of full paths to the given command.
94
+
95
+ :param command: is a the name of the executable to search for.
96
+ :param path: is an optional alternate path list to search. The default it
97
+ to use the PATH environment variable.
98
+ :param verbose: if true, will cause a 2-tuple to be returned for each
99
+ match. The second element is a textual description of where the
100
+ match was found.
101
+ :param exts: optionally allows one to specify a list of extensions to use
102
+ instead of the standard list for this system. This can
103
+ effectively be used as an optimization to, for example, avoid
104
+ stat's of "foo.vbs" when searching for "foo" and you know it is
105
+ not a VisualBasic script but ".vbs" is on PATHEXT. This option
106
+ is only supported on Windows.
107
+
108
+ :return: This method returns a generator which yields either full paths to
109
+ the given command or, if verbose, tuples of the form (<path to
110
+ command>, <where path found>).
111
+
112
+ """
113
+ matches = []
114
+ if path is None:
115
+ usingGivenPath = 0
116
+ path = os.environ.get("PATH", "").split(os.pathsep)
117
+ if sys.platform.startswith("win"):
118
+ path.insert(0, os.curdir) # implied by Windows shell
119
+ else:
120
+ usingGivenPath = 1
121
+
122
+ # Windows has the concept of a list of extensions (PATHEXT env var).
123
+ if sys.platform.startswith("win"):
124
+ if exts is None:
125
+ exts = os.environ.get("PATHEXT", "").split(os.pathsep)
126
+ # If '.exe' is not in exts then obviously this is Win9x and
127
+ # or a bogus PATHEXT, then use a reasonable default.
128
+ for ext in exts:
129
+ if ext.lower() == ".exe":
130
+ break
131
+ else:
132
+ exts = ['.COM', '.EXE', '.BAT']
133
+ elif not isinstance(exts, list):
134
+ raise TypeError("'exts' argument must be a list or None")
135
+ else:
136
+ if exts is not None:
137
+ raise WhichError("'exts' argument is not supported on "
138
+ "platform '%s'" % sys.platform)
139
+ exts = []
140
+
141
+ # File name cannot have path separators because PATH lookup does not
142
+ # work that way.
143
+ if os.sep in command or os.altsep and os.altsep in command:
144
+ pass
145
+ else:
146
+ for i in range(len(path)):
147
+ dirName = path[i]
148
+ # On windows the dirName *could* be quoted, drop the quotes
149
+ if sys.platform.startswith("win") and len(dirName) >= 2\
150
+ and dirName[0] == '"' and dirName[-1] == '"':
151
+ dirName = dirName[1:-1]
152
+ for ext in ['']+exts:
153
+ absName = os.path.abspath(
154
+ os.path.normpath(os.path.join(dirName, command+ext)))
155
+ if os.path.isfile(absName):
156
+ if usingGivenPath:
157
+ fromWhere = "from given path element %d" % i
158
+ elif not sys.platform.startswith("win"):
159
+ fromWhere = "from PATH element %d" % i
160
+ elif i == 0:
161
+ fromWhere = "from current directory"
162
+ else:
163
+ fromWhere = "from PATH element %d" % (i-1)
164
+ match = _cull((absName, fromWhere), matches, verbose)
165
+ if match:
166
+ if verbose:
167
+ yield match
168
+ else:
169
+ yield match[0]
170
+ match = _getRegisteredExecutable(command)
171
+ if match is not None:
172
+ match = _cull(match, matches, verbose)
173
+ if match:
174
+ if verbose:
175
+ yield match
176
+ else:
177
+ yield match[0]
178
+
179
+
180
+ def which(command, path=None, verbose=0, exts=None):
181
+ """Return the full path to the first match of the given command on
182
+ the path.
183
+
184
+ :param command: is a the name of the executable to search for.
185
+ :param path: is an optional alternate path list to search.
186
+ The default is to use the PATH environment variable.
187
+ :param verbose: if true, will cause a 2-tuple to be returned.
188
+ The second element is a textual description of where the match was found.
189
+ :param exts: optionally allows one to specify a list of extensions to use
190
+ instead of the standard list for this system. This can
191
+ effectively be used as an optimization to, for example, avoid
192
+ stat's of "foo.vbs" when searching for "foo" and you know it is
193
+ not a VisualBasic script but ".vbs" is on PATHEXT. This option
194
+ is only supported on Windows.
195
+
196
+ :return If no match is found for the command, an empty string is returned.
197
+
198
+ """
199
+ try:
200
+ match = next(whichgen(command, path, verbose, exts))
201
+ except StopIteration:
202
+ return ''
203
+ return match
204
+
205
+
206
+ def commandExists(command):
207
+ path = which(command)
208
+ return path != ''
209
+
210
+
211
+ def whichall(command, path=None, verbose=0, exts=None):
212
+ """Return a list of full paths to all matches of the given command
213
+ on the path.
214
+
215
+ :param command: is a the name of the executable to search for.
216
+ :param path: is an optional alternate path list to search. The default it
217
+ to use the PATH environment variable.
218
+ :param verbose: if true, will cause a 2-tuple to be returned for each
219
+ match. The second element is a textual description of where the
220
+ match was found.
221
+ :param exts: optionally allows one to specify a list of extensions to use
222
+ instead of the standard list for this system. This can
223
+ effectively be used as an optimization to, for example, avoid
224
+ stat's of "foo.vbs" when searching for "foo" and you know it is
225
+ not a VisualBasic script but ".vbs" is on PATHEXT. This option
226
+ is only supported on Windows.
227
+
228
+ """
229
+ return list(whichgen(command, path, verbose, exts))
@@ -0,0 +1,8 @@
1
+
2
+ # Export all configuration variables in case they will be needed outside this module
3
+ # Nonetheless, the recommend way is to import the classes or functions provided
4
+ # by the module
5
+ from .config import *
6
+
7
+ from .notifier import ProjectWorkflowNotifier
8
+ from .repository import WorkflowRepository
@@ -0,0 +1,8 @@
1
+ # Variable related to the online services (workflow stats and repository) that Scipion reports to or communicate.
2
+ # These variables may change between Scipion versions but do not depend on the user or system
3
+
4
+
5
+ # Web that handles workflows
6
+ WORKFLOW_REPOSITORY_SERVER = 'https://workflows.scipion.i2pc.es/'
7
+ WORKFLOW_PROG_STEP1 = 'workflowProgStep1_add/'
8
+ WORKFLOW_PROG_STEP2 = 'workflowProgStep2_add/'
@@ -0,0 +1,152 @@
1
+ # **************************************************************************
2
+ # *
3
+ # * Authors: Roberto Marabini (roberto@cnb.csic.es)
4
+ # J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
5
+ # *
6
+ # * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
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, write to the Free Software
20
+ # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21
+ # * 02111-1307 USA
22
+ # *
23
+ # * All comments concerning this program package may be sent to the
24
+ # * e-mail address 'scipion@cnb.csic.es'
25
+ # *
26
+ # **************************************************************************
27
+
28
+
29
+ import os
30
+ import time
31
+ import threading
32
+ import uuid
33
+ from datetime import timedelta, datetime
34
+ from urllib.parse import urlencode
35
+ from urllib.request import build_opener, HTTPHandler
36
+
37
+ import pyworkflow.utils as pwutils
38
+ from pyworkflow import Config
39
+
40
+
41
+
42
+ class ProjectWorkflowNotifier(object):
43
+ """ Implement different types of notifications about a given
44
+ project. Currently, the protocols in a workflow will be sent.
45
+ """
46
+
47
+ def __init__(self, project):
48
+ self.project = project
49
+
50
+ def _getUuidFileName(self):
51
+ return self.project.getLogPath("uuid.log")
52
+
53
+ def _getDataFileName(self, fileName="data.log"):
54
+ return self.project.getLogPath(fileName)
55
+
56
+ def _getUuid(self):
57
+ # Load (or create if not exits) a file
58
+ # in the project Logs folder to store an unique
59
+ # project identifier
60
+ uuidFn = self._getUuidFileName()
61
+ try:
62
+ with open(uuidFn) as f:
63
+ uuidValue = f.readline()
64
+ except IOError:
65
+ uuidValue = str(uuid.uuid4())
66
+ with open(uuidFn, 'w') as f:
67
+ f.write(uuidValue)
68
+
69
+ return uuidValue
70
+
71
+ def _modifiedBefore(self, seconds):
72
+ """ Return True if the uuid.log file has been modified within a given
73
+ number of seconds. """
74
+ uuidFn = self._getUuidFileName()
75
+ if not os.path.exists(uuidFn):
76
+ return False
77
+ mTime = datetime.fromtimestamp(os.path.getmtime(uuidFn))
78
+ delta = datetime.now() - mTime
79
+
80
+ return delta < timedelta(seconds=seconds)
81
+
82
+ def _sendData(self, url, project_workflow):
83
+ try:
84
+ # then connect to webserver a send json
85
+ # set debuglevel=0 for no messages
86
+
87
+ dataDict = {'project_uuid': self._getUuid(),
88
+ 'project_workflow': project_workflow}
89
+
90
+ opener = build_opener(HTTPHandler(debuglevel=0))
91
+ data = urlencode(dataDict).encode()
92
+ opener.open(url, data=data).read()
93
+
94
+ # Store file time stamp with last time it was sent
95
+ now = time.time()
96
+ os.utime(self._getUuidFileName(), (now, now))
97
+
98
+ # Write what was sent in a file for _modifiedBefore to check file TS and avoid resending stats
99
+ dataFile = self._getDataFileName()
100
+ # create the folder of the file path if not exists
101
+ pwutils.makeFilePath(dataFile)
102
+ with open(dataFile, 'w') as f:
103
+ f.write(project_workflow)
104
+
105
+ except Exception as e:
106
+ # Tolerate errors
107
+ pass
108
+
109
+ def _dataModified(self, projectWorfklow):
110
+ try:
111
+ with open(self._getDataFileName()) as f:
112
+ projectWorfklow2 = f.readline()
113
+ if projectWorfklow2 == projectWorfklow:
114
+ return False
115
+ except IOError:
116
+ pass
117
+ return True
118
+
119
+ def notifyWorkflow(self):
120
+
121
+ try:
122
+ # check if environment exists otherwise abort
123
+ if not Config.SCIPION_NOTIFY:
124
+ return
125
+
126
+ # if project specifies not to send stats
127
+ if self._isProjectMuted():
128
+ return
129
+
130
+ # Check the seconds range of the notify, by default one day
131
+ seconds = int(os.environ.get('SCIPION_NOTIFY_SECONDS', '86400'))
132
+
133
+ if self._modifiedBefore(seconds): # notify not more than once a day
134
+ return
135
+
136
+ # INFO: now we are only sending the protocols names in the project.
137
+ # We could pass namesOnly=False to get the full workflow template
138
+ project_workflow = self.project.getProjectUsage().toJSON() # self.project.getProtocolsJson(namesOnly=True)
139
+
140
+ urlName = Config.SCIPION_STATS_WORKFLOW_APP.strip()
141
+ urlName += "addOrUpdateWorkflow/"
142
+ t = threading.Thread(name="notifier", target=lambda: self._sendData(urlName, project_workflow))
143
+ t.start() # will execute function in a separate thread
144
+ except Exception as e:
145
+ print("Can't report usage: ", e)
146
+
147
+ def _isProjectMuted(self):
148
+ """ Projects are muted if they come from tests, Since there is no flag for it
149
+ we will assume that if the project name starts with Test it will be considered
150
+ a test and therefore no statistics will be sent"""
151
+ return os.path.basename(self.project.name).startswith("Test")
152
+
@@ -0,0 +1,59 @@
1
+ import webbrowser
2
+ import json
3
+ import requests
4
+
5
+ from pyworkflow import CORE_VERSION
6
+ from . import config
7
+
8
+
9
+ class WorkflowRepository(object):
10
+ """ Manager to communicate with the workflow repository services.
11
+ It will provide functions to:
12
+ - Search workflows (open the url in a browser).
13
+ - Upload a given workflow json file.
14
+ """
15
+ def __init__(self,
16
+ repositoryUrl=config.WORKFLOW_REPOSITORY_SERVER,
17
+ uploadFileSuffix=config.WORKFLOW_PROG_STEP1,
18
+ uploadMdSuffix=config.WORKFLOW_PROG_STEP2):
19
+ self._url = repositoryUrl
20
+ self._uploadFileUrl = repositoryUrl + uploadFileSuffix
21
+ self._uploadMdUrl = repositoryUrl + uploadMdSuffix
22
+
23
+ def search(self):
24
+ """ Open the repository URL in a web browser. """
25
+ webbrowser.open(self._url)
26
+
27
+ def upload(self, jsonFileName):
28
+ """ Upload a given workflow providing the path ot the json file.
29
+
30
+ First the file is uploaded, then the metadata is uploaded.
31
+ The script uploads the file and then opens a browser for the metadata
32
+ Note that the two steps are needed since no initial value can be passed
33
+ to a file field. poster3 module is needed. Poster3 is pure python
34
+ so it may be added to the directory rather than installed if needed.
35
+
36
+ The server is django a uses filefield and csrf_exempt.
37
+ csrf_exempt disable csrf checking. filefield
38
+ """
39
+
40
+ # we are going to upload a file so this is a multipart
41
+ # connection
42
+ with open(jsonFileName, "rb") as workflowFile:
43
+ file_dict = {"json": workflowFile}
44
+ response = requests.post(self._uploadFileUrl, files=file_dict)
45
+
46
+ # server returns a json stored as text at response.text
47
+ _dict = json.loads(response.text)
48
+
49
+ version = CORE_VERSION
50
+ # version hack end
51
+
52
+ fnUrl = "?jsonFileName=%s&versionInit=%s" % (_dict['jsonFileName'],
53
+ version) # use GET
54
+ # open browser to fill metadata, fileName will be saved as session
55
+ # variable. Note that I cannot save the file never in the
56
+ # session in the first connection because the browser changes
57
+ # from urlib2 to an actual browser
58
+ # so sessions are different
59
+ webbrowser.open(self._uploadMdUrl + fnUrl)
@@ -0,0 +1,74 @@
1
+ import json
2
+ from urllib.request import Request, urlopen
3
+ from pyworkflow.template import Template
4
+
5
+
6
+ class WHTemplate(Template):
7
+
8
+ def __init__(self, source, name, description, url):
9
+ super().__init__(source, name, description)
10
+ self.url = url
11
+
12
+ def loadContent(self):
13
+ """ Download the file pointer by url and read the content"""
14
+
15
+ return make_request(self.url, asJson=False)
16
+
17
+
18
+ def make_request(url, asJson=True):
19
+ """ Makes a request to the url and returns the json as a dictionary"""
20
+
21
+ req = Request(url)
22
+ req.add_header("accept", "application/json")
23
+
24
+ with urlopen(req, timeout=1) as response:
25
+ if asJson:
26
+ data = json.load(response)
27
+ else:
28
+ html_response = response.read()
29
+ encoding = response.headers.get_content_charset('utf-8')
30
+ data = html_response.decode(encoding)
31
+ return data
32
+
33
+
34
+ def get_workflow_file_url(workflow_id, version):
35
+ root_url = "https://workflowhub.eu/workflows/%s/git/%s/" % (workflow_id, version)
36
+ url = root_url + 'tree'
37
+
38
+ result = make_request(url)
39
+
40
+ for file in result["tree"]:
41
+ path = (file["path"])
42
+ if path.endswith(".json.template"):
43
+ return root_url + 'raw/' + path
44
+
45
+
46
+ def get_wh_templates(template_id=None, organization="Scipion%20CNB"):
47
+ """ Returns a list of scipion templates available in workflow hub"""
48
+
49
+ url = "https://workflowhub.eu/ga4gh/trs/v2/tools?organization=%s" % organization
50
+
51
+ response = make_request(url)
52
+
53
+ template_list = []
54
+
55
+ for workflow in response:
56
+ workflow_id = workflow["id"]
57
+ name = workflow["name"]
58
+ description = workflow['description']
59
+ version = workflow["versions"][-1]
60
+ version_id = version["id"]
61
+ template_url = get_workflow_file_url(workflow_id, version_id)
62
+
63
+ new_template = WHTemplate("Workflow hub", name, description, template_url)
64
+ if template_id is None or new_template.getObjId() == template_id:
65
+ template_list.append(new_template)
66
+
67
+ return template_list
68
+
69
+
70
+ if __name__ == "__main__":
71
+
72
+ templates = get_wh_templates()
73
+ for template in templates:
74
+ print(template)
File without changes
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python
2
+ # To run only the tests in this file, use:
3
+ # python -m unittest test_canvas -v
4
+
5
+ import pyworkflow.gui.canvas
6
+ import tkinter
7
+ import math
8
+ from pyworkflow.tests import *
9
+
10
+
11
+ class TestCanvas(BaseTest):
12
+ # IMPORTANT: Tk requires at least that DISPLAY is defined
13
+ # hence in some environments (like buildbot) the test may fail,
14
+ # check for the TclError exception
15
+
16
+ _labels = [PULL_REQUEST]
17
+
18
+ @classmethod
19
+ def setUpClass(cls):
20
+ setupTestOutput(cls)
21
+
22
+ def distance(self, c1, c2):
23
+ return round(math.hypot(c2[0] - c1[0], c2[1] - c1[1]), 2)
24
+
25
+ def allEqual(self, values):
26
+ return not values or values.count(values[0]) == len(values)
27
+
28
+ def allDifferent(self, values):
29
+ return not values or len(values) == len(set(values))
30
+
31
+ def test_connectorsCoords(self):
32
+ try:
33
+ root = tkinter.Tk()
34
+ canvas = pyworkflow.gui.canvas.Canvas(root, width=800, height=600)
35
+ tb1 = canvas.createTextbox("First", 100, 100, "blue")
36
+
37
+ connectorsCoords = tb1.getConnectorsCoordinates()
38
+ self.assertTrue(self.allDifferent(connectorsCoords))
39
+
40
+ print(connectorsCoords)
41
+
42
+ distances = {}
43
+ for i in range(len(connectorsCoords) - 1):
44
+ distances[i] = self.distance(connectorsCoords[i],
45
+ connectorsCoords[i + 1])
46
+
47
+ print(distances)
48
+ self.assertTrue(self.allEqual(list(distances.values())))
49
+ self.assertNotEqual(distances[0], 0)
50
+ except tkinter.TclError as ex:
51
+ print(ex)
52
+
53
+ def test_closestConnectors(self):
54
+ try:
55
+ root = tkinter.Tk()
56
+ canvas = pyworkflow.gui.canvas.Canvas(root, width=800, height=600)
57
+ tb1 = canvas.createTextbox("Textbox1", 100, 100, "blue")
58
+ tb2 = canvas.createTextbox("Textbox2", 300, 100, "blue")
59
+ tb3 = canvas.createTextbox("Textbox3", 100, 300, "blue")
60
+
61
+ tb1ConnectorsCoords = tb1.getConnectorsCoordinates()
62
+ tb2ConnectorsCoords = tb2.getConnectorsCoordinates()
63
+ tb3ConnectorsCoords = tb3.getConnectorsCoordinates()
64
+ c1, c2 = pyworkflow.gui.canvas.findStrictClosestConnectors(tb1, tb2)
65
+ c3, c4 = pyworkflow.gui.canvas.findStrictClosestConnectors(tb1, tb3)
66
+ # tb1 and tb2 are aligned vertically. tb1 and tb3, horizontally.
67
+ # So, their closest connectors must share one coordinate (y in case of c1&c2, x i n case of c3&c4)
68
+ self.assertEqual(c1[1], c2[1])
69
+ self.assertEqual(c3[0], c4[0])
70
+
71
+ except tkinter.TclError as ex:
72
+ print(ex)
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python
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
+
26
+ import pyworkflow.tests as pwtests
27
+ import pyworkflowtests.objects as objectsMod
28
+ from pyworkflowtests import Domain
29
+
30
+
31
+ class TestDomain(pwtests.BaseTest):
32
+
33
+ def test_objects(self):
34
+ """ Test that all objects are properly discovered. """
35
+ objects = Domain.getObjects()
36
+ for k in dir(objectsMod):
37
+ v = getattr(objectsMod, k)
38
+ if isinstance(v, objectsMod.MockObject):
39
+ self.assertEqual(objects[k], v)
40
+
41
+ def test_viewers(self):
42
+ pass
43
+
44
+ def test_wizards(self):
45
+ pass