diff --git a/rattail/barcodes.py b/rattail/barcodes.py index a2ca4ad9a39fd70c96d032c84412b1694784e61f..1c7e5f173673df1753a28b85a399dfa81e07ef72 100644 --- a/rattail/barcodes.py +++ b/rattail/barcodes.py @@ -1,8 +1,8 @@ -# -*- 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. # @@ -28,6 +28,8 @@ from __future__ import unicode_literals, absolute_import # import re +import six + def upc_check_digit(data): """ @@ -58,7 +60,7 @@ def luhn_check_digit(data): digit = int(reverse_data[i]) if i % 2 == 0: digit *= 2 - digit = sum([int(x) for x in str(digit)]) + digit = sum([int(x) for x in six.text_type(digit)]) sum_ += digit remainder = sum_ % 10 if remainder == 0: @@ -81,8 +83,8 @@ def price_check_digit(data): .. _Price Check Digit algorithm: http://barcodes.gs1us.org/GS1%20US%20BarCodes%20and%20eCom%20-%20The%20Global%20Language%20of%20Business.htm#PCD """ - if not isinstance(data, basestring) or len(data) != 4: - raise ValueError("'data' must be 4-character string; got: %s" % str(data)) + if not isinstance(data, six.string_types) or len(data) != 4: + raise ValueError("'data' must be 4-character string; got: {}".format(data)) map1 = {0:0, 1:2, 2:4, 3:6, 4:8, 5:9, 6:1, 7:3, 8:5, 9:7} map2 = {0:0, 1:3, 2:6, 3:9, 4:2, 5:5, 6:8, 7:1, 8:4, 9:7} @@ -90,7 +92,7 @@ def price_check_digit(data): data = [int(x) for x in data] sum_ = map1[data[0]] + map1[data[1]] + map2[data[2]] + map3[data[3]] - return int(str(3 * sum_)[-1]) + return int(six.text_type(3 * sum_)[-1]) def upce_to_upca(data, include_check_digit=False): @@ -142,7 +144,7 @@ def upce_to_upca(data, include_check_digit=False): upca = "0%05u00009" % int(upce[0:5]) if include_check_digit: - upca += str(upc_check_digit(upca)) + upca += six.text_type(upc_check_digit(upca)) return upca diff --git a/rattail/batch/labels.py b/rattail/batch/labels.py index ff4750bb344e65df6c7f4b738d34b5c6a5228193..4711a6fc940463a7da5d571dafced717bf9db3f3 100644 --- a/rattail/batch/labels.py +++ b/rattail/batch/labels.py @@ -280,7 +280,7 @@ class LabelBatchHandler(BatchHandler): # okay now print for real # TODO: this is compatible with legacy code but will re-read all needed # product data from master table instead of levaraging batch rows - for profile in profiles.itervalues(): + for profile in profiles.values(): printer = profile.get_printer(self.config) printer.print_labels(profile.labels, progress=progress) diff --git a/rattail/bouncer/daemon.py b/rattail/bouncer/daemon.py index fdba07f75359b6ca890e41b062aed292541feabc..c6c2592b87250dcd434ad8030627bf1ae4e876eb 100644 --- a/rattail/bouncer/daemon.py +++ b/rattail/bouncer/daemon.py @@ -54,7 +54,7 @@ class BouncerDaemon(Daemon): Starts watcher and worker threads according to configuration. """ watched = load_profiles(self.config) - for key, profile in watched.iteritems(): + for key, profile in watched.items(): # Create a thread for watching the IMAP folder. watcher = IMAPWatcher(profile) diff --git a/rattail/commands/core.py b/rattail/commands/core.py index a6d32a5c18ce00369a0bf32d6fcd8c01cd87b7dc..e280ceb8649e23460ba22840bf2065f2afa390f0 100644 --- a/rattail/commands/core.py +++ b/rattail/commands/core.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2017 Lance Edgar +# Copyright © 2010-2018 Lance Edgar # # This file is part of Rattail. # @@ -1004,14 +1004,14 @@ class PalmCommand(Subcommand): if args.subcommand == 'register': try: palm.register_conduit() - except PalmError, error: - sys.stderr.write(str(error) + '\n') + except PalmError as error: + sys.stderr.write("{}\n".format(error)) elif args.subcommand == 'unregister': try: palm.unregister_conduit() - except PalmError, error: - sys.stderr.write(str(error) + '\n') + except PalmError as error: + sys.stderr.write("{}\n".format(error)) class Upgrade(Subcommand): diff --git a/rattail/config.py b/rattail/config.py index 0b52dab8e0818822a64a9cd4fcd4e56cfe0bf44d..e97a03e28ad078cce1103b0ee8696186bebd99b4 100644 --- a/rattail/config.py +++ b/rattail/config.py @@ -1,8 +1,8 @@ -# -*- 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. # @@ -31,11 +31,13 @@ import re import sys import shlex import datetime -import ConfigParser +from six.moves import configparser import warnings import logging import logging.config +import six + from rattail.util import load_entry_points, import_module_path from rattail.exceptions import WindowsExtensionsNotInstalled, ConfigurationError from rattail.files import temp_path @@ -67,14 +69,14 @@ def parse_list(value): return [] # Per the shlex docs (https://docs.python.org/2/library/shlex.html): # "Prior to Python 2.7.3, this module did not support Unicode input." - if isinstance(value, unicode): + if six.PY2 and isinstance(value, six.text_type): value = value.encode('utf-8') parser = shlex.shlex(value) - parser.whitespace += u',' + parser.whitespace += ',' parser.whitespace_split = True values = list(parser) for i, value in enumerate(values): - if value.startswith(u'"') and value.endswith(u'"'): + if value.startswith('"') and value.endswith('"'): values[i] = value[1:-1] return values @@ -108,7 +110,7 @@ class RattailConfig(object): def __init__(self, files=[], usedb=None, preferdb=None): self.files_requested = [] self.files_read = [] - self.parser = ConfigParser.SafeConfigParser() + self.parser = configparser.SafeConfigParser() for path in files: self.read_file(path) self.usedb = usedb @@ -146,7 +148,7 @@ class RattailConfig(object): log.debug("will attempt to read config from file: {0}".format(path)) self.files_requested.append(path) - parser = ConfigParser.SafeConfigParser(dict( + parser = configparser.SafeConfigParser(dict( here=os.path.dirname(path), )) if not parser.read(path): @@ -189,7 +191,7 @@ class RattailConfig(object): try: logging.config.fileConfig(path, disable_existing_loggers=False) - except ConfigParser.NoSectionError as error: + except configparser.NoSectionError as error: log.warning("tried to configure logging, but got NoSectionError: {0}".format(error)) else: log.debug("configured logging") @@ -311,7 +313,7 @@ class RattailConfig(object): value = self.get(*args, **kwargs) if value is None: return None - if isinstance(value, basestring): + if isinstance(value, six.string_types): return parse_list(value) return value # maybe a caller-provided default? @@ -535,7 +537,7 @@ def make_config(files=None, usedb=None, preferdb=None, env=os.environ, winsvc=No files = files.split(os.pathsep) else: files = default_system_paths() + default_user_paths() - elif isinstance(files, basestring): + elif isinstance(files, six.string_types): files = [files] # If making config for a Windows service, we must read the default config @@ -545,7 +547,7 @@ def make_config(files=None, usedb=None, preferdb=None, env=os.environ, winsvc=No # pass parameters to a Windows service. This way we can effectively do # just that, via config. if winsvc is not None: - parser = ConfigParser.SafeConfigParser() + parser = configparser.SafeConfigParser() parser.read(files) if parser.has_section('rattail.config'): key = 'winsvc.{0}'.format(winsvc) @@ -573,7 +575,7 @@ def make_config(files=None, usedb=None, preferdb=None, env=os.environ, winsvc=No # Apply extra config for all available extensions. if extend: extensions = load_entry_points('rattail.config.extensions') - for extension in extensions.itervalues(): + for extension in extensions.values(): log.debug("applying '{0}' config extension".format(extension.key)) extension().configure(config) @@ -732,7 +734,7 @@ class FreeTDSLoggingFilter(logging.Filter): and record.levelno == logging.ERROR and record.msg == "Exception during reset or similar" and record.exc_info - and self.pattern.search(str(record.exc_info[1]))): + and self.pattern.search(six.text_type(record.exc_info[1]))): # Log this as a warning instead of error, to cut down on our noise. record.levelno = logging.WARNING diff --git a/rattail/contrib/vendors/catalogs/dutchvalley.py b/rattail/contrib/vendors/catalogs/dutchvalley.py index 7857c68aeb295b43a2c247746dc69f00aad9d580..15c19cddce5c00dfa28dce3214ef175acefa9da1 100644 --- a/rattail/contrib/vendors/catalogs/dutchvalley.py +++ b/rattail/contrib/vendors/catalogs/dutchvalley.py @@ -66,7 +66,7 @@ class DutchValleyCatalogParser(CatalogParser): # Product code is in first column...but so is a lot of other # garbage. Use strict regex to find good rows. code = sheet.cell_value(r, 0) - if not isinstance(code, basestring): + if not isinstance(code, six.string_types): continue if not code_pattern.match(code): continue diff --git a/rattail/core.py b/rattail/core.py index 9b48e348dcaa52ea0956e10eaa2944765bbc6c7f..5864313e9b7dc8174cd33002b0767b4d5a95b574 100644 --- a/rattail/core.py +++ b/rattail/core.py @@ -46,7 +46,7 @@ class Object(object): Constructor which assigns keyword arguments as attributes. """ - for key, value in kwargs.iteritems(): + for key, value in kwargs.items(): setattr(self, key, value) def __getitem__(self, key): diff --git a/rattail/daemon.py b/rattail/daemon.py index 1a99e49154b49212e922e7cfc5e48c711c5f3e36..d420a1ecd78a97003c921432605b4d1d9ad7e370 100644 --- a/rattail/daemon.py +++ b/rattail/daemon.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8; -*- # This code was (mostly, with some tweaks) stolen from: # http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ @@ -9,6 +9,9 @@ import sys, os, time, atexit import stat from signal import SIGTERM +import six + + class Daemon(object): """ A generic daemon class. @@ -35,7 +38,7 @@ class Daemon(object): if pid > 0: # exit first parent sys.exit(0) - except OSError, e: + except OSError as e: sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) @@ -50,7 +53,7 @@ class Daemon(object): if pid > 0: # exit from second parent sys.exit(0) - except OSError, e: + except OSError as e: sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) @@ -66,9 +69,9 @@ class Daemon(object): # write pidfile atexit.register(self.delpid) - pid = str(os.getpid()) + pid = six.text_type(os.getpid()) with open(self.pidfile, 'w+') as f: - f.write('{0}\n'.format(pid)) + f.write('{}\n'.format(pid)) os.chmod(self.pidfile, stat.S_IRUSR|stat.S_IWUSR) def delpid(self): @@ -125,13 +128,13 @@ class Daemon(object): while 1: os.kill(pid, SIGTERM) time.sleep(0.1) - except OSError, err: - err = str(err) + except OSError as err: + err = six.text_type(err) if err.find("No such process") > 0: if os.path.exists(self.pidfile): os.remove(self.pidfile) else: - print str(err) + print(six.text_type(err)) sys.exit(1) def restart(self): diff --git a/rattail/datasync/consumers.py b/rattail/datasync/consumers.py index 3e514f9c2c50aed6c48dfc76f10ea00a153c91b9..790b5bc4c3df009be1889fceb11fb8b0a6c30716 100644 --- a/rattail/datasync/consumers.py +++ b/rattail/datasync/consumers.py @@ -118,7 +118,7 @@ class NewDataSyncImportConsumer(DataSyncConsumer): """ handler = load_object(self.handler_spec) importers = self.get_importers_from_handler(handler, default_only=True) - for host_name, local_name in self.model_map.iteritems(): + for host_name, local_name in self.model_map.items(): if local_name in importers: importers[host_name] = importers[local_name] if self.skip_local_models: @@ -148,7 +148,7 @@ class NewDataSyncImportConsumer(DataSyncConsumer): Process all changes, leveraging importer(s) as much as possible. """ # Update all importers with current Rattail session. - for importer in self.importers.itervalues(): + for importer in self.importers.values(): importer.session = session for change in changes: diff --git a/rattail/datasync/daemon.py b/rattail/datasync/daemon.py index a463cc0c8b242cc36aa546cce830b337214f37f0..86bb6e54438a51109c7b49b7a5498a689accde60 100644 --- a/rattail/datasync/daemon.py +++ b/rattail/datasync/daemon.py @@ -53,7 +53,7 @@ class DataSyncDaemon(Daemon): """ Starts watcher and consumer threads according to configuration. """ - for key, profile in load_profiles(self.config).iteritems(): + for key, profile in load_profiles(self.config).items(): # Create watcher thread for the profile. name = '{0}-watcher'.format(key) diff --git a/rattail/datasync/rattail.py b/rattail/datasync/rattail.py index bf8b54be5befdf28a90f2f00e1a88dac14416098..23ec2c99126cfd67d12316931f074e36c327de69 100644 --- a/rattail/datasync/rattail.py +++ b/rattail/datasync/rattail.py @@ -295,7 +295,7 @@ class FromRattailToRattailExportConsumer(NewDataSyncImportConsumer): target_session.set_continuum_user(self.runas_username) # update all importers with current sessions - for importer in self.importers.itervalues(): + for importer in self.importers.values(): importer.host_session = session importer.session = target_session @@ -341,7 +341,7 @@ class FromRattailToRattailImportConsumer(NewDataSyncImportConsumer): local_session.set_continuum_user(self.runas_username) # update all importers with current sessions - for importer in self.importers.itervalues(): + for importer in self.importers.values(): importer.host_session = self.host_session importer.session = local_session diff --git a/rattail/db/cache.py b/rattail/db/cache.py index bff2c9c9ba61064b5c30825f86a199e1da65b0b8..3bb78b6d2686feb066a515369e831cce8d73b87e 100644 --- a/rattail/db/cache.py +++ b/rattail/db/cache.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2017 Lance Edgar +# Copyright © 2010-2018 Lance Edgar # # This file is part of Rattail. # @@ -28,6 +28,7 @@ from __future__ import unicode_literals, absolute_import import logging +import six from sqlalchemy.orm import joinedload from rattail.core import Object @@ -114,7 +115,7 @@ class ModelCacher(object): def get_key(self, instance, normalized): if callable(self.key): return self.key(instance, normalized) - if isinstance(self.key, basestring): + if isinstance(self.key, six.string_types): return getattr(instance, self.key) return tuple(getattr(instance, k) for k in self.key) diff --git a/rattail/db/continuum.py b/rattail/db/continuum.py index 0c629cfb9d61a9679e4b611bf64c0cfbf744b712..292926b7d66ca32b46b44f93d0c8868b11187d7d 100644 --- a/rattail/db/continuum.py +++ b/rattail/db/continuum.py @@ -93,7 +93,7 @@ def count_versions(obj): def value(o, k): v = getattr(o, k) - if isinstance(v, unicode): + if isinstance(v, six.text_type): v = v.encode('utf_8') return v diff --git a/rattail/db/model/batch/purchase.py b/rattail/db/model/batch/purchase.py index ef0e4ed5a92a90285013837fa7cf5931090aa65f..0ed7c1a14fcd7174bdfec963cf99c8c6be49985a 100644 --- a/rattail/db/model/batch/purchase.py +++ b/rattail/db/model/batch/purchase.py @@ -1,8 +1,8 @@ -# -*- 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. # @@ -26,6 +26,7 @@ Models for purchase order batches from __future__ import unicode_literals, absolute_import +import six import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.ext.declarative import declared_attr @@ -119,6 +120,7 @@ class PurchaseBatchRow(BatchRowMixin, PurchaseItemBase, Base): """) +@six.python_2_unicode_compatible class PurchaseBatchCredit(PurchaseCreditBase, Base): """ Represents a working copy of purchase credit tied to a batch row. @@ -146,7 +148,7 @@ class PurchaseBatchCredit(PurchaseCreditBase, Base): List of :class:`PurchaseBatchCredit` instances for the row. """)) - def __unicode__(self): + def __str__(self): if self.cases_shorted is not None and self.units_shorted is not None: qty = "{} cases, {} units".format( pretty_quantity(self.cases_shorted), diff --git a/rattail/db/model/datasync.py b/rattail/db/model/datasync.py index 16fe36d21e579b2fe8b7364f6819acc7ab7705b6..ecc2238f18ac42a786bb54bff47f06b2b1832bc9 100644 --- a/rattail/db/model/datasync.py +++ b/rattail/db/model/datasync.py @@ -1,8 +1,8 @@ -# -*- 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. # @@ -28,11 +28,13 @@ from __future__ import unicode_literals import datetime +import six import sqlalchemy as sa from rattail.db.model import Base, uuid_column +@six.python_2_unicode_compatible class DataSyncChange(Base): """ Represents a change obtained from a DataSync watcher thread, and destined @@ -72,9 +74,9 @@ class DataSyncChange(Base): configured as not "isolated". """) - def __unicode__(self): + def __str__(self): if self.payload_type and self.payload_key: - return "{0}: {1}{2}".format( + return "{}: {}{}".format( self.payload_type, self.payload_key, " (deletion)" if self.deletion else "") diff --git a/rattail/db/model/tempmon.py b/rattail/db/model/tempmon.py index d546c255297094317e5125c90f03208294a92498..bab53f8de3a84455502f1b9dbc8e6516c0c1a5e6 100644 --- a/rattail/db/model/tempmon.py +++ b/rattail/db/model/tempmon.py @@ -35,6 +35,7 @@ from sqlalchemy import orm from rattail.db.model import Base, uuid_column +@six.python_2_unicode_compatible class TempmonClient(Base): """ Represents a tempmon client. @@ -52,7 +53,7 @@ class TempmonClient(Base): enabled = sa.Column(sa.Boolean(), nullable=False, default=False) online = sa.Column(sa.Boolean(), nullable=False, default=False) - def __unicode__(self): + def __str__(self): return '{} ({})'.format(self.config_key, self.hostname) def enabled_probes(self): diff --git a/rattail/db/sync/__init__.py b/rattail/db/sync/__init__.py index d5727dfb9937ecf76394deb2b14b2441bf0dfca5..d276e5f6263c8069d98e96856da1a68286d03da3 100644 --- a/rattail/db/sync/__init__.py +++ b/rattail/db/sync/__init__.py @@ -1,8 +1,8 @@ -# -*- 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. # @@ -24,7 +24,7 @@ Database Synchronization """ -from __future__ import unicode_literals +from __future__ import unicode_literals, absolute_import import sys import time @@ -80,12 +80,12 @@ class Synchronizer(object): self.remote_engines = remote_engines def loop(self): - log.info("Synchronizer.loop: using remote engines: {0}".format( - ', '.join(self.remote_engines.iterkeys()))) + log.info("using remote engines: %s", + ', '.join(self.remote_engines.keys())) while True: try: self.synchronize() - except OperationalError, error: + except OperationalError as error: if error.connection_invalidated: # Presumably a database server restart; give it a moment # and try again. @@ -107,12 +107,12 @@ class Synchronizer(object): log.debug("Synchronizer.synchronize: found {0} change(s) to synchronize".format(len(local_changes))) remote_sessions = {} - for key, remote_engine in self.remote_engines.iteritems(): + for key, remote_engine in self.remote_engines.items(): remote_sessions[key] = self.Session(bind=remote_engine) self.synchronize_changes(local_changes, local_session, remote_sessions) - for remote_session in remote_sessions.itervalues(): + for remote_session in remote_sessions.values(): remote_session.commit() remote_session.close() local_session.commit() @@ -138,7 +138,7 @@ class Synchronizer(object): log.debug("Synchronizer.synchronize_changes: processing change: {0}".format(repr(change))) if change.deleted: - for remote_session in remote_sessions.itervalues(): + for remote_session in remote_sessions.values(): remote_instance = remote_session.query(klass).get(change.uuid) if remote_instance: self.delete_instance(remote_session, remote_instance) @@ -147,7 +147,7 @@ class Synchronizer(object): else: # new/dirty local_instance = local_session.query(klass).get(change.uuid) if local_instance: - for remote_session in remote_sessions.itervalues(): + for remote_session in remote_sessions.values(): self.merge_instance(remote_session, local_instance) remote_session.flush() diff --git a/rattail/db/util.py b/rattail/db/util.py index c81a8f78a22c74e866f533aaaecfd846d158c4f8..71c4fdf28849810f95f9608dec431a936b9e6f1d 100644 --- a/rattail/db/util.py +++ b/rattail/db/util.py @@ -114,7 +114,7 @@ def make_topo_sortkey(model, metadata=None): tables[table.name] = i log.debug("topo sortkeys for '{}' will be:\n{}".format(model.__name__, pprint.pformat( - [(i, name) for name, i in sorted(tables.iteritems(), key=lambda t: t[1])]))) + [(i, name) for name, i in sorted(tables.items(), key=lambda t: t[1])]))) def sortkey(name): mapper = orm.class_mapper(getattr(model, name)) diff --git a/rattail/exceptions.py b/rattail/exceptions.py index 14c1959bcf1615f54b776831879e9fcd4bf4844d..43c81bb2b43e19275c59bde5e9a13f24a55a1f2c 100644 --- a/rattail/exceptions.py +++ b/rattail/exceptions.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2017 Lance Edgar +# Copyright © 2010-2018 Lance Edgar # # This file is part of Rattail. # @@ -28,6 +28,8 @@ This module contains all core Rattail exception classes. from __future__ import unicode_literals, absolute_import +import six + class RattailError(Exception): """ @@ -41,6 +43,7 @@ class ConfigurationError(RattailError): """ +@six.python_2_unicode_compatible class SQLAlchemyNotInstalled(RattailError): """ Error raised when an operation is requested which requires SQLAlchemy @@ -49,24 +52,26 @@ class SQLAlchemyNotInstalled(RattailError): def __init__(self, error): self.error = error - def __unicode__(self): + def __str__(self): return ("Hm, looks like SQLAlchemy may not be installed? " "(Perhaps you should do: 'pip install rattail[db]'?) " - "Original error was: {0}".format(self.error)) + "Original error was: {}".format(self.error)) +@six.python_2_unicode_compatible class WindowsExtensionsNotInstalled(RattailError): """ Error raised when an operation is requested which requires the "Python for Windows Extensions" to be instealled, but it is not available. """ - def __unicode__(self): + def __str__(self): return ("Cannot proceed because Python for Windows Extensions is not installed. Please see " "https://rattailproject.org/moin/Installation/Windows/Python#Python_for_Windows_Extensions " "for more info.") +@six.python_2_unicode_compatible class MailTemplateNotFound(ConfigurationError): def __init__(self, key): @@ -78,26 +83,24 @@ class MailTemplateNotFound(ConfigurationError): "configured template folders.".format(self.key)) +@six.python_2_unicode_compatible class SenderNotFound(ConfigurationError): def __init__(self, key): self.key = key - def __unicode__(self): - return ("No email sender (From: address) found in config. Please set '{0}.from' " + def __str__(self): + return ("No email sender (From: address) found in config. Please set '{}.from' " "(or 'default.from') in the [rattail.mail] section.".format(self.key)) +@six.python_2_unicode_compatible class RecipientsNotFound(ConfigurationError): def __init__(self, key): self.key = key def __str__(self): - return (b"No recipients found in config for '{0}' emails. Please set '{0}.to' " - "(or 'default.to') in the [rattail.mail] section.".format(self.key)) - - def __unicode__(self): return ("No recipients found in config for '{0}' emails. Please set '{0}.to' " "(or 'default.to') in the [rattail.mail] section.".format(self.key)) @@ -108,6 +111,7 @@ class FileOperationError(RattailError): """ +@six.python_2_unicode_compatible class PathNotFound(FileOperationError): """ Raised when "path not found" errors are encountered within the @@ -119,8 +123,8 @@ class PathNotFound(FileOperationError): def __init__(self, original_error): self.original_error = original_error - def __unicode__(self): - return '{0}: {1}'.format( + def __str__(self): + return '{}: {}'.format( self.original_error.__class__.__name__, self.original_error) @@ -136,28 +140,32 @@ class PalmError(RattailError): """ +@six.python_2_unicode_compatible class PalmClassicDatabaseTypelibNotFound(PalmError): - def __unicode__(self): + def __str__(self): return ("The Python module for the Palm Classic Database type library " "could not be generated. (Is the HotSync Manager software " "installed?)") +@six.python_2_unicode_compatible class PalmConduitManagerNotFound(PalmError): - def __unicode__(self): + def __str__(self): return ("The Palm Desktop Conduit Manager could not be instantiated. " "(Is the HotSync Manager software installed?)") +@six.python_2_unicode_compatible class PalmConduitAlreadyRegistered(PalmError): - def __unicode__(self): + def __str__(self): return "The Rattail Palm conduit is already registered." +@six.python_2_unicode_compatible class PalmConduitNotRegistered(PalmError): - def __unicode__(self): + def __str__(self): return "The Rattail Palm conduit is not registered." diff --git a/rattail/fablib/core.py b/rattail/fablib/core.py index a9d8c5cbe852f628159b4bfd9405f75f3de7cb7e..440c427aac848bb14543f4ca1f854f7c3accbb67 100644 --- a/rattail/fablib/core.py +++ b/rattail/fablib/core.py @@ -1,8 +1,8 @@ -# -*- 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. # @@ -30,6 +30,8 @@ import os import re import tempfile +import six + from fabric.api import sudo, run, cd, local, env, settings, put as fab_put from fabric.contrib.files import exists, append, upload_template as fab_upload_template, is_link @@ -60,7 +62,7 @@ def mkdir(paths, owner='root:root', mode=None): """ Recursively make one or more directories. """ - if isinstance(paths, basestring): + if isinstance(paths, six.string_types): paths = [paths] sudo('mkdir --parents {0}'.format(' '.join(paths))) if owner != 'root:root': diff --git a/rattail/fablib/python.py b/rattail/fablib/python.py index f1f7069248a8923c09f370f79a2f0ab7dc05fbba..74755a73cedc39ffe5738fc61a96b76546876950 100644 --- a/rattail/fablib/python.py +++ b/rattail/fablib/python.py @@ -1,8 +1,8 @@ -# -*- 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. # @@ -24,11 +24,12 @@ Fabric Library for Python """ -from __future__ import unicode_literals -from __future__ import absolute_import +from __future__ import unicode_literals, absolute_import from contextlib import contextmanager +import six + from fabric.api import sudo, run, prefix, cd, settings, env from fabric.contrib.files import exists, append @@ -129,7 +130,7 @@ def cdvirtualenv(name, subdirs=[], workon_home=None): Context manager to prefix your command(s) with the ``cdvirtualenv`` command. """ workon_home = workon_home or getattr(env, 'python_workon_home', '/srv/envs') - if isinstance(subdirs, basestring): + if isinstance(subdirs, six.string_types): subdirs = [subdirs] path = '{0}/{1}'.format(workon_home, name) if subdirs: diff --git a/rattail/filemon/linux.py b/rattail/filemon/linux.py index a52362030d8c29eb23aa6ded2ffe36eca87dbe9a..5ee782d91dd17c80e86bc4b8c0bdaf8f05b97efe 100644 --- a/rattail/filemon/linux.py +++ b/rattail/filemon/linux.py @@ -97,7 +97,7 @@ class FileMonitorDaemon(Daemon): | pyinotify.IN_MOVED_TO) monitored = load_profiles(self.config) - for key, profile in monitored.iteritems(): + for key, profile in monitored.items(): # Create a file queue for the profile. profile.queue = Queue.Queue() diff --git a/rattail/filemon/win32.py b/rattail/filemon/win32.py index 8795b8a58e6b76efd030a5bf0fad504c43597a0a..e9fe45225da537f3180e2c65c7524301b73c1f87 100644 --- a/rattail/filemon/win32.py +++ b/rattail/filemon/win32.py @@ -68,7 +68,7 @@ class RattailFileMonitor(Service): return False # Create monitor and action threads for each profile. - for key, profile in self.monitored.iteritems(): + for key, profile in self.monitored.items(): # Create a file queue for the profile. profile.queue = Queue.Queue() diff --git a/rattail/gpc.py b/rattail/gpc.py index a1562d6e8543f75d906ff938e2aab36a17a00531..5d770a7c9b54caaca1f2b913a91090194e4f840c 100644 --- a/rattail/gpc.py +++ b/rattail/gpc.py @@ -59,9 +59,9 @@ class GPC(object): to ``'upc'``. """ - value = str(value) + value = six.text_type(value) if calc_check_digit is True or calc_check_digit == 'upc': - value += str(barcodes.upc_check_digit(value)) + value += six.text_type(barcodes.upc_check_digit(value)) self.value = int(value) def __eq__(self, other): diff --git a/rattail/importing/handlers.py b/rattail/importing/handlers.py index a77c02e4f89586bb214e4554d04a844564e9df92..b4327ac3c268b3c80488b393d2052bffd69643e2 100644 --- a/rattail/importing/handlers.py +++ b/rattail/importing/handlers.py @@ -57,7 +57,7 @@ class ImportHandler(object): self.config = config self.enum = config.get_enum() if config else None self.importers = self.get_importers() - for key, value in kwargs.iteritems(): + for key, value in kwargs.items(): setattr(self, key, value) def get_importers(self): @@ -74,7 +74,7 @@ class ImportHandler(object): """ Returns the list of keys corresponding to the available importers. """ - return list(self.importers.iterkeys()) + return list(self.importers.keys()) def get_default_keys(self): """ diff --git a/rattail/importing/importers.py b/rattail/importing/importers.py index 8f05ac4572f96273a95e129aea969ccffa34de0a..c3ee3d154fd6374bd8f5229161b351bf8683c768 100644 --- a/rattail/importing/importers.py +++ b/rattail/importing/importers.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2017 Lance Edgar +# Copyright © 2010-2018 Lance Edgar # # This file is part of Rattail. # @@ -28,6 +28,8 @@ from __future__ import unicode_literals, absolute_import import logging +import six + from rattail.db import cache from rattail.db.util import QuerySequence from rattail.time import make_utc @@ -105,7 +107,7 @@ class Importer(object): for field in exclude_fields: if field in self.fields: self.fields.remove(field) - if isinstance(self.key, basestring): + if isinstance(self.key, six.string_types): self.key = (self.key,) if self.key: for field in self.key: @@ -134,7 +136,7 @@ class Importer(object): self.create = kwargs.pop('create', self.allow_create) and self.allow_create self.update = kwargs.pop('update', self.allow_update) and self.allow_update self.delete = kwargs.pop('delete', False) and self.allow_delete - for key, value in kwargs.iteritems(): + for key, value in kwargs.items(): setattr(self, key, value) def setup(self): diff --git a/rattail/importing/postgresql.py b/rattail/importing/postgresql.py index 93bf02b2f749f660074def603a094140b1b9d3e6..1e5798c859177fdae3145b74a11d4f33b777533a 100644 --- a/rattail/importing/postgresql.py +++ b/rattail/importing/postgresql.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2017 Lance Edgar +# Copyright © 2010-2018 Lance Edgar # # This file is part of Rattail. # @@ -63,7 +63,7 @@ class BulkToPostgreSQL(BulkImporter, ToSQLAlchemy): def prep_data_for_postgres(self, data): data = dict(data) - for key, value in data.iteritems(): + for key, value in data.items(): data[key] = self.prep_value_for_postgres(value) return data @@ -77,7 +77,7 @@ class BulkToPostgreSQL(BulkImporter, ToSQLAlchemy): if isinstance(value, datetime.datetime): value = make_utc(value, tzinfo=False) - elif isinstance(value, basestring): + elif isinstance(value, six.string_types): value = value.replace('\\', '\\\\') value = value.replace('\r', '\\r') value = value.replace('\n', '\\n') diff --git a/rattail/palm.py b/rattail/palm.py index d1634cb5760f8f7a0c88af8a84703329aca59624..a0ed185658310f65ff0a4f16c63517cf0710fe9d 100644 --- a/rattail/palm.py +++ b/rattail/palm.py @@ -145,7 +145,7 @@ def get_conduit_manager(): try: conduit_mgr = Dispatch('PDStandard.PDSystemCondMgr') - except com_error, error: + except com_error as error: if error.hresult == CO_E_CLASSSTRING: # "Invalid class string" raise PalmConduitManagerNotFound() raise @@ -253,7 +253,7 @@ def unregister_com_server(): try: conduit = Dispatch('Rattail.PalmConduit') - except com_error, error: + except com_error as error: if error.hresult == CO_E_CLASSSTRING: # "Invalid class string" raise PalmConduitNotRegistered() raise diff --git a/rattail/sil/batches.py b/rattail/sil/batches.py index f541fe232619f7e3fe68a23857619b4a8184e195..186e991fae16dda4ea742f1c699a9a02908f6c3e 100644 --- a/rattail/sil/batches.py +++ b/rattail/sil/batches.py @@ -26,7 +26,7 @@ Batch Stuff from __future__ import unicode_literals, absolute_import -import ConfigParser +from six.moves import configparser import six import lockfile @@ -42,7 +42,7 @@ def consume_batch_id(source='RATAIL'): path = get_user_file('rattail.conf', createdir=True) with lockfile.LockFile(path): - parser = ConfigParser.SafeConfigParser() + parser = configparser.SafeConfigParser() parser.read(path) option = 'next_batch_id.{0}'.format(source) diff --git a/rattail/sil/columns.py b/rattail/sil/columns.py index af4007237f7cfc0bbb9c2b674e664664adfc2220..5b0771d545b43f048f06fcbb9c1fb03812c61fd3 100644 --- a/rattail/sil/columns.py +++ b/rattail/sil/columns.py @@ -150,7 +150,7 @@ def get_column(name): if supported_columns is None: supported_columns = {} providers = load_entry_points('rattail.sil.column_providers') - for provider in providers.itervalues(): + for provider in providers.values(): supported_columns.update(provider()) column = supported_columns.get(name) diff --git a/rattail/sil/writer.py b/rattail/sil/writer.py index 37e5e0df8e8a1e7733e19fd42e901706035f5776..69bb077e1312324d07001a4416be281ee11214fd 100644 --- a/rattail/sil/writer.py +++ b/rattail/sil/writer.py @@ -1,8 +1,8 @@ -# -*- 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. # @@ -24,14 +24,16 @@ SIL Writer """ -from __future__ import unicode_literals +from __future__ import unicode_literals, absolute_import import datetime from decimal import Decimal -from .._version import __version__ -from ..core import Object -from ..gpc import GPC +import six + +from rattail import __version__ +from rattail.core import Object +from rattail.gpc import GPC from rattail.files import temp_path from rattail.sil import batches @@ -89,24 +91,23 @@ class Writer(Object): Otherwise, it is converted to a string if necessary, and quoted with apostrophes escaped. """ - if value is None: return '' if isinstance(value, GPC): - return str(value) + return six.text_type(value) if isinstance(value, int): - return str(value) + return six.text_type(value) if isinstance(value, float): - return str(value) + return six.text_type(value) if isinstance(value, Decimal): - return str(value) + return six.text_type(value) if isinstance(value, datetime.date): return value.strftime('%Y%j') if isinstance(value, datetime.time): return value.strftime('%H%M') - if not isinstance(value, basestring): - value = str(value) - return "'%s'" % value.replace("'", "''") + if not isinstance(value, six.string_types): + value = six.text_type(value) + return "'{}'".format(value.replace("'", "''")) def write(self, string): self.fileobj.write(string) diff --git a/rattail/templates/mail/rattail_import_updates.html.mako b/rattail/templates/mail/rattail_import_updates.html.mako index 0b053c137124c32d9486d8ae1e40822aef7115f5..aa6d76d33f9e748d289d690fc5352598147853e2 100644 --- a/rattail/templates/mail/rattail_import_updates.html.mako +++ b/rattail/templates/mail/rattail_import_updates.html.mako @@ -63,7 +63,7 @@ Please investigate at your convenience.