1
2 """
3 Scripting types
4 - Classes:
5 - Type -- Top of parameter type hierarchy
6 - Long --
7 - String --
8 - Bool --
9 - List
10 - Map
11 - Set
12 - Functions:
13 - client -- Produces an omero.client object with given input/output constraints.
14
15 Copyright 2008 Glencoe Software, Inc. All rights reserved.
16 Use is subject to license terms supplied in LICENSE.txt
17
18 """
19
20 import os
21 import Ice
22 import logging
23 import exceptions
24
25 import omero
26 import omero_Scripts_ice
27 import omero.callbacks
28 import omero.util.concurrency
29 import omero.util.temp_files
30
31 from omero.rtypes import *
32
33
34 TYPE_LOG = logging.getLogger("omero.scripts.Type")
35 PROC_LOG = logging.getLogger("omero.scripts.ProcessCallback")
36
37
38 -class Type(omero.grid.Param):
39 """
40 omero.grid.Param subclass which provides convenience methods for input/output specification.
41 Further subclasses are responsible for creating proper prototypes.
42
43 kwargs
44 """
45 PROTOTYPE_FUNCTION = None
46 PROTOTYPE_DEFAULT = None
47 PROTOTYPE_MIN = None
48 PROTOTYPE_MAX = None
49 PROTOTYPE_VALUES = None
50
51 - def __init__(self, name, optional = True, out = False, description = None, default = None, **kwargs):
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
160
167
168
175
176
183
184
191
192
199
200
207
208
215
216
223
224
231
232
234 """
235 Base type providing the append and extend functionality.
236 Not for user use.
237 """
238 PROTOTYPE_DEFAULT = list
239
241 self.prototype.val.append(*arg)
242 return self
243
245 self.prototype.val.extend(*arg)
246 return self
247
249 if callable(obj):
250 obj = obj()
251
252
253
254 if self.useDefault and self.prototype.val:
255 if not isinstance(obj, self.prototype.val[0].__class__):
256 raise ValueError("ofType values doesn't match default value: %s <> %s" % (unwrap(obj), unwrap(self.prototype.val[0])))
257 else:
258 self.prototype.val.append(wrap(obj))
259
260 return self
261
263 """
264 Wraps an rset. To add values to the contents of the set,
265 use "append" or "extend" since set.val is of type list.
266 """
267 PROTOTYPE_FUNCTION = rset
268
269
271 """
272 Wraps an rlist. To add values to the contents of the list,
273 use "append" or "extend" since set.val is of type list.
274 """
275 PROTOTYPE_FUNCTION = rlist
276
277
279 """
280 Wraps an rmap. To add values to the contents of the map,
281 use "update" since map.val is of type dict.
282 """
283 PROTOTYPE_FUNCTION = rmap
284 PROTOTYPE_DEFAULT = dict
285
286 - def update(self, *args, **kwargs):
287 self.prototype.val.update(*args, **kwargs)
288 return self
289
290
292 """
293 Raised when this script should just parse parameters and return.
294 """
295
299
300
302 """
303 Entry point for all script engine scripts.
304
305 Typical usage consists of::
306
307 client = omero.scripts.client("name","description", \
308 omero.scripts.Long("name"),...)
309
310 where the returned client is created via the empty constructor to omero.client
311 using only --Ice.Config or ICE_CONFIG, and the function arguments are taken
312 as metdata about the current script. With this information, all script
313 consumers should be able to determine the required types for execution.
314
315 Possible types are all subclasses of omero.scripts.Type
316
317 To change the omero.model.Format of the stdout and stderr produced by
318 this script, use the constructor arguments::
319
320 client = omero.scripts.client(..., \
321 stdoutFormat = "text/plain",
322 stderrFormat = "text/plain")
323
324 If you would like to prevent stdout and/or stderr from being
325 uploaded, set the corresponding value to None. If you would like
326 to overwrite the value with another file, use
327 client.setOutput(). Though it is possible to attach any RType to
328 "stdout" or "stderr", there is an assumption that the value will
329 be an robject(OriginalFileI())
330
331 Providing your own client is possible via the kwarg "client = ...",
332 but be careful since this may break usage with the rest of the
333 scripting framework. The client should not have a session, and
334 must be configured for the argumentless version of createSession()
335 """
336
337 args = list(args)
338 if len(args) >= 1:
339 if isinstance(args[0], str):
340 kwargs["name"] = args.pop(0)
341 if len(args) >= 1:
342 if isinstance(args[0], str):
343 kwargs["description"] = args.pop(0)
344
345 if not kwargs.has_key("client"):
346 kwargs["client"] = omero.client()
347 c = kwargs["client"]
348 c.setAgent("OMERO.scripts")
349
350 if args and isinstance(args[0], omero.grid.JobParams):
351 c.params = args.pop(0)
352 else:
353 c.params = omero.grid.JobParams()
354 c.params.inputs = {}
355 c.params.outputs = {}
356
357 for k, v in kwargs.items():
358 if hasattr(c.params, k):
359 setattr(c.params, k, v)
360
361 if not c.params.stdoutFormat:
362 c.params.stdoutFormat = "text/plain"
363
364 if not c.params.stderrFormat:
365 c.params.stderrFormat = "text/plain"
366
367 for p in args:
368 if isinstance(p, Type):
369 if p._in:
370 c.params.inputs[p._name] = p
371 if p._out:
372 c.params.outputs[p._name] = p
373 else:
374 raise ValueError("Not Type: %s" % type(p))
375
376 handleParse(c)
377
378 c.createSession().detachOnDestroy()
379 return c
380
382 """
383 Raises ParseExit if the client has the configuration property
384 "omero.scripts.parse". If the value is anything other than "only",
385 then the parameters will also be sent to the server.
386 """
387 parse = c.getProperty("omero.scripts.parse")
388 if len(parse) > 0:
389 if parse != "only":
390 c.createSession().detachOnDestroy()
391 c.setOutput("omero.scripts.parse", rinternal(c.params))
392 raise ParseExit(c.params)
393
394 -def parse_text(scriptText):
395 """
396 Parses the given script text with "omero.scripts.parse" set
397 and catches the exception. The parameters are returned.
398
399 WARNING: This method calls "exec" on the given text.
400 Do NOT use this on data you don't trust.
401 """
402 try:
403 cfg = omero.util.temp_files.create_path()
404 cfg.write_lines(["omero.scripts.parse=only", "omero.host=localhost"])
405 old = os.environ.get("ICE_CONFIG")
406 try:
407 os.environ["ICE_CONFIG"] = cfg.abspath()
408 exec(scriptText, {"__name__":"__main__"})
409 raise exceptions.Exception("Did not throw ParseExit: %s" % scriptText)
410 finally:
411 if old:
412 os.environ["ICE_CONFIG"] = old
413 except ParseExit, exit:
414 return exit.params
415
417 """
418 Parses the given script file with "omero.scripts.parse" set
419 and catches the exception. The parameters are returned.
420
421 WARNING: This method calls "exec" on the given file's contents.
422 Do NOT use this on data you don't trust.
423 """
424 from path import path
425 scriptText = path(filename).text()
426 return parse_text(scriptText)
427
428
433
434
462
463
494
495
497 """
498 Walks through the inputs of the given JobParams
499 and returns a map-of-maps with Param names as
500 the leaf nodes.
501
502 For example, for the following:
503
504 Params("1", grouping = "A") # "A." is equivalent
505 Params("2", grouping = "A.B")
506 Params("3", grouping = "A.C")
507
508 this function returns:
509
510 {"A" {"": "1" : "B" : "2", "C" : "3"} }
511
512 while:
513
514 Params("1", grouping = "A")
515
516 returns:
517
518 {"A" : "1"}
519
520 """
521 groupings = dict()
522 for k, v in params.inputs.items():
523
524 val = v.grouping
525 if not val.endswith("."):
526 val = val + "."
527
528 parts = val.split(".")
529
530 g = groupings
531 while parts:
532 p = parts.pop(0)
533 try:
534 g = g[p]
535 except KeyError:
536 if parts:
537 g[p] = dict()
538 g = g[p]
539 else:
540 g[p] = k
541
542
543
544 tuples = [(groupings, k, v) for k, v in groupings.items()]
545 while tuples:
546 new_tuples = []
547 for g, k, v in tuples:
548 if isinstance(v, dict):
549 if len(v) == 1 and "" in v:
550 g[k] = v[""]
551 else:
552 new_tuples.extend([(v, k2, v2) for k2, v2 in v.items()])
553 tuples = new_tuples
554
555 return groupings
556
557 -def error_msg(category, key, format_string, *args):
558 c = "%s" % (category.upper())
559 s = """%s for "%s": %s\n""" % (c, key, format_string)
560 return s % args
561
563
564 if cache is None:
565 cache = {}
566
567 if id(proto) in cache and id(input) in cache:
568 return ""
569 else:
570 cache[id(proto)] = True
571 cache[id(input)] = True
572
573 itype = input is None and None or input.__class__
574 ptype = proto is None and None or proto.__class__
575
576 if not isinstance(input, ptype):
577 return error_msg("Wrong type", key, "%s != %s", itype, ptype)
578
579
580 errors = ""
581 if isinstance(proto, omero.RMap) and len(proto.val) > 0:
582 for x in input.val.values():
583 errors += compare_proto(key, proto.val.values()[0], x, cache)
584 elif isinstance(proto, omero.RCollection) and len(proto.val) > 0:
585 for x in input.val:
586 errors += compare_proto(key, proto.val[0], x, cache)
587 return errors
588
590 if input is None:
591 items = []
592 elif isinstance(input, (list, tuple)):
593 items = list(input)
594 elif isinstance(input, dict):
595 items = input.values()
596 else:
597 items = [input]
598 return items
599
616
618 errors = ""
619
620
621 values = unwrap(values)
622 input = unwrap(input)
623 items = expand(input)
624 values = expand(values)
625
626 if not values:
627 return errors
628
629 for x in items:
630 if x not in values:
631 errors += error_msg("Value list", key, "%s not in %s", x, values)
632
633 return errors
634
663
670
671
672
673
674 ProcessCallbackI = omero.callbacks.ProcessCallbackI
675
676 -def wait(client, process, ms = 500):
683