[Opensrf-commits] r2133 - trunk/src/python (erickson)
svn at svn.open-ils.org
svn at svn.open-ils.org
Sun Dec 19 20:20:49 EST 2010
Author: erickson
Date: 2010-12-19 20:20:48 -0500 (Sun, 19 Dec 2010)
New Revision: 2133
Modified:
trunk/src/python/srfsh.py
Log:
Python srfsh enhancements
* Srfsh plugins can now insert new commands and add words to the tab
completion word bank.
* Addded support reading script files
* Added support for service open/close (connect/disconnect) for stateful
connections
* Moved to class-based srfsh module for easier state maintenance
* More doc strings
Modified: trunk/src/python/srfsh.py
===================================================================
--- trunk/src/python/srfsh.py 2010-12-16 21:12:39 UTC (rev 2132)
+++ trunk/src/python/srfsh.py 2010-12-20 01:20:48 UTC (rev 2133)
@@ -39,393 +39,495 @@
SRFSH_LOCALE = <locale> - request responses to be returned in locale <locale> if available
"""
-
-import os, sys, time, readline, atexit, re, pydoc
+import os, sys, time, readline, atexit, re, pydoc, traceback
import osrf.json, osrf.system, osrf.ses, osrf.conf, osrf.log, osrf.net
-router_command_map = {
- 'services' : 'opensrf.router.info.class.list',
- 'service-stats' : 'opensrf.router.info.stats.class.node.all',
- 'service-nodes' : 'opensrf.router.info.stats.class.all'
-}
+class Srfsh(object):
-# List of words to use for readline tab completion
-tab_complete_words = [
- 'request',
- 'set',
- 'router',
- 'help',
- 'exit',
- 'quit',
- 'introspect',
- 'opensrf.settings',
- 'opensrf.math'
-]
-# add the router commands to the tab-complete list
-for rcommand in router_command_map.keys():
- tab_complete_words.append(rcommand)
+ def __init__(self, script_file=None):
-# -------------------------------------------------------------------
-# main listen loop
-# -------------------------------------------------------------------
-def do_loop():
+ # used for paging
+ self.output_buffer = ''
- command_map = {
- 'request' : handle_request,
- 'router' : handle_router,
- 'math_bench' : handle_math_bench,
- 'introspect' : handle_introspect,
- 'help' : handle_help,
- 'set' : handle_set,
- 'get' : handle_get,
- }
+ # true if invoked with a script file
+ self.reading_script = False
- while True:
+ # multi-request sessions
+ self.active_session = None
+ # default opensrf request timeout
+ self.timeout = 120
+
+ # map of command name to handler
+ self.command_map = {}
+
+ if script_file:
+ self.open_script(script_file)
+ self.reading_script = True
+
+ # map of router sub-commands to router API calls
+ self.router_command_map = {
+ 'services' : 'opensrf.router.info.class.list',
+ 'service-stats' : 'opensrf.router.info.stats.class.node.all',
+ 'service-nodes' : 'opensrf.router.info.stats.class.all'
+ }
+
+ # seed the tab completion word bank
+ self.tab_complete_words = self.router_command_map.keys() + [
+ 'exit',
+ 'quit',
+ 'opensrf.settings',
+ 'opensrf.math',
+ 'opensrf.dbmath',
+ 'opensrf.py-example'
+ ]
+
+ # add the default commands
+ for command in ['request', 'router', 'help', 'set',
+ 'get', 'math_bench', 'introspect', 'connect', 'disconnect' ]:
+
+ self.add_command(command = command, handler = getattr(Srfsh, 'handle_' + command))
+
+ # for compat w/ srfsh.c
+ self.add_command(command = 'open', handler = Srfsh.handle_connect)
+ self.add_command(command = 'close', handler = Srfsh.handle_disconnect)
+
+ def open_script(self, script_file):
+ ''' Opens the script file and redirects the contents to STDIN for reading. '''
+
try:
- report("", True)
- line = raw_input("srfsh# ")
+ script = open(script_file, 'r')
+ os.dup2(script.fileno(), sys.stdin.fileno())
+ script.close()
+ except Exception, e:
+ self.report_error("Error opening script file '%s': %s" % (script_file, str(e)))
+ raise e
- if not len(line):
- continue
- if str.lower(line) == 'exit' or str.lower(line) == 'quit':
+ def main_loop(self):
+ ''' Main listen loop. '''
+
+ self.set_vars()
+ self.do_connect()
+ self.load_plugins()
+ self.setup_readline()
+
+ while True:
+
+ try:
+ self.report("", True)
+ line = raw_input("srfsh# ")
+
+ if not len(line):
+ continue
+
+ if re.search('^\s*#', line): # ignore lines starting with #
+ continue
+
+ if str.lower(line) == 'exit' or str.lower(line) == 'quit':
+ break
+
+ parts = str.split(line)
+ command = parts.pop(0)
+
+ if command not in self.command_map:
+ self.report("unknown command: '%s'\n" % command)
+ continue
+
+ self.command_map[command](self, parts)
+
+ except EOFError: # ctrl-d
break
- parts = str.split(line)
- command = parts.pop(0)
+ except KeyboardInterrupt: # ctrl-c
+ self.report("\n")
- if command not in command_map:
- report("unknown command: '%s'\n" % command)
- continue
+ except Exception, e:
+ self.report("%s\n" % traceback.format_exc())
- command_map[command](parts)
+ self.cleanup()
- except EOFError: # ^-d
- sys.exit(0)
+ def handle_connect(self, parts):
+ ''' Opens a connected session to an opensrf service '''
- except KeyboardInterrupt: # ^-c
- report("\n")
+ if len(parts) == 0:
+ self.report("usage: connect <service>")
+ return
- except Exception, e:
- report("%s\n" % e)
+ service = parts.pop(0)
- cleanup()
+ if self.active_session:
+ if self.active_session['service'] == service:
+ return # use the existing active session
+ else:
+ # currently, we only support one active session at a time
+ self.handle_disconnect([self.active_session['service']])
-def handle_introspect(parts):
+ self.active_session = {
+ 'ses' : osrf.ses.ClientSession(service, locale = self.__get_locale()),
+ 'service' : service
+ }
- if len(parts) == 0:
- report("usage: introspect <service> [api_prefix]\n")
- return
+ self.active_session['ses'].connect()
- service = parts.pop(0)
- args = [service, 'opensrf.system.method']
+ def handle_disconnect(self, parts):
+ ''' Disconnects the currently active session. '''
- if len(parts) > 0:
- api_pfx = parts[0]
- if api_pfx[0] != '"': # json-encode if necessary
- api_pfx = '"%s"' % api_pfx
- args.append(api_pfx)
- else:
- args[1] += '.all'
+ if len(parts) == 0:
+ self.report("usage: disconnect <service>")
+ return
- return handle_request(args)
+ service = parts.pop(0)
+ if self.active_session:
+ if self.active_session['service'] == service:
+ self.active_session['ses'].disconnect()
+ self.active_session['ses'].cleanup()
+ self.active_session = None
+ else:
+ self.report_error("There is no open connection for service '%s'" % service)
-def handle_router(parts):
+ def handle_introspect(self, parts):
+ ''' Introspect an opensrf service. '''
- if len(parts) == 0:
- report("usage: router <query>\n")
- return
+ if len(parts) == 0:
+ self.report("usage: introspect <service> [api_prefix]\n")
+ return
- query = parts[0]
+ service = parts.pop(0)
+ args = [service, 'opensrf.system.method']
- if query not in router_command_map:
- report("router query options: %s\n" % ','.join(router_command_map.keys()))
- return
+ if len(parts) > 0:
+ api_pfx = parts[0]
+ if api_pfx[0] != '"': # json-encode if necessary
+ api_pfx = '"%s"' % api_pfx
+ args.append(api_pfx)
+ else:
+ args[1] += '.all'
- return handle_request(['router', router_command_map[query]])
+ return handle_request(args)
-# -------------------------------------------------------------------
-# Set env variables to control behavior
-# -------------------------------------------------------------------
-def handle_set(parts):
- cmd = "".join(parts)
- pattern = re.compile('(.*)=(.*)').match(cmd)
- key = pattern.group(1)
- val = pattern.group(2)
- set_var(key, val)
- report("%s = %s\n" % (key, val))
-def handle_get(parts):
- try:
- report("%s=%s\n" % (parts[0], get_var(parts[0])))
- except:
- report("\n")
+ def handle_router(self, parts):
+ ''' Send requests to the router. '''
+ if len(parts) == 0:
+ self.report("usage: router <query>\n")
+ return
-# -------------------------------------------------------------------
-# Prints help info
-# -------------------------------------------------------------------
-def handle_help(foo):
- report(__doc__)
+ query = parts[0]
-# -------------------------------------------------------------------
-# performs an opensrf request
-# -------------------------------------------------------------------
-def handle_request(parts):
+ if query not in self.router_command_map:
+ self.report("router query options: %s\n" % ','.join(self.router_command_map.keys()))
+ return
- if len(parts) < 2:
- report("usage: request <service> <api_name> [<param1>, <param2>, ...]\n")
- return
+ return handle_request(['router', self.router_command_map[query]])
- service = parts.pop(0)
- method = parts.pop(0)
- locale = __get_locale()
- jstr = '[%s]' % "".join(parts)
- params = None
+ def handle_set(self, parts):
+ ''' Set env variables to control srfsh behavior. '''
- try:
- params = osrf.json.to_object(jstr)
- except:
- report("Error parsing JSON: %s\n" % jstr)
- return
+ cmd = "".join(parts)
+ pattern = re.compile('(.*)=(.*)').match(cmd)
+ key = pattern.group(1)
+ val = pattern.group(2)
+ self.set_var(key, val)
+ self.report("%s = %s\n" % (key, val))
- ses = osrf.ses.ClientSession(service, locale=locale)
+ def handle_get(self, parts):
+ ''' Returns environment variable value '''
+ try:
+ self.report("%s=%s\n" % (parts[0], self.get_var(parts[0])))
+ except:
+ self.report("\n")
- start = time.time()
- req = ses.request2(method, tuple(params))
+ def handle_help(self, foo):
+ ''' Prints help info '''
+ self.report(__doc__)
+ def handle_request(self, parts):
+ ''' Performs an OpenSRF request and reports the results. '''
- while True:
- resp = None
+ if len(parts) < 2:
+ self.report("usage: request <service> <api_name> [<param1>, <param2>, ...]\n")
+ return
+ self.report("\n")
+
+ service = parts.pop(0)
+ method = parts.pop(0)
+ locale = self.__get_locale()
+ jstr = '[%s]' % "".join(parts)
+ params = None
+
try:
- resp = req.recv(timeout=120)
- except osrf.net.XMPPNoRecipient:
- report("Unable to communicate with %s\n" % service)
- total = 0
- break
+ params = osrf.json.to_object(jstr)
+ except:
+ self.report("Error parsing JSON: %s\n" % jstr)
+ return
- if not resp: break
+ using_active = False
+ if self.active_session and self.active_session['service'] == service:
+ # if we have an open connection to the same service, use it
+ ses = self.active_session['ses']
+ using_active = True
+ else:
+ ses = osrf.ses.ClientSession(service, locale=locale)
- total = time.time() - start
- content = resp.content()
+ start = time.time()
- if content is not None:
- if get_var('SRFSH_OUTPUT_NET_OBJ_KEYS') == 'true':
- report("Received Data: %s\n" % osrf.json.debug_net_object(content))
- else:
- if get_var('SRFSH_OUTPUT_FORMAT_JSON') == 'true':
- report("Received Data: %s\n" % osrf.json.pprint(osrf.json.to_json(content)))
+ req = ses.request2(method, tuple(params))
+
+ last_content = None
+ while True:
+ resp = None
+
+ try:
+ resp = req.recv(timeout=self.timeout)
+ except osrf.net.XMPPNoRecipient:
+ self.report("Unable to communicate with %s\n" % service)
+ total = 0
+ break
+
+ if not resp: break
+
+ total = time.time() - start
+ content = resp.content()
+
+ if content is not None:
+ last_content = content
+ if self.get_var('SRFSH_OUTPUT_NET_OBJ_KEYS') == 'true':
+ self.report("Received Data: %s\n" % osrf.json.debug_net_object(content))
else:
- report("Received Data: %s\n" % osrf.json.to_json(content))
+ if self.get_var('SRFSH_OUTPUT_FORMAT_JSON') == 'true':
+ self.report("Received Data: %s\n" % osrf.json.pprint(osrf.json.to_json(content)))
+ else:
+ self.report("Received Data: %s\n" % osrf.json.to_json(content))
- req.cleanup()
- ses.cleanup()
+ req.cleanup()
+ if not using_active:
+ ses.cleanup()
- report('-'*60 + "\n")
- report("Total request time: %f\n" % total)
- report('-'*60 + "\n")
+ self.report("\n" + '-'*60 + "\n")
+ self.report("Total request time: %f\n" % total)
+ self.report('-'*60 + "\n")
+ return last_content
-def handle_math_bench(parts):
- count = int(parts.pop(0))
- ses = osrf.ses.ClientSession('opensrf.math')
- times = []
+ def handle_math_bench(self, parts):
+ ''' Sends a series of request to the opensrf.math service and collects timing stats. '''
- for cnt in range(100):
- if cnt % 10:
- sys.stdout.write('.')
- else:
- sys.stdout.write( str( cnt / 10 ) )
- print ""
+ count = int(parts.pop(0))
+ ses = osrf.ses.ClientSession('opensrf.math')
+ times = []
- for cnt in range(count):
-
- starttime = time.time()
- req = ses.request('add', 1, 2)
- resp = req.recv(timeout=2)
- endtime = time.time()
-
- if resp.content() == 3:
- sys.stdout.write("+")
- sys.stdout.flush()
- times.append( endtime - starttime )
- else:
- print "What happened? %s" % str(resp.content())
-
- req.cleanup()
- if not ( (cnt + 1) % 100):
- print ' [%d]' % (cnt + 1)
-
- ses.cleanup()
- total = 0
- for cnt in times:
- total += cnt
- print "\naverage time %f" % (total / len(times))
+ for cnt in range(100):
+ if cnt % 10:
+ sys.stdout.write('.')
+ else:
+ sys.stdout.write( str( cnt / 10 ) )
+ print ""
+ for cnt in range(count):
+
+ starttime = time.time()
+ req = ses.request('add', 1, 2)
+ resp = req.recv(timeout=2)
+ endtime = time.time()
+
+ if resp.content() == 3:
+ sys.stdout.write("+")
+ sys.stdout.flush()
+ times.append( endtime - starttime )
+ else:
+ print "What happened? %s" % str(resp.content())
+
+ req.cleanup()
+ if not ( (cnt + 1) % 100):
+ print ' [%d]' % (cnt + 1)
+
+ ses.cleanup()
+ total = 0
+ for cnt in times:
+ total += cnt
+ print "\naverage time %f" % (total / len(times))
-# -------------------------------------------------------------------
-# Defines the tab-completion handling and sets up the readline history
-# -------------------------------------------------------------------
-def setup_readline():
- class SrfshCompleter(object):
+ def setup_readline(self):
+ ''' Initialize readline history and tab completion. '''
- def __init__(self, words):
- self.words = words
- self.prefix = None
-
- def complete(self, prefix, index):
+ class SrfshCompleter(object):
- if prefix != self.prefix:
+ def __init__(self, words):
+ self.words = words
+ self.prefix = None
+
+ def complete(self, prefix, index):
- self.prefix = prefix
+ if prefix != self.prefix:
- # find all words that start with this prefix
- self.matching_words = [
- w for w in self.words if w.startswith(prefix)
- ]
+ self.prefix = prefix
- if len(self.matching_words) == 0:
+ # find all words that start with this prefix
+ self.matching_words = [
+ w for w in self.words if w.startswith(prefix)
+ ]
+
+ if len(self.matching_words) == 0:
+ return None
+
+ if len(self.matching_words) == 1:
+ return self.matching_words[0]
+
+ # re-print the prompt w/ all of the possible word completions
+ sys.stdout.write('\n%s\nsrfsh# %s' %
+ (' '.join(self.matching_words), readline.get_line_buffer()))
+
return None
- if len(self.matching_words) == 1:
- return self.matching_words[0]
+ completer = SrfshCompleter(tuple(self.tab_complete_words))
+ readline.parse_and_bind("tab: complete")
+ readline.set_completer(completer.complete)
- sys.stdout.write('\n%s\nsrfsh# %s' %
- (' '.join(self.matching_words), readline.get_line_buffer()))
+ histfile = os.path.join(self.get_var('HOME'), ".srfsh_history")
+ try:
+ readline.read_history_file(histfile)
+ except IOError:
+ pass
+ atexit.register(readline.write_history_file, histfile)
- return None
+ readline.set_completer_delims(readline.get_completer_delims().replace('-',''))
- completer = SrfshCompleter(tuple(tab_complete_words))
- readline.parse_and_bind("tab: complete")
- readline.set_completer(completer.complete)
- histfile = os.path.join(get_var('HOME'), ".srfsh_history")
- try:
- readline.read_history_file(histfile)
- except IOError:
- pass
- atexit.register(readline.write_history_file, histfile)
+ def do_connect(self):
+ ''' Connects this instance to the OpenSRF network. '''
- readline.set_completer_delims(readline.get_completer_delims().replace('-',''))
+ file = os.path.join(self.get_var('HOME'), ".srfsh.xml")
+ osrf.system.System.connect(config_file=file, config_context='srfsh')
+ def add_command(self, **kwargs):
+ ''' Adds a new command to the supported srfsh commands.
-def do_connect():
- file = os.path.join(get_var('HOME'), ".srfsh.xml")
- osrf.system.System.connect(config_file=file, config_context='srfsh')
+ Command is also added to the tab-completion word bank.
-def load_plugins():
- # Load the user defined external plugins
- # XXX Make this a real module interface, with tab-complete words, commands, etc.
- try:
- plugins = osrf.conf.get('plugins')
+ kwargs :
+ command : the command name
+ handler : reference to a two-argument function.
+ Arguments are Srfsh instance and command arguments.
+ '''
- except:
- # XXX standard srfsh.xml does not yet define <plugins> element
- #print("No plugins defined in /srfsh/plugins/plugin\n")
- return
+ command = kwargs['command']
+ self.command_map[command] = kwargs['handler']
+ self.tab_complete_words.append(command)
- plugins = osrf.conf.get('plugins.plugin')
- if not isinstance(plugins, list):
- plugins = [plugins]
- for module in plugins:
- name = module['module']
- init = module['init']
- print "Loading module %s..." % name
+ def load_plugins(self):
+ ''' Load plugin modules from the srfsh configuration file '''
try:
- string = 'import %s\n%s.%s()' % (name, name, init)
- exec(string)
- print 'OK'
+ plugins = osrf.conf.get('plugins.plugin')
+ except:
+ return
- except Exception, e:
- sys.stderr.write("\nError importing plugin %s, with init symbol %s: \n%s\n" % (name, init, e))
+ if not isinstance(plugins, list):
+ plugins = [plugins]
-def cleanup():
- osrf.system.System.net_disconnect()
-
-_output_buffer = '' # collect output for pager
-def report(text, flush=False):
- global _output_buffer
+ for plugin in plugins:
+ module = plugin['module']
+ init = plugin.get('init', 'load')
+ self.report("Loading module %s..." % module, True, True)
- if get_var('SRFSH_OUTPUT_PAGED') == 'true':
- _output_buffer += text
+ try:
+ mod = __import__(module, fromlist=' ')
+ getattr(mod, init)(self, plugin)
+ self.report("OK.\n", True, True)
- if flush and _output_buffer != '':
- pipe = os.popen('less -EX', 'w')
- pipe.write(_output_buffer)
- pipe.close()
- _output_buffer = ''
+ except Exception, e:
+ self.report_error("Error importing plugin '%s' : %s\n" % (module, traceback.format_exc()))
- else:
- sys.stdout.write(text)
- if flush:
- sys.stdout.flush()
+ def cleanup(self):
+ ''' Disconnects from opensrf. '''
+ osrf.system.System.net_disconnect()
-def set_vars():
+ def report_error(self, msg):
+ ''' Log to stderr. '''
+ sys.stderr.write("%s\n" % msg)
+ sys.stderr.flush()
+
+ def report(self, text, flush=False, no_page=False):
+ ''' Logs to the pager or stdout, depending on env vars and context '''
- if not get_var('SRFSH_OUTPUT_NET_OBJ_KEYS'):
- set_var('SRFSH_OUTPUT_NET_OBJ_KEYS', 'false')
+ if self.reading_script or no_page or self.get_var('SRFSH_OUTPUT_PAGED') != 'true':
+ sys.stdout.write(text)
+ if flush:
+ sys.stdout.flush()
+ else:
+ self.output_buffer += text
- if not get_var('SRFSH_OUTPUT_FORMAT_JSON'):
- set_var('SRFSH_OUTPUT_FORMAT_JSON', 'true')
+ if flush and self.output_buffer != '':
+ pipe = os.popen('less -EX', 'w')
+ pipe.write(self.output_buffer)
+ pipe.close()
+ self.output_buffer = ''
- if not get_var('SRFSH_OUTPUT_PAGED'):
- set_var('SRFSH_OUTPUT_PAGED', 'true')
+ def set_vars(self):
+ ''' Set defaults for environment variables. '''
- # XXX Do we need to differ between LANG and LC_MESSAGES?
- if not get_var('SRFSH_LOCALE'):
- set_var('SRFSH_LOCALE', get_var('LC_ALL'))
+ if not self.get_var('SRFSH_OUTPUT_NET_OBJ_KEYS'):
+ self.set_var('SRFSH_OUTPUT_NET_OBJ_KEYS', 'false')
-def set_var(key, val):
- os.environ[key] = val
+ if not self.get_var('SRFSH_OUTPUT_FORMAT_JSON'):
+ self.set_var('SRFSH_OUTPUT_FORMAT_JSON', 'true')
-def get_var(key):
- return os.environ.get(key, '')
-
-def __get_locale():
- """
- Return the defined locale for this srfsh session.
+ if not self.get_var('SRFSH_OUTPUT_PAGED'):
+ self.set_var('SRFSH_OUTPUT_PAGED', 'true')
- A locale in OpenSRF is currently defined as a [a-z]{2}-[A-Z]{2} pattern.
- This function munges the LC_ALL setting to conform to that pattern; for
- example, trimming en_CA.UTF-8 to en-CA.
+ # XXX Do we need to differ between LANG and LC_MESSAGES?
+ if not self.get_var('SRFSH_LOCALE'):
+ self.set_var('SRFSH_LOCALE', self.get_var('LC_ALL'))
- >>> import srfsh
- >>> srfsh.set_var('SRFSH_LOCALE', 'zz-ZZ')
- >>> print __get_locale()
- zz-ZZ
- >>> srfsh.set_var('SRFSH_LOCALE', 'en_CA.UTF-8')
- >>> print __get_locale()
- en-CA
- """
+ def set_var(self, key, val):
+ ''' Sets an environment variable's value. '''
+ os.environ[key] = val
- env_locale = get_var('SRFSH_LOCALE')
- if env_locale:
- pattern = re.compile(r'^\s*([a-z]+)[^a-zA-Z]([A-Z]+)').search(env_locale)
- lang = pattern.group(1)
- region = pattern.group(2)
- locale = "%s-%s" % (lang, region)
- else:
- locale = 'en-US'
+ def get_var(self, key):
+ ''' Returns an environment variable's value. '''
+ return os.environ.get(key, '')
+
+ def __get_locale(self):
+ """
+ Return the defined locale for this srfsh session.
- return locale
+ A locale in OpenSRF is currently defined as a [a-z]{2}-[A-Z]{2} pattern.
+ This function munges the LC_ALL setting to conform to that pattern; for
+ example, trimming en_CA.UTF-8 to en-CA.
+
+ >>> import srfsh
+ >>> shell = srfsh.Srfsh()
+ >>> shell.set_var('SRFSH_LOCALE', 'zz-ZZ')
+ >>> print shell.__get_locale()
+ zz-ZZ
+ >>> shell.set_var('SRFSH_LOCALE', 'en_CA.UTF-8')
+ >>> print shell.__get_locale()
+ en-CA
+ """
+
+ env_locale = self.get_var('SRFSH_LOCALE')
+ if env_locale:
+ pattern = re.compile(r'^\s*([a-z]+)[^a-zA-Z]([A-Z]+)').search(env_locale)
+ lang = pattern.group(1)
+ region = pattern.group(2)
+ locale = "%s-%s" % (lang, region)
+ else:
+ locale = 'en-US'
+
+ return locale
if __name__ == '__main__':
+ script = sys.argv[1] if len(sys.argv) > 1 else None
+ Srfsh(script).main_loop()
- # Kick it off
- set_vars()
- setup_readline()
- do_connect()
- load_plugins()
- do_loop()
-
More information about the opensrf-commits
mailing list