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

Source Code for Module omero.scripts

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  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 # Non-Param attributes 53 omero.grid.Param.__init__(self) 54 55 # Non-Param attributes 56 self._name = name 57 self._in = True 58 self._out = out 59 60 # Other values will be filled in by the kwargs 61 # Mostly leaving these for backwards compatibility 62 self.description = description 63 self.optional = optional 64 65 # Assign all the kwargs 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 # Someone specifically set the prototype, then 78 # we assume that useDefault should be True 79 if default is not None: # For whatever reason, inheritance isn't working. 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 # To prevent weirdness, if the class default is 99 # callable, we'll assume its a constructor and 100 # create a new one to prevent modification. 101 try: 102 _def = _DEF() 103 except TypeError: 104 _def = _DEF 105 self.prototype = _FUN(_def) 106 107 # The following use wrap to guarantee that an rtype is present 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 # Now if useDefault has been set, either manually, or 127 # via setting default="..." we check that the default 128 # value matches a value if present 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
139 - def out(self):
140 self._in = False 141 self._out = True 142 return self
143
144 - def inout(self):
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 -class Object(Type):
161 """ 162 Wraps an robject 163 """ 164 PROTOTYPE_FUNCTION = robject 165 PROTOTYPE_DEFAULT = None
166 167
168 -class Long(Type):
169 """ 170 Wraps an rlong 171 """ 172 PROTOTYPE_FUNCTION = rlong 173 PROTOTYPE_DEFAULT = 0
174 175
176 -class Int(Type):
177 """ 178 Wraps an rint 179 """ 180 PROTOTYPE_FUNCTION = rint 181 PROTOTYPE_DEFAULT = 0
182 183
184 -class Double(Type):
185 """ 186 Wraps an rdouble 187 """ 188 PROTOTYPE_FUNCTION = rdouble 189 PROTOTYPE_DEFAULT = 0.0
190 191
192 -class Float(Type):
193 """ 194 Wraps an rfloat 195 """ 196 PROTOTYPE_FUNCTION = rfloat 197 PROTOTYPE_DEFAULT = 0.0
198 199
200 -class String(Type):
201 """ 202 Wraps an rstring 203 """ 204 PROTOTYPE_FUNCTION = rstring 205 PROTOTYPE_DEFAULT = ""
206 207
208 -class Bool(Type):
209 """ 210 Wraps an rbool 211 """ 212 PROTOTYPE_FUNCTION = rbool 213 PROTOTYPE_DEFAULT = False
214 215
216 -class Color(Type):
217 """ 218 Wraps an rinternal(Color) 219 """ 220 PROTOTYPE_FUNCTION = rinternal 221 PROTOTYPE_DEFAULT = omero.Color
222 223
224 -class Point(Type):
225 """ 226 Wraps an rinternal(Point) 227 """ 228 PROTOTYPE_FUNCTION = rinternal 229 PROTOTYPE_FUNCTION = omero.Point
230 231
232 -class Plane(Type):
233 """ 234 Wraps an rinternal(Plane) 235 """ 236 PROTOTYPE_FUNCTION = rinternal 237 PROTOTYPE_DEFAULT = omero.Plane
238 239
240 -class __Coll(Type):
241 """ 242 Base type providing the append and extend functionality. 243 Not for user use. 244 """ 245 PROTOTYPE_DEFAULT = list 246
247 - def append(self, *arg):
248 self.prototype.val.append(*arg) 249 return self
250
251 - def extend(self, *arg):
252 self.prototype.val.extend(*arg) 253 return self
254
255 - def ofType(self, obj):
256 if callable(obj): 257 obj = obj() # Ctors, etc. 258 259 # If someone used default=, then "ofType()" is not necessary 260 # and so we check for their correspondence. 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
269 -class Set(__Coll):
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
277 -class List(__Coll):
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
285 -class Map(Type):
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
298 -class ParseExit(Exception):
299 """ 300 Raised when this script should just parse parameters and return. 301 """ 302
303 - def __init__(self, params):
304 Exception.__init__(self) 305 self.params = params
306 307
308 -def client(*args, **kwargs):
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) # May throw 384 385 c.createSession().detachOnDestroy() 386 return c
387
388 -def handleParse(c):
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: # TODO Add to omero/Constants.ice 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
423 -def parse_file(filename):
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
436 -class MissingInputs(Exception):
437 - def __init__(self):
438 Exception.__init__(self) 439 self.keys = []
440 441
442 -def parse_inputs(inputs_strings, params):
443 """ 444 Parses a command-line like string representation of input parameters 445 into an RDict (a map from string to RType). The input should be an 446 iterable of arguments of the form "key=value". 447 448 For example, "a=1" gets mapped to {"a":1} 449 """ 450 inputs = {} 451 for input_string in inputs_strings: 452 rv = parse_input(input_string, params) 453 inputs.update(rv) 454 455 missing = MissingInputs() 456 for key in sorted(params.inputs, key=lambda name: params.inputs.get(name).grouping): 457 param = params.inputs.get(key) 458 a = inputs.get(key, None) 459 if not a: 460 if param.useDefault: 461 inputs[key] = param.prototype 462 elif not param.optional: 463 missing.keys.append(key) 464 465 if missing.keys: 466 missing.inputs = inputs 467 raise missing 468 469 return inputs
470 471
472 -def parse_input(input_string, params):
473 """ 474 Parse a single input_string. The params 475 """ 476 parts = input_string.split("=") 477 if len(parts) == 1: 478 parts.append("") 479 key = parts[0] 480 val = parts[1] 481 482 param = params.inputs.get(key) 483 if param is None: 484 return {} 485 elif isinstance(param.prototype, omero.RBool): 486 if val.lower() in ("false", "False", "0", ""): 487 val = rbool(False) 488 else: 489 val = rbool(True) 490 elif isinstance(param.prototype,\ 491 (omero.RLong, omero.RString, omero.RInt, \ 492 omero.RTime, omero.RDouble, omero.RFloat)): 493 val = param.prototype.__class__(val) 494 elif isinstance(param.prototype, omero.RList): 495 items = val.split(",") 496 if len(param.prototype.val) == 0: 497 # Don't know what needs to be added here, so calling wrap 498 # which will produce an rlist of rstrings. 499 val = omero.rtypes.wrap(items) 500 else: 501 p = param.prototype.val[0] 502 val = omero.rtypes.rlist([p.__class__(x) for x in items]) 503 elif isinstance(param.prototype, omero.RObject): 504 try: 505 parts2 = val.split(":") 506 kls = parts2[0] 507 _id = long(parts2[1]) 508 if not kls.endswith("I"): 509 kls = "%sI" % kls 510 kls = getattr(omero.model, kls) 511 except: 512 raise ValueError("Format for objects: Class:id or ClassI:id. Not:%s" % val) 513 val = omero.rtypes.robject(kls(_id, False)) 514 else: 515 raise ValueError("No converter for: %s (type=%s)" % (key, param.prototype.__class__)) 516 517 return {key:val}
518 519
520 -def group_params(params):
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 # Now find all subtrees of the form {"": "key"} and 567 # replace them by themselves 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
586 -def compare_proto(key, proto, input, cache=None):
587 588 if cache is None: 589 cache = {} 590 591 if id(proto) in cache and id(input) in cache: 592 return "" # Prevent StackOverflow 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 # Now recurse if a collection type 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
613 -def expand(input):
614 if input is None: 615 items = [] 616 elif isinstance(input, (list, tuple)): 617 items = list(input) 618 elif isinstance(input, dict): 619 items = input.values() 620 else: 621 items = [input] 622 return items
623
624 -def check_boundaries(key, min, max, input):
625 errors = "" 626 627 # Unwrap 628 min = unwrap(min) 629 max = unwrap(max) 630 input = unwrap(input) 631 items = expand(input) 632 633 # Check 634 for x in items: 635 if min is not None and min > x: 636 errors += error_msg("Out of bounds", key, "%s is below min %s", x, min) 637 if max is not None and max < x: 638 errors += error_msg("Out of bounds", key, "%s is above max %s", x, max) 639 return errors
640
641 -def check_values(key, values, input):
642 errors = "" 643 644 # Unwrap 645 values = unwrap(values) 646 input = unwrap(input) 647 items = expand(input) 648 values = expand(values) 649 650 if not values: 651 return errors 652 653 for x in items: 654 if x not in values: 655 errors += error_msg("Value list", key, "%s not in %s", x, values) 656 657 return errors
658
659 -def validate_inputs(params, inputs, svc = None, session = None):
660 """ 661 Method used by processor.py to check the input values 662 provided by the user launching the script. If a non-empty 663 errors string is returned, then the inputs fail validation. 664 665 A service instance can be provided in order to add default 666 values to the session. If no service instance is provided, 667 values with a default which are missing will be counted as 668 errors. 669 """ 670 errors = "" 671 for key, param in params.inputs.items(): 672 if key not in inputs: 673 if param.optional: 674 if param.useDefault and svc is not None: 675 ignore = set_input(svc, session, key, param.prototype) 676 else: # Not optional 677 if param.useDefault: 678 errors += set_input(svc, session, key, param.prototype) 679 else: 680 errors += error_msg("Missing input", key, "") 681 else: 682 input = inputs[key] 683 errors += compare_proto(key, param.prototype, input) 684 errors += check_boundaries(key, param.min, param.max, input) 685 errors += check_values(key, param.values, input) 686 return errors
687
688 -def set_input(svc, session, key, value):
689 try: 690 svc.setInput(session, key, value) 691 return "" 692 except Exception, e: 693 return error_msg("Failed to set intput", key, "%s=%s. Error: %s", key, value, e)
694 695 # 696 # Importing into omero.scripts namespace 697 # 698 ProcessCallbackI = omero.callbacks.ProcessCallbackI 699
700 -def wait(client, process, ms = 500):
701 """ 702 Wrapper around the use of ProcessCallbackI 703 """ 704 cb = ProcessCallbackI(client, process) 705 while cb.block(500) is None: 706 process.poll()
707