Package omero :: Module cli
[hide private]
[frames] | no frames]

Source Code for Module omero.cli

   1  #!/usr/bin/env python 
   2  # -*- coding: utf-8 -*- 
   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  # Help text 
  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  # Static setup 
  51  # 
  52   
  53  try: 
  54      from omero_version import omero_version 
  55      VERSION=omero_version 
  56  except ImportError: 
  57      VERSION="Unknown" # Usually during testing 
  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  # Possibilities: 
  88  #  - Always return and print any output 
  89  #  - Have a callback on the fired event 
  90  #  - how should state machine work? 
  91  #   -- is the last control stored somwhere? in a stack history[3] 
  92  #   -- or do they all share a central memory? self.ctx["MY_VARIABLE"] 
  93  #  - In almost all cases, mark a flag in the CLI "lastError" and continue, 
  94  #    allowing users to do something of the form: on_success or on_fail 
  95   
  96   
  97  ##################################################### 
  98  # 
  99  # Exceptions 
 100  # 
101 -class NonZeroReturnCode(Exception):
102 - def __init__(self, rv, *args):
103 self.rv = rv 104 Exception.__init__(self, *args)
105 106 107 ##################################################### 108 # 109
110 -class HelpFormatter(RawTextHelpFormatter):
111 """ 112 argparse.HelpFormatter subclass which cleans up our usage, preventing very long 113 lines in subcommands. 114 """ 115
116 - def __init__(self, prog, indent_increment=2, max_help_position=40, width=None):
117 RawTextHelpFormatter.__init__(self, prog, indent_increment, max_help_position, width) 118 self._action_max_length = 20
119
120 - def _split_lines(self, text, width):
121 return [text.splitlines()[0]]
122
123 - class _Section(RawTextHelpFormatter._Section):
124
125 - def __init__(self, formatter, parent, heading=None):
126 #if heading: 127 # heading = "\n%s\n%s" % ("=" * 40, heading) 128 RawTextHelpFormatter._Section.__init__(self, formatter, parent, heading)
129 130
131 -class WriteOnceNamespace(Namespace):
132 """ 133 Namespace subclass which prevents overwriting any values by accident. 134 """
135 - def __setattr__(self, name, value):
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
142 -class Parser(ArgumentParser):
143 """ 144 Extension of ArgumentParser for simplifying the 145 _configure() code in most Controls 146 """ 147
148 - def __init__(self, *args, **kwargs):
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
155 - def sub(self):
156 return self.add_subparsers(title = "Subcommands", description = OMEROSUBS, metavar = OMEROSUBM)
157
158 - def add(self, sub, func, help, **kwargs):
159 parser = sub.add_parser(func.im_func.__name__, help=help, description=help) 160 parser.set_defaults(func=func, **kwargs) 161 return parser
162
163 - def add_login_arguments(self):
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
180 - def _check_value(self, action, value):
181 # converted value must be one of the choices (if specified) 182 if action.choices is not None and value not in action.choices: 183 msg = 'invalid choice: %r\n\nchoose from:\n' % value 184 choices = sorted(action.choices) 185 msg += self._format_list(choices) 186 raise ArgumentError(action, msg)
187
188 - def _format_list(self, choices):
189 lines = ["\t"] 190 if choices: 191 while len(choices) > 1: 192 choice = choices.pop(0) 193 lines[-1] += ("%s, " % choice) 194 if len(lines[-1]) > 62: 195 lines.append("\t") 196 lines[-1] += choices.pop(0) 197 return "\n".join(lines)
198
199 -class NewFileType(FileType):
200 """ 201 Extension of the argparse.FileType to prevent 202 overwrite existing files. 203 """
204 - def __call__(self, string):
205 if string != "-" and os.path.exists(string): 206 raise ValueError("File exists: %s" % string) 207 return FileType.__call__(self, string)
208 209
210 -class ExistingFile(FileType):
211 """ 212 Extension of the argparse.FileType that requires 213 an existing file. 214 """
215 - def __call__(self, string):
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
224 -class DirectoryType(FileType):
225 """ 226 Extension of the argparse.FileType to only allow 227 existing directories. 228 """
229 - def __call__(self, string):
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
238 -class ExceptionHandler(object):
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 """
244 - def is_constraint_violation(self, ve):
245 if isinstance(ve, omero.ValidationException): 246 if "org.hibernate.exception.ConstraintViolationException: could not insert" in str(ve): 247 return True
248 249
250 -class Context:
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 # This usage will go away and default will be False 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
354 - def userdir(self):
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):
417 self.out(str(args))
418
419 - def popen(self, args):
420 self.out(str(args))
421
422 - def sleep(self, time):
423 self.event.wait(time)
424 425 ##################################################### 426 #
427 -class BaseControl(object):
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 # Mostly reusable code 450 #
451 - def __init__(self, ctx = None, dir = OMERODIR):
452 self.dir = path(dir) # Guaranteed to be a path 453 self.ctx = ctx 454 if self.ctx is None: 455 self.ctx = Context() # Prevents unncessary stop_event creation
456
457 - def _isWindows(self):
458 p_s = platform.system() 459 if p_s == 'Windows': 460 return True 461 else: 462 return False
463
464 - def _host(self):
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
494 - def _icedata(self, property):
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
517 - def _initDir(self):
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
529 - def _nodedata(self):
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
540 - def _regdata(self):
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
551 - def _pid(self):
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
559 - def _cfglist(self):
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 # Look for <platform>.cfg 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
577 - def _icecfg(self):
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
586 - def _intcfg(self):
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
595 - def _properties(self, prefix=""):
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
612 - def _ask_for_password(self, reason = "", root_pass = None, strict = True):
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 # Methods likely to be implemented by subclasses 631 # 632
633 - def _complete_file(self, f, dir = None):
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 # Relative to cwd 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] # Guaranteed one 672 choice = subparsers.choices.get(items[-1]) 673 if choice and choice._actions: 674 actions = choice._actions 675 if len(items) > 2: 676 actions = [] # TBD 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 # Fallback 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
698 - class PluginsLoaded(object):
699 """ 700 Thread-safe class for storing whether or not all the plugins 701 have been loaded 702 """
703 - def __init__(self):
704 self.lock = Lock() 705 self.done = False
706 - def get(self):
707 self.lock.acquire() 708 try: 709 return self.done 710 finally: 711 self.lock.release()
712 - def set(self):
713 self.lock.acquire() 714 try: 715 self.done = True 716 finally: 717 self.lock.release()
718
719 - def __init__(self, prog = sys.argv[0]):
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 #: Return value to be returned 730 self._stack = [] #: List of commands being processed 731 self._client = None #: Single client for all activities 732 self._plugin_paths = [OMEROCLI / "plugins"] #: Paths to be loaded; initially official plugins 733 self._pluginsLoaded = CLI.PluginsLoaded()
734
735 - def assertRC(self):
736 if self.rv != 0: 737 raise NonZeroReturnCode(self.rv, "assert failed")
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
755 - def invokeloop(self):
756 # First we add a few special commands to the loop 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 # Calls the same thing as invoke 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 # We've done the intro once now. Don't repeat yourself. 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 # Starting a new command. Reset the return value to 0 809 # If err or die are called, set rv non-0 value 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: # Thrown by argparse 820 self.dbg("SystemExit raised\n%s" % traceback.format_exc()) 821 self.rv = exc.code 822 return False 823 # 824 # This was perhaps only needed previously 825 # Omitting for the moment with the new 826 # argparse refactoring 827 # 828 #except AttributeError, ae: 829 # self.err("Possible error in plugin:") 830 # self.err(str(ae)) 831 # if self.isdebug: 832 # traceback.print_exc() 833 except NonZeroReturnCode, nzrc: 834 self.dbg(traceback.format_exc()) 835 self.rv = nzrc.rv 836 return False # Continue
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 # EARLY EXIT! 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
914 - def completedefault(self, *args):
915 return []
916
917 - def completenames(self, text, line, begidx, endidx):
918 names = self.controls.keys() 919 return [ str(n + " ") for n in names if n.startswith(line) ]
920 921 ########################################## 922 ## 923 ## Context interface 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):
930 self.err(text, newline) 931 self.rv = rc 932 # self.interrupt_loop = True 933 raise NonZeroReturnCode(rc, "die called: %s" % text)
934
935 - def _env(self):
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
983 - def readDefaults(self):
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
996 - def parsePropertyFile(self, data, output):
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
1009 - def initData(self, properties=None):
1010 """ 1011 Uses "omero prefs" to create an Ice.InitializationData(). 1012 """ 1013 1014 if properties is None: properties = {} 1015 1016 from omero.plugins.prefs import getprefs 1017 try: 1018 output = getprefs(["get"], str(path(self._cwd(None)) / "lib")) 1019 except OSError, err: 1020 self.err("Error getting preferences") 1021 self.dbg(err) 1022 output = "" 1023 1024 import Ice 1025 data = Ice.InitializationData() 1026 data.properties = Ice.createProperties() 1027 for k,v in properties.items(): 1028 data.properties.setProperty(k,v) 1029 self.parsePropertyFile(data, output) 1030 return data
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 # Possibly added by "login"
1062
1063 - def close(self):
1064 client = self._client 1065 if client: 1066 self.dbg("Closing client: %s" % client) 1067 client.__del__()
1068 1069 ## 1070 ## Plugin registry 1071 ## 1072
1073 - def register(self, name, Control, help):
1074 self.register_only(name, Control, help) 1075 self.configure_plugins()
1076
1077 - def register_only(self, name, Control, help):
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
1084 - def configure_plugins(self):
1085 """ 1086 Run to instantiate and configure all plugins 1087 which were registered via register_only() 1088 """ 1089 for name in sorted(self.controls): 1090 control = self.controls[name] 1091 if isinstance(control, tuple): 1092 Control = control[0] 1093 help = control[1] 1094 control = Control(ctx = self, dir = self.dir) 1095 self.controls[name] = control 1096 setattr(self, "complete_%s" % name, control._complete) 1097 parser = self.subparsers.add_parser(name, help=help) 1098 parser.description = help 1099 if hasattr(control, "_configure"): 1100 control._configure(parser) 1101 elif hasattr(control, "__call__"): 1102 parser.set_defaults(func=control.__call__) 1103 control.parser = parser
1104
1105 - def waitForPlugins(self):
1106 if True: 1107 return # Disabling. See comment in argv 1108 self.dbg("Starting waitForPlugins") 1109 while not self._pluginsLoaded.get(): 1110 self.dbg("Waiting for plugins...") 1111 time.sleep(0.1)
1112
1113 - def loadplugins(self):
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
1128 - def loadpath(self, pathobj):
1129 if pathobj.isdir(): 1130 for plugin in pathobj.walkfiles("*.py"): 1131 if -1 == plugin.find("#"): # Omit emacs files 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 ## End Cli 1146 ########################################################### 1147
1148 -def argv(args=sys.argv):
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 # Modiying the run-time environment 1161 old_ice_config = os.getenv("ICE_CONFIG") 1162 os.unsetenv("ICE_CONFIG") 1163 try: 1164 1165 # Modifying the args list if the name of the file 1166 # has arguments encoded in it 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 # Now load other plugins. After debugging is turned on, but before tracing. 1176 cli = CLI(prog = original_executable.split("-")[0]) 1177 1178 parser = Parser(add_help = False) 1179 #parser.add_argument("-d", "--debug", help="Use 'help debug' for more information", default = SUPPRESS) 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 # Disabling background loading 1191 # until 2.4 hangs are fixed 1192 PluginLoader().run() # start() 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 # Specific argument types 1207
1208 -class ExperimenterGroupArg(object):
1209
1210 - def __init__(self, arg):
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
1224 - def lookup(self, client):
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
1234 -class GraphArg(object):
1235
1236 - def __init__(self, cmd_type):
1237 self.cmd_type = cmd_type
1238
1239 - def __call__(self, arg):
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
1255 - def __repr__(self):
1256 return "argument"
1257 1258 ##################################################### 1259 # 1260 # Specific superclasses for various controls 1261
1262 -class CmdControl(BaseControl):
1263
1264 - def cmd_type(self):
1265 raise Exception("Must be overridden by subclasses")
1266
1267 - def _configure(self, parser):
1268 parser.set_defaults(func=self.main_method) 1269 parser.add_argument("--wait", type=long, help="Number of seconds to"+\ 1270 " wait for the processing to complete (Indefinite < 0; No wait=0).", default=-1)
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
1278 - def _process_request(self, req, args, client):
1279 """ 1280 Allow specific filling of parameters in the request. 1281 """ 1282 cb = None 1283 try: 1284 rsp, status, cb = self.response(client, req, wait = args.wait) 1285 self.print_report(req, rsp, status, args.report) 1286 finally: 1287 if cb is not None: 1288 cb.close(True) # Close handle
1289
1290 - def get_error(self, rsp):
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
1301 - def print_report(self, req, rsp, status, detailed):
1302 ### Note: this should be in the GraphControl subclass 1303 ### due to the use of req.type 1304 import omero 1305 type = self.cmd_type().ice_staticId()[2:].replace("::", ".") 1306 self.ctx.out(("%s %s %s... " % (type, req.type, req.id)), newline = False) 1307 err = self.get_error(rsp) 1308 if err: 1309 self.ctx.err(err) 1310 else: 1311 self.ctx.out("ok") 1312 1313 if detailed: 1314 self.ctx.out("Steps: %s" % status.steps) 1315 if status.stopTime > 0 and status.startTime > 0: 1316 elapse = status.stopTime - status.startTime 1317 self.ctx.out("Elapsed time: %s secs." % (elapse/1000.0)) 1318 else: 1319 self.ctx.out("Unfinished.") 1320 self.ctx.out("Flags: %s" % status.flags) 1321 self.print_detailed_report(req, rsp, status)
1322
1323 - def print_detailed_report(self, req, rsp, status):
1324 """ 1325 Extension point for subclasses. 1326 """ 1327 pass
1328
1329 - def line_to_opts(self, line, opts):
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):
1338 import omero.callbacks 1339 handle = client.sf.submit(req) 1340 cb = omero.callbacks.CmdCallbackI(client, handle) 1341 1342 if wait is None: 1343 cb.loop(loops, ms) 1344 elif wait == 0: 1345 self.ctx.out("Exiting immediately") 1346 elif wait > 0: 1347 ms = wait * 1000 1348 ms = ms / loops 1349 self.ctx.out("Waiting %s loops of %s ms" % (ms, loops)) 1350 rsp = cb.loop(loops, ms) 1351 else: 1352 try: 1353 # Wait for finish 1354 while True: 1355 found = cb.block(ms) 1356 if found: 1357 break 1358 1359 # If user uses Ctrl-C, then cancel 1360 except KeyboardInterrupt: 1361 self.ctx.out("Attempting cancel...") 1362 if handle.cancel(): 1363 self.ctx.out("Cancelled") 1364 else: 1365 self.ctx.out("Failed to cancel") 1366 1367 return cb.getResponse(), cb.getStatus(), cb
1368
1369 -class GraphControl(CmdControl):
1370
1371 - def cmd_type(self):
1372 raise Exception("Must be overridden by subclasses")
1373
1374 - def _configure(self, parser):
1375 parser.set_defaults(func=self.main_method) 1376 parser.add_argument("--wait", type=long, help="Number of seconds to"+\ 1377 " wait for the processing to complete (Indefinite < 0; No wait=0).", default=-1) 1378 parser.add_argument("--edit", action="store_true", help="""Configure options in a text editor""") 1379 parser.add_argument("--opt", action="append", help="""Modifies the given option (e.g. /Image:KEEP). Applied *after* 'edit' """) 1380 parser.add_argument("--list", action="store_true", help="""Print a list of all available graph specs""") 1381 parser.add_argument("--list-details", action="store_true", 1382 help="""Print a list of all available graph specs along with detailed info""") 1383 parser.add_argument("--report", action="store_true", help="""Print more detailed report of each action""") 1384 self._pre_objects(parser) 1385 parser.add_argument("obj", nargs="*", type=GraphArg(self.cmd_type()), \ 1386 help="""Objects to be processedd in the form "<Class>:<Id>""")
1387
1388 - def _pre_objects(self, parser):
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) # Close handle 1408 1409 ### Could be put in positive_response helper 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 # Early exit. 1427 elif args.list: 1428 self.ctx.out("\n".join(keys)) 1429 return # Early exit. 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
1440 - def edit_options(self, req, specmap):
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
1463 - def append_options(self, key, specmap, indent = 0):
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
1473 -class UserGroupControl(BaseControl):
1474
1475 - def error_no_input_group(self, msg="No input group is specified", code = 501, fatal = True):
1476 if fatal: 1477 self.ctx.die(code, msg) 1478 else: 1479 self.ctx.err(msg)
1480
1481 - def error_invalid_groupid(self, group_id, msg="Not a valid group ID: %s", code = 502, fatal = True):
1482 if fatal: 1483 self.ctx.die(code, msg % group_id) 1484 else: 1485 self.ctx.err(msg % group_id)
1486
1487 - def error_invalid_group(self, group, msg="Unknown group: %s", code = 503, fatal = True):
1488 if fatal: 1489 self.ctx.die(code, msg % group) 1490 else: 1491 self.ctx.err(msg % group)
1492
1493 - def error_no_group_found(self, msg="No group found", code = 504, fatal = True):
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
1505 - def error_no_input_user(self, msg="No input user is specified", code = 511, fatal = True):
1506 if fatal: 1507 self.ctx.die(code, msg) 1508 else: 1509 self.ctx.err(msg)
1510
1511 - def error_invalid_userid(self, user_id, msg="Not a valid user ID: %s", code = 512, fatal = True):
1512 if fatal: 1513 self.ctx.die(code, msg % user_id) 1514 else: 1515 self.ctx.err(msg % user_id)
1516
1517 - def error_invalid_user(self, user, msg="Unknown user: %s", code = 513, fatal = True):
1518 if fatal: 1519 self.ctx.die(code, msg % user) 1520 else: 1521 self.ctx.err(msg % user)
1522
1523 - def error_no_user_found(self, msg="No user found", code = 514, fatal = True):
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
1535 - def find_group_by_id(self, admin, group_id, fatal = False):
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
1548 - def find_group_by_name(self, admin, group_name, fatal = False):
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 # Find by group by name 1562 try: 1563 g1 = admin.lookupGroup(id_or_name) 1564 except omero.ApiUsageException: 1565 g1 = None 1566 1567 # Find by group by id 1568 try: 1569 g2 = admin.getGroup(long(id_or_name)) 1570 except (ValueError, omero.ApiUsageException): 1571 g2 = None 1572 1573 # Test found groups 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
1590 - def find_user_by_id(self, admin, user_id, fatal = False):
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
1603 - def find_user_by_name(self, admin, user_name, fatal = False):
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 # Find user by name 1617 try: 1618 u1 = admin.lookupExperimenter(id_or_name) 1619 except omero.ApiUsageException: 1620 u1 = None 1621 1622 # Find user by id 1623 try: 1624 u2 = admin.getExperimenter(long(id_or_name)) 1625 except (ValueError, omero.ApiUsageException): 1626 u2 = None 1627 1628 # Test found users 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
1645 - def addusersbyid(self, admin, group, users):
1646 import omero 1647 for user in list(users): 1648 admin.addGroups(omero.model.ExperimenterI(user, False), [group]) 1649 self.ctx.out("Added %s to group %s" % (user, group.id.val))
1650
1651 - def removeusersbyid(self, admin, group, users):
1652 import omero 1653 for user in list(users): 1654 admin.removeGroups(omero.model.ExperimenterI(user, False), [group]) 1655 self.ctx.out("Removed %s from group %s" % (user, group.id.val))
1656
1657 - def addownersbyid(self, admin, group, users):
1658 import omero 1659 for user in list(users): 1660 admin.addGroupOwners(group, [omero.model.ExperimenterI(user, False)]) 1661 self.ctx.out("Added %s to the owner list of group %s" % (user, group.id.val))
1662
1663 - def removeownersbyid(self, admin, group, users):
1664 import omero 1665 for user in list(users): 1666 admin.removeGroupOwners(group, [omero.model.ExperimenterI(user, False)]) 1667 self.ctx.out("Removed %s from the owner list of group %s" % (user, group.id.val))
1668
1669 - def getuserids(self, group):
1670 import omero 1671 ids = [x.child.id.val for x in group.copyGroupExperimenterMap()] 1672 return ids
1673
1674 - def getmemberids(self, group):
1675 import omero 1676 ids = [x.child.id.val for x in group.copyGroupExperimenterMap() if not x.owner.val] 1677 return ids
1678
1679 - def getownerids(self, group):
1680 import omero 1681 ids = [x.child.id.val for x in group.copyGroupExperimenterMap() if x.owner.val] 1682 return ids
1683