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 = self.ctx._event_context.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 try: 334 params = svc.getParams(script_id) 335 except omero.ValidationException, ve: 336 self.ctx.die(502, "ValidationException: %s" % ve.message) 337 338 m = self._parse_inputs(args, params) 339 340 try: 341 proc = svc.runScript(script_id, m, None) 342 job = proc.getJob() 343 except omero.ValidationException, ve: 344 self.ctx.err("Bad parameters:\n%s" % ve) 345 return # EARLY EXIT 346 347 # Adding notification to wait on result 348 cb = omero.scripts.ProcessCallbackI(client, proc) 349 try: 350 self.ctx.out("Job %s ready" % job.id.val) 351 self.ctx.out("Waiting....") 352 count = 0 353 while proc.poll() is None: 354 cb.block(1000) 355 self.ctx.out("Callback received: %s" % cb.block(0)) 356 rv = proc.getResults(3) 357 finally: 358 cb.close() 359 360 def p(m): 361 class handle(object): 362 def write(this, val): 363 val = "\t* %s" % val 364 val = val.replace("\n","\n\t* ") 365 self.ctx.out(val, newline=False)
366 def close(this): 367 pass 368 369 f = rv.get(m, None) 370 if f and f.val: 371 self.ctx.out("\n\t*** start %s ***" % m) 372 try: 373 client.download(ofile=f.val, filehandle=handle()) 374 except: 375 self.ctx.err("Failed to display %s" % m) 376 self.ctx.out("\n\t*** end %s ***\n" % m) 377 378 p("stdout") 379 p("stderr") 380 self.ctx.out("\n\t*** out parameters ***") 381 for k, v in rv.items(): 382 if k not in ("stdout", "stderr", "omero.scripts.parse"): 383 self.ctx.out("\t* %s=%s" % (k, omero.rtypes.unwrap(v))) 384 self.ctx.out("\t*** done ***") 385
386 - def list(self, args):
387 import omero_api_IScript_ice 388 client = self.ctx.conn(args) 389 sf = client.sf 390 svc = sf.getScriptService() 391 ec = self.ctx._event_context 392 if args.who: 393 who = [self._parse_who(w) for w in args.who] 394 scripts = svc.getUserScripts(who) 395 banner = "Scripts for %s" % ", ".join(args.who) 396 else: 397 scripts = svc.getScripts() 398 banner = "Official scripts" 399 self._parse_scripts(scripts, banner)
400
401 - def log(self, args):
402 print args 403 pass
404
405 - def params(self, args):
406 client = self.ctx.conn(args) 407 script_id, ofile = self._file(args, client) 408 import omero 409 import omero_api_IScript_ice 410 svc = client.sf.getScriptService() 411 412 try: 413 job_params = svc.getParams(script_id) 414 except omero.ResourceError, re: 415 self.ctx.die(455, "Could not get params: %s" % re.message) 416 417 if job_params: 418 self.ctx.out("") 419 self.ctx.out("id: %s" % script_id) 420 self.ctx.out("name: %s" % job_params.name) 421 self.ctx.out("version: %s" % job_params.version) 422 self.ctx.out("authors: %s" % ", ".join(job_params.authors)) 423 self.ctx.out("institutions: %s" % ", ".join(job_params.institutions)) 424 self.ctx.out("description: %s" % job_params.description) 425 self.ctx.out("namespaces: %s" % ", ".join(job_params.namespaces)) 426 self.ctx.out("stdout: %s" % job_params.stdoutFormat) 427 self.ctx.out("stderr: %s" % job_params.stderrFormat) 428 def print_params(which, params): 429 import omero 430 self.ctx.out(which) 431 for k, v in params.items(): 432 self.ctx.out(" %s - %s" % (k, (v.description and v.description or "(no description)"))) 433 self.ctx.out(" Optional: %s" % v.optional) 434 self.ctx.out(" Type: %s" % v.prototype.ice_staticId()) 435 if isinstance(v.prototype, omero.RCollection): 436 coll = v.prototype.val 437 if len(coll) == 0: 438 self.ctx.out(" Subtype: (empty)") 439 else: 440 self.ctx.out(" Subtype: %s" % coll[0].ice_staticId()) 441 442 elif isinstance(v.prototype, omero.RMap): 443 try: 444 proto_value = v.prototype.val.values[0].ice_staticId() 445 except: 446 proto_value = None 447 448 self.ctx.out(" Subtype: %s" % proto_value) 449 self.ctx.out(" Min: %s" % (v.min and v.min.val or "")) 450 self.ctx.out(" Max: %s" % (v.max and v.max.val or "")) 451 values = omero.rtypes.unwrap(v.values) 452 self.ctx.out(" Values: %s" % (values and ", ".join(values) or ""))
453 print_params("inputs:", job_params.inputs) 454 print_params("outputs:", job_params.outputs) 455
456 - def serve(self, args):
457 458 # List of processors which have been started 459 if not hasattr(self, "_processors"): 460 self._processors = [] 461 462 debug = args.verbose 463 background = args.background 464 timeout = args.timeout 465 client = self.ctx.conn(args) 466 sf = client.sf 467 who = [self._parse_who(w) for w in args.who] 468 if not who: 469 who = [] # Official scripts only 470 471 # Similar to omero.util.Server starting here 472 import logging 473 original = list(logging._handlerList) 474 roots = list(logging.getLogger().handlers) 475 logging._handlerList = [] 476 logging.getLogger().handlers = [] 477 478 from omero.util import configure_logging 479 from omero.processor import usermode_processor 480 lvl = debug and 10 or 20 481 configure_logging(loglevel=lvl) 482 483 try: 484 try: 485 impl = usermode_processor(client, serverid = "omer.scripts.serve", accepts_list = who, omero_home=self.ctx.dir) 486 self._processors.append(impl) 487 except exceptions.Exception, e: 488 self.ctx.die(100, "Failed initialization: %s" % e) 489 490 if background: 491 def cleanup(): 492 impl.cleanup() 493 logging._handlerList = original 494 logging.getLogger().handlers = roots
495 atexit.register(cleanup) 496 else: 497 try: 498 def handler(signum, frame): 499 raise SystemExit() 500 old = signal.signal(signal.SIGALRM, handler) 501 signal.alarm(timeout) 502 self.ctx.input("Press any key to exit...\n") 503 signal.alarm(0) 504 finally: 505 self.ctx.dbg("DONE") 506 signal.signal(signal.SIGTERM, old) 507 impl.cleanup() 508 finally: 509 if not background: 510 logging._handlerList = original 511 logging.getLogger().handlers = roots 512 513 return impl 514 515
516 - def upload(self, args):
517 518 p = path(args.file) 519 if not p.exists(): 520 self.ctx.die(502, "File does not exist: %s" % p.abspath()) 521 522 import omero 523 c = self.ctx.conn(args) 524 scriptSvc = c.sf.getScriptService() 525 526 if args.official: 527 try: 528 id = scriptSvc.uploadOfficialScript(args.file, p.text()) 529 except omero.ApiUsageException, aue: 530 if "editScript" in aue.message: 531 self.ctx.die(502, "%s already exists; use 'replace' instead" % args.file) 532 else: 533 self.ctx.die(504, "ApiUsageException: %s" % aue.message) 534 except omero.SecurityViolation, sv: 535 self.ctx.die(503, "SecurityViolation: %s" % sv.message) 536 else: 537 id = scriptSvc.uploadScript(args.file, p.text()) 538 539 self.ctx.out("Uploaded %sscript as original file #%s" % ((args.official and "official " or ""), id)) 540 self.ctx.set("script.file.id", id)
541
542 - def replace(self, args):
543 ofile = args.id 544 fpath = args.file 545 546 client = self.ctx.conn(args) 547 ofile = client.sf.getQueryService().get("OriginalFile", ofile) 548 #client.upload(fpath, ofile=ofile) 549 550 file = open(fpath) 551 scriptText = file.read() 552 file.close() 553 scriptSvc = client.sf.getScriptService() 554 scriptSvc.editScript(ofile, scriptText)
555
556 - def delete(self, args):
557 if len(args) != 1: 558 self.ctx.die(123, "Usage: <original file id>") 559 560 ofile = long(args.args[0]) 561 import omero_api_IScript_ice 562 client = self.ctx.conn(args) 563 try: 564 client.sf.getScriptService().deleteScript(ofile) 565 except exceptions.Exception, e: 566 self.ctx.err("Failed to delete script: %s (%s)" % (ofile, e))
567
568 - def disable(self, args):
569 ofile = self.setmimetype(args) 570 self.ctx.out("Disabled %s by setting mimetype to %s" % (ofile.id.val, args.mimetype))
571
572 - def enable(self, args):
573 ofile = self.setmimetype(args) 574 self.ctx.out("Enabled %s by setting mimetype to %s" % (ofile.id.val, args.mimetype))
575
576 - def setmimetype(self, args):
577 from omero.rtypes import rstring 578 client = self.ctx.conn(args) 579 script_id, ofile = self._file(args, client) 580 ofile.setMimetype(rstring(args.mimetype)) 581 return client.sf.getUpdateService().saveAndReturnObject(ofile)
582 583 # 584 # Other 585 #
586 - def run(self, args):
587 if not os.path.exists(args.file): 588 self.ctx.die(670, "No such file: %s" % args.file) 589 else: 590 client = self.ctx.conn(args) 591 store = SessionsStore() 592 srv, usr, uuid = store.get_current() 593 props = store.get(srv, usr, uuid) 594 595 from omero.scripts import parse_file 596 from omero.util.temp_files import create_path 597 path = create_path() 598 text = """ 599 omero.host=%(omero.host)s 600 omero.user=%(omero.sess)s 601 omero.pass=%(omero.sess)s 602 """ 603 path.write_text(text % props) 604 605 params = parse_file(args.file) 606 m = self._parse_inputs(args, params) 607 for k, v in m.items(): 608 if v is not None: 609 client.setInput(k, v) 610 611 p = self.ctx.popen([sys.executable, args.file], stdout=sys.stdout, stderr=sys.stderr,\ 612 ICE_CONFIG = str(path)) 613 p.wait() 614 if p.poll() != 0: 615 self.ctx.die(p.poll(), "Execution failed.")
616 617 # 618 # Helpers 619 #
620 - def _parse_inputs(self, args, params):
621 from omero.scripts import parse_inputs, parse_input, MissingInputs 622 try: 623 rv = parse_inputs(args.input, params) 624 except MissingInputs, mi: 625 rv = mi.inputs 626 for key in mi.keys: 627 value = self.ctx.input("""Enter value for "%s": """ % key, required = True) 628 rv.update(parse_input("%s=%s" % (key, value), params)) 629 return rv
630
631 - def _parse_scripts(self, scripts, msg):
632 """ 633 Parses a list of scripts to self.ctx.out 634 """ 635 from omero.util.text import TableBuilder 636 tb = TableBuilder("id", msg) 637 for x in scripts: 638 tb.row(x.id.val, x.path.val + x.name.val) 639 self.ctx.out(str(tb.build()))
640
641 - def _file(self, args, client):
642 import omero 643 f = args.original_file 644 q = client.sf.getQueryService() 645 svc = client.sf.getScriptService() 646 647 if f is None: 648 self.ctx.die(100, "No script provided") 649 elif f.startswith("file="): 650 f = f[5:] 651 652 try: 653 script_id = long(f) 654 except: 655 script_path = str(f) 656 script_id = svc.getScriptID(script_path) 657 ofile = q.get("OriginalFile", script_id) 658 659 return script_id, ofile
660
661 - def _parse_who(self, who):
662 """ 663 Parses who items of the form: "user", "group", "user=1", "group=6" 664 """ 665 666 import omero 667 WHO_FACTORY = {"user":omero.model.ExperimenterI, "group":omero.model.ExperimenterGroupI} 668 WHO_CURRENT = { "user":lambda ec: ec.userId, 669 "group":lambda ec: ec.groupId} 670 671 for key, factory in WHO_FACTORY.items(): 672 if who.startswith(key): 673 if who == key: 674 id = WHO_CURRENT[key](self.ctx._event_context) 675 return factory(id, False) 676 else: 677 parts = who.split("=") 678 if len(parts) != 2: 679 continue 680 else: 681 id = long(parts[1]) 682 return factory(id, False)
683 684 try: 685 register("script", ScriptControl, HELP) 686 except NameError: 687 if __name__ == "__main__": 688 cli = CLI() 689 cli.register("script", ScriptControl, HELP) 690 cli.invoke(sys.argv[1:]) 691