Alternative Resolution for Issue #387

This commit is contained in:
BJ Dierkes 2016-07-11 16:28:16 -05:00
parent 739d2b82f3
commit 0b1e97f89b
5 changed files with 97 additions and 25 deletions

View File

@ -1064,8 +1064,8 @@ class CementApp(meta.MetaMixin):
self.hook.define(label)
# register some built-in framework hooks
self.hook.register(
'post_setup', add_handler_override_options, weight=-99)
self.hook.register('post_setup', add_handler_override_options,
weight=-99)
self.hook.register('post_argument_parsing',
handler_override, weight=-99)

View File

@ -158,7 +158,7 @@ class HandlerManager(object):
else:
return False
def register(self, handler_obj):
def register(self, handler_obj, force=False):
"""
Register a handler object to a handler. If the same object is already
registered then no exception is raised, however if a different object
@ -166,6 +166,8 @@ class HandlerManager(object):
raised.
:param handler_obj: The uninstantiated handler object to register.
:param force: Whether to allow replacement if an existing
handler of the same ``label`` is already registered.
:raises: :class:`cement.core.exc.InterfaceError`
:raises: :class:`cement.core.exc.FrameworkError`
@ -210,8 +212,18 @@ class HandlerManager(object):
handler_type)
if obj._meta.label in self.__handlers__[handler_type] and \
self.__handlers__[handler_type][obj._meta.label] != orig_obj:
raise exc.FrameworkError("handlers['%s']['%s'] already exists" %
(handler_type, obj._meta.label))
if force is True:
LOG.debug(
"handlers['%s']['%s'] already exists" %
(handler_type, obj._meta.label) +
", but `force==True`"
)
else:
raise exc.FrameworkError(
"handlers['%s']['%s'] already exists" %
(handler_type, obj._meta.label)
)
interface = self.__handlers__[handler_type]['__interface__']
if hasattr(interface.IMeta, 'validator'):
@ -528,7 +540,7 @@ def defined(handler_type):
return False
def register(handler_obj):
def register(handler_obj, force=False):
"""
DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
will be removed in future versions of Cement.
@ -542,6 +554,8 @@ def register(handler_obj):
raised.
:param handler_obj: The uninstantiated handler object to register.
:param force: Whether to allow replacement if an existing
handler of the same ``label`` is already registered.
:raises: cement.core.exc.InterfaceError
:raises: cement.core.exc.FrameworkError
@ -595,8 +609,17 @@ def register(handler_obj):
handler_type)
if obj._meta.label in backend.__handlers__[handler_type] and \
backend.__handlers__[handler_type][obj._meta.label] != obj:
raise exc.FrameworkError("handlers['%s']['%s'] already exists" %
(handler_type, obj._meta.label))
if force is True:
LOG.debug(
"handlers['%s']['%s'] already exists" %
(handler_type, obj._meta.label) +
", but `force==True`"
)
else:
raise exc.FrameworkError(
"handlers['%s']['%s'] already exists" %
(handler_type, obj._meta.label)
)
interface = backend.__handlers__[handler_type]['__interface__']
if hasattr(interface.IMeta, 'validator'):

View File

