Changeset - cdbb6d29d98e
[Not reviewed]
0 10 0
Lance Edgar (lance) - 7 years ago 2018-02-15 12:47:20
lance@edbob.org
More tweaks for python 3
10 files changed with 33 insertions and 30 deletions:
0 comments (0 inline, 0 general)
rattail/commands/core.py
Show inline comments
 
@@ -674,25 +674,25 @@ class FileMonitorCommand(Subcommand):
 

	
 
    name = 'filemon'
 
    description = "Manage the file monitor daemon"
 

	
 
    def add_parser_args(self, parser):
 
        subparsers = parser.add_subparsers(title='subcommands')
 

	
 
        start = subparsers.add_parser('start', help="Start service")
 
        start.set_defaults(subcommand='start')
 
        stop = subparsers.add_parser('stop', help="Stop service")
 
        stop.set_defaults(subcommand='stop')
 

	
 
        if sys.platform == 'linux2':
 
        if sys.platform in ('linux', 'linux2'):
 
            parser.add_argument('-p', '--pidfile',
 
                                help="Path to PID file.", metavar='PATH')
 
            parser.add_argument('--daemonize', action='store_true', default=True, # TODO: should default to False
 
                                help="Daemonize when starting.")
 
            parser.add_argument('--no-daemonize',
 
                                '-D', '--do-not-daemonize', # TODO: (re)move these?
 
                                action='store_false', dest='daemonize',
 
                                help="Do not daemonize when starting.")
 

	
 
        elif sys.platform == 'win32': # pragma no cover
 

	
 
            install = subparsers.add_parser('install', help="Install service")
 
@@ -700,25 +700,25 @@ class FileMonitorCommand(Subcommand):
 
            install.add_argument('-a', '--auto-start', action='store_true',
 
                                 help="Configure service to start automatically.")
 
            install.add_argument('-U', '--username',
 
                                 help="User account under which the service should run.")
 

	
 
            remove = subparsers.add_parser('remove', help="Uninstall (remove) service")
 
            remove.set_defaults(subcommand='remove')
 

	
 
            uninstall = subparsers.add_parser('uninstall', help="Uninstall (remove) service")
 
            uninstall.set_defaults(subcommand='remove')
 

	
 
    def run(self, args):
 
        if sys.platform == 'linux2':
 
        if sys.platform in ('linux', 'linux2'):
 
            from rattail.filemon import linux as filemon
 

	
 
            if args.subcommand == 'start':
 
                filemon.start_daemon(self.config, args.pidfile, args.daemonize)
 

	
 
            elif args.subcommand == 'stop':
 
                filemon.stop_daemon(self.config, args.pidfile)
 

	
 
        elif sys.platform == 'win32': # pragma no cover
 
            self.run_win32(args)
 

	
 
        else:
rattail/filemon/actions.py
Show inline comments
 
@@ -20,25 +20,25 @@
 
#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 
"""
 
File Monitor Actions
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
import os
 
import sys
 
import time
 
import Queue
 
from six.moves import queue
 
import socket
 
import logging
 
from traceback import format_exception
 

	
 
from rattail.mail import send_email
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class Action(object):
 
    """
 
@@ -80,25 +80,25 @@ def perform_actions(profile):
 
        def wait_for_file(path):
 
            while not file_is_free(path):
 
                win32api.Sleep(0)
 

	
 
    stop = False
 
    while not stop:
 

	
 
        # Suspend execution briefly, to avoid consuming so much CPU...
 
        time.sleep(0.01)
 

	
 
        try:
 
            path = profile.queue.get_nowait()
 
        except Queue.Empty:
 
        except queue.Empty:
 
            pass
 
        except StopProcessing:
 
            stop = True
 
        else:
 
            log.debug(u"queue contained a file: {0}".format(repr(path)))
 

	
 
            # In some cases, processing one file may cause other related files
 
            # to also be processed.  When this happens, a path on the queue may
 
            # point to a file which no longer exists.
 
            if not os.path.exists(path):
 
                log.warning(u"file path does not exist: {0}".format(path))
 
                continue
rattail/filemon/config.py
Show inline comments
 
