1
2
3 """
4 Scripting types
5 - Classes:
6 - Type -- Top of parameter type hierarchy
7 - Long --
8 - String --
9 - Bool --
10 - List
11 - Map
12 - Set
13 - Functions:
14 - client -- Produces an omero.client object with given input/output constraints.
15
16 Copyright 2008 Glencoe Software, Inc. All rights reserved.
17 Use is subject to license terms supplied in LICENSE.txt
18
19 """
20
21 import os
22 import Ice
23 import logging
24
25 import omero
26 import omero.callbacks
27 import omero.util.concurrency
28 import omero.util.temp_files
29
30 from omero.rtypes import *
31
32
33 TYPE_LOG = logging.getLogger("omero.scripts.Type")
34 PROC_LOG = logging.getLogger("omero.scripts.ProcessCallback")
35
36
37 -class Type(omero.grid.Param):
38 """
39 omero.grid.Param subclass which provides convenience methods for input/output specification.
40 Further subclasses are responsible for creating proper prototypes.
41
42 kwargs
43 """
44 PROTOTYPE_FUNCTION = None
45 PROTOTYPE_DEFAULT = None
46 PROTOTYPE_MIN = None
47 PROTOTYPE_MAX = None
48 PROTOTYPE_VALUES = None
49
50 - def __init__(self, name, optional = True, out = False, description = None, default = None, **kwargs):
51
52
53 omero.grid.Param.__init__(self)
54
55
56 self._name = name
57 self._in = True
58 self._out = out
59
60
61
62 self.description = description
63 self.optional = optional
64
65
66 for k, v in kwargs.items():
67 if not hasattr(self, k):
68 TYPE_LOG.warn("Unknown property: %s", k)
69 setattr(self, k, v)
70
71 _DEF = self.__get(self.PROTOTYPE_DEFAULT, False)
72 _FUN = self.__get(self.PROTOTYPE_FUNCTION)
73 _MAX = self.__get(self.PROTOTYPE_MAX)
74 _MIN = self.__get(self.PROTOTYPE_MIN)
75 _VAL = self.__get(self.PROTOTYPE_VALUES)
76
77
78
79 if default is not None:
80 newfunc = _FUN
81 newdefault = default
82 if isinstance(self, List):
83 if isinstance(default, (list, tuple)):
84 newdefault = wrap(default).val
85 elif isinstance(default, omero.RCollection):
86 newfunc = lambda x: x
87 elif isinstance(default, omero.RType):
88 default = [default]
89 else:
90 newfunc = lambda x: x
91 newdefault = rlist([rtype(default)])
92 self.useDefault = True
93 self.prototype = newfunc(newdefault)
94 else:
95 if not callable(_FUN):
96 raise ValueError("Bad prototype function: %s" % _FUN)
97
98
99
100
101 try:
102 _def = _DEF()
103 except TypeError:
104 _def = _DEF
105 self.prototype = _FUN(_def)
106
107
108 if self.min is not None:
109 if _MIN is None:
110 self.min = _FUN(self.min)
111 else:
112 self.min = _MIN(self.min)
113
114 if self.max is not None:
115 if _MAX is None:
116 self.max = _FUN(self.max)
117 else:
118 self.max = _MAX(self.max)
119
120 if self.values is not None:
121 if _VAL is None:
122 self.values = wrap(self.values)
123 else:
124 self.values = _VAL(self.values)
125
126
127
128
129 if self.values is not None and self.values and self.useDefault:
130 if isinstance(self.prototype, omero.RCollection):
131 test = unwrap(self.prototype.val[0])
132 else:
133 test = unwrap(self.prototype)
134 values = unwrap(self.values)
135 if test not in values:
136 raise ValueError("%s is not in %s" % (test, values))
137
138
140 self._in = False
141 self._out = True
142 return self
143
145 self._in = True
146 self._out = True
147 return self
148
149 - def type(self, *arg):
150 self.prototype = wrap(arg)
151 return self
152
153 - def __get(self, val, func = True):
154 if val is not None:
155 if func:
156 return val.im_func
157 else:
158 return val
159
166
167
174
175
182
183
190
191
198
199
206
207
214
215
222
223
230
231
238
239
241 """
242 Base type providing the append and extend functionality.
243 Not for user use.
244 """
245 PROTOTYPE_DEFAULT = list
246
248 self.prototype.val.append(*arg)
249 return self
250
252 self.prototype.val.extend(*arg)
253 return self
254
256 if callable(obj):
257 obj = obj()
258
259
260
261 if self.useDefault and self.prototype.val:
262 if not isinstance(obj, self.prototype.val[0].__class__):
263 raise ValueError("ofType values doesn't match default value: %s <> %s" % (unwrap(obj), unwrap(self.prototype.val[0])))
264 else:
265 self.prototype.val.append(wrap(obj))
266
267 return self
268
270 """
271 Wraps an rset. To add values to the contents of the set,
272 use "append" or "extend" since set.val is of type list.
273 """
274 PROTOTYPE_FUNCTION = rset
275
276
278 """
279 Wraps an rlist. To add values to the contents of the list,
280 use "append" or "extend" since set.val is of type list.
281 """
282 PROTOTYPE_FUNCTION = rlist
283
284
286 """
287 Wraps an rmap. To add values to the contents of the map,
288 use "update" since map.val is of type dict.
289 """
290 PROTOTYPE_FUNCTION = rmap
291 PROTOTYPE_DEFAULT = dict
292
293 - def update(self, *args, **kwargs):
294 self.prototype.val.update(*args, **kwargs)
295 return self
296
297
299 """
300 Raised when this script should just parse parameters and return.
301 """
302
306
307
309 """
310 Entry point for all script engine scripts.
311
312 Typical usage consists of::
313
314 client = omero.scripts.client("name","description", \
315 omero.scripts.Long("name"),...)
316
317 where the returned client is created via the empty constructor to omero.client
318 using only --Ice.Config or ICE_CONFIG, and the function arguments are taken
319 as metdata about the current script. With this information, all script
320 consumers should be able to determine the required types for execution.
321
322 Possible types are all subclasses of omero.scripts.Type
323
324 To change the omero.model.Format of the stdout and stderr produced by
325 this script, use the constructor arguments::
326
327 client = omero.scripts.client(..., \
328 stdoutFormat = "text/plain",
329 stderrFormat = "text/plain")
330
331 If you would like to prevent stdout and/or stderr from being
332 uploaded, set the corresponding value to None. If you would like
333 to overwrite the value with another file, use
334 client.setOutput(). Though it is possible to attach any RType to
335 "stdout" or "stderr", there is an assumption that the value will
336 be an robject(OriginalFileI())
337
338 Providing your own client is possible via the kwarg "client = ...",
339 but be careful since this may break usage with the rest of the
340 scripting framework. The client should not have a session, and
341 must be configured for the argumentless version of createSession()
342 """
343
344 args = list(args)
345 if len(args) >= 1:
346 if isinstance(args[0], str):
347 kwargs["name"] = args.pop(0)
348 if len(args) >= 1:
349 if isinstance(args[0], str):
350 kwargs["description"] = args.pop(0)
351
352 if not kwargs.has_key("client"):
353 kwargs["client"] = omero.client()
354 c = kwargs["client"]
355 c.setAgent("OMERO.scripts")
356
357 if args and isinstance(args[0], omero.grid.JobParams):
358 c.params = args.pop(0)
359 else:
360 c.params = omero.grid.JobParams()
361 c.params.inputs = {}
362 c.params.outputs = {}
363
364 for k, v in kwargs.items():
365 if hasattr(c.params, k):
366 setattr(c.params, k, v)
367
368 if not c.params.stdoutFormat:
369 c.params.stdoutFormat = "text/plain"
370
371 if not c.params.stderrFormat:
372 c.params.stderrFormat = "text/plain"
373
374 for p in args:
375 if isinstance(p, Type):
376 if p._in:
377 c.params.inputs[p._name] = p
378 if p._out:
379 c.params.outputs[p._name] = p
380 else:
381 raise ValueError("Not Type: %s" % type(p))
382
383 handleParse(c)
384
385 c.createSession().detachOnDestroy()
386 return c
387
389 """
390 Raises ParseExit if the client has the configuration property
391 "omero.scripts.parse". If the value is anything other than "only",
392 then the parameters will also be sent to the server.
393 """
394 parse = c.getProperty("omero.scripts.parse")
395 if len(parse) > 0:
396 if parse != "only":
397 c.createSession().detachOnDestroy()
398 c.setOutput("omero.scripts.parse", rinternal(c.params))
399 raise ParseExit(c.params)
400
401 -def parse_text(scriptText):
402 """
403 Parses the given script text with "omero.scripts.parse" set
404 and catches the exception. The parameters are returned.
405
406 WARNING: This method calls "exec" on the given text.
407 Do NOT use this on data you don't trust.
408 """
409 try:
410 cfg = omero.util.temp_files.create_path()
411 cfg.write_lines(["omero.scripts.parse=only", "omero.host=localhost"])
412 old = os.environ.get("ICE_CONFIG")
413 try:
414 os.environ["ICE_CONFIG"] = cfg.abspath()
415 exec(scriptText, {"__name__":"__main__"})
416 raise Exception("Did not throw ParseExit: %s" % scriptText)
417 finally:
418 if old:
419 os.environ["ICE_CONFIG"] = old
420 except ParseExit, exit:
421 return exit.params
422
424 """
425 Parses the given script file with "omero.scripts.parse" set
426 and catches the exception. The parameters are returned.
427
428 WARNING: This method calls "exec" on the given file's contents.
429 Do NOT use this on data you don't trust.
430 """
431 from path import path
432 scriptText = path(filename).text()
433 return parse_text(scriptText)
434
435
440
441
470
471
518
519
521 """
522 Walks through the inputs of the given JobParams
523 and returns a map-of-maps with Param names as
524 the leaf nodes.
525
526 For example, for the following:
527
528 Params("1", grouping = "A") # "A." is equivalent
529 Params("2", grouping = "A.B")
530 Params("3", grouping = "A.C")
531
532 this function returns:
533
534 {"A" {"": "1" : "B" : "2", "C" : "3"} }
535
536 while:
537
538 Params("1", grouping = "A")
539
540 returns:
541
542 {"A" : "1"}
543
544 """
545 groupings = dict()
546 for k, v in params.inputs.items():
547
548 val = v.grouping
549 if not val.endswith("."):
550 val = val + "."
551
552 parts = val.split(".")
553
554 g = groupings
555 while parts:
556 p = parts.pop(0)
557 try:
558 g = g[p]
559 except KeyError:
560 if parts:
561 g[p] = dict()
562 g = g[p]
563 else:
564 g[p] = k
565
566
567
568 tuples = [(groupings, k, v) for k, v in groupings.items()]
569 while tuples:
570 new_tuples = []
571 for g, k, v in tuples:
572 if isinstance(v, dict):
573 if len(v) == 1 and "" in v:
574 g[k] = v[""]
575 else:
576 new_tuples.extend([(v, k2, v2) for k2, v2 in v.items()])
577 tuples = new_tuples
578
579 return groupings
580
581 -def error_msg(category, key, format_string, *args):
582 c = "%s" % (category.upper())
583 s = """%s for "%s": %s\n""" % (c, key, format_string)
584 return s % args
585
587
588 if cache is None:
589 cache = {}
590
591 if id(proto) in cache and id(input) in cache:
592 return ""
593 else:
594 cache[id(proto)] = True
595 cache[id(input)] = True
596
597 itype = input is None and None or input.__class__
598 ptype = proto is None and None or proto.__class__
599
600 if not isinstance(input, ptype):
601 return error_msg("Wrong type", key, "%s != %s", itype, ptype)
602
603
604 errors = ""
605 if isinstance(proto, omero.RMap) and len(proto.val) > 0:
606 for x in input.val.values():
607 errors += compare_proto(key, proto.val.values()[0], x, cache)
608 elif isinstance(proto, omero.RCollection) and len(proto.val) > 0:
609 for x in input.val:
610 errors += compare_proto(key, proto.val[0], x, cache)
611 return errors
612
623
640
658
687
694
695
696
697
698 ProcessCallbackI = omero.callbacks.ProcessCallbackI
699
700 -def wait(client, process, ms = 500):
707