Add initial support for rattail "providers"

so integration projects etc. can supplement the main AppHandler
without subclassing it
This commit is contained in:
Lance Edgar 2023-09-14 12:55:27 -05:00
parent 08c5da5de3
commit 69ff5f73db

View file

@ -84,13 +84,31 @@ class AppHandler(object):
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.enum = self.config.get_enum()
# app may not use the db layer, but if so we set the model # app may not use the db layer, but if so we set the model
try: try:
self.model = config.get_model() self.model = self.config.get_model()
except: # pragma: no cover except: # pragma: no cover
pass pass
self.handlers = {}
self.providers = self.get_all_providers()
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):
for provider in self.providers.values():
if hasattr(provider, name):
return getattr(provider, name)
def get_title(self, default=None): def get_title(self, default=None):
""" """
Returns the configured title (name) of the app. Returns the configured title (name) of the app.
@ -532,12 +550,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.auth.AuthHandler` instance for :returns: The :class:`~rattail.auth.AuthHandler` instance for
the app. the app.
""" """
if not hasattr(self, 'auth_handler'): if 'auth' not in self.handlers:
spec = self.config.get('rattail', 'auth.handler', spec = self.config.get('rattail', 'auth.handler',
default='rattail.auth:AuthHandler') default='rattail.auth:AuthHandler')
factory = load_object(spec) factory = load_object(spec)
self.auth_handler = factory(self.config, **kwargs) self.handlers['auth'] = factory(self.config, **kwargs)
return self.auth_handler return self.handlers['auth']
def get_batch_handler(self, key, default=None, error=True, **kwargs): def get_batch_handler(self, key, default=None, error=True, **kwargs):
""" """
@ -585,10 +603,10 @@ class AppHandler(object):
:returns: The :class:`~rattail.board.BoardHandler` instance :returns: The :class:`~rattail.board.BoardHandler` instance
for the app. for the app.
""" """
if not hasattr(self, 'board_handler'): if 'board' not in self.handlers:
from rattail.board import get_board_handler from rattail.board import get_board_handler
self.board_handler = get_board_handler(self.config, **kwargs) self.handlers['board'] = get_board_handler(self.config, **kwargs)
return self.board_handler return self.handlers['board']
def get_bounce_handler(self, key, **kwargs): def get_bounce_handler(self, key, **kwargs):
""" """
@ -601,10 +619,11 @@ class AppHandler(object):
instance of the requested type. If no such handler can be instance of the requested type. If no such handler can be
found, an error will raise. found, an error will raise.
""" """
if not hasattr(self, 'bounce_handlers'): if 'bounce' not in self.handlers:
self.bounce_handlers = {} self.handlers['bounce'] = {}
handlers = self.handlers['bounce']
if key not in self.bounce_handlers: if key not in handlers:
spec = self.config.get('rattail.bouncer', spec = self.config.get('rattail.bouncer',
'{}.handler'.format(key)) '{}.handler'.format(key))
if not spec and key == 'default': if not spec and key == 'default':
@ -614,9 +633,9 @@ class AppHandler(object):
"type: {}".format(key)) "type: {}".format(key))
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.bounce_handlers[key] = Handler(self.config, key) handlers[key] = Handler(self.config, key)
return self.bounce_handlers[key] return handlers[key]
def get_cleanup_handler(self, **kwargs): def get_cleanup_handler(self, **kwargs):
""" """
@ -625,12 +644,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.cleanup.CleanupHandler` :returns: The :class:`~rattail.cleanup.CleanupHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'cleanup_handler'): if 'cleanup' not in self.handlers:
spec = self.config.get('rattail.cleanup', 'handler', spec = self.config.get('rattail.cleanup', 'handler',
default='rattail.cleanup:CleanupHandler') default='rattail.cleanup:CleanupHandler')
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.cleanup_handler = Handler(self.config) self.handlers['cleanup'] = Handler(self.config)
return self.cleanup_handler return self.handlers['cleanup']
def get_clientele_handler(self, **kwargs): def get_clientele_handler(self, **kwargs):
""" """
@ -639,10 +658,10 @@ class AppHandler(object):
:returns: The :class:`~rattail.clientele.ClienteleHandler` :returns: The :class:`~rattail.clientele.ClienteleHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'clientele_handler'): if 'clientele' not in self.handlers:
from rattail.clientele import get_clientele_handler from rattail.clientele import get_clientele_handler
self.clientele_handler = get_clientele_handler(self.config, **kwargs) self.handlers['clientele'] = get_clientele_handler(self.config, **kwargs)
return self.clientele_handler return self.handlers['clientele']
def get_custorder_handler(self, **kwargs): def get_custorder_handler(self, **kwargs):
""" """
@ -651,12 +670,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.custorders.CustomerOrderHandler` :returns: The :class:`~rattail.custorders.CustomerOrderHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'custorder_handler'): if 'custorder' not in self.handlers:
spec = self.config.get('rattail', 'custorders.handler', spec = self.config.get('rattail', 'custorders.handler',
default='rattail.custorders:CustomerOrderHandler') default='rattail.custorders:CustomerOrderHandler')
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.custorder_handler = Handler(self.config) self.handlers['custorder'] = Handler(self.config)
return self.custorder_handler return self.handlers['custorder']
def get_datasync_handler(self, **kwargs): def get_datasync_handler(self, **kwargs):
""" """
@ -666,12 +685,12 @@ class AppHandler(object):
:class:`~rattail.datasync.handler.DatasyncHandler` instance :class:`~rattail.datasync.handler.DatasyncHandler` instance
for the app. for the app.
""" """
if not hasattr(self, 'datasync_handler'): if 'datasync' not in self.handlers:
spec = self.config.get('rattail.datasync', 'handler', spec = self.config.get('rattail.datasync', 'handler',
default='rattail.datasync.handler:DatasyncHandler') default='rattail.datasync.handler:DatasyncHandler')
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.datasync_handler = Handler(self.config, **kwargs) self.handlers['datasync'] = Handler(self.config, **kwargs)
return self.datasync_handler return self.handlers['datasync']
def get_db_handler(self, **kwargs): def get_db_handler(self, **kwargs):
""" """
@ -680,12 +699,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.db.handler.DatabaseHandler` :returns: The :class:`~rattail.db.handler.DatabaseHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'db_handler'): if 'db' not in self.handlers:
spec = self.config.get('rattail.db', 'handler', spec = self.config.get('rattail.db', 'handler',
default='rattail.db.handler:DatabaseHandler') default='rattail.db.handler:DatabaseHandler')
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.db_handler = Handler(self.config) self.handlers['db'] = Handler(self.config)
return self.db_handler return self.handlers['db']
def get_employment_handler(self, **kwargs): def get_employment_handler(self, **kwargs):
""" """
@ -694,10 +713,10 @@ class AppHandler(object):
:returns: The :class:`~rattail.employment.EmploymentHandler` :returns: The :class:`~rattail.employment.EmploymentHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'employment_handler'): if 'employment' not in self.handlers:
from rattail.employment import get_employment_handler from rattail.employment import get_employment_handler
self.employment_handler = get_employment_handler(self.config, **kwargs) self.handlers['employment'] = get_employment_handler(self.config, **kwargs)
return self.employment_handler return self.handlers['employment']
def get_feature_handler(self, **kwargs): def get_feature_handler(self, **kwargs):
""" """
@ -706,10 +725,10 @@ class AppHandler(object):
:returns: The :class:`~rattail.features.handlers.FeatureHandler` :returns: The :class:`~rattail.features.handlers.FeatureHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'feature_handler'): if 'feature' not in self.handlers:
from rattail.features import FeatureHandler from rattail.features import FeatureHandler
self.feature_handler = FeatureHandler(self.config, **kwargs) self.handlers['feature'] = FeatureHandler(self.config, **kwargs)
return self.feature_handler return self.handlers['feature']
def get_email_handler(self, **kwargs): def get_email_handler(self, **kwargs):
""" """
@ -718,7 +737,7 @@ class AppHandler(object):
:returns: The :class:`~rattail.mail.EmailHandler` instance for :returns: The :class:`~rattail.mail.EmailHandler` instance for
the app. the app.
""" """
if not hasattr(self, 'email_handler'): if 'email' not in self.handlers:
spec = self.config.get('rattail.mail', 'handler') spec = self.config.get('rattail.mail', 'handler')
if not spec: if not spec:
spec = self.config.get('rattail', 'email.handler') spec = self.config.get('rattail', 'email.handler')
@ -729,8 +748,8 @@ class AppHandler(object):
else: else:
spec = 'rattail.mail:EmailHandler' spec = 'rattail.mail:EmailHandler'
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.email_handler = Handler(self.config, **kwargs) self.handlers['email'] = Handler(self.config, **kwargs)
return self.email_handler return self.handlers['email']
def get_mail_handler(self, **kwargs): # pragma: no cover def get_mail_handler(self, **kwargs): # pragma: no cover
warnings.warn("method is deprecated, please use " warnings.warn("method is deprecated, please use "
@ -937,12 +956,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.labels.LabelHandler` instance :returns: The :class:`~rattail.labels.LabelHandler` instance
for the app. for the app.
""" """
if not hasattr(self, 'label_handler'): if 'label' not in self.handlers:
spec = self.config.get('rattail', 'labels.handler', spec = self.config.get('rattail', 'labels.handler',
default='rattail.labels:LabelHandler') default='rattail.labels:LabelHandler')
factory = self.load_object(spec) factory = self.load_object(spec)
self.label_handler = factory(self.config, **kwargs) self.handlers['label'] = factory(self.config, **kwargs)
return self.label_handler return self.handlers['label']
def get_luigi_handler(self, **kwargs): def get_luigi_handler(self, **kwargs):
""" """
@ -951,12 +970,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.luigi.handler.LuigiHandler` :returns: The :class:`~rattail.luigi.handler.LuigiHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'luigi_handler'): if 'luigi' not in self.handlers:
spec = self.config.get('rattail.luigi', 'handler', spec = self.config.get('rattail.luigi', 'handler',
default='rattail.luigi.handler:LuigiHandler') default='rattail.luigi.handler:LuigiHandler')
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.luigi_handler = Handler(self.config, **kwargs) self.handlers['luigi'] = Handler(self.config, **kwargs)
return self.luigi_handler return self.handlers['luigi']
def get_membership_handler(self, **kwargs): def get_membership_handler(self, **kwargs):
""" """
@ -967,12 +986,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.membership.MembershipHandler` :returns: The :class:`~rattail.membership.MembershipHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'membership_handler'): if 'membership' not in self.handlers:
spec = self.config.get('rattail', 'membership.handler', spec = self.config.get('rattail', 'membership.handler',
default='rattail.membership:MembershipHandler') default='rattail.membership:MembershipHandler')
factory = load_object(spec) factory = load_object(spec)
self.membership_handler = factory(self.config, **kwargs) self.handlers['membership'] = factory(self.config, **kwargs)
return self.membership_handler return self.handlers['membership']
def get_org_handler(self, **kwargs): def get_org_handler(self, **kwargs):
""" """
@ -981,12 +1000,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.org.OrgHandler` instance for :returns: The :class:`~rattail.org.OrgHandler` instance for
the app. the app.
""" """
if not hasattr(self, 'org_handler'): if 'org' not in self.handlers:
spec = self.config.get('rattail', 'org.handler', spec = self.config.get('rattail', 'org.handler',
default='rattail.org:OrgHandler') default='rattail.org:OrgHandler')
factory = load_object(spec) factory = load_object(spec)
self.org_handler = factory(self.config, **kwargs) self.handlers['org'] = factory(self.config, **kwargs)
return self.org_handler return self.handlers['org']
def get_people_handler(self, **kwargs): def get_people_handler(self, **kwargs):
""" """
@ -997,12 +1016,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.people.PeopleHandler` instance :returns: The :class:`~rattail.people.PeopleHandler` instance
for the app. for the app.
""" """
if not hasattr(self, 'people_handler'): if 'people' not in self.handlers:
spec = self.config.get('rattail', 'people.handler', spec = self.config.get('rattail', 'people.handler',
default='rattail.people:PeopleHandler') default='rattail.people:PeopleHandler')
factory = load_object(spec) factory = load_object(spec)
self.people_handler = factory(self.config, **kwargs) self.handlers['people'] = factory(self.config, **kwargs)
return self.people_handler return self.handlers['people']
def get_poser_handler(self, **kwargs): def get_poser_handler(self, **kwargs):
""" """
@ -1011,12 +1030,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.poser.PoserHandler` instance :returns: The :class:`~rattail.poser.PoserHandler` instance
for the app. for the app.
""" """
if not hasattr(self, 'poser_handler'): if 'poser' not in self.handlers:
spec = self.config.get('rattail', 'poser.handler', spec = self.config.get('rattail', 'poser.handler',
default='rattail.poser:PoserHandler') default='rattail.poser:PoserHandler')
factory = self.load_object(spec) factory = self.load_object(spec)
self.poser_handler = factory(self.config, **kwargs) self.handlers['poser'] = factory(self.config, **kwargs)
return self.poser_handler return self.handlers['poser']
def get_products_handler(self, **kwargs): def get_products_handler(self, **kwargs):
""" """
@ -1025,10 +1044,10 @@ class AppHandler(object):
:returns: The :class:`~rattail.products.ProductsHandler` :returns: The :class:`~rattail.products.ProductsHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'products_handler'): if 'products' not in self.handlers:
from rattail.products import get_products_handler from rattail.products import get_products_handler
self.products_handler = get_products_handler(self.config, **kwargs) self.handlers['products'] = get_products_handler(self.config, **kwargs)
return self.products_handler return self.handlers['products']
def get_report_handler(self, **kwargs): def get_report_handler(self, **kwargs):
""" """
@ -1037,10 +1056,10 @@ class AppHandler(object):
:returns: The :class:`~rattail.reporting.handlers.ReportHandler` :returns: The :class:`~rattail.reporting.handlers.ReportHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'report_handler'): if 'report' not in self.handlers:
from rattail.reporting import get_report_handler from rattail.reporting import get_report_handler
self.report_handler = get_report_handler(self.config, **kwargs) self.handlers['report'] = get_report_handler(self.config, **kwargs)
return self.report_handler return self.handlers['report']
def get_problem_report_handler(self, **kwargs): def get_problem_report_handler(self, **kwargs):
""" """
@ -1049,11 +1068,10 @@ class AppHandler(object):
:returns: The :class:`~rattail.problems.handlers.ProblemReportHandler` :returns: The :class:`~rattail.problems.handlers.ProblemReportHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'problem_report_handler'): if 'problem_report' not in self.handlers:
from rattail.problems import get_problem_report_handler from rattail.problems import get_problem_report_handler
self.problem_report_handler = get_problem_report_handler( self.handlers['problem_report'] = get_problem_report_handler(self.config, **kwargs)
self.config, **kwargs) return self.handlers['problem_report']
return self.problem_report_handler
def get_project_handler(self, **kwargs): def get_project_handler(self, **kwargs):
""" """
@ -1062,12 +1080,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.projects.handler.ProjectHandler` :returns: The :class:`~rattail.projects.handler.ProjectHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'project_handler'): if 'project' not in self.handlers:
spec = self.config.get('project', 'handler', spec = self.config.get('project', 'handler',
default='rattail.projects.handler:ProjectHandler') default='rattail.projects.handler:ProjectHandler')
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.project_handler = Handler(self.config) self.handlers['project'] = Handler(self.config)
return self.project_handler return self.handlers['project']
def get_tailbone_handler(self, **kwargs): def get_tailbone_handler(self, **kwargs):
""" """
@ -1076,12 +1094,12 @@ class AppHandler(object):
:returns: The :class:`~tailbone:tailbone.handler.TailboneHandler` :returns: The :class:`~tailbone:tailbone.handler.TailboneHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'tailbone_handler'): if 'tailbone' not in self.handlers:
spec = self.config.get('tailbone', 'handler', spec = self.config.get('tailbone', 'handler',
default='tailbone.handler:TailboneHandler') default='tailbone.handler:TailboneHandler')
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.tailbone_handler = Handler(self.config) self.handlers['tailbone'] = Handler(self.config)
return self.tailbone_handler return self.handlers['tailbone']
def get_telemetry_handler(self, **kwargs): def get_telemetry_handler(self, **kwargs):
""" """
@ -1090,12 +1108,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.telemetry.handler.TelemetryHandler` :returns: The :class:`~rattail.telemetry.handler.TelemetryHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'telemetry_handler'): if 'telemetry' not in self.handlers:
spec = self.config.get('rattail.telemetry', 'handler', spec = self.config.get('rattail.telemetry', 'handler',
default='rattail.telemetry:TelemetryHandler') default='rattail.telemetry:TelemetryHandler')
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.telemetry_handler = Handler(self.config) self.handlers['telemetry'] = Handler(self.config)
return self.telemetry_handler return self.handlers['telemetry']
def get_trainwreck_handler(self, **kwargs): def get_trainwreck_handler(self, **kwargs):
""" """
@ -1104,12 +1122,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.trainwreck.handler.TrainwreckHandler` :returns: The :class:`~rattail.trainwreck.handler.TrainwreckHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'trainwreck_handler'): if 'trainwreck' not in self.handlers:
spec = self.config.get('trainwreck', 'handler', spec = self.config.get('trainwreck', 'handler',
default='rattail.trainwreck.handler:TrainwreckHandler') default='rattail.trainwreck.handler:TrainwreckHandler')
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.trainwreck_handler = Handler(self.config) self.handlers['trainwreck'] = Handler(self.config)
return self.trainwreck_handler return self.handlers['trainwreck']
def get_upgrade_handler(self, **kwargs): def get_upgrade_handler(self, **kwargs):
""" """
@ -1118,7 +1136,7 @@ class AppHandler(object):
:returns: The :class:`~rattail.upgrades.UpgradeHandler` :returns: The :class:`~rattail.upgrades.UpgradeHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'upgrade_handler'): if 'upgrade' not in self.handlers:
default = 'rattail.upgrades:UpgradeHandler' default = 'rattail.upgrades:UpgradeHandler'
# nb. previous get_upgrade_handler() function accepted # nb. previous get_upgrade_handler() function accepted
# a 'default' kwarg, so i guess we still do here too # a 'default' kwarg, so i guess we still do here too
@ -1131,8 +1149,8 @@ class AppHandler(object):
spec = self.config.get('rattail.upgrades', 'handler', spec = self.config.get('rattail.upgrades', 'handler',
default=default) default=default)
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.upgrade_handler = Handler(self.config) self.handlers['upgrade'] = Handler(self.config)
return self.upgrade_handler return self.handlers['upgrade']
def get_vendor_handler(self, **kwargs): def get_vendor_handler(self, **kwargs):
""" """
@ -1141,12 +1159,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.vendors.handler.VendorHandler` :returns: The :class:`~rattail.vendors.handler.VendorHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'vendor_handler'): if 'vendor' not in self.handlers:
spec = self.config.get('rattail', 'vendors.handler', spec = self.config.get('rattail', 'vendors.handler',
default='rattail.vendors:VendorHandler') default='rattail.vendors:VendorHandler')
factory = self.load_object(spec) factory = self.load_object(spec)
self.vendor_handler = factory(self.config, **kwargs) self.handlers['vendor'] = factory(self.config, **kwargs)
return self.vendor_handler return self.handlers['vendor']
def get_workorder_handler(self, **kwargs): def get_workorder_handler(self, **kwargs):
""" """
@ -1155,12 +1173,12 @@ class AppHandler(object):
:returns: The :class:`~rattail.workorders.WorkOrderHandler` :returns: The :class:`~rattail.workorders.WorkOrderHandler`
instance for the app. instance for the app.
""" """
if not hasattr(self, 'workorder_handler'): if 'workorder' not in self.handlers:
spec = self.config.get('rattail', 'workorders.handler', spec = self.config.get('rattail', 'workorders.handler',
default='rattail.workorders:WorkOrderHandler') default='rattail.workorders:WorkOrderHandler')
Handler = self.load_object(spec) Handler = self.load_object(spec)
self.workorder_handler = Handler(self.config) self.handlers['workorder'] = Handler(self.config)
return self.workorder_handler return self.handlers['workorder']
def progress_loop(self, *args, **kwargs): def progress_loop(self, *args, **kwargs):
""" """
@ -2044,6 +2062,32 @@ class MergeMixin(object):
setattr(keeping, field['name'], removing_value) setattr(keeping, field['name'], removing_value)
class RattailProvider:
"""
Base class for Rattail providers. These can add arbitrary extra
functionality to the main AppHandler.
"""
def __init__(self, app):
self.app = app
self.config = app.config
self.model = app.model
self.enum = app.enum
def load_object(self, *args, **kwargs):
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 def make_app(config, **kwargs): # pragma: no cover
warnings.warn("function is deprecated, please use " warnings.warn("function is deprecated, please use "
"RattailConfig.get_app() method instead", "RattailConfig.get_app() method instead",