@@ -190,25 +190,25 @@ class Profile(object):
 
        action.config = self.config
 

	
 
        if function:
 
            action.spec = function
 
            action.action = load_object(action.spec)
 
        else:
 
            action.spec = class_
 
            action.action = load_object(action.spec)(self.config)
 

	
 
        action.args = self._config_list(u'action.{0}.args'.format(name))
 

	
 
        action.kwargs = {}
 
        pattern = re.compile(ur'^{0}\.action\.{1}\.kwarg\.(?P<keyword>\w+)$'.format(self.key, name), re.IGNORECASE)
 
        pattern = re.compile(r'^{0}\.action\.{1}\.kwarg\.(?P<keyword>\w+)$'.format(self.key, name), re.IGNORECASE)
 
        for option in self.config.options(u'rattail.filemon'):
 
            match = pattern.match(option)
 
            if match:
 
                action.kwargs[match.group(u'keyword')] = self.config.get(u'rattail.filemon', option)
 

	
 
        action.retry_attempts = self._config_int(u'action.{0}.retry_attempts'.format(name), minimum=1)
 
        action.retry_delay = self._config_int(u'action.{0}.retry_delay'.format(name), minimum=0)
 
        return action
 

	
 
    def _config_boolean(self, option, default=None):
 
        return self.config.getbool('rattail.filemon', '{0}.{1}'.format(self.key, option),
 
                                   default=default)
rattail/filemon/linux.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# -*- coding: utf-8; -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2017 Lance Edgar
 
#  Copyright © 2010-2018 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/>.
 
#
 
################################################################################
 
"""
 
File Monitor for Linux
 
"""
 

	
 
from __future__ import unicode_literals
 
from __future__ import unicode_literals, absolute_import
 

	
 
import Queue
 
from six.moves import queue
 
import logging
 

	
 
import pyinotify
 

	
 
from rattail.daemon import Daemon
 
from rattail.threads import Thread
 
from rattail.filemon.config import load_profiles
 
from rattail.filemon.actions import perform_actions
 
from rattail.filemon.util import queue_existing
 

	
 

	
 
log = logging.getLogger(__name__)
 
@@ -91,25 +91,25 @@ class FileMonitorDaemon(Daemon):
 
        mask = (pyinotify.IN_ACCESS
 
                | pyinotify.IN_ATTRIB
 
                | pyinotify.IN_CLOSE_WRITE
 
                | pyinotify.IN_CREATE
 
                | pyinotify.IN_DELETE
 
                | pyinotify.IN_MODIFY
 
                | pyinotify.IN_MOVED_TO)
 

	
 
        monitored = load_profiles(self.config)
 
        for key, profile in monitored.items():
 

	
 
            # Create a file queue for the profile.
 
            profile.queue = Queue.Queue()
 
            profile.queue = queue.Queue()
 

	
 
            # Perform setup for each of the watched folders.
 
            for path in profile.dirs:
 

	
 
                # Maybe put all pre-existing files in the queue.
 
                if profile.process_existing:
 
                    queue_existing(profile, path)
 

	
 
                # Create a watch for the folder.
 
                log.debug(u"adding watch to profile '{0}' for folder: {1}".format(key, path))
 
                watch_manager.add_watch(path, mask, proc_fun=EventHandler(profile=profile))
 

	
rattail/filemon/win32.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# -*- coding: utf-8; -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2017 Lance Edgar
 
#  Copyright © 2010-2018 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/>.
 
#
 
################################################################################
 
"""
 
File Monitor for Windows
 
"""
 

	
 
from __future__ import unicode_literals
 
from __future__ import unicode_literals, absolute_import
 

	
 
import os
 
import time
 
import datetime
 
import Queue
 
from six.moves import queue
 
import logging
 

	
 
from rattail.win32.service import Service
 
from rattail.threads import Thread
 
from rattail.filemon.config import load_profiles
 
from rattail.filemon.actions import perform_actions
 
from rattail.filemon.util import queue_existing
 

	
 

	
 
# TODO: Would be nice to have a note explaining why this hack exists.
 
name = __name__
 
if name == u'win32':
 
