From 28a01c259df23e587352035f08e97295e01b3fe2 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 12 Aug 2010 13:15:29 -0400 Subject: [PATCH] Add successful Curve.command_stack maintenance. Now commands successfully track CurveCommands applied to them, and run them again when they're reloaded. Still to do: * save/load .command_stacks with the playlist * hooke.plugin.command_stack --- hooke/curve.py | 24 +++++++++++++++++++--- hooke/engine.py | 11 +++++++++- hooke/playlist.py | 9 ++++++--- hooke/plugin/curve.py | 43 ++++++++++++++++++++++++++++++++++++++++ hooke/plugin/flatfilt.py | 1 + hooke/plugin/playlist.py | 9 +++++---- hooke/plugin/vclamp.py | 4 ++++ 7 files changed, 90 insertions(+), 11 deletions(-) diff --git a/hooke/curve.py b/hooke/curve.py index 34fb34e..ed76c8a 100644 --- a/hooke/curve.py +++ b/hooke/curve.py @@ -20,7 +20,9 @@ storing force curves. """ +import logging import os.path + import numpy from .command_stack import CommandStack @@ -172,6 +174,15 @@ class Curve (object): self.command_stack = CommandStack() self._hooke = None # Hooke instance for Curve.load() + def __str__(self): + return str(self.__unicode__()) + + def __unicode__(self): + return u'<%s %s>' % (self.__class__.__name__, self.name) + + def __repr__(self): + return self.__str__() + def set_hooke(self, hooke=None): if hooke != None: self._hooke = hooke @@ -201,15 +212,22 @@ class Curve (object): :meth:`hooke.command_stack.CommandStack.execute`. """ self.set_hooke(hooke) + log = logging.getLogger('hooke') + log.debug('loading curve %s with driver %s' % (self.name, self.driver)) data,info = self.driver.read(self.path, self.info) self.data = data for key,value in info.items(): self.info[key] = value - if self.hooke != None: - # TODO: set 'curve' argument explicitly for CurveCommands - self.command_stack.execute(self.hooke) + if self._hooke != None: + self.command_stack.execute(self._hooke) + elif len(self.command_stack) > 0: + log.warn( + 'could not execute command stack for %s without Hooke instance' + % self.name) def unload(self): """Release memory intensive :attr:`.data`. """ + log = logging.getLogger('hooke') + log.debug('unloading curve %s' % self.name) self.data = None diff --git a/hooke/engine.py b/hooke/engine.py index e5fd184..75f74fe 100644 --- a/hooke/engine.py +++ b/hooke/engine.py @@ -21,6 +21,7 @@ """ import logging +from Queue import Queue, Empty from .command import NullQueue @@ -88,7 +89,15 @@ class CommandEngine (object): communication queues, so make sure they will not need user interaction. """ + log = logging.getLogger('hooke') log.debug('engine running internal %s with %s' % (command, arguments)) cmd = hooke.command_by_name[command] - cmd.run(hooke, NullQueue(), NullQueue(), arguments) + outqueue = Queue() + cmd.run(hooke, NullQueue(), outqueue, **arguments) + while True: + try: + msg = outqueue.get(block=False) + except Empty: + break + log.debug('engine message from %s (%s): %s' % (command, type(msg), msg)) diff --git a/hooke/playlist.py b/hooke/playlist.py index d3ca4c6..9eb7e8b 100644 --- a/hooke/playlist.py +++ b/hooke/playlist.py @@ -119,9 +119,10 @@ class Playlist (NoteIndexList): self._loaded = [] # List of loaded curves, see :meth:`._setup_item`. self._max_loaded = 100 # curves to hold in memory simultaneously. - def append_curve_by_path(self, path, info=None, identify=True): + def append_curve_by_path(self, path, info=None, identify=True, hooke=None): path = os.path.normpath(path) c = curve.Curve(path, info=info) + c.set_hooke(hooke) if identify == True: c.identify(self.drivers) self.append(c) @@ -338,14 +339,16 @@ class FilePlaylist (Playlist): doc = xml.dom.minidom.parseString(string) self._from_xml_doc(doc, identify=identify) - def load(self, path=None, identify=True): + def load(self, path=None, identify=True, hooke=None): """Load a playlist from a file. """ self.set_path(path) doc = xml.dom.minidom.parse(self.path) self._from_xml_doc(doc, identify=identify) self._digest = self.digest() - + for curve in self: + curve.set_hooke(hooke) + def save(self, path=None): """Saves the playlist in a XML file. """ diff --git a/hooke/plugin/curve.py b/hooke/plugin/curve.py index f0d847c..75f28ee 100644 --- a/hooke/plugin/curve.py +++ b/hooke/plugin/curve.py @@ -30,6 +30,7 @@ import numpy from ..command import Command, Argument, Failure from ..curve import Data +from ..engine import CommandMessage from ..util.calculus import derivative from ..util.fft import unitary_avg_power_spectrum from ..util.si import ppSI, join_data_label, split_data_label @@ -85,6 +86,15 @@ class CurveCommand (Command): super(CurveCommand, self).__init__(**kwargs) def _curve(self, hooke, params): + """Get the selected curve. + + Notes + ----- + `hooke` is intended to attach the selected curve to the local + playlist, and the returned curve should not be effected by the + state of `hooke`. This is important for reliable + :class:`~hooke.command_stack.CommandStack`\s. + """ # HACK? rely on params['curve'] being bound to the local hooke # playlist (i.e. not a copy, as you would get by passing a # curve through the queue). Ugh. Stupid queues. As an @@ -92,6 +102,36 @@ class CurveCommand (Command): # queue... return params['curve'] + def _add_to_command_stack(self, params): + """Store the command name and current `params` values in the + curve's `.command_stack`. + + If this would duplicate the command currently on top of the + stack, no action is taken. Call early on, or watch out for + repeated param processing. + + Recommended practice is to *not* lock in argument values that + are loaded from the plugin's :attr:`.config`. + + Notes + ----- + Perhaps we should subclass :meth:`_run` and use :func:`super`, + or embed this in :meth:`run` to avoid subclasses calling this + method explicitly, with all the tedium and brittality that + implies. On the other hand, the current implemtnation allows + CurveCommands that don't effect the curve itself + (e.g. :class:`GetCommand`) to avoid adding themselves to the + stack entirely. + """ + curve = self._curve(hooke=None, params=params) + if (len(curve.command_stack) > 0 + and curve.command_stack[-1].command == self.name + and curve.command_stack[-1].arguments == params): + pass # no need to place duplicate calls on the stack. + else: + curve.command_stack.append(CommandMessage( + self.name, params)) + class BlockCommand (CurveCommand): """A :class:`CurveCommand` operating on a :class:`~hooke.curve.Data` block. @@ -399,6 +439,7 @@ Name of the new column for storing the difference (without units, defaults to help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): + self._add_to_command_stack(params) params = self.__setup_params(hooke=hooke, params=params) data_A = self._get_column(hooke=hooke, params=params, block_name='block A', @@ -473,6 +514,7 @@ central differencing. help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): + self._add_to_command_stack(params) params = self.__setup_params(hooke=hooke, params=params) x_data = self._get_column(hooke=hooke, params=params, column_name='x column') @@ -537,6 +579,7 @@ Otherwise, the chunks are end-to-end, and not overlapping. help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): + self._add_to_command_stack(params) params = self.__setup_params(hooke=hooke, params=params) data = self._get_column(hooke=hooke, params=params) bounds = params['bounds'] diff --git a/hooke/plugin/flatfilt.py b/hooke/plugin/flatfilt.py index 08c5fa5..1daeca2 100644 --- a/hooke/plugin/flatfilt.py +++ b/hooke/plugin/flatfilt.py @@ -144,6 +144,7 @@ dictionary. help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): + self._add_to_command_stack(params) params = self.__setup_params(hooke=hooke, params=params) block = self._block(hooke=hooke, params=params) dist_data = self._get_column(hooke=hooke, params=params, diff --git a/hooke/plugin/playlist.py b/hooke/plugin/playlist.py index a924701..1ab04fd 100644 --- a/hooke/plugin/playlist.py +++ b/hooke/plugin/playlist.py @@ -202,7 +202,7 @@ Drivers for loading curves. def _run(self, hooke, inqueue, outqueue, params): p = FilePlaylist(drivers=params['drivers'], path=params['input']) - p.load() + p.load(hooke=hooke) hooke.playlists.append(p) outqueue.put(p) @@ -226,8 +226,8 @@ Additional information for the input :class:`hooke.curve.Curve`. help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): - params['playlist'].append_curve_by_path(params['input'], - params['info']) + params['playlist'].append_curve_by_path( + params['input'], params['info'], hooke=hooke) class AddGlobCommand (Command): """Add curves to a playlist with file globbing. @@ -254,7 +254,8 @@ Additional information for the input :class:`hooke.curve.Curve`. def _run(self, hooke, inqueue, outqueue, params): for path in sorted(glob.glob(params['input'])): - params['playlist'].append_curve_by_path(path, params['info']) + params['playlist'].append_curve_by_path( + path, params['info'], hooke=hooke) class RemoveCommand (Command): """Remove a curve from a playlist. diff --git a/hooke/plugin/vclamp.py b/hooke/plugin/vclamp.py index 9f3f6d6..1115cde 100644 --- a/hooke/plugin/vclamp.py +++ b/hooke/plugin/vclamp.py @@ -260,6 +260,7 @@ Name (without units) for storing fit parameters in the `.info` dictionary. help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): + self._add_to_command_stack(params) params = self.__setup_params(hooke=hooke, params=params) block = self._block(hooke=hooke, params=params) dist_data = self._get_column(hooke=hooke, params=params, @@ -474,6 +475,7 @@ Name of the spring constant in the `.info` dictionary. help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): + self._add_to_command_stack(params) params = self.__setup_params(hooke=hooke, params=params) def_data = self._get_column(hooke=hooke, params=params, column_name='deflection column') @@ -531,6 +533,7 @@ Name of the spring constant in the `.info` dictionary. help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): + self._add_to_command_stack(params) params = self.__setup_params(hooke=hooke, params=params) def_data = self._get_column(hooke=hooke, params=params, column_name='deflection column') @@ -607,6 +610,7 @@ Name of the flattening information in the `.info` dictionary. help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): + self._add_to_command_stack(params) params = self.__setup_params(hooke=hooke, params=params) block = self._block(hooke=hooke, params=params) dist_data = self._get_column(hooke=hooke, params=params, -- 2.26.2