Source code for rattail.datasync.watchers

# -*- coding: utf-8; -*-
################################################################################
#
#  Rattail -- Retail Software Framework
#  Copyright © 2010-2023 Lance Edgar
#
#  This file is part of Rattail.
#
#  Rattail is free software: you can redistribute it and/or modify it under the
#  terms of the GNU General Public License as published by the Free Software
#  Foundation, either version 3 of the License, or (at your option) any later
#  version.
#
#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
#  details.
#
#  You should have received a copy of the GNU General Public License along with
#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
DataSync Watchers
"""

import datetime

import sqlalchemy as sa

from rattail.time import make_utc, localtime


[docs] class DataSyncWatcher(object): """ Base class for all DataSync watchers. """ prunes_changes = False retry_attempts = 1 retry_delay = 1 # seconds def __init__(self, config, key, dbkey=None, **kwargs): """ Constructor. Note that while arbitrary kwargs are allowed, the default constructor method does *not* process these in any way. So if you need to accept some kwargs then you must also define the processing thereof. The primary reason for this is because these kwargs, if present, will *always* be simple strings due to how they are read from config. Therefore the burden is on each custom watcher, to interpret them as whatever data type is necessary. """ self.config = config self.key = key self.dbkey = dbkey self.delay = 1 # seconds self.model = self.config.get_model() self.app = self.config.get_app()
[docs] def setup(self): """ This method is called when the watcher thread is first started. """
[docs] def localize_lastrun(self, session, lastrun): """ Calculates a timestamp using `lastrun` as the starting point, but also taking into account a possible (?) time drift between the local and "other" server. """ # get current time according to "other" server, and convert to UTC before = make_utc() other_now = session.execute(sa.text('select current_timestamp'))\ .fetchone()[0] after = make_utc() other_now = make_utc(localtime(self.config, other_now)) # get current time according to local server (i.e. Rattail), in UTC local_now = after # drift is essentially the difference between the two timestamps. it # is meant to be a positive value (or zero) since it will be # "subtracted" from the `lastrun` time in order to obtain the timestamp # we should use for "other" queries. note that we also add one second # to the drift, just to be on the safe side. if other_now < local_now: drift = local_now - other_now else: drift = datetime.timedelta(seconds=0) drift += datetime.timedelta(seconds=1) # convert lastrun time to local timezone, for "other" queries lastrun = localtime(self.config, lastrun, tzinfo=False) # and finally, apply the drift return lastrun - drift
[docs] def get_changes(self, lastrun): """ This must be implemented by the subclass. It should check the source database for pending changes, and return a list of :class:`rattail.db.model.DataSyncChange` instances representing the source changes. """ return []
[docs] def prune_changes(self, keys): """ Prune change records from the source database, if relevant. """
[docs] def process_changes(self, session, changes): """ Process (consume) a batch of changes. """
[docs] class NullWatcher(DataSyncWatcher): """ Null watcher, will never actually check for or report any changes. """
[docs] class ErrorTestWatcher(DataSyncWatcher): """ Watcher which always raises an error when attempting to get changes. Useful for testing error handling etc. """
[docs] def get_changes(self, lastrun): raise RuntimeError("Fake exception, to test error handling")