@@ -62,25 +62,25 @@ class RattailFileMonitor(Service):
 
        """
 
        # Read monitor profile(s) from config.
 
        self.monitored = load_profiles(config)
 

	
 
        # Make sure we have something to do.
 
        if not self.monitored:
 
            return False
 

	
 
        # Create monitor and action threads for each profile.
 
        for key, profile in self.monitored.items():
 

	
 
            # Create a file queue for the profile.
 
            profile.queue = Queue.Queue()
 
            profile.queue = queue.Queue()
 

	
 
            # Perform setup for each of the watched folders.
 
            for i, path in enumerate(profile.dirs, 1):
 

	
 
                # Maybe put all pre-existing files in the queue.
 
                if profile.process_existing:
 
                    queue_existing(profile, path)
 

	
 
                # Create a watcher thread for the folder.
 
                name = u'watcher_{0}-{1}'.format(key, i)
 
                log.debug(u"starting {0} thread for folder: {1}".format(repr(name), repr(path)))
 
                thread = Thread(target=watch_directory, name=name, args=(profile, path))
rattail/sil/writer.py
Show inline comments
 
@@ -42,25 +42,25 @@ __all__ = ['Writer']
 

	
 

	
 
class Writer(Object):
 

	
 
    def __init__(self, path=None, **kwargs):
 
        Object.__init__(self, **kwargs)
 
        if path is None:
 
            path = self.temp_path(suffix='.sil')
 
        self.sil_path = path
 
        self.fileobj = self.get_fileobj()
 

	
 
    def get_fileobj(self):
 
        return open(self.sil_path, 'w')
 
        return open(self.sil_path, 'wb')
 

	
 
    def close(self):
 
        self.fileobj.close()
 

	
 
    @classmethod
 
    def temp_path(cls, **kwargs):
 
        """
 
        Generate a temporary file path, using
 
        :func:`rattail.files.temp_path()`.
 
        """
 
        kwargs.setdefault('prefix', 'rattail.')
 
        kwargs.setdefault('suffix', '.sil')
rattail/tests/filemon/test_actions.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# -*- coding: utf-8; -*-
 

	
 
from __future__ import unicode_literals
 
from __future__ import unicode_literals, absolute_import
 

	
 
import os
 
import time
 
import Queue
 
from six.moves import queue
 
from unittest import TestCase
 

	
 
from mock import Mock, patch, call
 
from fixture import TempIO
 

	
 
from rattail.config import make_config, RattailConfig
 
from rattail.filemon import actions
 
from rattail.filemon.config import Profile, ProfileAction
 

	
 

	
 
class TestAction(TestCase):
 

	
 
@@ -31,25 +31,25 @@ class TestPerformActions(TestCase):
 
        self.config = make_config([])
 
        self.config.set(u'rattail.filemon', u'monitor', u'foo')
 
        self.config.set(u'rattail.filemon', u'foo.dirs', self.tmp)
 
        self.config.set(u'rattail.filemon', u'foo.actions', u'noop')
 
        self.config.set(u'rattail.filemon', u'foo.action.noop.func', u'rattail.filemon.actions:noop')
 
        # Must delay creating the profile since doing it here would bypass our mock of noop.
 

	
 
    def get_profile(self, stop_on_error=False):
 
        profile = Profile(self.config, u'foo')
 
        profile.stop_on_error = stop_on_error
 
        profile.queue = Mock()
 
        profile.queue.get_nowait.side_effect = [
 
            Queue.Empty, # for coverage sake; will be effectively skipped
 
            queue.Empty, # for coverage sake; will be effectively skipped
 
            self.tmp.putfile(u'file1', u''),
 
            self.tmp.putfile(u'file2', u''),
 
            self.tmp.putfile(u'file3', u''),
 
            actions.StopProcessing,
 
            ]
 
        return profile
 

	
 
    def test_action_is_invoked_for_each_file_in_queue(self, noop):
 
        profile = self.get_profile()
 
        actions.perform_actions(profile)
 
        self.assertEqual(noop.call_count, 3)
 
        noop.assert_has_calls([
rattail/tests/filemon/test_linux.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# -*- coding: utf-8; -*-
 

	
 
from __future__ import unicode_literals
 
from __future__ import unicode_literals, absolute_import
 

	
 
import Queue
 
from six.moves import queue
 
from unittest import TestCase
 

	
 
from mock import Mock
 
from fixture import TempIO
 

	
 
from rattail.config import make_config
 
from rattail.filemon import linux
 
from rattail.filemon.config import Profile
 

	
 

	
 
class TestEventHandler(TestCase):
 

	
 
    def setUp(self):
 
        self.tmp = TempIO()
 
        self.config = make_config([])
 
        self.config.set(u'rattail.filemon', u'monitor', u'foo')
 
        self.config.set(u'rattail.filemon', u'foo.dirs', self.tmp)
 
        self.config.set(u'rattail.filemon', u'foo.actions', u'noop')
 
        self.config.set(u'rattail.filemon', u'foo.action.noop.func', u'rattail.filemon.actions:noop')
 
        self.profile = Profile(self.config, u'foo')
 
        self.profile.queue = Queue.Queue()
 
        self.profile.queue = queue.Queue()
 
        self.handler = linux.EventHandler()
 
        self.handler.my_init(self.profile)
 

	
 
    def test_in_access_event_does_nothing(self):
 
        event = Mock(pathname=self.tmp.putfile(u'file', u''))
 
        self.handler.process_IN_ACCESS(event)
 
        self.assertTrue(self.profile.queue.empty())
 

	
 
    def test_in_attrib_event_does_nothing(self):
 
        event = Mock(pathname=self.tmp.putfile(u'file', u''))
 
        self.handler.process_IN_ATTRIB(event)
 
        self.assertTrue(self.profile.queue.empty())
rattail/tests/filemon/test_util.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# -*- coding: utf-8; -*-
 

	
 
from __future__ import unicode_literals
 
from __future__ import unicode_literals, absolute_import
 

	
 
import Queue
 
from six.moves import queue
 
from unittest import TestCase
 

	
 
from fixture import TempIO
 

	
 
from rattail.config import make_config
 
from rattail.filemon import util
 
from rattail.filemon.config import Profile
 

	
 

	
 
class TestQueueExisting(TestCase):
 

	
 
    def setUp(self):
 
        self.tmp = TempIO()
 
        self.config = make_config([])
 
        self.config.set(u'rattail.filemon', u'monitor', u'foo')
 
        self.config.set(u'rattail.filemon', u'foo.dirs', self.tmp)
 
        self.config.set(u'rattail.filemon', u'foo.actions', u'noop')
 
        self.config.set(u'rattail.filemon', u'foo.action.noop.func', u'rattail.filemon.actions:noop')
 
        self.profile = Profile(self.config, u'foo')
 
        self.profile.queue = Queue.Queue()
 
        self.profile.queue = queue.Queue()
 

	
 
    def test_nothing_queued_if_no_files_exist(self):
 
        util.queue_existing(self.profile, self.tmp)
 
        self.assertTrue(self.profile.queue.empty())
 

	
 
    def test_normal_files_are_queued_but_not_folders(self):
 
        self.tmp.putfile(u'file', u'')
 
        self.tmp.mkdir(u'folder')
 
        util.queue_existing(self.profile, self.tmp)
 
        self.assertEqual(self.profile.queue.qsize(), 1)
 
        self.assertEqual(self.profile.queue.get_nowait(), self.tmp.join(u'file'))
 
        self.assertTrue(self.profile.queue.empty())
rattail/vendors/catalogs.py
Show inline comments
 
# -*- coding: utf-8; -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2017 Lance Edgar
 
#  Copyright © 2010-2018 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.
 
@@ -66,26 +66,29 @@ class CatalogParser(object):
 
        Parse the given data file, returning all rows found within it.
 
        """
 
        raise NotImplementedError("Catalog parser has no `parse_rows()` method: {0}".format(repr(self.key)))
 

	
 
    def decimal(self, value, scale=4):
 
        """
 
        Convert a value to a decimal, unless it's ``None``.
 
        """
 
        if value is None:
 
            return None
 

	
 
        # No reason to convert integers, really.
 
        if isinstance(value, (Decimal, int, long)):
 
        if isinstance(value, six.integer_types):
 
            return value
 
        if isinstance(value, Decimal):
 
            return value
 

	
 
        if isinstance(value, float):
 
            value = "{{0:0.{0}f}}".format(scale).format(value)
 
        else:
 
            value = value.strip()
 
        return Decimal(value)
 

	
 
    def int_(self, value):
 
        """
 
        Convert a value to an integer.
 
        """
 
        value = value.strip() or 0
 
        return int(value)
0 comments (0 inline, 0 general)