Add wutta wrapper for AppProvider

This commit is contained in:
Lance Edgar 2023-11-24 23:11:49 -06:00
parent 20cd12d682
commit a073be3529
4 changed files with 154 additions and 191 deletions

View file

@ -41,7 +41,7 @@ from functools import partial
import humanize
from mako.template import Template
from wuttjamaican.app import AppHandler as WuttaAppHandler
from wuttjamaican.app import AppHandler as WuttaAppHandler, AppProvider as WuttaAppProvider
from wuttjamaican.util import parse_bool
from rattail.util import (load_object, load_entry_points,
@ -113,28 +113,6 @@ class AppHandler(WuttaAppHandler):
"""
return self.config.get_model()
def get_all_providers(self):
"""
Returns a dict of all registered providers.
"""
providers = load_entry_points('rattail.providers')
for key in list(providers):
providers[key] = providers[key](self)
return providers
def __getattr__(self, name):
if name == 'providers':
self.providers = self.get_all_providers()
return self.providers
if 'providers' not in self.__dict__:
self.__dict__['providers'] = self.get_all_providers()
for provider in self.providers.values():
if hasattr(provider, name):
return getattr(provider, name)
def get_title(self, default='Rattail'):
"""
Returns the configured title (name) of the app.
@ -2286,33 +2264,42 @@ class MergeMixin(object):
setattr(keeping, field['name'], removing_value)
class RattailProvider:
class RattailProvider(WuttaAppProvider):
"""
Base class for Rattail providers. These can add arbitrary extra
functionality to the main AppHandler.
Base class for Rattail app providers.
This inherits from upstream
:class:`~wuttjamaican:wuttjamaican.app.AppProvider` and adds the
following to it:
.. attribute:: enum
Reference to the ``enum`` module for the app.
.. attribute:: model
Reference to the ``model`` module for the app.
.. attribute:: handlers
Dictionary of "secondary" handlers used by the provider, if
applicable.
"""
def __init__(self, app):
self.app = app
self.config = app.config
self.model = app.model
self.enum = app.enum
def __init__(self, config):
super().__init__(config)
self.enum = self.app.enum
self.model = self.app.model
self.handlers = {}
def load_object(self, *args, **kwargs):
"""
Convenience method which calls
:meth:`AppHandler.load_object()`.
"""
return self.app.load_object(*args, **kwargs)
def get_all_providers(config):
"""
Returns a dict of all registered providers.
"""
providers = load_entry_points('rattail.providers')
for key in list(providers):
providers[key] = providers[key](config)
return providers
def make_app(config, **kwargs): # pragma: no cover
warnings.warn("function is deprecated, please use "
"RattailConfig.get_app() method instead",

View file

@ -40,9 +40,9 @@ from getpass import getpass
import humanize
from wuttjamaican.commands.base import (Command as WuttaCommand,
CommandArgumentParser,
Subcommand as WuttaSubcommand)
from wuttjamaican.cmd.base import (Command as WuttaCommand,
CommandArgumentParser,
Subcommand as WuttaSubcommand)
from wuttjamaican.util import parse_list
from rattail import __version__

View file

@ -50,7 +50,7 @@ install_requires =
requests
six
texttable
WuttJamaican>=0.1.7
WuttJamaican>=0.1.8
xlrd
# TODO: revisit this, comment seems dubious

View file

@ -26,16 +26,9 @@ from rattail.gpc import GPC
class TestAppHandler(TestCase):
def setUp(self):
self.config = self.make_config()
# TODO: rename handler to app?
self.handler = self.make_handler()
self.app = self.handler
def make_config(self):
return make_config([], extend=False)
def make_handler(self):
return mod.AppHandler(self.config)
self.config = make_config([], extend=False)
self.app = mod.AppHandler(self.config)
self.config.app = self.app
def test_get_setting(self):
Session = orm.sessionmaker()
@ -66,15 +59,15 @@ class TestAppHandler(TestCase):
def test_get_title(self):
# default for unconfigured title
self.assertEqual(self.handler.get_title(), "Rattail")
self.assertEqual(self.app.get_title(), "Rattail")
# unless default is provided
self.assertEqual(self.handler.get_title(default="Foo"), "Foo")
self.assertEqual(self.app.get_title(default="Foo"), "Foo")
# or title can be configured
self.config.setdefault('rattail', 'app_title', 'Bar')
self.assertEqual(self.handler.get_title(), "Bar")
self.assertEqual(self.handler.get_title(default="Foo"), "Bar")
self.assertEqual(self.app.get_title(), "Bar")
self.assertEqual(self.app.get_title(default="Foo"), "Bar")
def test_make_engine_from_config_record_changes(self):
@ -106,36 +99,19 @@ class TestAppHandler(TestCase):
})
self.assertTrue(engine.rattail_log_pool_status)
def test_getattr_providers(self):
# collection of providers is loaded on demand
self.assertNotIn('providers', self.app.__dict__)
self.assertIsNotNone(self.app.providers)
# and then providers can supply other attributes
self.app.providers['mytest'] = MagicMock(foo_value='bar')
self.assertEqual(self.app.foo_value, 'bar')
def test_getattr_other(self):
# attributes are loaded on demand, simply null by default
self.assertNotIn('nuttin_honey', self.app.__dict__)
# nb. this will force a lookup via the providers
self.assertIsNone(self.app.nuttin_honey)
def test_get_timezone(self):
# unconfigured zone causes error
self.assertRaises(ConfigurationError, self.handler.get_timezone)
self.assertRaises(ConfigurationError, self.app.get_timezone)
# or one can be configured
self.config.setdefault('rattail', 'timezone.default', 'America/Chicago')
self.assertEqual(str(self.handler.get_timezone()), 'America/Chicago')
self.assertEqual(str(self.app.get_timezone()), 'America/Chicago')
# also can configure alternate zones
self.assertRaises(ConfigurationError, self.handler.get_timezone, key='other')
self.assertRaises(ConfigurationError, self.app.get_timezone, key='other')
self.config.setdefault('rattail', 'timezone.other', 'America/New_York')
self.assertEqual(str(self.handler.get_timezone(key='other')), 'America/New_York')
self.assertEqual(str(self.app.get_timezone(key='other')), 'America/New_York')
def test_localtime(self):
@ -144,21 +120,21 @@ class TestAppHandler(TestCase):
# just confirm the method works on a basic level; the
# underlying function is tested elsewhere
now = self.handler.localtime()
now = self.app.localtime()
self.assertIsNotNone(now)
def test_make_utc(self):
# just confirm the method works on a basic level; the
# underlying function is tested elsewhere
now = self.handler.make_utc()
now = self.app.make_utc()
self.assertIsNotNone(now)
def test_load_object(self):
# just confirm the method works on a basic level; the
# underlying function is tested elsewhere
cls = self.handler.load_object('rattail.core:Object')
cls = self.app.load_object('rattail.core:Object')
self.assertIs(cls, Object)
def test_get_active_stores(self):
@ -168,7 +144,7 @@ class TestAppHandler(TestCase):
session = Session(bind=engine)
# no stores by default
stores = self.handler.get_active_stores(session)
stores = self.app.get_active_stores(session)
self.assertEqual(len(stores), 0)
# add a basic store
@ -179,7 +155,7 @@ class TestAppHandler(TestCase):
self.assertIsNone(store001.archived)
# that one store should be returned
stores = self.handler.get_active_stores(session)
stores = self.app.get_active_stores(session)
self.assertEqual(len(stores), 1)
self.assertIs(stores[0], store001)
@ -190,7 +166,7 @@ class TestAppHandler(TestCase):
session.flush()
# now only store 002 should be returned
stores = self.handler.get_active_stores(session)
stores = self.app.get_active_stores(session)
self.assertEqual(len(stores), 1)
self.assertIs(stores[0], store002)
@ -201,142 +177,142 @@ class TestAppHandler(TestCase):
# built-in autocompleter should be got okay
from rattail.autocomplete.products import ProductAutocompleter
autocompleter = self.handler.get_autocompleter('products')
autocompleter = self.app.get_autocompleter('products')
self.assertIsInstance(autocompleter, ProductAutocompleter)
# now let's invent one, but first make sure it is not yet valid
self.assertRaises(ValueError, self.handler.get_autocompleter, 'foobars')
self.assertRaises(ValueError, self.app.get_autocompleter, 'foobars')
# okay now configure it and then it should be got okay
self.config.setdefault('rattail', 'autocomplete.foobars',
'tests.test_app:FooBarAutocompleter')
autocompleter = self.handler.get_autocompleter('foobars')
autocompleter = self.app.get_autocompleter('foobars')
self.assertIsInstance(autocompleter, FooBarAutocompleter)
def test_get_auth_handler(self):
# first call gets the default handler
auth01 = self.handler.get_auth_handler()
auth01 = self.app.get_auth_handler()
self.assertIsNotNone(auth01)
# second call gets the same handler instance
auth02 = self.handler.get_auth_handler()
auth02 = self.app.get_auth_handler()
self.assertIs(auth02, auth01)
def test_get_batch_handler(self):
# unknown batch type raises error by default
self.assertRaises(ValueError, self.handler.get_batch_handler, 'foobar')
self.assertRaises(ValueError, self.app.get_batch_handler, 'foobar')
# or returns None if error is suppressed
bhandler = self.handler.get_batch_handler('foobar', error=False)
bhandler = self.app.get_batch_handler('foobar', error=False)
self.assertIsNone(bhandler)
# but we can provide our own spec
bhandler = self.handler.get_batch_handler(
bhandler = self.app.get_batch_handler(
'foobar', default='tests.test_app:FooBarBatchHandler')
self.assertIsInstance(bhandler, FooBarBatchHandler)
# we also can configure our handler
self.config.setdefault('rattail.batch', 'foobar.handler',
'tests.test_app:FooBarBatchHandler')
bhandler = self.handler.get_batch_handler('foobar')
bhandler = self.app.get_batch_handler('foobar')
self.assertIsInstance(bhandler, FooBarBatchHandler)
# for some reason (?) the "importer" batch handler is special
# and can be returned with no config
from rattail.batch.importer import ImporterBatchHandler
bhandler = self.handler.get_batch_handler('importer')
bhandler = self.app.get_batch_handler('importer')
self.assertIsInstance(bhandler, ImporterBatchHandler)
def test_get_board_handler(self):
# first call gets the default handler
board01 = self.handler.get_board_handler()
board01 = self.app.get_board_handler()
self.assertIsNotNone(board01)
# second call gets the same handler instance
board02 = self.handler.get_board_handler()
board02 = self.app.get_board_handler()
self.assertIs(board02, board01)
def test_get_bounce_handler(self):
# unknown type raises error by default
self.assertRaises(ValueError, self.handler.get_bounce_handler, 'foobar')
self.assertRaises(ValueError, self.app.get_bounce_handler, 'foobar')
# but we can configure our own too
self.config.setdefault('rattail.bouncer', 'foobar.handler',
'tests.test_app:FooBarBounceHandler')
bhandler = self.handler.get_bounce_handler('foobar')
bhandler = self.app.get_bounce_handler('foobar')
self.assertIsInstance(bhandler, FooBarBounceHandler)
# default handler is special and works out of the box
bhandler = self.handler.get_bounce_handler('default')
bhandler = self.app.get_bounce_handler('default')
self.assertIsInstance(bhandler, BounceHandler)
def test_get_clientele_handler(self):
# first call gets the default handler
client01 = self.handler.get_clientele_handler()
client01 = self.app.get_clientele_handler()
self.assertIsNotNone(client01)
# second call gets the same handler instance
client02 = self.handler.get_clientele_handler()
client02 = self.app.get_clientele_handler()
self.assertIs(client02, client01)
def test_get_custorder_handler(self):
# first call gets the default handler
custorder01 = self.handler.get_custorder_handler()
custorder01 = self.app.get_custorder_handler()
self.assertIsNotNone(custorder01)
# second call gets the same handler instance
custorder02 = self.handler.get_custorder_handler()
custorder02 = self.app.get_custorder_handler()
self.assertIs(custorder02, custorder01)
def test_get_employment_handler(self):
# first call gets the default handler
employ01 = self.handler.get_employment_handler()
employ01 = self.app.get_employment_handler()
self.assertIsNotNone(employ01)
# second call gets the same handler instance
employ02 = self.handler.get_employment_handler()
employ02 = self.app.get_employment_handler()
self.assertIs(employ02, employ01)
def test_get_feature_handler(self):
# first call gets the default handler
feature01 = self.handler.get_feature_handler()
feature01 = self.app.get_feature_handler()
self.assertIsNotNone(feature01)
# second call gets the same handler instance
feature02 = self.handler.get_feature_handler()
feature02 = self.app.get_feature_handler()
self.assertIs(feature02, feature01)
def test_get_email_handler(self):
# first call gets the default handler
email01 = self.handler.get_email_handler()
email01 = self.app.get_email_handler()
self.assertIsNotNone(email01)
# second call gets the same handler instance
email02 = self.handler.get_email_handler()
email02 = self.app.get_email_handler()
self.assertIs(email02, email01)
def test_get_all_import_handlers(self):
# several default handlers exist, but not our custom handler
Handlers = self.handler.get_all_import_handlers()
Handlers = self.app.get_all_import_handlers()
self.assertTrue(Handlers)
self.assertNotIn(FromFooToBar, Handlers)
# and by default there are no errors to be raised
Handlers = self.handler.get_all_import_handlers(ignore_errors=False)
Handlers = self.app.get_all_import_handlers(ignore_errors=False)
self.assertTrue(Handlers)
# and just to make sure sorting "works" (no error)
Handlers = self.handler.get_all_import_handlers(sort=True)
Handlers = self.app.get_all_import_handlers(sort=True)
self.assertTrue(Handlers)
# finally let's configure a custom handler, and be sure it
@ -346,14 +322,14 @@ class TestAppHandler(TestCase):
self.config.setdefault('rattail.importing',
'to_rattail.from_rattail.import.handler',
'tests.test_app:FromFooToBar')
Handlers = self.handler.get_all_import_handlers()
Handlers = self.app.get_all_import_handlers()
self.assertTrue(Handlers)
self.assertIn(FromFooToBar, Handlers)
def test_get_designated_import_handlers(self):
# several default handlers exist, but not our custom handler
handlers = self.handler.get_designated_import_handlers()
handlers = self.app.get_designated_import_handlers()
self.assertTrue(handlers)
self.assertFalse(any([isinstance(h, FromFooToBar)
for h in handlers]))
@ -364,14 +340,14 @@ class TestAppHandler(TestCase):
self.config.setdefault('rattail.importing',
'to_rattail.from_rattail.import.handler',
'tests.test_app:FromFooToBar')
handlers = self.handler.get_designated_import_handlers()
handlers = self.app.get_designated_import_handlers()
self.assertTrue(any([isinstance(h, FromFooToBar)
for h in handlers]))
self.assertFalse(any([isinstance(h, FromRattailToRattailImport)
for h in handlers]))
# but then original default is included with alternates
handlers = self.handler.get_designated_import_handlers(with_alternates=True)
handlers = self.app.get_designated_import_handlers(with_alternates=True)
matches = [h for h in handlers
if isinstance(h, FromFooToBar)]
self.assertEqual(len(matches), 1)
@ -383,45 +359,45 @@ class TestAppHandler(TestCase):
def test_get_import_handler(self):
# make sure a basic fetch works
handler = self.handler.get_import_handler('to_rattail.from_rattail.import')
handler = self.app.get_import_handler('to_rattail.from_rattail.import')
self.assertIsInstance(handler, FromRattailToRattailImport)
# and make sure custom override works
self.config.setdefault('rattail.importing',
'to_rattail.from_rattail.import.handler',
'tests.test_app:FromFooToBar')
handler = self.handler.get_import_handler('to_rattail.from_rattail.import')
handler = self.app.get_import_handler('to_rattail.from_rattail.import')
# unknown importer cannot be found
handler = self.handler.get_import_handler('this_should_not_work')
handler = self.app.get_import_handler('this_should_not_work')
self.assertIsNone(handler)
# and if we require it, error will raise
self.assertRaises(ValueError, self.handler.get_import_handler,
self.assertRaises(ValueError, self.app.get_import_handler,
'this_should_not_work', require=True)
def test_get_designated_import_handler_spec(self):
# fetch of unknown key returns none
spec = self.handler.get_designated_import_handler_spec('test01')
spec = self.app.get_designated_import_handler_spec('test01')
self.assertIsNone(spec)
# unless we require it, in which case, error
self.assertRaises(ValueError, self.handler.get_designated_import_handler_spec,
self.assertRaises(ValueError, self.app.get_designated_import_handler_spec,
'test01', require=True)
# we configure one for whatever key we like
self.config.setdefault('rattail.importing',
'test02.handler',
'tests.test_app:FromFooToBar')
spec = self.handler.get_designated_import_handler_spec('test02')
spec = self.app.get_designated_import_handler_spec('test02')
self.assertEqual(spec, 'tests.test_app:FromFooToBar')
# we can also define a "default" designated handler
self.config.setdefault('rattail.importing',
'test03.default_handler',
'tests.test_app:FromFooToBar')
spec = self.handler.get_designated_import_handler_spec('test03')
spec = self.app.get_designated_import_handler_spec('test03')
self.assertEqual(spec, 'tests.test_app:FromFooToBar')
# we can also designate handler w/ legacy config
@ -432,87 +408,87 @@ class TestAppHandler(TestCase):
self.config.setdefault('rattail.importing',
'test04.custom_handler',
'tests.test_app:FromFooToBar')
spec = self.handler.get_designated_import_handler_spec('test04')
spec = self.app.get_designated_import_handler_spec('test04')
self.assertEqual(spec, 'tests.test_app:FromFooToBar')
def test_get_label_handler(self):
# first call gets the default handler
labels01 = self.handler.get_label_handler()
labels01 = self.app.get_label_handler()
self.assertIsNotNone(labels01)
# second call gets the same handler instance
labels02 = self.handler.get_label_handler()
labels02 = self.app.get_label_handler()
self.assertIs(labels01, labels01)
def test_get_membership_handler(self):
# first call gets the default handler
membership01 = self.handler.get_membership_handler()
membership01 = self.app.get_membership_handler()
self.assertIsNotNone(membership01)
# second call gets the same handler instance
membership02 = self.handler.get_membership_handler()
membership02 = self.app.get_membership_handler()
self.assertIs(membership02, membership01)
def test_get_people_handler(self):
# first call gets the default handler
people01 = self.handler.get_people_handler()
people01 = self.app.get_people_handler()
self.assertIsNotNone(people01)
# second call gets the same handler instance
people02 = self.handler.get_people_handler()
people02 = self.app.get_people_handler()
self.assertIs(people02, people01)
def test_get_products_handler(self):
# first call gets the default handler
products01 = self.handler.get_products_handler()
products01 = self.app.get_products_handler()
self.assertIsNotNone(products01)
# second call gets the same handler instance
products02 = self.handler.get_products_handler()
products02 = self.app.get_products_handler()
self.assertIs(products02, products01)
def test_get_report_handler(self):
# first call gets the default handler
report01 = self.handler.get_report_handler()
report01 = self.app.get_report_handler()
self.assertIsNotNone(report01)
# second call gets the same handler instance
report02 = self.handler.get_report_handler()
report02 = self.app.get_report_handler()
self.assertIs(report02, report01)
def test_get_problem_report_handler(self):
# first call gets the default handler
problems01 = self.handler.get_problem_report_handler()
problems01 = self.app.get_problem_report_handler()
self.assertIsNotNone(problems01)
# second call gets the same handler instance
problems02 = self.handler.get_problem_report_handler()
problems02 = self.app.get_problem_report_handler()
self.assertIs(problems02, problems01)
def test_get_trainwreck_handler(self):
# first call gets the default handler
trainwreck01 = self.handler.get_trainwreck_handler()
trainwreck01 = self.app.get_trainwreck_handler()
self.assertIsNotNone(trainwreck01)
# second call gets the same handler instance
trainwreck02 = self.handler.get_trainwreck_handler()
trainwreck02 = self.app.get_trainwreck_handler()
self.assertIs(trainwreck02, trainwreck01)
def test_get_vendor_handler(self):
# first call gets the default handler
vendor01 = self.handler.get_vendor_handler()
vendor01 = self.app.get_vendor_handler()
self.assertIsNotNone(vendor01)
# second call gets the same handler instance
vendor02 = self.handler.get_vendor_handler()
vendor02 = self.app.get_vendor_handler()
self.assertIs(vendor02, vendor01)
def test_progress_loop(self):
@ -527,21 +503,21 @@ class TestAppHandler(TestCase):
result.append(obj)
# this is just a basic test to get coverage..
self.handler.progress_loop(inspect, range(5), NullProgress)
self.app.progress_loop(inspect, range(5), NullProgress)
self.assertEqual(result, list(range(5)))
def test_make_object(self):
# basic test
obj = self.handler.make_object()
obj = self.app.make_object()
self.assertIsNotNone(obj)
# make sure attr is set
obj = self.handler.make_object(answer=42)
obj = self.app.make_object(answer=42)
self.assertEqual(obj.answer, 42)
def test_make_uuid(self):
uuid = self.handler.make_uuid()
uuid = self.app.make_uuid()
self.assertIsInstance(uuid, str)
self.assertEqual(len(uuid), 32)
@ -554,16 +530,16 @@ class TestAppHandler(TestCase):
# giving an unrelated object raises error
person = Object()
self.assertRaises(orm.exc.UnmappedInstanceError,
self.handler.get_session, person)
self.app.get_session, person)
# a related object still may not be in a session
person = model.Person()
result = self.handler.get_session(person)
result = self.app.get_session(person)
self.assertIsNone(result)
# okay then let's add to session, then should work
session.add(person)
result = self.handler.get_session(person)
result = self.app.get_session(person)
self.assertIs(result, session)
session.rollback()
@ -576,19 +552,19 @@ class TestAppHandler(TestCase):
# default behavior should "work" albeit with no engine bound,
# and no continuum user set
session = self.handler.make_session()
session = self.app.make_session()
self.assertIsNotNone(session)
self.assertIsNone(session.bind)
self.assertIsNone(session.continuum_user)
# okay then let's create one with engine bound, and add a user
session = self.handler.make_session(bind=engine)
session = self.app.make_session(bind=engine)
user = model.User(username='ferdinand')
session.add(user)
session.commit()
# now we can make a session with that user bound
session = self.handler.make_session(bind=engine, user='ferdinand')
session = self.app.make_session(bind=engine, user='ferdinand')
self.assertEqual(session.continuum_user.username, 'ferdinand')
# okay add another user, configure it as default, then confirm
@ -596,7 +572,7 @@ class TestAppHandler(TestCase):
session.add(user)
session.commit()
self.config.setdefault('rattail', 'runas.default', 'beaufort')
session = self.handler.make_session(bind=engine)
session = self.app.make_session(bind=engine)
self.assertEqual(session.continuum_user.username, 'beaufort')
def test_short_session(self):
@ -627,7 +603,7 @@ class TestAppHandler(TestCase):
session.commit()
# just do a basic cache to prove the concept
stores = self.handler.cache_model(session, model.Store, key='id')
stores = self.app.cache_model(session, model.Store, key='id')
self.assertEqual(len(stores), 2)
self.assertIn('001', stores)
self.assertIn('002', stores)
@ -637,22 +613,22 @@ class TestAppHandler(TestCase):
def test_make_temp_dir(self):
# things work with no args
path = self.handler.make_temp_dir()
path = self.app.make_temp_dir()
self.assertTrue(os.path.exists(path))
os.rmdir(path)
# we can specify an alternate parent dir (in this case also temp)
parent = self.handler.make_temp_dir()
child = self.handler.make_temp_dir(dir=parent)
parent = self.app.make_temp_dir()
child = self.app.make_temp_dir(dir=parent)
self.assertTrue(os.path.exists(child))
self.assertEqual(os.path.dirname(child), parent)
os.rmdir(child)
os.rmdir(parent)
# also can configure the workdir, to be used as (indirect) parent
workdir = self.handler.make_temp_dir()
workdir = self.app.make_temp_dir()
self.config.setdefault('rattail', 'workdir', workdir)
child = self.handler.make_temp_dir()
child = self.app.make_temp_dir()
parent = os.path.dirname(child)
self.assertEqual(os.path.dirname(parent), workdir)
os.rmdir(child)
@ -662,22 +638,22 @@ class TestAppHandler(TestCase):
def test_make_temp_file(self):
# things work with no args
path = self.handler.make_temp_file()
path = self.app.make_temp_file()
self.assertTrue(os.path.exists(path))
os.remove(path)
# we can specify an alternate parent dir (in this case also temp)
parent = self.handler.make_temp_dir()
path = self.handler.make_temp_file(dir=parent)
parent = self.app.make_temp_dir()
path = self.app.make_temp_file(dir=parent)
self.assertTrue(os.path.exists(path))
self.assertEqual(os.path.dirname(path), parent)
os.remove(path)
os.rmdir(parent)
# also can configure the workdir, to be used as (indirect) parent
workdir = self.handler.make_temp_dir()
workdir = self.app.make_temp_dir()
self.config.setdefault('rattail', 'workdir', workdir)
path = self.handler.make_temp_file()
path = self.app.make_temp_file()
self.assertTrue(os.path.exists(path))
parent = os.path.dirname(path)
self.assertEqual(os.path.dirname(parent), workdir)
@ -689,54 +665,54 @@ class TestAppHandler(TestCase):
# pre-normalized value is unchanged
number = '8885551234'
result = self.handler.normalize_phone_number(number)
result = self.app.normalize_phone_number(number)
self.assertEqual(result, number)
# now a basic real-world example
number = '(888) 555-1234'
result = self.handler.normalize_phone_number(number)
result = self.app.normalize_phone_number(number)
self.assertEqual(result, '8885551234')
# and another for good measure
number = '888.555.1234'
result = self.handler.normalize_phone_number(number)
result = self.app.normalize_phone_number(number)
self.assertEqual(result, '8885551234')
def test_phone_number_is_invalid(self):
# basic real-world example
self.assertIsNone(self.handler.phone_number_is_invalid(
self.assertIsNone(self.app.phone_number_is_invalid(
'(888) 555-1234'))
# and another for good measure
self.assertIsNone(self.handler.phone_number_is_invalid(
self.assertIsNone(self.app.phone_number_is_invalid(
'888.555.1234'))
# 10 digits are required, so 9 or 11 digits should fail
self.assertEqual(self.handler.phone_number_is_invalid('123456789'),
self.assertEqual(self.app.phone_number_is_invalid('123456789'),
"Phone number must have 10 digits")
self.assertEqual(self.handler.phone_number_is_invalid('12345678901'),
self.assertEqual(self.app.phone_number_is_invalid('12345678901'),
"Phone number must have 10 digits")
def test_format_phone_number(self):
# basic real-world example
result = self.handler.format_phone_number('8885551234')
result = self.app.format_phone_number('8885551234')
self.assertEqual(result, '(888) 555-1234')
# garbage in garbage out
result = self.handler.format_phone_number('garbage')
result = self.app.format_phone_number('garbage')
self.assertEqual(result, 'garbage')
def test_make_gpc(self):
# basic real-world example
result = self.handler.make_gpc('074305001321')
result = self.app.make_gpc('074305001321')
self.assertIsInstance(result, GPC)
self.assertEqual(str(result), '00074305001321')
# and let it calculate check digit
result = self.handler.make_gpc('7430500132', calc_check_digit='upc')
result = self.app.make_gpc('7430500132', calc_check_digit='upc')
self.assertIsInstance(result, GPC)
self.assertEqual(str(result), '00074305001321')
@ -744,70 +720,70 @@ class TestAppHandler(TestCase):
# basic real-world example
gpc = GPC('00074305001321')
result = self.handler.render_gpc(gpc)
result = self.app.render_gpc(gpc)
self.assertEqual(result, '0007430500132-1')
def test_render_currency(self):
# basic decimal example
value = decimal.Decimal('42.00')
self.assertEqual(self.handler.render_currency(value), '$42.00')
self.assertEqual(self.app.render_currency(value), '$42.00')
# basic float example
value = 42.00
self.assertEqual(self.handler.render_currency(value), '$42.00')
self.assertEqual(self.app.render_currency(value), '$42.00')
# decimal places will be rounded
value = decimal.Decimal('42.12345')
self.assertEqual(self.handler.render_currency(value), '$42.12')
self.assertEqual(self.app.render_currency(value), '$42.12')
# but we can declare the scale
value = decimal.Decimal('42.12345')
self.assertEqual(self.handler.render_currency(value, scale=4), '$42.1234')
self.assertEqual(self.app.render_currency(value, scale=4), '$42.1234')
# negative numbers get parens
value = decimal.Decimal('-42.42')
self.assertEqual(self.handler.render_currency(value), '($42.42)')
self.assertEqual(self.app.render_currency(value), '($42.42)')
def test_render_quantity(self):
# integer decimals become integers
value = decimal.Decimal('1.000')
self.assertEqual(self.handler.render_quantity(value), '1')
self.assertEqual(self.app.render_quantity(value), '1')
# but decimal places are preserved
value = decimal.Decimal('1.234')
self.assertEqual(self.handler.render_quantity(value), '1.234')
self.assertEqual(self.app.render_quantity(value), '1.234')
def test_render_cases_units(self):
# basic examples, note the singular noun
self.assertEqual(self.handler.render_cases_units(1, None), '1 case')
self.assertEqual(self.handler.render_cases_units(None, 1), '1 unit')
self.assertEqual(self.app.render_cases_units(1, None), '1 case')
self.assertEqual(self.app.render_cases_units(None, 1), '1 unit')
# mix it up a bit
self.assertEqual(self.handler.render_cases_units(3, 2), '3 cases + 2 units')
self.assertEqual(self.app.render_cases_units(3, 2), '3 cases + 2 units')
# also note that zero is not hidden
self.assertEqual(self.handler.render_cases_units(3, 0), '3 cases + 0 units')
self.assertEqual(self.app.render_cases_units(3, 0), '3 cases + 0 units')
def test_render_date(self):
# basic example
date = datetime.date(2021, 12, 31)
self.assertEqual(self.handler.render_date(date), '2021-12-31')
self.assertEqual(self.app.render_date(date), '2021-12-31')
def test_render_datetime(self):
# basic example
dt = datetime.datetime(2021, 12, 31, 8, 30)
self.assertEqual(self.handler.render_datetime(dt), '2021-12-31 08:30:00 AM')
self.assertEqual(self.app.render_datetime(dt), '2021-12-31 08:30:00 AM')
@patch('rattail.app.send_email')
def test_send_email(self, send_email):
# just make sure underlying function is invoked..
self.handler.send_email('test')
self.app.send_email('test')
send_email.assert_called()