1
2
3
4 """
5 Python driver for OMERO
6
7 Provides access to various OMERO.blitz server- and client-side
8 utilities, including starting and stopping servers, running
9 analyses, configuration, and more.
10
11 Usable via the ./omero script provided with the distribution
12 as well as from python via "import omero.cli; omero.cli.argv()"
13
14 Arguments are taken from (in order of priority): the run method
15 arguments, sys.argv, and finally from standard-in using the
16 cmd.Cmd.cmdloop method.
17
18 Josh Moore, josh at glencoesoftware.com
19 Copyright (c) 2007, Glencoe Software, Inc.
20 See LICENSE for details.
21
22 """
23
24 sys = __import__("sys")
25 cmd = __import__("cmd")
26
27 import string, re, os, subprocess, socket, traceback, glob, platform, time
28 import shlex
29 from threading import Thread, Lock
30 from path import path
31
32 from omero_ext.argparse import ArgumentError
33 from omero_ext.argparse import ArgumentParser
34 from omero_ext.argparse import FileType
35 from omero_ext.argparse import Namespace
36
37
38 from omero_ext.argparse import ArgumentDefaultsHelpFormatter
39 from omero_ext.argparse import RawDescriptionHelpFormatter
40 from omero_ext.argparse import RawTextHelpFormatter
41 from omero_ext.argparse import SUPPRESS
42
43 from omero.util import get_user
44 from omero.util.concurrency import get_event
45 from omero.util.sessions import SessionsStore
46
47 import omero
48
49
50
51
52
53 try:
54 from omero_version import omero_version
55 VERSION=omero_version
56 except ImportError:
57 VERSION="Unknown"
58
59 DEBUG = 0
60 if os.environ.has_key("DEBUG"):
61 try:
62 DEBUG = int(os.environ["DEBUG"])
63 except ValueError:
64 DEBUG = 1
65 print "Deprecated warning: use the 'bin/omero --debug=x [args]' to debug"
66 print "Running omero with debugging == 1"
67
68 OMERODOC = """
69 Command-line tool for local and remote interactions with OMERO.
70 """
71 OMEROSHELL = """OMERO Python Shell. Version %s""" % str(VERSION)
72 OMEROHELP = """Type "help" for more information, "quit" or Ctrl-D to exit"""
73 OMEROSUBS = """Use %(prog)s <subcommand> -h for more information."""
74 OMEROSUBM = """<subcommand>"""
75 OMEROCLI = path(__file__).expand().dirname()
76 OMERODIR = os.getenv('OMERODIR', None)
77 if OMERODIR is not None:
78 OMERODIR = path(OMERODIR)
79 else:
80 OMERODIR = OMEROCLI.dirname().dirname().dirname()
81
82 COMMENT = re.compile("^\s*#")
83 RELFILE = re.compile("^\w")
84 LINEWSP = re.compile("^\s*\w+\s+")
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
103 self.rv = rv
104 Exception.__init__(self, *args)
105
106
107
108
109
129
130
132 """
133 Namespace subclass which prevents overwriting any values by accident.
134 """
136 if hasattr(self, name):
137 raise Exception("%s already has field %s" % (self.__class__.__name__, name))
138 else:
139 return Namespace.__setattr__(self, name, value)
140
141
143 """
144 Extension of ArgumentParser for simplifying the
145 _configure() code in most Controls
146 """
147
149 kwargs["formatter_class"] = HelpFormatter
150 ArgumentParser.__init__(self, *args, **kwargs)
151 self._positionals.title = "Positional Arguments"
152 self._optionals.title = "Optional Arguments"
153 self._optionals.description = "In addition to any higher level options"
154
157
158 - def add(self, sub, func, help, **kwargs):
162
164 group = self.add_argument_group('Login arguments',
165 'Optional session arguments')
166 group.add_argument("-C", "--create", action = "store_true",
167 help = "Create a new session regardless of existing ones")
168 group.add_argument("-s", "--server",
169 help = "Hostname of the OMERO server")
170 group.add_argument("-p", "--port",
171 help = "Port of the OMERO server")
172 group.add_argument("-g", "--group",
173 help = "OMERO server default group")
174 group.add_argument("-u", "--user",
175 help = "OMERO server login username")
176 group.add_argument("-w", "--password",
177 help = "OMERO server login password")
178 group.add_argument("-k", "--key", help = "UUID of an active session")
179
187
198
200 """
201 Extension of the argparse.FileType to prevent
202 overwrite existing files.
203 """
205 if string != "-" and os.path.exists(string):
206 raise ValueError("File exists: %s" % string)
207 return FileType.__call__(self, string)
208
209
211 """
212 Extension of the argparse.FileType that requires
213 an existing file.
214 """
216 if not string == "-" and not os.path.exists(string):
217 raise ValueError("File does not exist: %s" % string)
218 if not string == "-":
219 return FileType.__call__(self, string)
220 else:
221 return string
222
223
225 """
226 Extension of the argparse.FileType to only allow
227 existing directories.
228 """
230 p = path(string)
231 if not p.exists():
232 raise ValueError("Directory does not exist: %s" % string)
233 elif not p.isdir():
234 raise ValueError("Path is not a directory: %s" % string)
235 return str(p.abspath())
236
237
239 """
240 Location for all logic which maps from server exceptions
241 to specific states. This could likely be moved elsewhere
242 for general client-side usage.
243 """
245 if isinstance(ve, omero.ValidationException):
246 if "org.hibernate.exception.ConstraintViolationException: could not insert" in str(ve):
247 return True
248
249
251 """Simple context used for default logic. The CLI registry which registers
252 the plugins installs itself as a fully functional Context.
253
254 The Context class is designed to increase pluggability. Rather than
255 making calls directly on other plugins directly, the pub() method
256 routes messages to other commands. Similarly, out() and err() should
257 be used for printing statements to the user, and die() should be
258 used for exiting fatally.
259
260 """
261
262 - def __init__(self, controls = None, params = None, prog = sys.argv[0]):
263 self.controls = controls
264 if self.controls is None:
265 self.controls = {}
266 self.params = params
267 if self.params is None:
268 self.params = {}
269 self.event = get_event(name="CLI")
270 self.dir = OMERODIR
271 self.isdebug = DEBUG
272 self.topics = {"debug":"""
273
274 debug options for developers:
275
276 The value to the debug argument is a comma-separated list of commands:
277
278 * 'debug' prints at the "debug" level. Similar to setting DEBUG=1 in the environment.
279 * 'trace' runs the command with tracing enabled.
280 * 'profile' runs the command with profiling enabled.
281
282 Only one of "trace" and "profile" can be chosen.
283
284 Example:
285
286 bin/omero --debug=debug,trace admin start # Debugs at level 1 and prints tracing
287 bin/omero -d1 admin start # Debugs at level 1
288 bin/omero -dp admin start # Prints profiling
289 bin/omero -dt,p admin start # Fails!; can't print tracing and profiling together
290 bin/omero -d0 admin start # Disables debugging
291 """}
292 self.parser = Parser(prog = prog, description = OMERODOC)
293 self.subparsers = self.parser_init(self.parser)
294
295 - def post_process(self):
296 """
297 Runs further processing once all the controls have been added.
298 """
299 sessions = self.controls["sessions"]
300
301 login = self.subparsers.add_parser("login", help="Shortcut for 'sessions login'")
302 login.set_defaults(func=lambda args:sessions.login(args))
303 sessions._configure_login(login)
304
305 logout = self.subparsers.add_parser("logout", help="Shortcut for 'sessions logout'")
306 logout.set_defaults(func=lambda args:sessions.logout(args))
307 sessions._configure_dir(logout)
308
309 - def parser_init(self, parser):
310 parser.add_argument("-v", "--version", action="version", version="%%(prog)s %s" % VERSION)
311 parser.add_argument("-d", "--debug", help="Use 'help debug' for more information", default = SUPPRESS)
312 parser.add_argument("--path", help="Add file or directory to plugin list. Supports globs.", action = "append")
313 parser.add_login_arguments()
314 subparsers = parser.add_subparsers(title="Subcommands", description=OMEROSUBS, metavar=OMEROSUBM)
315 return subparsers
316
317 - def get(self, key, defvalue = None):
318 return self.params.get(key, defvalue)
319
320 - def set(self, key, value = True):
321 self.params[key] = value
322
323 - def safePrint(self, text, stream, newline = True):
324 """
325 Prints text to a given string, capturing any exceptions.
326 """
327 try:
328 stream.write(str(text))
329 if newline:
330 stream.write("\n")
331 else:
332 stream.flush()
333 except:
334 print >>sys.stderr, "Error printing text"
335 print >>sys.stdout, text
336 if self.isdebug:
337 traceback.print_exc()
338
339 - def pythonpath(self):
340 """
341 Converts the current sys.path to a PYTHONPATH string
342 to be used by plugins which must start a new process.
343
344 Note: this was initially created for running during
345 testing when PYTHONPATH is not properly set.
346 """
347 path = list(sys.path)
348 for i in range(0,len(path)-1):
349 if path[i] == '':
350 path[i] = os.getcwd()
351 pythonpath = ":".join(path)
352 return pythonpath
353
355 """
356 Returns a user directory (as path.path) which can be used
357 for storing configuration. The directory is guaranteed to
358 exist and be private (700) after execution.
359 """
360 dir = path(os.path.expanduser("~")) / "omero" / "cli"
361 if not dir.exists():
362 dir.mkdir()
363 elif not dir.isdir():
364 raise Exception("%s is not a directory"%dir)
365 dir.chmod(0700)
366 return dir
367
368 - def pub(self, args, strict = False):
369 self.safePrint(str(args), sys.stdout)
370
371 - def input(self, prompt, hidden = False, required = False):
372 """
373 Reads from standard in. If hidden == True, then
374 uses getpass
375 """
376 try:
377 while True:
378 if hidden:
379 import getpass
380 rv = getpass.getpass(prompt)
381 else:
382 rv = raw_input(prompt)
383 if required and not rv:
384 self.out("Input required")
385 continue
386 return rv
387 except KeyboardInterrupt:
388 self.die(1, "Cancelled")
389
390 - def out(self, text, newline = True):
391 """
392 Expects as single string as argument"
393 """
394 self.safePrint(text, sys.stdout, newline)
395
396 - def err(self, text, newline = True):
397 """
398 Expects a single string as argument.
399 """
400 self.safePrint(text, sys.stderr, newline)
401
402 - def dbg(self, text, newline = True, level = 1):
403 """
404 Passes text to err() if self.isdebug is set
405 """
406 if self.isdebug >= level:
407 self.err(text, newline)
408
409 - def die(self, rc, args):
410 raise Exception((rc,args))
411
412 - def exit(self, args):
413 self.out(args)
414 self.interrupt_loop = True
415
416 - def call(self, args):
418
419 - def popen(self, args):
421
422 - def sleep(self, time):
423 self.event.wait(time)
424
425
426
428 """Controls get registered with a CLI instance on loadplugins().
429
430 To create a new control, subclass BaseControl, implement _configure,
431 and end your module with::
432
433 try:
434 register("name", MyControl, HELP)
435 except:
436 if __name__ == "__main__":
437 cli = CLI()
438 cli.register("name", MyControl, HELP)
439 cli.invoke(sys.argv[1:])
440
441 This module should be put in the omero.plugins package.
442
443 All methods which do NOT begin with "_" are assumed to be accessible
444 to CLI users.
445 """
446
447
448
449
450
452 self.dir = path(dir)
453 self.ctx = ctx
454 if self.ctx is None:
455 self.ctx = Context()
456
458 p_s = platform.system()
459 if p_s == 'Windows':
460 return True
461 else:
462 return False
463
465 """
466 Return hostname of current machine. Termed to be the
467 value return from socket.gethostname() up to the first
468 decimal.
469 """
470 if not hasattr(self, "hostname") or not self.hostname:
471 self.hostname = socket.gethostname()
472 if self.hostname.find(".") > 0:
473 self.hostname = self.hostname.split(".")[0]
474 return self.hostname
475
476 - def _node(self, omero_node = None):
477 """
478 Return the name of this node, using either the environment
479 vairable OMERO_NODE or _host(). Some subclasses may
480 override this functionality, most notably "admin" commands
481 which assume a node name of "master".
482
483 If the optional argument is not None, then the OMERO_NODE
484 environment variable will be set.
485 """
486 if omero_node != None:
487 os.environ["OMERO_NODE"] = omero_node
488
489 if os.environ.has_key("OMERO_NODE"):
490 return os.environ["OMERO_NODE"]
491 else:
492 return self._host()
493
495 """
496 General data method for creating a path from an Ice property.
497 """
498 try:
499 nodepath = self._properties()[property]
500
501 if RELFILE.match(nodepath):
502 nodedata = self.dir / path(nodepath)
503 else:
504 nodedata = path(nodepath)
505
506 created = False
507 if not nodedata.exists():
508 self.ctx.out("Creating "+nodedata)
509 nodedata.makedirs()
510 created = True
511 return (nodedata, created)
512
513 except KeyError, ke:
514 self.ctx.err(property + " is not configured")
515 self.ctx.die(4, str(ke))
516
518 """
519 Initialize the directory into which the current node will log.
520 """
521 props = self._properties()
522 nodedata = self._nodedata()
523 logdata = self.dir / path(props["Ice.StdOut"]).dirname()
524 if not logdata.exists():
525 self.ctx.out("Initializing %s" % logdata)
526 logdata.makedirs()
527
528
530 """
531 Returns the data directory path for this node. This is determined
532 from the "IceGrid.Node.Data" property in the _properties()
533 map.
534
535 The directory will be created if it does not exist.
536 """
537 data, created = self._icedata("IceGrid.Node.Data")
538 return data
539
541 """
542 Returns the data directory for the IceGrid registry.
543 This is determined from the "IceGrid.Registry.Data" property
544 in the _properties() map.
545
546 The directory will be created if it does not exist, and
547 a warning issued.
548 """
549 data, created = self._icedata("IceGrid.Registry.Data")
550
552 """
553 Returns a path of the form "_nodedata() / _node() + ".pid",
554 i.e. a file named NODENAME.pid in the node's data directory.
555 """
556 pidfile = self._nodedata() / (self._node() + ".pid")
557 return pidfile
558
560 """
561 Returns a list of configuration files for this node. This
562 defaults to the internal configuration for all nodes,
563 followed by a file named NODENAME.cfg under the etc/
564 directory, following by PLATFORM.cfg if it exists.
565 """
566 cfgs = self.dir / "etc"
567 internal = cfgs / "internal.cfg"
568 owncfg = cfgs / self._node() + ".cfg"
569 results = [internal,owncfg]
570
571 p_s = platform.system()
572 p_c = cfgs / p_s + ".cfg"
573 if p_c.exists():
574 results.append(p_c)
575 return results
576
578 """
579 Uses _cfglist() to return a string argument of the form
580 "--Ice.Config=..." suitable for passing to omero.client
581 as an argument.
582 """
583 icecfg = "--Ice.Config=%s" % ",".join(self._cfglist())
584 return str(icecfg)
585
587 """
588 Returns an Ice.Config string with only the internal configuration
589 file for connecting to the IceGrid Locator.
590 """
591 intcfg = self.dir / "etc" / "internal.cfg"
592 intcfg.abspath()
593 return str("--Ice.Config=%s" % intcfg)
594
596 """
597 Loads all files returned by _cfglist() into a new
598 Ice.Properties instance and return the map from
599 getPropertiesForPrefix(prefix) where the default is
600 to return all properties.
601 """
602 import Ice
603 if getattr(self, "_props", None) is None:
604 self._props = Ice.createProperties()
605 for cfg in self._cfglist():
606 try:
607 self._props.load(str(cfg))
608 except Exception, exc:
609 self.ctx.die(3, "Could not find file: "+cfg + "\nDid you specify the proper node?")
610 return self._props.getPropertiesForPrefix(prefix)
611
613 while not root_pass or len(root_pass) < 1:
614 root_pass = self.ctx.input("Please enter password%s: "%reason, hidden = True)
615 if not strict:
616 return root_pass
617 if root_pass == None or root_pass == "":
618 self.ctx.err("Password cannot be empty")
619 continue
620 confirm = self.ctx.input("Please re-enter password%s: "%reason, hidden = True)
621 if root_pass != confirm:
622 root_pass = None
623 self.ctx.err("Passwords don't match")
624 continue
625 break
626 return root_pass
627
628
629
630
631
632
634 """
635 f: path part
636 """
637 if dir is None:
638 dir = self.dir
639 else:
640 dir = path(dir)
641 p = path(f)
642 if p.exists() and p.isdir():
643 if not f.endswith(os.sep):
644 return [p.basename()+os.sep]
645 return [ str(x)[len(f):] for x in p.listdir() ]
646 else:
647 results = [ str(x.basename()) for x in dir.glob(f+"*") ]
648 if len(results) == 1:
649
650 maybe_dir = path(results[0])
651 if maybe_dir.exists() and maybe_dir.isdir():
652 return [ results[0] + os.sep ]
653 return results
654
655 - def _complete(self, text, line, begidx, endidx):
656 try:
657 return self._complete2(text, line, begidx, endidx)
658 except:
659 self.ctx.dbg("Complete error: %s" % traceback.format_exc())
660
661 - def _complete2(self, text, line, begidx, endidx):
662 items = shlex.split(line)
663 parser = getattr(self, "parser", None)
664 if parser:
665 result = []
666 actions = getattr(parser, "_actions")
667 if actions:
668 if len(items) > 1:
669 subparsers = [x for x in actions if x.__class__.__name__ == "_SubParsersAction"]
670 if subparsers:
671 subparsers = subparsers[0]
672 choice = subparsers.choices.get(items[-1])
673 if choice and choice._actions:
674 actions = choice._actions
675 if len(items) > 2:
676 actions = []
677
678 for action in actions:
679 if action.__class__.__name__ == "_HelpAction":
680 result.append("-h")
681 elif action.__class__.__name__ == "_SubParsersAction":
682 result.extend(action.choices)
683
684 return ["%s " % x for x in result if (not text or x.startswith(text)) and line.find(" %s " % x) < 0]
685
686
687 completions = [method for method in dir(self) if callable(getattr(self, method)) ]
688 return [ str(method + " ") for method in completions if method.startswith(text) and not method.startswith("_") ]
689
690
691 -class CLI(cmd.Cmd, Context):
692 """
693 Command line interface class. Supports various styles of executing the
694 registered plugins. Each plugin is given the chance to update this class
695 by adding methods of the form "do_<plugin name>".
696 """
697
699 """
700 Thread-safe class for storing whether or not all the plugins
701 have been loaded
702 """
704 self.lock = Lock()
705 self.done = False
707 self.lock.acquire()
708 try:
709 return self.done
710 finally:
711 self.lock.release()
713 self.lock.acquire()
714 try:
715 self.done = True
716 finally:
717 self.lock.release()
718
720 """
721 Also sets the "_client" field for this instance to None. Each cli
722 maintains a single active client. The "session" plugin is responsible
723 for the loading of the client object.
724 """
725 cmd.Cmd.__init__(self)
726 Context.__init__(self, prog = prog)
727 self.prompt = 'omero> '
728 self.interrupt_loop = False
729 self.rv = 0
730 self._stack = []
731 self._client = None
732 self._plugin_paths = [OMEROCLI / "plugins"]
733 self._pluginsLoaded = CLI.PluginsLoaded()
734
738
739 - def invoke(self, line, strict = False, previous_args = None):
740 """
741 Copied from cmd.py
742 """
743 try:
744 line = self.precmd(line)
745 stop = self.onecmd(line, previous_args)
746 stop = self.postcmd(stop, line)
747 if strict:
748 self.assertRC()
749 finally:
750 if len(self._stack) == 0:
751 self.close()
752 else:
753 self.dbg("Delaying close for stack: %s" % len(self._stack), level = 2)
754
756
757 class PWD(BaseControl):
758 def __call__(self, args):
759 self.ctx.out(os.getcwd())
760 class LS(BaseControl):
761 def __call__(self, args):
762 for p in sorted(path(os.getcwd()).listdir()):
763 self.ctx.out(str(p.basename()))
764 class CD(BaseControl):
765 def _complete(self, text, line, begidx, endidx):
766 RE = re.compile("\s*cd\s*")
767 m = RE.match(line)
768 if m:
769 replaced = RE.sub('', line)
770 return self._complete_file(replaced, path(os.getcwd()))
771 return []
772 def _configure(self, parser):
773 parser.set_defaults(func=self.__call__)
774 parser.add_argument("dir", help = "Target directory")
775 def __call__(self, args):
776 os.chdir(args.dir)
777 self.register("pwd", PWD, "Print the current directory")
778 self.register("ls", LS, "Print files in the current directory")
779 self.register("dir", LS, "Alias for 'ls'")
780 self.register("cd", CD, "Change the current directory")
781
782 try:
783 self.selfintro = "\n".join([OMEROSHELL, OMEROHELP])
784 if not self.stdin.isatty():
785 self.selfintro = ""
786 self.prompt = ""
787 while not self.interrupt_loop:
788 try:
789
790 self.cmdloop(self.selfintro)
791 except KeyboardInterrupt, ki:
792 self.selfintro = ""
793 self.out("Use quit to exit")
794 finally:
795 self.close()
796
797 - def postloop(self):
798
799 self.selfintro = ""
800
801 - def onecmd(self, line, previous_args = None):
802 """
803 Single command logic. Overrides the cmd.Cmd logic
804 by calling execute. Also handles various exception
805 conditions.
806 """
807 try:
808
809
810 self.rv = 0
811 try:
812 self._stack.insert(0, line)
813 self.dbg("Stack+: %s" % len(self._stack), level=2)
814 self.execute(line, previous_args)
815 return True
816 finally:
817 self._stack.pop(0)
818 self.dbg("Stack-: %s" % len(self._stack), level=2)
819 except SystemExit, exc:
820 self.dbg("SystemExit raised\n%s" % traceback.format_exc())
821 self.rv = exc.code
822 return False
823
824
825
826
827
828
829
830
831
832
833 except NonZeroReturnCode, nzrc:
834 self.dbg(traceback.format_exc())
835 self.rv = nzrc.rv
836 return False
837
838 - def postcmd(self, stop, line):
839 """
840 Checks interrupt_loop for True and return as much
841 which will end the call to cmdloop. Otherwise use
842 the default postcmd logic (which simply returns stop)
843 """
844 if self.interrupt_loop:
845 return True
846 return cmd.Cmd.postcmd(self, stop, line)
847
848 - def execute(self, line, previous_args):
849 """
850 String/list handling as well as EOF and comment handling.
851 Otherwise, parses the arguments as shlexed and runs the
852 function returned by argparse.
853 """
854
855 if isinstance(line, (str, unicode)):
856 if COMMENT.match(line):
857 return
858 args = shlex.split(line)
859 elif isinstance(line, (tuple, list)):
860 args = list(line)
861 else:
862 self.die(1, "Bad argument type: %s ('%s')" % (type(line), line))
863
864 if not args:
865 return
866 elif args == ["EOF"]:
867 self.exit("")
868 return
869
870 args = self.parser.parse_args(args, previous_args)
871 args.prog = self.parser.prog
872 self.waitForPlugins()
873
874 debug_str = getattr(args, "debug", "")
875 debug_opts = set([x.lower() for x in debug_str.split(",")])
876 if "" in debug_opts:
877 debug_opts.remove("")
878
879 old_debug = self.isdebug
880 if "debug" in debug_opts:
881 self.isdebug = 1
882 debug_opts.remove("debug")
883 elif "0" in debug_opts:
884 self.isdebug = 0
885 debug_opts.remove("0")
886
887 for x in range(1, 9):
888 if str(x) in debug_opts:
889 self.isdebug = x
890 debug_opts.remove(str(x))
891
892 try:
893 if len(debug_opts) == 0:
894 args.func(args)
895 elif len(debug_opts) > 1:
896 self.die(9, "Conflicting debug options: %s" % ", ".join(debug_opts))
897 elif "t" in debug_opts or "trace" in debug_opts:
898 import trace
899 tracer = trace.Trace()
900 tracer.runfunc(args.func, args)
901 elif "p" in debug_opts or "profile" in debug_opts:
902 import hotshot
903 from hotshot import stats
904 prof = hotshot.Profile("hotshot_edi_stats")
905 rv = prof.runcall( lambda: args.func(args) )
906 prof.close()
907 s = stats.load("hotshot_edi_stats")
908 s.sort_stats("time").print_stats()
909 else:
910 self.die(10, "Unknown debug action: %s" % debug_opts)
911 finally:
912 self.isdebug = old_debug
913
916
918 names = self.controls.keys()
919 return [ str(n + " ") for n in names if n.startswith(line) ]
920
921
922
923
924
925 - def exit(self, args, newline=True):
926 self.out(args, newline)
927 self.interrupt_loop = True
928
929 - def die(self, rc, text, newline=True):
934
936 """
937 Configure environment with PYTHONPATH as
938 setup by bin/omero
939
940 This list needs to be kept in line with OmeroPy/bin/omero
941
942 """
943 lpy = str(self.dir / "lib" / "python")
944 ipy = str(self.dir / "lib" / "fallback")
945 vlb = str(self.dir / "var" / "lib")
946 paths = os.path.pathsep.join([lpy, vlb, ipy])
947
948 env = dict(os.environ)
949 pypath = env.get("PYTHONPATH", None)
950 if pypath is None:
951 pypath = paths
952 else:
953 if pypath.endswith(os.path.pathsep):
954 pypath = "%s%s" % (pypath, paths)
955 else:
956 pypath = "%s%s%s" % (pypath, os.path.pathsep, paths)
957 env["PYTHONPATH"] = pypath
958 return env
959
960 - def _cwd(self, cwd):
961 if cwd is None:
962 cwd = str(self.dir)
963 else:
964 cwd = str(cwd)
965 return cwd
966
967 - def call(self, args, strict = True, cwd = None):
968 """
969 Calls the string in a subprocess and dies if the return value is not 0
970 """
971 self.dbg("Executing: %s" % args)
972 rv = subprocess.call(args, env = self._env(), cwd = self._cwd(cwd))
973 if strict and not rv == 0:
974 raise NonZeroReturnCode(rv, "%s => %d" % (" ".join(args), rv))
975 return rv
976
977 - def popen(self, args, cwd = None, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **kwargs):
978 self.dbg("Returning popen: %s" % args)
979 env = self._env()
980 env.update(kwargs)
981 return subprocess.Popen(args, env = env, cwd = self._cwd(cwd), stdout = stdout, stderr = stderr)
982
984 try:
985 f = path(self._cwd(None)) / "etc" / "omero.properties"
986 f = f.open()
987 output = "".join(f.readlines())
988 f.close()
989 except:
990 if self.isdebug:
991 raise
992 print "No omero.properties found"
993 output = ""
994 return output
995
997 for line in output.splitlines():
998 if line.startswith("Listening for transport dt_socket at address"):
999 self.dbg("Ignoring stdout 'Listening for transport' from DEBUG=1")
1000 continue
1001 parts = line.split("=",1)
1002 if len(parts) == 2:
1003 data.properties.setProperty(parts[0],parts[1])
1004 self.dbg("Set property: %s=%s" % (parts[0],parts[1]) )
1005 else:
1006 self.dbg("Bad property:"+str(parts))
1007 return data
1008
1031
1032 - def conn(self, args = None):
1033 """
1034 Returns any active _client object. If one is present but
1035 not alive, it will be removed.
1036
1037 If no client is found and arguments are available,
1038 will use the current settings to connect.
1039
1040 If required attributes are missing, will delegate to the login command.
1041
1042 FIXME: Currently differing setting sessions on the same CLI instance
1043 will misuse a client.
1044 """
1045 if self._client:
1046 self.dbg("Found client")
1047 try:
1048 self._client.getSession().keepAlive(None)
1049 self.dbg("Using client")
1050 return self._client
1051 except KeyboardInterrupt:
1052 raise
1053 except Exception, e:
1054 self.dbg("Removing client: %s" % e)
1055 self._client.closeSession()
1056 self._client = None
1057
1058 if args is not None:
1059 self.controls["sessions"].login(args)
1060
1061 return self._client
1062
1068
1069
1070
1071
1072
1073 - def register(self, name, Control, help):
1076
1078 """ This method is added to the globals when execfile() is
1079 called on each plugin. A Control class should be
1080 passed to the register method which will be added to the CLI.
1081 """
1082 self.controls[name] = (Control, help)
1083
1104
1106 if True:
1107 return
1108 self.dbg("Starting waitForPlugins")
1109 while not self._pluginsLoaded.get():
1110 self.dbg("Waiting for plugins...")
1111 time.sleep(0.1)
1112
1114 """
1115 Finds all plugins and gives them a chance to register
1116 themselves with the CLI instance. Here register_only()
1117 is used to guarantee the orderedness of the plugins
1118 in the parser
1119 """
1120
1121 for plugin_path in self._plugin_paths:
1122 self.loadpath(path(plugin_path))
1123
1124 self.configure_plugins()
1125 self._pluginsLoaded.set()
1126 self.post_process()
1127
1129 if pathobj.isdir():
1130 for plugin in pathobj.walkfiles("*.py"):
1131 if -1 == plugin.find("#"):
1132 self.loadpath(path(plugin))
1133 else:
1134 if self.isdebug:
1135 print "Loading %s" % pathobj
1136 try:
1137 loc = {"register": self.register_only}
1138 execfile( str(pathobj), loc )
1139 except KeyboardInterrupt:
1140 raise
1141 except:
1142 self.err("Error loading: %s" % pathobj)
1143 traceback.print_exc()
1144
1145
1146
1147
1149 """
1150 Main entry point for the OMERO command-line interface. First
1151 loads all plugins by passing them the classes defined here
1152 so they can register their methods.
1153
1154 Then the case where arguments are passed on the command line are
1155 handled.
1156
1157 Finally, the cli enters a command loop reading from standard in.
1158 """
1159
1160
1161 old_ice_config = os.getenv("ICE_CONFIG")
1162 os.unsetenv("ICE_CONFIG")
1163 try:
1164
1165
1166
1167 original_executable = path(args[0])
1168 base_executable = str(original_executable.basename())
1169 if base_executable.find("-") >= 0:
1170 parts = base_executable.split("-")
1171 for arg in args[1:]:
1172 parts.append(arg)
1173 args = parts
1174
1175
1176 cli = CLI(prog = original_executable.split("-")[0])
1177
1178 parser = Parser(add_help = False)
1179
1180 parser.add_argument("--path", help="Add file or directory to plugin list. Supports globs.", action = "append")
1181 ns, args = parser.parse_known_args(args)
1182 if getattr(ns, "path"):
1183 for p in ns.path:
1184 for g in glob.glob(p):
1185 cli._plugin_paths.append(g)
1186
1187 class PluginLoader(Thread):
1188 def run(self):
1189 cli.loadplugins()
1190
1191
1192 PluginLoader().run()
1193
1194 if len(args) > 1:
1195 cli.invoke(args[1:])
1196 return cli.rv
1197 else:
1198 cli.invokeloop()
1199 return cli.rv
1200 finally:
1201 if old_ice_config:
1202 os.putenv("ICE_CONFIG", old_ice_config)
1203
1204
1205
1206
1207
1209
1211 self.orig = arg
1212 self.grp = None
1213 try:
1214 self.grp = long(arg)
1215 except ValueError, ve:
1216 if ":" in arg:
1217 parts = arg.split(":", 1)
1218 if parts[0] == "Group" or "ExperimenterGroup":
1219 try:
1220 self.grp = long(parts[1])
1221 except ValueError, ve:
1222 pass
1223
1225 if self.grp is None:
1226 import omero
1227 a = client.sf.getAdminService()
1228 try:
1229 self.grp = a.lookupGroup(self.orig).id.val
1230 except omero.ApiUsageException, aue:
1231 pass
1232 return self.grp
1233
1235
1238
1240 try:
1241 parts = arg.split(":", 1)
1242 assert len(parts) == 2
1243 type = parts[0]
1244 id = long(parts[1])
1245
1246 import omero
1247 import omero.cmd
1248 return self.cmd_type(\
1249 type=type,\
1250 id=id,\
1251 options={})
1252 except:
1253 raise ValueError("Bad object: %s", arg)
1254
1257
1258
1259
1260
1261
1263
1265 raise Exception("Must be overridden by subclasses")
1266
1271
1272 - def main_method(self, args):
1273 import omero
1274 client = self.ctx.conn(args)
1275 req = self.cmd_type()
1276 self._process_request(req, args, client)
1277
1289
1291 if not isinstance(rsp, omero.cmd.ERR):
1292 return None
1293 else:
1294 sb = "failed: '%s'\n" % rsp.name
1295 if rsp.parameters:
1296 for k in sorted(rsp.parameters):
1297 v = rsp.parameters.get(k, "")
1298 sb += "\t%s=%s\n" % (k, v)
1299 return sb
1300
1322
1324 """
1325 Extension point for subclasses.
1326 """
1327 pass
1328
1330 if not line or line.startswith("#"):
1331 return
1332 parts = line.split("=", 1)
1333 if len(parts) == 1:
1334 parts.append("")
1335 opts[parts[0].strip()] = parts[1].strip()
1336
1337 - def response(self, client, req, loops = 8, ms = 500, wait = None):
1368
1370
1372 raise Exception("Must be overridden by subclasses")
1373
1387
1389 """
1390 Allows configuring before the "obj" n-argument is added.
1391 """
1392 pass
1393
1394 - def main_method(self, args):
1395
1396 import omero
1397 client = self.ctx.conn(args)
1398 cb = None
1399 req = omero.cmd.GraphSpecList()
1400 try:
1401 try:
1402 speclist, status, cb = self.response(client, req)
1403 except omero.LockTimeout, lt:
1404 self.ctx.die(446, "LockTimeout: %s" % lt.message)
1405 finally:
1406 if cb is not None:
1407 cb.close(True)
1408
1409
1410 err = self.get_error(speclist)
1411 if err:
1412 self.ctx.die(367, err)
1413
1414 specs = speclist.list
1415 specmap = dict()
1416 for s in specs:
1417 specmap[s.type] = s
1418 keys = sorted(specmap)
1419
1420 if args.list_details:
1421 for key in keys:
1422 spec = specmap[key]
1423 self.ctx.out("=== %s ===" % key)
1424 for k, v in spec.options.items():
1425 self.ctx.out("%s" % (k,))
1426 return
1427 elif args.list:
1428 self.ctx.out("\n".join(keys))
1429 return
1430
1431 for req in args.obj:
1432 if args.edit:
1433 req.options = self.edit_options(req, specmap)
1434 if args.opt:
1435 for opt in args.opt:
1436 self.line_to_opts(opt, req.options)
1437
1438 self._process_request(req, args, client)
1439
1441
1442 from omero.util import edit_path
1443 from omero.util.temp_files import create_path, remove_path
1444
1445 start_text = """# Edit options for your operation below.\n"""
1446 start_text += ("# === %s ===\n" % req.type)
1447 if req.type not in specmap:
1448 self.ctx.die(162, "Unknown type: %s" % req.type)
1449 start_text += self.append_options(req.type, dict(specmap))
1450
1451 temp_file = create_path()
1452 try:
1453 edit_path(temp_file, start_text)
1454 txt = temp_file.text()
1455 print txt
1456 rv = dict()
1457 for line in txt.split("\n"):
1458 self.line_to_opts(line, rv)
1459 return rv
1460 except RuntimeError, re:
1461 self.ctx.die(954, "%s: Failed to edit %s" % (getattr(re, "pid", "Unknown"), temp_file))
1462
1464 spec = specmap.pop(key)
1465 start_text = ""
1466 for optkey in sorted(spec.options):
1467 optval = spec.options[optkey]
1468 start_text += ("%s%s=%s\n" % (" " * indent, optkey, optval))
1469 if optkey in specmap:
1470 start_text += self.append_options(optkey, specmap, indent+1)
1471 return start_text
1472
1474
1480
1482 if fatal:
1483 self.ctx.die(code, msg % group_id)
1484 else:
1485 self.ctx.err(msg % group_id)
1486
1492
1494 if fatal:
1495 self.ctx.die(code, msg)
1496 else:
1497 self.ctx.err(msg)
1498
1499 - def error_ambiguous_group(self, id_or_name, msg="Ambiguous group identifier: %s", code = 505, fatal = True):
1500 if fatal:
1501 self.ctx.die(code, msg % id_or_name)
1502 else:
1503 self.ctx.err(msg % id_or_name)
1504
1510
1512 if fatal:
1513 self.ctx.die(code, msg % user_id)
1514 else:
1515 self.ctx.err(msg % user_id)
1516
1522
1524 if fatal:
1525 self.ctx.die(code, msg)
1526 else:
1527 self.ctx.err(msg)
1528
1529 - def error_ambiguous_user(self, id_or_name, msg="Ambiguous user identifier: %s", code = 515, fatal = True):
1530 if fatal:
1531 self.ctx.die(code, msg % id_or_name)
1532 else:
1533 self.ctx.err(msg % id_or_name)
1534
1536 import omero
1537 try:
1538 gid = long(group_id)
1539 g = admin.getGroup(gid)
1540 except ValueError:
1541 self.error_invalid_groupid(group_id, fatal = fatal)
1542 return None, None
1543 except omero.ApiUsageException:
1544 self.error_invalid_group(gid, fatal = fatal)
1545 return None, None
1546 return gid, g
1547
1549 import omero
1550 try:
1551 g = admin.lookupGroup(group_name)
1552 gid = g.id.val
1553 except omero.ApiUsageException:
1554 self.error_invalid_group(group_name, fatal = fatal)
1555 return None, None
1556 return gid, g
1557
1558 - def find_group(self, admin, id_or_name, fatal = False):
1559 import omero
1560
1561
1562 try:
1563 g1 = admin.lookupGroup(id_or_name)
1564 except omero.ApiUsageException:
1565 g1 = None
1566
1567
1568 try:
1569 g2 = admin.getGroup(long(id_or_name))
1570 except (ValueError, omero.ApiUsageException):
1571 g2 = None
1572
1573
1574 if g1 and g2:
1575 if not g1.id.val == g2.id.val:
1576 self.error_ambiguous_group(id_or_name, fatal = fatal)
1577 return None, None
1578 else:
1579 g = g1
1580 elif g1:
1581 g = g1
1582 elif g2:
1583 g = g2
1584 else:
1585 self.error_invalid_group(id_or_name, fatal = fatal)
1586 return None, None
1587
1588 return g.id.val, g
1589
1591 import omero
1592 try:
1593 uid = long(user_id)
1594 u = admin.getExperimenter(uid)
1595 except ValueError:
1596 self.error_invalid_userid(user_id, fatal = fatal)
1597 return None, None
1598 except omero.ApiUsageException:
1599 self.error_invalid_user(uid, fatal = fatal)
1600 return None, None
1601 return uid, u
1602
1604 import omero
1605 try:
1606 u = admin.lookupExperimenter(user_name)
1607 uid = u.id.val
1608 except omero.ApiUsageException:
1609 self.error_invalid_user(user_name, fatal = fatal)
1610 return None, None
1611 return uid, u
1612
1613 - def find_user(self, admin, id_or_name, fatal = False):
1614 import omero
1615
1616
1617 try:
1618 u1 = admin.lookupExperimenter(id_or_name)
1619 except omero.ApiUsageException:
1620 u1 = None
1621
1622
1623 try:
1624 u2 = admin.getExperimenter(long(id_or_name))
1625 except (ValueError, omero.ApiUsageException):
1626 u2 = None
1627
1628
1629 if u1 and u2:
1630 if not u1.id.val == u2.id.val:
1631 self.error_ambiguous_user(id_or_name, fatal = fatal)
1632 return None, None
1633 else:
1634 u = u1
1635 elif u1:
1636 u = u1
1637 elif u2:
1638 u = u2
1639 else:
1640 self.error_invalid_user(id_or_name, fatal = fatal)
1641 return None, None
1642
1643 return u.id.val, u
1644
1650
1656
1662
1668
1670 import omero
1671 ids = [x.child.id.val for x in group.copyGroupExperimenterMap()]
1672 return ids
1673
1675 import omero
1676 ids = [x.child.id.val for x in group.copyGroupExperimenterMap() if not x.owner.val]
1677 return ids
1678
1680 import omero
1681 ids = [x.child.id.val for x in group.copyGroupExperimenterMap() if x.owner.val]
1682 return ids
1683