@ -14,11 +14,8 @@ Requirements
Configuration
-------------
This extension supports the following application metadata settings:
This extension does not support any configuration settings.
* ``CementApp.Meta.alternative_module_mapping`` - By using the alternative
module mapping feature, the developer can optionally replace the ``json``
module with another drop-in replacement module such as ``ujson``.
Usage
_____
@ -71,6 +68,31 @@ See ``CementApp.Meta.handler_override_options``.
$ python myapp.py -o json
{"foo": "bar"}
What if I Want To Use UltraJson or Something Else?
--------------------------------------------------
It is possible to override the backend ``json`` library module to use, for
example if you wanted to use UltraJson (``ujson``) or another
**drop-in replacement** library. The recommended solution would be to
override the ``JsonOutputHandler`` with you're own sub-classed version, and
modify the ``json_module`` meta-data option.
.. code-block:: python
from cement.ext.ext_json import JsonOutputHandler
class MyJsonHandler(JsonOutputHandler):
class Meta:
json_module = 'ujson'
# then, the class must be replaced via a 'post_setup' hook
def override_json(app):
app.handler.register(MyJsonHandler, force=True)
app.hook.register('post_setup', override_json)
"""
from ..core import output
@ -149,11 +171,11 @@ class JsonOutputHandler(output.CementOutputHandler):
label = 'json'
"""The string identifier of this handler."""
#: Whether or not to include ``json`` as an available to choice
#: Whether or not to include ``json`` as an available choice
#: to override the ``output_handler`` via command line options.
overridable = True
# Backend JSON library module to use
#: Backend JSON library module to use (`json`, `ujson`)
json_module = 'json'
def __init__(self, *args, **kw):
@ -162,7 +184,8 @@ class JsonOutputHandler(output.CementOutputHandler):
def _setup(self, app):
super(JsonOutputHandler, self)._setup(app)
self._json = self.app.__import__('json')
self._json = __import__(self._meta.json_module,
globals(), locals(), [], 0)
def render(self, data_dict, template=None, **kw):
"""
@ -196,7 +219,7 @@ class JsonConfigHandler(ConfigParserConfigHandler):
label = 'json'
# Backend JSON library module to use
#: Backend JSON library module to use (`json`, `ujson`).
json_module = 'json'
def __init__(self, *args, **kw):
@ -205,7 +228,8 @@ class JsonConfigHandler(ConfigParserConfigHandler):
def _setup(self, app):
super(JsonConfigHandler, self)._setup(app)
self._json = self.app.__import__('json')
self._json = __import__(self._meta.json_module,
globals(), locals(), [], 0)
def _parse_file(self, file_path):
"""

View File

@ -13,11 +13,7 @@ Requirements
Configuration
-------------
This extension supports the following application metadata settings:
* ``CementApp.Meta.alternative_module_mapping`` - By using the alternative
module mapping feature, the developer can optionally replace the ``json``
module with another drop-in replacement module such as ``ujson``.
This extension does not support any configuration settings.
Usage
@ -87,7 +83,7 @@ class JsonConfigObjConfigHandler(ConfigObjConfigHandler):
#: The string identifier of this handler.
label = 'json_configobj'
#: Backend JSON module to use
#: Backend JSON module to use (``json``, ``ujson``, etc)
json_module = 'json'
def __init__(self, *args, **kw):
@ -96,7 +92,8 @@ class JsonConfigObjConfigHandler(ConfigObjConfigHandler):
def _setup(self, app):
super(JsonConfigObjConfigHandler, self)._setup(app)
self._json = self.app.__import__('json')
self._json = __import__(self._meta.json_module,
globals(), locals(), [], 0)
def _parse_file(self, file_path):
"""

View File

@ -3,6 +3,7 @@
from cement.core import exc, backend, handler, output, meta
from cement.core import interface
from cement.utils import test
from cement.ext import ext_dummy
from cement.ext.ext_configparser import ConfigParserConfigHandler
@ -91,13 +92,40 @@ class HandlerTestCase(test.CementCoreTestCase):
@test.raises(exc.FrameworkError)
def test_register_duplicate_handler(self):
from cement.ext import ext_dummy
self.app.handler.register(ext_dummy.DummyOutputHandler)
try:
self.app.handler.register(DuplicateHandler)
except exc.FrameworkError:
raise
def test_register_force(self):
class MyDummy(ext_dummy.DummyOutputHandler):
pass
# register once, verify
self.app.handler.register(ext_dummy.DummyOutputHandler)
res = self.app.handler.get('output', 'dummy')
self.eq(res, ext_dummy.DummyOutputHandler)
# register again with force, and verify we get new class back
self.app.handler.register(MyDummy, force=True)
res = self.app.handler.get('output', 'dummy')
self.eq(res, MyDummy)
def test_register_force_deprecated(self):
class MyDummy(ext_dummy.DummyOutputHandler):
pass
# register once, verify
handler.register(ext_dummy.DummyOutputHandler)
res = self.app.handler.get('output', 'dummy')
self.eq(res, ext_dummy.DummyOutputHandler)
# register again with force, and verify we get new class back
handler.register(MyDummy, force=True)
res = self.app.handler.get('output', 'dummy')
self.eq(res, MyDummy)
@test.raises(exc.InterfaceError)
def test_register_unproviding_handler(self):
try: