diff --git a/docs/contrib.rst b/docs/contrib.rst index ee9ccdb..0fed5fc 100644 --- a/docs/contrib.rst +++ b/docs/contrib.rst @@ -30,3 +30,55 @@ It will automatically create a table to store your flags in, or you can override ff.add_handler(SQLAlchemyFeatureFlags(db, model=MyModel)) + +Inline +------ + +``InlineFeatureFlag`` checks for any flag in app's config with `FEATURE_FLAGS_X` format, +where `X` is the name of a specific feature. + +The difference between this handler and default handler is, +instead of defining flag in ``dict``-style: + +.. sourcecode:: python + + FEATURE_FLAGS { + 'finished': False, + } + +the feature name must use uppercased plain string: + +.. sourcecode:: python + + FEATURE_FLAGS_FINISHED = False + +The motivation behind this inline handler is to interopt with other Flask extensions +that rely on environment variable, e.g. `Flask-AppConfig `_. + +Usage ++++++ + +A typical usage is as trivial as the following snippet: + +.. sourcecode:: python + + from flask import Flask + import flask_featureflags as feature_flags + from flask_featureflags.contrib.inline import InlineFeatureFlag + + # feature flags config + FEATURE_FLAGS_FINISHED = False + + app = Flask(__name__) + app.config.from_object(__name__) + ff = feature_flags.FeatureFlag(app) + ff.add_handler(InlineFeatureFlag()) + + @app.route("/") + def index(): + return "Homepage" + + @app.route("/new") + @feature_flags.is_active_feature("FINISHED", redirect_to="/") + def new(): + return "New feature" diff --git a/flask_featureflags/contrib/inline/__init__.py b/flask_featureflags/contrib/inline/__init__.py new file mode 100644 index 0000000..cb4a413 --- /dev/null +++ b/flask_featureflags/contrib/inline/__init__.py @@ -0,0 +1,18 @@ +from flask import current_app +from flask.ext.featureflags import FEATURE_FLAGS_CONFIG +from flask.ext.featureflags import NoFeatureFlagFound +from flask.ext.featureflags import log + + +class InlineFeatureFlag(object): + def __call__(self, feature): + if not current_app: + log.warn(u"Got a request to check for {feature} but we're outside the request context. Returning False".format(feature=feature)) + return False + + feature_cfg = "{prefix}_{feature}".format(prefix=FEATURE_FLAGS_CONFIG, feature=feature) + + try: + return current_app.config[feature_cfg] + except KeyError: + raise NoFeatureFlagFound() diff --git a/tests/contrib/test_inline_handler.py b/tests/contrib/test_inline_handler.py new file mode 100644 index 0000000..ff3b9fb --- /dev/null +++ b/tests/contrib/test_inline_handler.py @@ -0,0 +1,42 @@ +import unittest + +import flask_featureflags as feature_flags +from flask_featureflags.contrib.inline import InlineFeatureFlag + +from tests.fixtures import app +from tests.fixtures import feature_setup + + +inline_feature_flag = InlineFeatureFlag() + + +class InlineFeatureFlagTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + feature_setup.add_handler(inline_feature_flag) + + @classmethod + def tearDownClass(cls): + feature_setup.clear_handlers() + + def setUp(self): + self.app_ctx = app.app_context() + self.app_ctx.push() + app.config["FEATURE_FLAGS_ACTIVE"] = True + app.config["FEATURE_FLAGS_INACTIVE"] = False + + def tearDown(self): + self.app_ctx.pop() + + def test_flag_active(self): + self.assertTrue(feature_flags.is_active("ACTIVE")) + + def test_flag_inactive(self): + self.assertFalse(feature_flags.is_active("INACTIVE")) + + def test_flag_not_found(self): + self.assertFalse(feature_flags.is_active("NOT_FOUND")) + + def test_flag_not_found_raise_handler_exception(self): + self.assertRaises(feature_flags.NoFeatureFlagFound, + inline_feature_flag, "NOT_FOUND")