def __init__(self, app=None, **configuration): r""" :param Flask app: The Flask app (or Blueprint) to wrap. :param \**configuration: Configuration options that define in what way a :any:`MicronMethod` that is created using this Micron instance must behave. These configuration options can be overridden by method-specific configuration options, defined in the ``@micron.method()`` decorator. Example:: from flask import Flask from flask_micron import Micron app = Flask(__name__) micron = Micron(app) @micron.method() def hello(): return "Hello, world!" """ self.config = MicronMethodConfig(**configuration) self.plugins = plugin.Container(json_input.Plugin(), normalize_input.Plugin(), call_function.Plugin(), json_output.Plugin()) self.app = None if app is not None: self.init_app(app)
def test_OptionNamesProvidesListOfAllOptionsNamesInHierarchy(self): level1 = MicronMethodConfig(my='option') level2 = MicronMethodConfig(level1, another='option') level3 = MicronMethodConfig(level2, final='option') self.assertEqual({'my'}, level1.option_names) self.assertEqual({'my', 'another'}, level2.option_names) self.assertEqual({'my', 'another', 'final'}, level3.option_names)
def test_DerivedConfigCanOverrideValueWithNoneValue(self): level1 = MicronMethodConfig().configure(option1=2, option2=3) level2 = MicronMethodConfig(parent=level1).configure(option1=None) self.assertEqual(2, level1.option1) self.assertEqual(3, level1.option2) self.assertEqual(None, level2.option1) self.assertEqual(3, level2.option2)
def test_WhenValueIsNotSet_ValueIsRetrievedFromParent(self): level1 = MicronMethodConfig(option1=2, option2=3) level2 = MicronMethodConfig(parent=level1, option1='a') level3 = MicronMethodConfig(parent=level2, option2='x') self.assertEqual(2, level1.option1) self.assertEqual(3, level1.option2) self.assertEqual('a', level2.option1) self.assertEqual(3, level2.option2) self.assertEqual('a', level3.option1) self.assertEqual('x', level3.option2)
def test_CompileDerivedPlugin(self): class DerivedPlugin(flask_micron.Plugin): def normalize_input(self, ctx): ctx.input = "DerivedPlugin input %s" % ctx.config.option1 def process_output(self, ctx): ctx.output = "%s %s" % (ctx.config.option1, ctx.config.option2) hooks = flask_micron.plugin.Compiler().compile(DerivedPlugin()) # Check for extraction of the correct hook functions. self.assertEqual({'normalize_input', 'process_output'}, set(hooks.keys())) # Check if the compiled hook functions can be called. config = MicronMethodConfig(option1='value1', option2='value2') ctx = flask_micron.plugin.Context() ctx.config = config ctx.input = 'orig input' hooks['normalize_input'](ctx) self.assertEqual('DerivedPlugin input value1', ctx.input) self.assertEqual(None, ctx.output) hooks['process_output'](ctx) self.assertEqual('DerivedPlugin input value1', ctx.input) self.assertEqual('value1 value2', ctx.output)
def test_HookMethodsInPluginsCanBeRun(self): container = plugin.Container() container.add(Dummy()) config = MicronMethodConfig(dommy='yo') ctx = plugin.Context() ctx.config = config container.call_all(ctx, 'process_output') self.assertEqual('I made it, yo', ctx.output)
def test_FlattenedProvidesResolvedDictOfAllOptions(self): level1 = MicronMethodConfig(first='value1', one=1) level2 = MicronMethodConfig(level1, one='one', second='value2') level3 = MicronMethodConfig(level2, second='value two', third='value3') self.assertEqual({'first': 'value1', 'one': 1}, level1.flattened) self.assertEqual({ 'first': 'value1', 'one': 'one', 'second': 'value2' }, level2.flattened) self.assertEqual( { 'first': 'value1', 'one': 'one', 'second': 'value two', 'third': 'value3' }, level3.flattened)
def test_PluginsCanBeDuckTyped(self): container = plugin.Container() container.add(Ducky()) config = MicronMethodConfig() ctx = plugin.Context() ctx.config = config container.call_all(ctx, 'normalize_input') container.call_all(ctx, 'process_output') self.assertEqual('Quack!', ctx.output)
def test_CompileDuckTypedPlugin(self): class DuckTypedPlugin(object): def normalize_input(self, ctx): duck = ctx.config.duck ctx.input = "DuckTypedPlugin input %s" % duck hooks = flask_micron.plugin.Compiler().compile(DuckTypedPlugin()) # Check for extraction of the correct hook functions. self.assertEqual({'normalize_input'}, set(hooks.keys())) # Check if the compiled hook functions can be called. config = MicronMethodConfig(duck='Dagobert') ctx = flask_micron.plugin.Context() ctx.config = config hooks['normalize_input'](ctx) self.assertEqual('DuckTypedPlugin input Dagobert', ctx.input)
def test_CompileDictPlugin(self): def process_output(ctx): simple = ctx.config.simple ctx.output = simple * simple dict_plugin = {'process_output': process_output} hooks = flask_micron.plugin.Compiler().compile(dict_plugin) # Check for extraction of the correct hook functions. self.assertEqual({'process_output'}, set(hooks.keys())) # Check if the compiled hook functions can be called. config = MicronMethodConfig(simple=11) ctx = flask_micron.plugin.Context() ctx.config = config hooks['process_output'](ctx) self.assertEqual(121, ctx.output)
def test_CompileConstructedPlugin(self): def process_output(ctx): ctx.output = ctx.config.drill class ConstructedPlugin: def __init__(self): self.process_output = process_output hooks = flask_micron.plugin.Compiler().compile(ConstructedPlugin()) # Check for extraction of the correct hook functions. self.assertEqual({'process_output'}, set(hooks.keys())) # Check if the compiled hook functions can be called. config = MicronMethodConfig(drill=True) ctx = flask_micron.plugin.Context() ctx.config = config hooks['process_output'](ctx) self.assertEqual(True, ctx.output)
def test_ValuesCanBeSetUsingConfigure(self): config = MicronMethodConfig().configure(option1=2, option2=3) self.assertEqual(2, config.option1) self.assertEqual(3, config.option2)
class Micron(object): """Used to decorate a regular function, to become an all singing and all dancing :any:`MicronMethod`, which is plugged into the routing of a `Flask`_ application. """ def __init__(self, app=None, **configuration): r""" :param Flask app: The Flask app (or Blueprint) to wrap. :param \**configuration: Configuration options that define in what way a :any:`MicronMethod` that is created using this Micron instance must behave. These configuration options can be overridden by method-specific configuration options, defined in the ``@micron.method()`` decorator. Example:: from flask import Flask from flask_micron import Micron app = Flask(__name__) micron = Micron(app) @micron.method() def hello(): return "Hello, world!" """ self.config = MicronMethodConfig(**configuration) self.plugins = plugin.Container(json_input.Plugin(), normalize_input.Plugin(), call_function.Plugin(), json_output.Plugin()) self.app = None if app is not None: self.init_app(app) def init_app(self, app): """Initializes a Flask app as a Micron app. :param Flask app: The Flask app to initialize Micron for. Example:: from flask import Flask from flask_micron import Micron micron = Micron() app = Flask(__name__) micron.init_app(app) """ self.app = app def plugin(self, plugin_object): """Adds a :class:`Plugin <flask_micron.Plugin>` to this Micron object. See :ref:`user_plugins` for information on writing and using plugins. :param flask_micron.Plugin plugin_object: The plugin to add to this Micron object. :returns: This Micron instance, useful for fluent syntax. Example:: from flask import Flask from flask_micron import Micron import my_stuff app = Flask(__name__) micron = Micron(app) my_plugin = my_stuff.Plugin() micron.plugin(my_plugin) """ self.plugins.add(plugin_object) return self def configure(self, **configuration): r"""Updates the configuration for this Micron instance. :param \**configuration: Configuration options that define in what way Micron methods that are created using this Micron instance must behave. These configuration options can be overridden by method-specific configuration options, defined in the @micron.method(...) decorator. :returns: This Micron instance, useful for fluent syntax. Example:: from flask import Flask from flask_micron import Micron app = Flask(__name__) micron = Micron(app).configure(option_name=some_value) Note: the last line is equivalent to:: micron = Micron(app, option_name=some_value) """ self.config.configure(**configuration) return self def method(self, rule=None, **configuration): r"""Decorates a function to make it work as a Micron method. :param string rule: The URL rule to use for this method. Default value: /<name of decorated function> :param \**configuration: Configuration options that define in what way the Micron method must behave. These configuration options can be used to override the default configuration as set for the Micron object. :returns: A decorator that will take care of embedding the Micron method in the Flask application and hooking it up with the Micron request handling. Example:: from flask import Flask from flask_micron import Micron app = Flask(__name__) micron = Micron(app, x="default config value") @micron.method(x="function-specific config value") def hello(who='World'): return 'Hello, %s' % who """ if self.app is None: raise ImplementationError( 'The @micron.method decorator can only be used when ' 'the Micron class is linked to a Flask app') def _decorator(func, rule=rule): if rule is None: rule = _create_url_rule(func) wrapped = MicronMethod(self, func).configure(**configuration) self.app.add_url_rule(rule, view_func=wrapped, methods=['POST']) return func return _decorator
def test_EmptyConfigCanBeCreated(self): MicronMethodConfig()
def test_InvalidIdentifier_RaisesException(self): with self.assertRaises(ImplementationError): MicronMethodConfig(**{"I N V A L I D": "I D E N T I F I E R"})
def test_NewOptionsCanBeAdded(self): level1 = MicronMethodConfig() level2 = MicronMethodConfig(level1) level1.my = 'option' self.assertEqual('option', level1.my) self.assertEqual('option', level2.my)
def test_GivenEmptyConfigValue_WhenRetrievingValue_ExceptionIsRaised(self): with self.assertRaises(KeyError): MicronMethodConfig().nosuchvalue
def test_ValuesCanBeSetInTheConstructor(self): config = MicronMethodConfig(option1=2, option2=3) self.assertEqual(2, config.option1) self.assertEqual(3, config.option2)
def test_ValuesCanBeSetUsingSetters(self): config = MicronMethodConfig() config.option1 = 2 config.option2 = 3 self.assertEqual(2, config.option1) self.assertEqual(3, config.option2)