Package omero :: Module scripts
[hide private]
[frames] | no frames]

Source Code for Module omero.scripts

  1  #!/usr/bin/env python 
  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 uuid 
 23  import logging 
 24  import exceptions 
 25   
 26  import omero 
 27  import omero_Scripts_ice 
 28  import omero.callbacks 
 29  import omero.util.concurrency 
 30  import omero.util.temp_files 
 31   
 32  from omero.rtypes import * 
 33   
 34   
 35  TYPE_LOG = logging.getLogger("omero.scripts.Type") 
 36  PROC_LOG = logging.getLogger("omero.scripts.ProcessCallback") 
 37   
 38   
39 -class Type(omero.grid.Param):
40 """ 41 omero.grid.Param subclass which provides convenience methods for input/output specification. 42 Further subclasses are responsible for creating proper prototypes. 43 44 kwargs 45 """ 46 PROTOTYPE_FUNCTION = None 47 PROTOTYPE_DEFAULT = None 48 PROTOTYPE_MIN = None 49 PROTOTYPE_MAX = None 50 PROTOTYPE_VALUES = None 51
52 - def __init__(self, name, optional = True, out = False, description = None, default = None, **kwargs):
53 54 omero.grid.Param.__init__(self) 55 56 # Non-Param attributes 57 self._name = name 58 self._in = True 59 self._out = out 60 61 # Other values will be filled in by the kwargs 62 # Mostly leaving these for backwards compatibility 63 self.description = description 64 self.optional = optional 65 66 # Assign all the kwargs 67 for k, v in kwargs.items(): 68 if not hasattr(self, k): 69 TYPE_LOG.warn("Unknown property: %s", k) 70 setattr(self, k, v) 71 72 _DEF = self.__get(self.PROTOTYPE_DEFAULT, False) 73 _FUN = self.__get(self.PROTOTYPE_FUNCTION) 74 _MAX = self.__get(self.PROTOTYPE_MAX) 75 _MIN = self.__get(self.PROTOTYPE_MIN) 76 _VAL = self.__get(self.PROTOTYPE_VALUES) 77 78 # Someone specifically set the prototype, then 79 # we assume that useDefault should be True 80 if default is not None: # For whatever reason, inheritance isn't working. 81 newfunc = _FUN 82 newdefault = default 83 if isinstance(self, List): 84 if isinstance(default, (list, tuple)): 85 newdefault = wrap(default).val 86 elif isinstance(default, omero.RCollection): 87 newfunc = lambda x: x 88 elif isinstance(default, omero.RType): 89 default = [default] 90 else: 91 newfunc = lambda x: x 92 newdefault = rlist([rtype(default)]) 93 self.useDefault = True 94 self.prototype = newfunc(newdefault) 95 else: 96 if not callable(_FUN): 97 raise ValueError("Bad prototype function: %s" % _FUN) 98 99 # To prevent weirdness, if the class default is 100 # callable, we'll assume its a constructor and 101 # create a new one to prevent modification. 102 try: 103 _def = _DEF() 104 except TypeError: 105 _def = _DEF 106 self.prototype = _FUN(_def) 107 108 # The following use wrap to guarantee that an rtype is present 109 if self.min is not None: 110 if _MIN is None: 111 self.min = _FUN(self.min) 112 else: 113 self.min = _MIN(self.min) 114 115 if self.max is not None: 116 if _MAX is None: 117 self.max = _FUN(self.max) 118 else: 119 self.max = MAX(self.max) 120 121 if self.values is not None: 122 if _VAL is None: 123 self.values = wrap(self.values) 124 else: 125 self.values = _VAL(self.values) 126 127 # Now if useDefault has been set, either manually, or 128 # via setting default="..." we check that the default 129 # value matches a value if present 130 if self.values is not None and self.values and self.useDefault: 131 if isinstance(self.prototype, omero.RCollection): 132 test = unwrap(self.prototype.val[0]) 133 else: 134 test = unwrap(self.prototype) 135 values = unwrap(self.values) 136 if test not in values: 137 raise ValueError("%s is not in %s" % (test, values))
138 139
140 - def out(self):
141 self._in = False 142 self._out = True 143 return self
144
145 - def inout(self):
146 self._in = True 147 self._out = True 148 return self
149
150 - def type(self, *arg):
151 self.prototype = wrap(arg) 152 return self
153
154 - def __get(self, val, func = True):
155 if val is not None: 156 if func: 157 return val.im_func 158 else: 159 return val
160 161
162 -class Long(Type):
163 """ 164 Wraps an rlong 165 """ 166 PROTOTYPE_FUNCTION = rlong 167 PROTOTYPE_DEFAULT = 0
168 169
170 -class Int(Type):
171 """ 172 Wraps an rint 173 """ 174 PROTOTYPE_FUNCTION = rint 175 PROTOTYPE_DEFAULT = 0
176 177
178 -class Double(Type):
179 """ 180 Wraps an rdouble 181 """ 182 PROTOTYPE_FUNCTION = rdouble 183 PROTOTYPE_DEFAULT = 0.0
184 185
186 -class Float(Type):
187 """ 188 Wraps an rfloat 189 """ 190 PROTOTYPE_FUNCTION = rfloat 191 PROTOTYPE_DEFAULT = 0.0
192 193
194 -class String(Type):
195 """ 196 Wraps an rstring 197 """ 198 PROTOTYPE_FUNCTION = rstring 199 PROTOTYPE_DEFAULT = ""
200 201
202 -class Bool(Type):
203 """ 204 Wraps an rbool 205 """ 206 PROTOTYPE_FUNCTION = rbool 207 PROTOTYPE_DEFAULT = False
208 209
210 -class Color(Type):
211 """ 212 Wraps an rinternal(Color) 213 """ 214 PROTOTYPE_FUNCTION = rinternal 215 PROTOTYPE_DEFAULT = omero.Color
216 217
218 -class Point(Type):
219 """ 220 Wraps an rinternal(Point) 221 """ 222 PROTOTYPE_FUNCTION = rinternal 223 PROTOTYPE_FUNCTION = omero.Point
224 225
226 -class Plane(Type):
227 """ 228 Wraps an rinternal(Plane) 229 """ 230 PROTOTYPE_FUNCTION = rinternal 231 PROTOTYPE_DEFAULT = omero.Plane
232 233
234 -class __Coll(Type):
235 """ 236 Base type providing the append and extend functionality. 237 Not for user use. 238 """ 239 PROTOTYPE_DEFAULT = list 240
241 - def append(self, *arg):
242 self.prototype.val.append(*arg) 243 return self
244
245 - def extend(self, *arg):
246 self.prototype.val.extend(*arg) 247 return self
248
249 - def ofType(self, obj):
250 if callable(obj): 251 obj = obj() # Ctors, etc. 252 253 # If someone used default=, then "ofType()" is not necessary 254 # and so we check for their correspondence. 255 if self.useDefault and self.prototype.val: 256 if not isinstance(obj, self.prototype.val[0].__class__): 257 raise ValueError("ofType values doesn't match default value: %s <> %s" % (unwrap(obj), unwrap(self.prototype.val[0]))) 258 else: 259 self.prototype.val.append(wrap(obj)) 260 261 return self
262
263 -class Set(__Coll):
264 """ 265 Wraps an rset. To add values to the contents of the set, 266 use "append" or "extend" since set.val is of type list. 267 """ 268 PROTOTYPE_FUNCTION = rset
269 270
271 -class List(__Coll):
272 """ 273 Wraps an rlist. To add values to the contents of the list, 274 use "append" or "extend" since set.val is of type list. 275 """ 276 PROTOTYPE_FUNCTION = rlist
277 278
279 -class Map(Type):
280 """ 281 Wraps an rmap. To add values to the contents of the map, 282 use "update" since map.val is of type dict. 283 """ 284 PROTOTYPE_FUNCTION = rmap 285 PROTOTYPE_DEFAULT = dict 286
287 - def update(self, *args, **kwargs):
288 self.prototype.val.update(*args, **kwargs) 289 return self
290 291
292 -class ParseExit(exceptions.Exception):
293 """ 294 Raised when this script should just parse parameters and return. 295 """ 296
297 - def __init__(self, params):
298 exceptions.Exception.__init__(self) 299 self.params = params
300 301
302 -def client(*args, **kwargs):
303 """ 304 Entry point for all script engine scripts. 305 306 Typical usage consists of:: 307 308 client = omero.scripts.client("name","description", \ 309 omero.scripts.Long("name"),...) 310 311 where the returned client is created via the empty constructor to omero.client 312 using only --Ice.Config or ICE_CONFIG, and the function arguments are taken 313 as metdata about the current script. With this information, all script 314 consumers should be able to determine the required types for execution. 315 316 Possible types are all subclasses of omero.scripts.Type 317 318 To change the omero.model.Format of the stdout and stderr produced by 319 this script, use the constructor arguments:: 320 321 client = omero.scripts.client(..., \ 322 stdoutFormat = "text/plain", 323 stderrFormat = "text/plain") 324 325 If you would like to prevent stdout and/or stderr from being 326 uploaded, set the corresponding value to None. If you would like 327 to overwrite the value with another file, use 328 client.setOutput(). Though it is possible to attach any RType to 329 "stdout" or "stderr", there is an assumption that the value will 330 be an robject(OriginalFileI()) 331 332 Providing your own client is possible via the kwarg "client = ...", 333 but be careful since this may break usage with the rest of the 334 scripting framework. The client should not have a session, and 335 must be configured for the argumentless version of createSession() 336 """ 337 338 args = list(args) 339 if len(args) >= 1: 340 if isinstance(args[0], str): 341 kwargs["name"] = args.pop(0) 342 if len(args) >= 1: 343 if isinstance(args[0], str): 344 kwargs["description"] = args.pop(0) 345 346 if not kwargs.has_key("client"): 347 kwargs["client"] = omero.client() 348 c = kwargs["client"] 349 c.setAgent("OMERO.scripts") 350 351 if args and isinstance(args[0], omero.grid.JobParams): 352 c.params = args.pop(0) 353 else: 354 c.params = omero.grid.JobParams() 355 c.params.inputs = {} 356 c.params.outputs = {} 357 358 for k, v in kwargs.items(): 359 if hasattr(c.params, k): 360 setattr(c.params, k, v) 361 362 if not c.params.stdoutFormat: 363 c.params.stdoutFormat = "text/plain" 364 365 if not c.params.stderrFormat: 366 c.params.stderrFormat = "text/plain" 367 368 for p in args: 369 if isinstance(p, Type): 370 if p._in: 371 c.params.inputs[p._name] = p 372 if p._out: 373 c.params.outputs[p._name] = p 374 else: 375 raise ValueError("Not Type: %s" % type(p)) 376 377 handleParse(c) # May throw 378 379 c.createSession().detachOnDestroy() 380 return c
381
382 -def handleParse(c):
383 """ 384 Raises ParseExit if the client has the configuration property 385 "omero.scripts.parse". If the value is anything other than "only", 386 then the parameters will also be sent to the server. 387 """ 388 parse = c.getProperty("omero.scripts.parse") 389 if len(parse) > 0: # TODO Add to omero/Constants.ice 390 if parse != "only": 391 c.createSession().detachOnDestroy() 392 c.setOutput("omero.scripts.parse", rinternal(c.params)) 393 raise ParseExit(c.params)
394
395 -def parse_text(scriptText):
396 """ 397 Parses the given script text with "omero.scripts.parse" set 398 and catches the exception. The parameters are returned. 399 400 WARNING: This method calls "exec" on the given text. 401 Do NOT use this on data you don't trust. 402 """ 403 try: 404 cfg = omero.util.temp_files.create_path() 405 cfg.write_lines(["omero.scripts.parse=only", "omero.host=localhost"]) 406 old = os.environ.get("ICE_CONFIG") 407 try: 408 os.environ["ICE_CONFIG"] = cfg.abspath() 409 exec(scriptText, {"__name__":"__main__"}) 410 raise exceptions.Exception("Did not throw ParseExit: %s" % scriptText) 411 finally: 412 if old: 413 os.environ["ICE_CONFIG"] = old 414 except ParseExit, exit: 415 return exit.params
416
417 -def parse_file(filename):
418 """ 419 Parses the given script file with "omero.scripts.parse" set 420 and catches the exception. The parameters are returned. 421 422 WARNING: This method calls "exec" on the given file's contents. 423 Do NOT use this on data you don't trust. 424 """ 425 from path import path 426 scriptText = path(filename).text() 427 return parse_text(scriptText)
428 429
430 -class MissingInputs(exceptions.Exception):
431 - def __init__(self):
432 exceptions.Exception.__init__(self) 433 self.keys = []
434 435
436 -def parse_inputs(inputs_strings, params):
437 """ 438 Parses a command-line like string representation of input parameters 439 into an RDict (a map from string to RType). The input should be an 440 iterable of arguments of the form "key=value". 441 442 For example, "a=1" gets mapped to {"a":1} 443 """ 444 inputs = {} 445 for input_string in inputs_strings: 446 rv = parse_input(input_string, params) 447 inputs.update(rv) 448 449 missing = MissingInputs() 450 for key, param in params.inputs.items(): 451 a = inputs.get(key, None) 452 if not a: 453 if param.useDefault: 454 inputs[key] = param.prototype 455 elif not param.optional: 456 missing.keys.append(key) 457 458 if missing.keys: 459 missing.inputs = inputs 460 raise missing 461 462 return inputs
463 464
465 -def parse_input(input_string, params):
466 """ 467 Parse a single input_string. The params 468 """ 469 parts = input_string.split("=") 470 if len(parts) == 1: 471 parts.append("") 472 key = parts[0] 473 val = parts[1] 474 475 param = params.inputs.get(key) 476 if param is None: 477 return {} 478 elif isinstance(param.prototype,\ 479 (omero.RLong, omero.RString, omero.RInt, omero.RBool,\ 480 omero.RTime, omero.RDouble, omero.RFloat)): 481 val = param.prototype.__class__(val) 482 elif isinstance(param.prototype, omero.RList): 483 items = val.split(",") 484 if len(param.prototype.val) == 0: 485 # Don't know what needs to be added here, so calling wrap 486 # which will produce an rlist of rstrings. 487 val = omero.rtypes.wrap(items) 488 else: 489 p = param.prototype.val[0] 490 val = omero.rtypes.rlist([p.__class__(x) for x in items]) 491 else: 492 raise ValueError("No converter for: %s (type=%s)" % (key, param.prototype.__class__)) 493 494 return {key:val}
495 496
497 -def group_params(params):
498 """ 499 Walks through the inputs of the given JobParams 500 and returns a map-of-maps with Param names as 501 the leaf nodes. 502 503 For example, for the following: 504 505 Params("1", grouping = "A") # "A." is equivalent 506 Params("2", grouping = "A.B") 507 Params("3", grouping = "A.C") 508 509 this function returns: 510 511 {"A" {"": "1" : "B" : "2", "C" : "3"} } 512 513 while: 514 515 Params("1", grouping = "A") 516 517 returns: 518 519 {"A" : "1"} 520 521 """ 522 groupings = dict() 523 for k, v in params.inputs.items(): 524 525 val = v.grouping 526 if not val.endswith("."): 527 val = val + "." 528 529 parts = val.split(".") 530 531 g = groupings 532 while parts: 533 p = parts.pop(0) 534 try: 535 g = g[p] 536 except KeyError: 537 if parts: 538 g[p] = dict() 539 g = g[p] 540 else: 541 g[p] = k 542 543 # Now find all subtrees of the form {"": "key"} and 544 # replace them by themselves 545 tuples = [(groupings, k, v) for k, v in groupings.items()] 546 while tuples: 547 new_tuples = [] 548 for g, k, v in tuples: 549 if isinstance(v, dict): 550 if len(v) == 1 and "" in v: 551 g[k] = v[""] 552 else: 553 new_tuples.extend([(v, k2, v2) for k2, v2 in v.items()]) 554 tuples = new_tuples 555 556 return groupings
557
558 -def error_msg(category, key, format_string, *args):
559 c = "%s" % (category.upper()) 560 s = """%s for "%s": %s\n""" % (c, key, format_string) 561 return s % args
562
563 -def compare_proto(key, proto, input, cache=None):
564 565 if cache is None: 566 cache = {} 567 568 if id(proto) in cache and id(input) in cache: 569 return "" # Prevent StackOverflow 570 else: 571 cache[id(proto)] = True 572 cache[id(input)] = True 573 574 itype = input is None and None or input.__class__ 575 ptype = proto is None and None or proto.__class__ 576 577 if not isinstance(input, ptype): 578 return error_msg("Wrong type", key, "%s != %s", itype, ptype) 579 580 # Now recurse if a collection type 581 errors = "" 582 if isinstance(proto, omero.RMap) and len(proto.val) > 0: 583 for x in input.val.values(): 584 errors += compare_proto(key, proto.val.values()[0], x, cache) 585 elif isinstance(proto, omero.RCollection) and len(proto.val) > 0: 586 for x in input.val: 587 errors += compare_proto(key, proto.val[0], x, cache) 588 return errors
589
590 -def expand(input):
591 if input is None: 592 items = [] 593 elif isinstance(input, (list, tuple)): 594 items = list(input) 595 elif isinstance(input, dict): 596 items = input.values() 597 else: 598 items = [input] 599 return items
600
601 -def check_boundaries(key, min, max, input):
602 errors = "" 603 604 # Unwrap 605 min = unwrap(min) 606 max = unwrap(max) 607 input = unwrap(input) 608 items = expand(input) 609 610 # Check 611 for x in items: 612 if min is not None and min > x: 613 errors += error_msg("Out of bounds", key, "%s is below min %s", x, min) 614 if max is not None and max < x: 615 errors += error_msg("Out of bounds", key, "%s is above max %s", x, max) 616 return errors
617
618 -def check_values(key, values, input):
619 errors = "" 620 621 # Unwrap 622 values = unwrap(values) 623 input = unwrap(input) 624 items = expand(input) 625 values = expand(values) 626 627 if not values: 628 return errors 629 630 for x in items: 631 if x not in values: 632 errors += error_msg("Value list", key, "%s not in %s", x, values) 633 634 return errors
635
636 -def validate_inputs(params, inputs, svc = None, session = None):
637 """ 638 Method used by processor.py to check the input values 639 provided by the user launching the script. If a non-empty 640 errors string is returned, then the inputs fail validation. 641 642 A service instance can be provided in order to add default 643 values to the session. If no service instance is provided, 644 values with a default which are missing will be counted as 645 errors. 646 """ 647 errors = "" 648 for key, param in params.inputs.items(): 649 if key not in inputs: 650 if param.optional: 651 if param.useDefault and svc is not None: 652 ignore = set_input(svc, session, key, param.prototype) 653 else: # Not optional 654 if param.useDefault: 655 errors += set_input(svc, session, key, param.prototype) 656 else: 657 errors += error_msg("Missing input", key, "") 658 else: 659 input = inputs[key] 660 errors += compare_proto(key, param.prototype, input) 661 errors += check_boundaries(key, param.min, param.max, input) 662 errors += check_values(key, param.values, input) 663 return errors
664
665 -def set_input(svc, session, key, value):
666 try: 667 svc.setInput(session, key, value) 668 return "" 669 except exceptions.Exception, e: 670 return error_msg("Failed to set intput", key, "%s=%s. Error: %s", key, value, e)
671 672 # 673 # Importing into omero.scripts namespace 674 # 675 ProcessCallbackI = omero.callbacks.ProcessCallbackI 676
677 -def wait(client, process, ms = 500):
678 """ 679 Wrapper around the use of ProcessCallbackI 680 """ 681 cb = ProcessCallbackI(client, process) 682 while cb.block(500) is None: 683 process.poll()
684