Package omero :: Package plugins :: Module script
[hide private]
[frames] | no frames]

Source Code for Module omero.plugins.script

  1  #!/usr/bin/env python 
  2  """ 
  3     script plugin 
  4   
  5     Plugin read by omero.cli.Cli during initialization. The method(s) 
  6     defined here will be added to the Cli class for later use. 
  7   
  8     The script plugin is used to run arbitrary blitz scripts which 
  9     take as their sole input Ice configuration arguments, including 
 10     --Ice.Config=file1,file2. 
 11   
 12     The first parameter, the script itself, should be natively executable 
 13     on a given platform. I.e. invokable by subprocess.call([file,...]) 
 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 exceptions, subprocess, re, os, sys, signal, time, atexit 
 21   
 22  from omero.cli import CLI 
 23  from omero.cli import BaseControl 
 24   
 25  from omero.util.sessions import SessionsStore 
 26   
 27  from omero_ext.argparse import Action 
 28  from omero_ext.strings import shlex 
 29  from omero_ext.functional import wraps 
 30   
 31  from path import path 
 32   
 33  HELP = """Support for launching, uploading and otherwise managing OMERO.scripts""" 
 34   
 35  DEMO_SCRIPT = """#!/usr/bin/env python 
 36  import omero 
 37  import omero.rtypes as rtypes 
 38  import omero.scripts as scripts 
 39   
 40  o = scripts.Long("opt", min=0, max=5) 
 41  a = scripts.String("a", values=("foo", "bar"), optional=False) 
 42  b = scripts.Long("b").out() 
 43   
 44  client = scripts.client("length of input string", 
 45  \"\"\" 
 46      Trivial example script which calculates the length 
 47      of the string passed in as the "a" input, and returns 
 48      the value as the long "b" 
 49  \"\"\", a, b, o, 
 50   
 51  authors = ["OME Team"], 
 52  institutions = ["openmicroscopy.org"]) 
 53   
 54  print "Starting script" 
 55   
 56  try: 
 57      a = client.getInput("a").getValue() 
 58      b = len(a) 
 59      client.setOutput("b", rtypes.rlong(b)) 
 60      client.setOutput("unregistered-output-param", rtypes.wrap([1,2,3])) 
 61  finally: 
 62      client.closeSession() 
 63   
 64  print "Finished script" 
 65  """ 
 66   
 67  RE0 = re.compile("\s*script\s+upload\s*") 
 68  RE1 = re.compile("\s*script\s+upload\s+--official\s*") 
 69   
70 -class ScriptControl(BaseControl):
71
72 - def _complete(self, text, line, begidx, endidx):
73 """ 74 Returns a file after "upload" and otherwise delegates to the BaseControl 75 """ 76 for RE in (RE1, RE0): 77 m = RE.match(line) 78 if m: 79 replaced = RE.sub('', line) 80 suggestions = self._complete_file(replaced, os.getcwd()) 81 if False: #line.find("--official") < 0: 82 add = "--official" 83 parts = line.split(" ") 84 if "--official".startswith(parts[-1]): 85 new = add[len(parts[-1]):] 86 if new: 87 add = new 88 suggestions.insert(0, add) 89 return suggestions 90 return BaseControl._complete(self, text, line, begidx, endidx)
91
92 - def _configure(self, parser):
93 def _who(parser): 94 return parser.add_argument("who", nargs="*", help="Who to execute for: user, group, user=1, group=5 (default=official)")
95 96 sub = parser.sub() 97 98 ## Disabling for 4.2 release. help = parser.add(sub, self.help, "Extended help") 99 100 demo = parser.add(sub, self.demo, "Runs a short demo of the scripting system") 101 102 list = parser.add(sub, self.list, help = "List files for user or group") 103 _who(list) 104 105 cat = parser.add(sub, self.cat, "Prints a script to standard out") 106 edit = parser.add(sub, self.edit, "Opens a script in $EDITOR and saves it back to the server") 107 params = parser.add(sub, self.params, help="Print the parameters for a given script") 108 launch = parser.add(sub, self.launch, help = "Launch a script with parameters") 109 disable = parser.add(sub, self.disable, help = "Makes script non-executable by setting the mimetype") 110 disable.add_argument("--mimetype", default="text/plain", help="Use a mimetype other than the default (%(default)s)") 111 enable = parser.add(sub, self.enable, help = "Makes a script non-executable (sets mimetype to text/x-python)") 112 enable.add_argument("--mimetype", default="text/x-python", help="Use a mimetype other than the default (%(default)s)") 113 114 for x in (launch, params, cat, disable, enable, edit): 115 x.add_argument("original_file", help="Id or path of a script file stored in OMERO") 116 launch.add_argument("input", nargs="*", help="Inputs for the script of the form 'param=value'") 117 118 jobs = parser.add(sub, self.jobs, help = "List current jobs for user or group") 119 jobs.add_argument("--all", action="store_true", help="Show all jobs, not just running ones") 120 _who(jobs) 121 122 serve = parser.add(sub, self.serve, help = "Start a usermode processor for scripts") 123 serve.add_argument("--verbose", action="store_true", help="Enable debug logging on processor") 124 serve.add_argument("-b", "--background", action="store_true", help="Run processor in background. Used in demo") 125 serve.add_argument("-t", "--timeout", default=0, type=long, help="Seconds that the processor should run. 0 means no timeout") 126 _who(serve) 127 128 upload = parser.add(sub, self.upload, help = "Upload a script") 129 upload.add_argument("--official", action="store_true", help = "If set, creates a system script. Must be an admin") 130 upload.add_argument("file", help = "Local script file to upload to OMERO") 131 132 replace = parser.add(sub, self.replace, help = "Replace an existing script with a new value") 133 replace.add_argument("id", type=long, help = "Id of the original file which is to be replaced") 134 replace.add_argument("file", help = "Local script which should overwrite the existing one") 135 136 run = parser.add(sub, self.run, help = "Run a script with the OMERO libraries loaded and current login") 137 run.add_argument("file", help = "Local script file to run") 138 run.add_argument("input", nargs="*", help="Inputs for the script of the form 'param=value'")
139 140 # log = parser.add(sub, self.log, help = "TBD", tbd="TRUE") 141
142 - def help(self, args):
143 self.ctx.out(""" 144 145 Available or planned(*) commands: 146 ================================ 147 demo 148 149 set 150 set file=[id|name] 151 set user=[id|name] 152 set group=[id|name] 153 set user= 154 155 Note: Some of the other actions call set internally. 156 157 158 cat 159 cat file=[id|name] 160 mv 161 rm 162 cp 163 --replace / --overwrite 164 165 register 166 publish 167 processors 168 chain 169 edit 170 171 jobs 172 jobs user 173 jobs group 174 175 launch 176 launch file=[id|name] 177 178 list 179 list user 180 list group 181 list user=[id|name] 182 list group=[id|name] 183 list publication=[regex] 184 list ofifical 185 list namespace=[] 186 187 log 188 log file 189 log user 190 log group 191 log file=[id|name] 192 log user=[id|name] 193 log group=[id|name] 194 195 params 196 params file=[id|name] 197 198 serve 199 serve --background 200 serve --timeout={min} 201 serve --verbose 202 serve user 203 serve group 204 serve user=[id|name] 205 serve group=[id|name] 206 serve group=[id|name] user=[id|name] 207 serve count=1 208 serve log 209 serve log=some/file/somewhere 210 211 upload file=/tmp/my_script.py 212 replace 213 delete 214 215 # 216 # Other 217 # 218 219 run 220 """)
221 222
223 - def demo(self, args):
224 from omero.util.temp_files import create_path 225 t = create_path("Demo_Script", ".py") 226 227 try: 228 from hashlib import sha1 as sha_new 229 except ImportError: 230 from sha import new as sha_new 231 232 digest = sha_new() 233 digest.update(DEMO_SCRIPT) 234 sha1 = digest.hexdigest() 235 236 self.ctx.out("\nExample script writing session") 237 self.ctx.out("="*80) 238 239 def msg(title, method = None, *arguments): 240 self.ctx.out("\n") 241 self.ctx.out("\t+" + ("-"*68) + "+") 242 title = "\t| %-66.66s | " % title 243 self.ctx.out(title) 244 if method: 245 cmd = "%s %s" % (method.__name__, " ".join(arguments)) 246 cmd = "\t| COMMAND: bin/omero script %-40.40s | " % cmd 247 self.ctx.out(cmd) 248 self.ctx.out("\t+" + ("-"*68) + "+") 249 self.ctx.out(" ") 250 if method: 251 try: 252 self.ctx.invoke(['script', method.__name__] + list(arguments)) 253 except exceptions.Exception, e: 254 import traceback 255 self.ctx.out("\nEXECUTION FAILED: %s" % e) 256 self.ctx.dbg(traceback.format_exc())
257 258 client = self.ctx.conn(args) 259 admin = client.sf.getAdminService() 260 current_user = admin.getEventContext().userId 261 query = "select o from OriginalFile o where o.sha1 = '%s' and o.details.owner.id = %s" % (sha1, current_user) 262 files = client.sf.getQueryService().findAllByQuery(query, None) 263 if len(files) == 0: 264 msg("Saving demo script to %s" % t) 265 t.write_text(DEMO_SCRIPT) 266 267 msg("Uploading script", self.upload, str(t)) 268 id = self.ctx.get("script.file.id") 269 else: 270 id = files[0].id.val 271 msg("Reusing demo script %s" % id) 272 273 msg("Listing available scripts for user", self.list, "user") 274 msg("Printing script content for file %s" % id, self.cat, str(id)) 275 msg("Serving file %s in background" % id, self.serve, "user", "--background") 276 msg("Printing script params for file %s" % id, self.params, "file=%s" % id) 277 msg("Launching script with parameters: a=bad-string (fails)", self.launch, "file=%s" % id, "a=bad-string") 278 msg("Launching script with parameters: a=bad-string opt=6 (fails)", self.launch, "file=%s" % id, "a=bad-string", "opt=6") 279 msg("Launching script with parameters: a=foo opt=1 (passes)", self.launch, "file=%s" % id, "a=foo", "opt=1") 280 try: 281 for p in list(getattr(self, "_processors", [])): 282 p.cleanup() 283 self._processors.remove(p) 284 except exceptions.Exception, e: 285 self.ctx.err("Failed to clean processors: %s" % e) 286 287 ## self.ctx.out("\nDeleting script from server...") 288 ## self.delete(args.for_pub(str(id))) 289
290 - def cat(self, args):
291 client = self.ctx.conn(args) 292 script_id, ofile = self._file(args, client) 293 try: 294 self.ctx.out(client.sf.getScriptService().getScriptText(script_id)) 295 except exceptions.Exception, e: 296 self.ctx.err("Failed to find script: %s (%s)" % (script_id, e))
297
298 - def edit(self, args):
299 client = self.ctx.conn(args) 300 scriptSvc = client.sf.getScriptService() 301 script_id, ofile = self._file(args, client) 302 try: 303 txt = client.sf.getScriptService().getScriptText(script_id) 304 from omero.util.temp_files import create_path 305 from omero.util import edit_path 306 p = create_path() 307 edit_path(p, txt) 308 scriptSvc.editScript(ofile, p.text()) 309 except exceptions.Exception, e: 310 self.ctx.err("Failed to find script: %s (%s)" % (script_id, e))
311
312 - def jobs(self, args):
313 client = self.ctx.conn(args) 314 cols = ("username", "groupname", "started", "finished") 315 query = "select j, %s, s.value from Job j join j.status s" % (",".join(["j.%s" % j for j in cols])) 316 if not args.all: 317 query += " where j.finished is null" 318 319 self.ctx.out("Running query via 'hql' subcommand: %s" % query) 320 self.ctx.invoke("""hql "%s" """ % query)
321
322 - def launch(self, args):
323 """ 324 """ 325 326 client = self.ctx.conn(args) 327 script_id, ofile = self._file(args, client) 328 329 import omero 330 import omero.scripts 331 import omero.rtypes 332 svc = client.sf.getScriptService() 333 params = svc.getParams(script_id) 334 m = self._parse_inputs(args, params) 335 336 try: 337 proc = svc.runScript(script_id, m, None) 338 job = proc.getJob() 339 except omero.ValidationException, ve: 340 self.ctx.err("Bad parameters:\n%s" % ve) 341 return # EARLY EXIT 342 343 # Adding notification to wait on result 344 cb = omero.scripts.ProcessCallbackI(client, proc) 345 try: 346 self.ctx.out("Job %s ready" % job.id.val) 347 self.ctx.out("Waiting....") 348 count = 0 349 while proc.poll() is None: 350 cb.block(1000) 351 self.ctx.out("Callback received: %s" % cb.block(0)) 352 rv = proc.getResults(3) 353 finally: 354 cb.close() 355 356 def p(m): 357 class handle(object): 358 def write(this, val): 359 val = "\t* %s" % val 360 val = val.replace("\n","\n\t* ") 361 self.ctx.out(val, newline=False)
362 def close(this): 363 pass 364 365 f = rv.get(m, None) 366 if f and f.val: 367 self.ctx.out("\n\t*** start %s ***" % m) 368 try: 369 client.download(ofile=f.val, filehandle=handle()) 370 except: 371 self.ctx.err("Failed to display %s" % m) 372 self.ctx.out("\n\t*** end %s ***\n" % m) 373 374 p("stdout") 375 p("stderr") 376 self.ctx.out("\n\t*** out parameters ***") 377 for k, v in rv.items(): 378 if k not in ("stdout", "stderr", "omero.scripts.parse"): 379 self.ctx.out("\t* %s=%s" % (k, omero.rtypes.unwrap(v))) 380 self.ctx.out("\t*** done ***") 381
382 - def list(self, args):
383 import omero_api_IScript_ice 384 client = self.ctx.conn(args) 385 sf = client.sf 386 svc = sf.getScriptService() 387 if args.who: 388 who = [self._parse_who(sf, w) for w in args.who] 389 scripts = svc.getUserScripts(who) 390 banner = "Scripts for %s" % ", ".join(args.who) 391 else: 392 scripts = svc.getScripts() 393 banner = "Official scripts" 394 self._parse_scripts(scripts, banner)
395
396 - def log(self, args):
397 print args 398 pass
399
400 - def params(self, args):
401 client = self.ctx.conn(args) 402 script_id, ofile = self._file(args, client) 403 import omero_api_IScript_ice 404 svc = client.sf.getScriptService() 405 job_params = svc.getParams(script_id) 406 if job_params: 407 self.ctx.out("") 408 self.ctx.out("id: %s" % script_id) 409 self.ctx.out("name: %s" % job_params.name) 410 self.ctx.out("version: %s" % job_params.version) 411 self.ctx.out("authors: %s" % ", ".join(job_params.authors)) 412 self.ctx.out("institutions: %s" % ", ".join(job_params.institutions)) 413 self.ctx.out("description: %s" % job_params.description) 414 self.ctx.out("namespaces: %s" % ", ".join(job_params.namespaces)) 415 self.ctx.out("stdout: %s" % job_params.stdoutFormat) 416 self.ctx.out("stderr: %s" % job_params.stderrFormat) 417 def print_params(which, params): 418 import omero 419 self.ctx.out(which) 420 for k, v in params.items(): 421 self.ctx.out(" %s - %s" % (k, (v.description and v.description or "(no description)"))) 422 self.ctx.out(" Optional: %s" % v.optional) 423 self.ctx.out(" Type: %s" % v.prototype.ice_staticId()) 424 if isinstance(v.prototype, omero.RCollection): 425 self.ctx.out(" Subtype: %s" % v.prototype.val[0].ice_staticId()) 426 elif isinstance(v.prototype, omero.RMap): 427 self.ctx.out(" Subtype: %s" % v.prototype.val.values[0].ice_staticId()) 428 self.ctx.out(" Min: %s" % (v.min and v.min.val or "")) 429 self.ctx.out(" Max: %s" % (v.max and v.max.val or "")) 430 values = omero.rtypes.unwrap(v.values) 431 self.ctx.out(" Values: %s" % (values and ", ".join(values) or ""))
432 print_params("inputs:", job_params.inputs) 433 print_params("outputs:", job_params.outputs) 434
435 - def serve(self, args):
436 437 # List of processors which have been started 438 if not hasattr(self, "_processors"): 439 self._processors = [] 440 441 debug = args.verbose 442 background = args.background 443 timeout = args.timeout 444 client = self.ctx.conn(args) 445 sf = client.sf 446 who = [self._parse_who(sf, w) for w in args.who] 447 if not who: 448 who = [] # Official scripts only 449 450 # Similar to omero.util.Server starting here 451 import logging 452 original = list(logging._handlerList) 453 roots = list(logging.getLogger().handlers) 454 logging._handlerList = [] 455 logging.getLogger().handlers = [] 456 457 from omero.util import configure_logging 458 from omero.processor import usermode_processor 459 lvl = debug and 10 or 20 460 configure_logging(loglevel=lvl) 461 462 try: 463 try: 464 impl = usermode_processor(client, serverid = "omer.scripts.serve", accepts_list = who) 465 self._processors.append(impl) 466 except exceptions.Exception, e: 467 self.ctx.die(100, "Failed initialization: %s" % e) 468 469 if background: 470 def cleanup(): 471 impl.cleanup() 472 logging._handlerList = original 473 logging.getLogger().handlers = roots
474 atexit.register(cleanup) 475 else: 476 try: 477 def handler(signum, frame): 478 raise SystemExit() 479 old = signal.signal(signal.SIGALRM, handler) 480 signal.alarm(timeout) 481 self.ctx.input("Press any key to exit...\n") 482 signal.alarm(0) 483 finally: 484 self.ctx.dbg("DONE") 485 signal.signal(signal.SIGTERM, old) 486 impl.cleanup() 487 finally: 488 if not background: 489 logging._handlerList = original 490 logging.getLogger().handlers = roots 491 492 return impl 493 494
495 - def upload(self, args):
496 497 p = path(args.file) 498 if not p.exists(): 499 self.ctx.die(502, "File does not exist: %s" % p.abspath()) 500 501 import omero 502 c = self.ctx.conn(args) 503 scriptSvc = c.sf.getScriptService() 504 505 if args.official: 506 try: 507 id = scriptSvc.uploadOfficialScript(args.file, p.text()) 508 except omero.ApiUsageException, aue: 509 if "editScript" in aue.message: 510 self.ctx.die(502, "%s already exists; use 'replace' instead" % args.file) 511 else: 512 self.ctx.die(504, "ApiUsageException: %s" % aue.message) 513 except omero.SecurityViolation, sv: 514 self.ctx.die(503, "SecurityViolation: %s" % sv.message) 515 else: 516 id = scriptSvc.uploadScript(args.file, p.text()) 517 518 self.ctx.out("Uploaded %sscript as original file #%s" % ((args.official and "official " or ""), id)) 519 self.ctx.set("script.file.id", id)
520
521 - def replace(self, args):
522 ofile = args.id 523 fpath = args.file 524 525 client = self.ctx.conn(args) 526 ofile = client.sf.getQueryService().get("OriginalFile", ofile) 527 #client.upload(fpath, ofile=ofile) 528 529 file = open(fpath) 530 scriptText = file.read() 531 file.close() 532 scriptSvc = client.sf.getScriptService() 533 scriptSvc.editScript(ofile, scriptText)
534
535 - def delete(self, args):
536 if len(args) != 1: 537 self.ctx.die(123, "Usage: <original file id>") 538 539 ofile = long(args.args[0]) 540 import omero_api_IScript_ice 541 client = self.ctx.conn(args) 542 try: 543 client.sf.getScriptService().deleteScript(ofile) 544 except exceptions.Exception, e: 545 self.ctx.err("Failed to delete script: %s (%s)" % (ofile, e))
546
547 - def disable(self, args):
548 ofile = self.setmimetype(args) 549 self.ctx.out("Disabled %s by setting mimetype to %s" % (ofile.id.val, args.mimetype))
550
551 - def enable(self, args):
552 ofile = self.setmimetype(args) 553 self.ctx.out("Enabled %s by setting mimetype to %s" % (ofile.id.val, args.mimetype))
554
555 - def setmimetype(self, args):
556 from omero.rtypes import rstring 557 client = self.ctx.conn(args) 558 script_id, ofile = self._file(args, client) 559 ofile.setMimetype(rstring(args.mimetype)) 560 return client.sf.getUpdateService().saveAndReturnObject(ofile)
561 562 # 563 # Other 564 #
565 - def run(self, args):
566 if not os.path.exists(args.file): 567 self.ctx.die(670, "No such file: %s" % args.file) 568 else: 569 client = self.ctx.conn(args) 570 store = SessionsStore() 571 srv, usr, uuid = store.get_current() 572 props = store.get(srv, usr, uuid) 573 574 from omero.scripts import parse_file 575 from omero.util.temp_files import create_path 576 path = create_path() 577 text = """ 578 omero.host=%(omero.host)s 579 omero.user=%(omero.sess)s 580 omero.pass=%(omero.sess)s 581 """ 582 path.write_text(text % props) 583 584 params = parse_file(args.file) 585 m = self._parse_inputs(args, params) 586 for k, v in m.items(): 587 if v is not None: 588 client.setInput(k, v) 589 590 p = self.ctx.popen([sys.executable, args.file], stdout=sys.stdout, stderr=sys.stderr,\ 591 ICE_CONFIG = str(path)) 592 p.wait() 593 if p.poll() != 0: 594 self.ctx.die(p.poll(), "Execution failed.")
595 596 # 597 # Helpers 598 #
599 - def _parse_inputs(self, args, params):
600 from omero.scripts import parse_inputs, parse_input, MissingInputs 601 try: 602 rv = parse_inputs(args.input, params) 603 except MissingInputs, mi: 604 rv = mi.inputs 605 for key in mi.keys: 606 value = self.ctx.input("""Enter value for "%s": """ % key, required = True) 607 rv.update(parse_input("%s=%s" % (key, value), params)) 608 return rv
609
610 - def _parse_scripts(self, scripts, msg):
611 """ 612 Parses a list of scripts to self.ctx.out 613 """ 614 from omero.util.text import TableBuilder 615 tb = TableBuilder("id", msg) 616 for x in scripts: 617 tb.row(x.id.val, x.path.val + x.name.val) 618 self.ctx.out(str(tb.build()))
619
620 - def _file(self, args, client):
621 import omero 622 f = args.original_file 623 q = client.sf.getQueryService() 624 svc = client.sf.getScriptService() 625 626 if f is None: 627 self.ctx.die(100, "No script provided") 628 elif f.startswith("file="): 629 f = f[5:] 630 631 try: 632 script_id = long(f) 633 except: 634 script_path = str(f) 635 script_id = svc.getScriptID(script_path) 636 ofile = q.get("OriginalFile", script_id) 637 638 return script_id, ofile
639
640 - def _parse_who(self, sf, who):
641 """ 642 Parses who items of the form: "user", "group", "user=1", "group=6" 643 """ 644 645 import omero 646 WHO_FACTORY = {"user":omero.model.ExperimenterI, "group":omero.model.ExperimenterGroupI} 647 WHO_CURRENT = { "user":lambda sf: sf.getAdminService().getEventContext().userId, 648 "group":lambda sf: sf.getAdminService().getEventContext().groupId} 649 650 for key, factory in WHO_FACTORY.items(): 651 if who.startswith(key): 652 if who == key: 653 id = WHO_CURRENT[key](sf) 654 return factory(id, False) 655 else: 656 parts = who.split("=") 657 if len(parts) != 2: 658 continue 659 else: 660 id = long(parts[1]) 661 return factory(id, False)
662 663 try: 664 register("script", ScriptControl, HELP) 665 except NameError: 666 if __name__ == "__main__": 667 cli = CLI() 668 cli.register("script", ScriptControl, HELP) 669 cli.invoke(sys.argv[1:]) 670