From: W. Trevor King Date: Wed, 12 May 2010 19:57:33 +0000 (-0400) Subject: Moved config handling commands from hooke_cli to hooke.plugin.config. X-Git-Url: http://git.tremily.us/?p=hooke.git;a=commitdiff_plain;h=5846fa85d1c090deddc9df50924f0ed5c28cdbda Moved config handling commands from hooke_cli to hooke.plugin.config. Also: * Moved construct_graph and IsSubclass from hooke.plugin to hooke.util.pluggable to avoid import cycles and cleanup hooke.plugin. * Added hooke.interaction.ReloadUserInterfaceConfig to trigger UI reconfigure. * Added hooke.ui.UserInterface.reload_config (triggered by above). * Pass CommandLine instance to HookeCmd, so it can reconfigure the UI. --- diff --git a/hooke/driver/__init__.py b/hooke/driver/__init__.py index beaa991..c8a85e7 100644 --- a/hooke/driver/__init__.py +++ b/hooke/driver/__init__.py @@ -7,7 +7,8 @@ to write your own to handle your lab's specific format. """ from ..config import Setting -from ..plugin import construct_graph, IsSubclass +from ..util.pluggable import IsSubclass, construct_graph + DRIVER_MODULES = [ # ('csvdriver', True), diff --git a/hooke/hooke_cli.py b/hooke/hooke_cli.py index 1ab299e..95c8ea1 100644 --- a/hooke/hooke_cli.py +++ b/hooke/hooke_cli.py @@ -119,40 +119,3 @@ class HookeCli(cmd.Cmd, object): point.absolute_coords=xvector[index],yvector[index] point.find_graph_coords(xvector,yvector) return point - -#HERE COMMANDS BEGIN - - def help_set(self): - print ''' -SET -Sets a local configuration variable -------------- -Syntax: set [variable] [value] - ''' - def do_set(self,args): - #FIXME: some variables in self.config should be hidden or intelligently configurated... - args=args.split() - if len(args)==0: - print 'You must specify a variable and a value' - print 'Available variables:' - print self.config.keys() - return - if args[0] not in self.config.keys(): - print 'This is not an internal Hooke variable!' - return - if len(args)==1: - #FIXME:we should reload the config file and reset the config value - print self.config[args[0]] - return - key=args[0] - try: #try to have a numeric value - value=float(args[1]) - except ValueError: #if it cannot be converted to float, it's None, or a string... - value=args[1] - if value.lower()=='none': - value=None - else: - value=args[1] - - self.config[key]=value - self.do_plot(0) diff --git a/hooke/interaction.py b/hooke/interaction.py index 6904f7d..0f91d6a 100644 --- a/hooke/interaction.py +++ b/hooke/interaction.py @@ -109,3 +109,14 @@ class PointRequest (Request): class PointResponse (Response): def __init__(self, value): super(PointResponse, self).__init__('point', value) + + +class Notification (object): + def __init__(self, type): + self.type = type + +class ReloadUserInterfaceConfig (Notification): + def __init__(self, config): + super(ReloadUserInterfaceConfig, self).__init__( + 'reload user interface config') + self.config = config diff --git a/hooke/plugin/__init__.py b/hooke/plugin/__init__.py index 440c3bf..98862c6 100644 --- a/hooke/plugin/__init__.py +++ b/hooke/plugin/__init__.py @@ -7,7 +7,7 @@ All of the science happens in here. import ConfigParser as configparser from ..config import Setting -from ..util.graph import Node, Graph +from ..util.pluggable import IsSubclass, construct_graph PLUGIN_MODULES = [ @@ -38,6 +38,7 @@ default. TODO: autodiscovery """ BUILTIN_MODULES = [ + 'config', 'debug', 'note', 'playlist', @@ -93,81 +94,6 @@ class Builtin (Plugin): # Construct plugin dependency graph and load plugin instances. -def construct_graph(this_modname, submodnames, class_selector, - assert_name_match=True): - """Search the submodules `submodnames` of a module `this_modname` - for class objects for which `class_selector(class)` returns - `True`. These classes are instantiated, and the `instance.name` - is compared to the `submodname` (if `assert_name_match` is - `True`). - - The instances are further arranged into a dependency - :class:`hooke.util.graph.Graph` according to their - `instance.dependencies()` values. The topologically sorted graph - is returned. - """ - instances = {} - for submodname in submodnames: - count = len([s for s in submodnames if s == submodname]) - assert count > 0, 'No %s entries: %s' % (submodname, submodnames) - assert count == 1, 'Multiple (%d) %s entries: %s' \ - % (count, submodname, submodnames) - this_mod = __import__(this_modname, fromlist=[submodname]) - submod = getattr(this_mod, submodname) - for objname in dir(submod): - obj = getattr(submod, objname) - if class_selector(obj): - instance = obj() - if assert_name_match == True and instance.name != submodname: - raise Exception( - 'Instance name %s does not match module name %s' - % (instance.name, submodname)) - instances[instance.name] = instance - nodes = {} - for i in instances.values(): # make nodes for each instance - nodes[i.name] = Node(data=i) - for n in nodes.values(): # fill in dependencies for each node - n.extend([nodes[name] for name in n.data.dependencies()]) - graph = Graph(nodes.values()) - graph.topological_sort() - return graph - -class IsSubclass (object): - """A safe subclass comparator. - - Examples - -------- - - >>> class A (object): - ... pass - >>> class B (A): - ... pass - >>> C = 5 - >>> is_subclass = IsSubclass(A) - >>> is_subclass(A) - True - >>> is_subclass = IsSubclass(A, blacklist=[A]) - >>> is_subclass(A) - False - >>> is_subclass(B) - True - >>> is_subclass(C) - False - """ - def __init__(self, base_class, blacklist=None): - self.base_class = base_class - if blacklist == None: - blacklist = [] - self.blacklist = blacklist - def __call__(self, other): - try: - subclass = issubclass(other, self.base_class) - except TypeError: - return False - if other in self.blacklist: - return False - return subclass - PLUGIN_GRAPH = construct_graph( this_modname=__name__, submodnames=[name for name,include in PLUGIN_MODULES] + BUILTIN_MODULES, diff --git a/hooke/plugin/config.py b/hooke/plugin/config.py new file mode 100644 index 0000000..594ffa9 --- /dev/null +++ b/hooke/plugin/config.py @@ -0,0 +1,88 @@ +"""The `config` module provides :class:`ConfigPlugin` and several +associated :class:`hooke.command.Command`\s for handling +:mod:`hooke.config` classes. +""" + +from StringIO import StringIO + +from ..command import Command, Argument, Failure +from ..interaction import ReloadUserInterfaceConfig +from ..plugin import Builtin + + +class ConfigPlugin (Builtin): + def __init__(self): + super(ConfigPlugin, self).__init__(name='config') + + def commands(self): + return [GetCommand(), SetCommand(), PrintCommand()] + + +# Define common or complicated arguments + +SectionArgument = Argument( + name='section', type='string', optional=False, + help=""" +Configuration section to act on. +""".strip()) + +OptionArgument = Argument( + name='option', type='string', optional=False, + help=""" +Configuration option to act on. +""".strip()) + + +# Define commands + +class GetCommand (Command): + """Get the current value of a configuration option. + """ + def __init__(self): + super(GetCommand, self).__init__( + name='get config', + arguments=[SectionArgument, OptionArgument], + help=self.__doc__) + + def _run(self, hooke, inqueue, outqueue, params): + outqueue.put(hooke.config.get(params['section'], params['option'])) + +class SetCommand (Command): + """Set the current value of a configuration option. + + Currently many config options are read at startup time, and config + dicts are passed out to their target classes. This means that changes + to the central :attr:`hooke.hooke.Hooke.config` location *will not* be + noticed by the target classes unless the configuration is reloaded. + This reloading may cause problems in poorly written UIs. + """ + def __init__(self): + super(SetCommand, self).__init__( + name='set config', + arguments=[ + SectionArgument, OptionArgument, + Argument( + name='value', type='string', optional=False, + help='Value to set.'), + ], + help=self.__doc__) + + def _run(self, hooke, inqueue, outqueue, params): + hooke.config.set(params['section'], params['option'], params['value']) + # push config changes + hooke.load_plugins() + hooke.load_drivers() + # notify UI to update config + outqueue.put(ReloadUserInterfaceConfig(hooke.config)) + +class PrintCommand (Command): + """Get the current value of a configuration option. + """ + def __init__(self): + super(PrintCommand, self).__init__( + name='print config', help=self.__doc__) + + def _run(self, hooke, inqueue, outqueue, params): + out = StringIO() + hooke.config.write(out) + outqueue.put(out.getvalue()) diff --git a/hooke/ui/__init__.py b/hooke/ui/__init__.py index 778eadf..0912edc 100644 --- a/hooke/ui/__init__.py +++ b/hooke/ui/__init__.py @@ -6,7 +6,7 @@ import ConfigParser as configparser from .. import version from ..compat.odict import odict from ..config import Setting -from ..plugin import IsSubclass +from ..util.pluggable import IsSubclass USER_INTERFACE_MODULES = [ @@ -20,6 +20,7 @@ USER_INTERFACE_SETTING_SECTION = 'user interfaces' """Name of the config section which controls UI selection. """ + class QueueMessage (object): def __str__(self): return self.__class__.__name__ @@ -42,9 +43,9 @@ class UserInterface (object): """ def __init__(self, name): self.name = name - self.setting_section = '%s user interface' % self.name + self.setting_section = '%s user interface' % (self.name) self.config = {} - + def default_settings(self): """Return a list of :class:`hooke.config.Setting`\s for any configurable UI settings. @@ -55,6 +56,17 @@ class UserInterface (object): """ return [] + def reload_config(self, config): + """Update the user interface for new config settings. + + Should be called with the new `config` upon recipt of + `ReloadUserInterfaceConfig` from the `CommandEngine`. + """ + try: + self.config = dict(config.items(self.setting_section)) + except configparser.NoSectionError: + self.config = {} + def run(self, hooke, ui_to_command_queue, command_to_ui_queue): return diff --git a/hooke/ui/commandline.py b/hooke/ui/commandline.py index 8d85a95..6767cb4 100644 --- a/hooke/ui/commandline.py +++ b/hooke/ui/commandline.py @@ -8,7 +8,7 @@ import readline # including readline makes cmd.Cmd.cmdloop() smarter import shlex from ..command import CommandExit, Exit, Command, Argument, StoreValue -from ..interaction import Request, BooleanRequest +from ..interaction import Request, BooleanRequest, ReloadUserInterfaceConfig from ..ui import UserInterface, CommandMessage @@ -91,6 +91,9 @@ class DoCommand (CommandMethod): self.cmd.stdout.write(msg.__class__.__name__+'\n') self.cmd.stdout.write(str(msg).rstrip()+'\n') break + elif isinstance(msg, ReloadUserInterfaceConfig): + self.cmd.ui.reload_config(msg.config) + continue elif isinstance(msg, Request): self._handle_request(msg) continue @@ -311,8 +314,9 @@ typing mistakes ;). # Now onto the main attraction. class HookeCmd (cmd.Cmd): - def __init__(self, commands, inqueue, outqueue): + def __init__(self, ui, commands, inqueue, outqueue): cmd.Cmd.__init__(self) + self.ui = ui self.commands = commands self.local_commands = [LocalExitCommand(), LocalHelpCommand()] self.prompt = 'hooke> ' @@ -343,7 +347,7 @@ class CommandLine (UserInterface): super(CommandLine, self).__init__(name='command line') def run(self, commands, ui_to_command_queue, command_to_ui_queue): - cmd = HookeCmd(commands, + cmd = HookeCmd(self, commands, inqueue=ui_to_command_queue, outqueue=command_to_ui_queue) cmd.cmdloop(self._splash_text()) diff --git a/hooke/util/pluggable.py b/hooke/util/pluggable.py new file mode 100644 index 0000000..4f595c0 --- /dev/null +++ b/hooke/util/pluggable.py @@ -0,0 +1,80 @@ +"""`pluggable` +""" + +from ..util.graph import Node, Graph + + +class IsSubclass (object): + """A safe subclass comparator. + + Examples + -------- + + >>> class A (object): + ... pass + >>> class B (A): + ... pass + >>> C = 5 + >>> is_subclass = IsSubclass(A) + >>> is_subclass(A) + True + >>> is_subclass = IsSubclass(A, blacklist=[A]) + >>> is_subclass(A) + False + >>> is_subclass(B) + True + >>> is_subclass(C) + False + """ + def __init__(self, base_class, blacklist=None): + self.base_class = base_class + if blacklist == None: + blacklist = [] + self.blacklist = blacklist + def __call__(self, other): + try: + subclass = issubclass(other, self.base_class) + except TypeError: + return False + if other in self.blacklist: + return False + return subclass + +def construct_graph(this_modname, submodnames, class_selector, + assert_name_match=True): + """Search the submodules `submodnames` of a module `this_modname` + for class objects for which `class_selector(class)` returns + `True`. These classes are instantiated, and the `instance.name` + is compared to the `submodname` (if `assert_name_match` is + `True`). + + The instances are further arranged into a dependency + :class:`hooke.util.graph.Graph` according to their + `instance.dependencies()` values. The topologically sorted graph + is returned. + """ + instances = {} + for submodname in submodnames: + count = len([s for s in submodnames if s == submodname]) + assert count > 0, 'No %s entries: %s' % (submodname, submodnames) + assert count == 1, 'Multiple (%d) %s entries: %s' \ + % (count, submodname, submodnames) + this_mod = __import__(this_modname, fromlist=[submodname]) + submod = getattr(this_mod, submodname) + for objname in dir(submod): + obj = getattr(submod, objname) + if class_selector(obj): + instance = obj() + if assert_name_match == True and instance.name != submodname: + raise Exception( + 'Instance name %s does not match module name %s' + % (instance.name, submodname)) + instances[instance.name] = instance + nodes = {} + for i in instances.values(): # make nodes for each instance + nodes[i.name] = Node(data=i) + for n in nodes.values(): # fill in dependencies for each node + n.extend([nodes[name] for name in n.data.dependencies()]) + graph = Graph(nodes.values()) + graph.topological_sort() + return graph