Changeset - 827d8cf9243d
[Not reviewed]
0 3 0
Lance Edgar (lance) - 7 years ago 2017-11-29 18:22:50
lance@edbob.org
Add basic "auto-execute" logic for new batches created via filemon

this is for the sake of label batches in particular, when deploying several
copies from host to all store nodes
3 files changed with 44 insertions and 8 deletions:
0 comments (0 inline, 0 general)
rattail/batch/filemon.py
Show inline comments
 
@@ -28,25 +28,28 @@ from __future__ import unicode_literals, absolute_import
 

	
 
import logging
 

	
 
from rattail.db import Session, model
 
from rattail.config import parse_bool
 
from rattail.filemon import Action
 
from rattail.time import make_utc
 
from rattail.util import load_object
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class MakeBatch(Action):
 
    """
 
    Filemon action for making new file-based batches.  Note that this only
 
    handles the simple case of creating a batch with a single input data file.
 
    """
 

	
 
    def __call__(self, path, batch_type='', handler='', user='', delete_if_empty='false', **kwargs):
 
    def __call__(self, path, batch_type='', handler='', user='',
 
                 delete_if_empty='false', auto_execute_allowed='true',
 
                 **kwargs):
 
        """
 
        Make a batch from the given file path.
 

	
 
        :param path: Path to the source data file for the batch.
 

	
 
        :param type: Type of batch, specified by the batch "key" - e.g. 'labels'.
 
@@ -58,27 +61,40 @@ class MakeBatch(Action):
 
        :param user: Username of the user which is to be credited with creating
 
           the batch.
 

	
 
        :param delete_if_empty: Set this to 'true' if you wish the batch to be
 
           auto-deleted in the event that it has no data rows, after being
 
           initially populated from the given data file.
 

	
 
        :param auto_execute_allowed: Set this to 'false' if you wish to disable
 
           auto-execution of new batches.  Default behavior is for the handler
 
           to decide whether each batch is eligible for auto-execution.  Note
 
           that it is not possible to "force" auto-execution of a batch; you
 
           may only "allow" or "prevent" it.
 
        """
 
        if not handler:
 
            assert batch_type
 
            handler = self.config.require('rattail.batch', '{}.handler'.format(batch_type))
 
        handler = load_object(handler)(self.config)
 
        session = Session()
 
        kwargs['created_by'] = session.query(model.User).filter_by(username=user).one()
 
        user = session.query(model.User).filter_by(username=user).one()
 
        kwargs['created_by'] = user
 
        batch = handler.make_batch(session, **kwargs)
 
        handler.set_input_file(batch, path)
 
        handler.populate(batch)
 
        handler.refresh_batch_status(batch)
 

	
 
        if parse_bool(delete_if_empty):
 
            session.flush()
 
            if not batch.data_rows:
 
                log.debug("auto-deleting empty '{}' batch: {}".format(handler.batch_key, batch))
 
                batch.delete_data(self.config)
 
                session.delete(batch)
 
                batch = None
 

	
 
        if batch and parse_bool(auto_execute_allowed) and handler.auto_executable(batch):
 
            handler.execute(batch, user=user)
 
            batch.executed = make_utc()
 
            batch.executed_by = user
 

	
 
        session.commit()
 
        session.close()
rattail/batch/handlers.py
Show inline comments
 
@@ -305,12 +305,19 @@ class BatchHandler(object):
 
        the Execute button within the Tailbone batch view.
 
        """
 
        if batch is None:
 
            return True
 
        return not bool(batch.executed)
 

	
 
    def auto_executable(self, batch):
 
        """
 
        Must return a boolean indicating whether the given bath is eligible for
 
        "automatic" execution, i.e. immediately after batch is created.
 
        """
 
        return False
 

	
 
    def execute(self, batch, progress=None, **kwargs):
 
        """
 
        Execute the given batch, with given progress and kwargs.  That is an
 
        intentionally generic statement, the meaning of which must be further
 
        defined by the handler subclass since default is ``NotImplementedError``.
 
        """
rattail/batch/labels.py
Show inline comments
 
@@ -34,12 +34,13 @@ import sqlalchemy as sa
 
from sqlalchemy import orm
 

	
 
from rattail import enum
 
from rattail.db import model, api
 
from rattail.gpc import GPC
 
from rattail.batch import BatchHandler
 
from rattail.csvutil import UnicodeDictReader
 
from rattail.util import progress_loop
 
from rattail.time import make_utc
 
from rattail.config import parse_bool
 

	
 

	
 
log = logging.getLogger(__name__)
 
@@ -64,12 +65,21 @@ class LabelBatchHandler(BatchHandler):
 
        self.skip_first_line = parse_bool(kwargs.pop('skip_first_line', False))
 
        self.calc_check_digit = kwargs.pop('calc_check_digit', False)
 
        if self.calc_check_digit != 'upc':
 
            self.calc_check_digit = parse_bool(self.calc_check_digit)
 
        return super(LabelBatchHandler, self).make_batch(session, progress, **kwargs)
 

	
 
    def auto_executable(self, batch):
 
        """
 
        Must return a boolean indicating whether the given bath is eligible for
 
        "automatic" execution, i.e. immediately after batch is created.
 
        """
 
        if batch.filename and '.autoexecute.' in batch.filename:
 
            return True
 
        return False
 

	
 
    def populate(self, batch, progress=None):
 
        """
 
        Pre-fill batch with row data from handheld batch, etc.
 
        """
 
        assert batch.handheld_batch or batch.filename or batch.products
 
        session = orm.object_session(batch)
 
@@ -114,31 +124,34 @@ class LabelBatchHandler(BatchHandler):
 
        # TODO: should this actually happen here? vs refresh and just mark product not found?
 
        """
 
        path = batch.filepath(self.config)
 
        with open(path, 'rb') as f:
 
            if self.skip_first_line:
 
                f.readline()
 
            reader = csv.reader(f)
 
            data = list(reader)
 
                reader = csv.reader(f)
 
                data = [{'upc': row[0]} for row in reader]
 
            else:
 
                reader = UnicodeDictReader(f)
 
                data = list(reader)
 

	
 
        products = []
 
        session = orm.object_session(batch)
 

	
 
        def append(entry, i):
 
            upc = entry[0].strip()
 
            upc = entry['upc'].strip()
 
            if upc:
 
                upc = GPC(upc, calc_check_digit=self.calc_check_digit)
 
                product = api.get_product_by_upc(session, upc)
 
                if product:
 
                    products.append(product)
 
                else:
 
                    log.warning("product not found: {}".format(upc))
 

	
 
        if self.progress_loop(append, data, progress,
 
                              message="Reading data from CSV file"):
 
            return products
 
        self.progress_loop(append, data, progress,
 
                           message="Reading data from CSV file")
 
        return products
 

	
 
    def get_label_profile(self, session):
 
        code = self.config.get('rattail.batch', 'labels.default_code')
 
        if code:
 
            return session.query(model.LabelProfile)\
 
                          .filter(model.LabelProfile.code == code)\
0 comments (0 inline, 0 general)