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