1
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
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:
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
95
96 sub = parser.sub()
97
98
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
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
288
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):
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
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
346
347
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 if args.who:
392 who = [self._parse_who(sf, w) for w in args.who]
393 scripts = svc.getUserScripts(who)
394 banner = "Scripts for %s" % ", ".join(args.who)
395 else:
396 scripts = svc.getScripts()
397 banner = "Official scripts"
398 self._parse_scripts(scripts, banner)
399
400 - def log(self, args):
403
405 client = self.ctx.conn(args)
406 script_id, ofile = self._file(args, client)
407 import omero_api_IScript_ice
408 svc = client.sf.getScriptService()
409 job_params = svc.getParams(script_id)
410 if job_params:
411 self.ctx.out("")
412 self.ctx.out("id: %s" % script_id)
413 self.ctx.out("name: %s" % job_params.name)
414 self.ctx.out("version: %s" % job_params.version)
415 self.ctx.out("authors: %s" % ", ".join(job_params.authors))
416 self.ctx.out("institutions: %s" % ", ".join(job_params.institutions))
417 self.ctx.out("description: %s" % job_params.description)
418 self.ctx.out("namespaces: %s" % ", ".join(job_params.namespaces))
419 self.ctx.out("stdout: %s" % job_params.stdoutFormat)
420 self.ctx.out("stderr: %s" % job_params.stderrFormat)
421 def print_params(which, params):
422 import omero
423 self.ctx.out(which)
424 for k, v in params.items():
425 self.ctx.out(" %s - %s" % (k, (v.description and v.description or "(no description)")))
426 self.ctx.out(" Optional: %s" % v.optional)
427 self.ctx.out(" Type: %s" % v.prototype.ice_staticId())
428 if isinstance(v.prototype, omero.RCollection):
429 self.ctx.out(" Subtype: %s" % v.prototype.val[0].ice_staticId())
430 elif isinstance(v.prototype, omero.RMap):
431 self.ctx.out(" Subtype: %s" % v.prototype.val.values[0].ice_staticId())
432 self.ctx.out(" Min: %s" % (v.min and v.min.val or ""))
433 self.ctx.out(" Max: %s" % (v.max and v.max.val or ""))
434 values = omero.rtypes.unwrap(v.values)
435 self.ctx.out(" Values: %s" % (values and ", ".join(values) or ""))
436 print_params("inputs:", job_params.inputs)
437 print_params("outputs:", job_params.outputs)
438
440
441
442 if not hasattr(self, "_processors"):
443 self._processors = []
444
445 debug = args.verbose
446 background = args.background
447 timeout = args.timeout
448 client = self.ctx.conn(args)
449 sf = client.sf
450 who = [self._parse_who(sf, w) for w in args.who]
451 if not who:
452 who = []
453
454
455 import logging
456 original = list(logging._handlerList)
457 roots = list(logging.getLogger().handlers)
458 logging._handlerList = []
459 logging.getLogger().handlers = []
460
461 from omero.util import configure_logging
462 from omero.processor import usermode_processor
463 lvl = debug and 10 or 20
464 configure_logging(loglevel=lvl)
465
466 try:
467 try:
468 impl = usermode_processor(client, serverid = "omer.scripts.serve", accepts_list = who, omero_home=self.ctx.dir)
469 self._processors.append(impl)
470 except exceptions.Exception, e:
471 self.ctx.die(100, "Failed initialization: %s" % e)
472
473 if background:
474 def cleanup():
475 impl.cleanup()
476 logging._handlerList = original
477 logging.getLogger().handlers = roots
478 atexit.register(cleanup)
479 else:
480 try:
481 def handler(signum, frame):
482 raise SystemExit()
483 old = signal.signal(signal.SIGALRM, handler)
484 signal.alarm(timeout)
485 self.ctx.input("Press any key to exit...\n")
486 signal.alarm(0)
487 finally:
488 self.ctx.dbg("DONE")
489 signal.signal(signal.SIGTERM, old)
490 impl.cleanup()
491 finally:
492 if not background:
493 logging._handlerList = original
494 logging.getLogger().handlers = roots
495
496 return impl
497
498
500
501 p = path(args.file)
502 if not p.exists():
503 self.ctx.die(502, "File does not exist: %s" % p.abspath())
504
505 import omero
506 c = self.ctx.conn(args)
507 scriptSvc = c.sf.getScriptService()
508
509 if args.official:
510 try:
511 id = scriptSvc.uploadOfficialScript(args.file, p.text())
512 except omero.ApiUsageException, aue:
513 if "editScript" in aue.message:
514 self.ctx.die(502, "%s already exists; use 'replace' instead" % args.file)
515 else:
516 self.ctx.die(504, "ApiUsageException: %s" % aue.message)
517 except omero.SecurityViolation, sv:
518 self.ctx.die(503, "SecurityViolation: %s" % sv.message)
519 else:
520 id = scriptSvc.uploadScript(args.file, p.text())
521
522 self.ctx.out("Uploaded %sscript as original file #%s" % ((args.official and "official " or ""), id))
523 self.ctx.set("script.file.id", id)
524
538
540 if len(args) != 1:
541 self.ctx.die(123, "Usage: <original file id>")
542
543 ofile = long(args.args[0])
544 import omero_api_IScript_ice
545 client = self.ctx.conn(args)
546 try:
547 client.sf.getScriptService().deleteScript(ofile)
548 except exceptions.Exception, e:
549 self.ctx.err("Failed to delete script: %s (%s)" % (ofile, e))
550
552 ofile = self.setmimetype(args)
553 self.ctx.out("Disabled %s by setting mimetype to %s" % (ofile.id.val, args.mimetype))
554
556 ofile = self.setmimetype(args)
557 self.ctx.out("Enabled %s by setting mimetype to %s" % (ofile.id.val, args.mimetype))
558
565
566
567
568
569 - def run(self, args):
570 if not os.path.exists(args.file):
571 self.ctx.die(670, "No such file: %s" % args.file)
572 else:
573 client = self.ctx.conn(args)
574 store = SessionsStore()
575 srv, usr, uuid = store.get_current()
576 props = store.get(srv, usr, uuid)
577
578 from omero.scripts import parse_file
579 from omero.util.temp_files import create_path
580 path = create_path()
581 text = """
582 omero.host=%(omero.host)s
583 omero.user=%(omero.sess)s
584 omero.pass=%(omero.sess)s
585 """
586 path.write_text(text % props)
587
588 params = parse_file(args.file)
589 m = self._parse_inputs(args, params)
590 for k, v in m.items():
591 if v is not None:
592 client.setInput(k, v)
593
594 p = self.ctx.popen([sys.executable, args.file], stdout=sys.stdout, stderr=sys.stderr,\
595 ICE_CONFIG = str(path))
596 p.wait()
597 if p.poll() != 0:
598 self.ctx.die(p.poll(), "Execution failed.")
599
600
601
602
613
623
624 - def _file(self, args, client):
625 import omero
626 f = args.original_file
627 q = client.sf.getQueryService()
628 svc = client.sf.getScriptService()
629
630 if f is None:
631 self.ctx.die(100, "No script provided")
632 elif f.startswith("file="):
633 f = f[5:]
634
635 try:
636 script_id = long(f)
637 except:
638 script_path = str(f)
639 script_id = svc.getScriptID(script_path)
640 ofile = q.get("OriginalFile", script_id)
641
642 return script_id, ofile
643
645 """
646 Parses who items of the form: "user", "group", "user=1", "group=6"
647 """
648
649 import omero
650 WHO_FACTORY = {"user":omero.model.ExperimenterI, "group":omero.model.ExperimenterGroupI}
651 WHO_CURRENT = { "user":lambda sf: sf.getAdminService().getEventContext().userId,
652 "group":lambda sf: sf.getAdminService().getEventContext().groupId}
653
654 for key, factory in WHO_FACTORY.items():
655 if who.startswith(key):
656 if who == key:
657 id = WHO_CURRENT[key](sf)
658 return factory(id, False)
659 else:
660 parts = who.split("=")
661 if len(parts) != 2:
662 continue
663 else:
664 id = long(parts[1])
665 return factory(id, False)
666
667 try:
668 register("script", ScriptControl, HELP)
669 except NameError:
670 if __name__ == "__main__":
671 cli = CLI()
672 cli.register("script", ScriptControl, HELP)
673 cli.invoke(sys.argv[1:])
674