Ruby Challenge: Feature flagging in Python
I’ve been programming in Python for the last year. I’d rather work in Ruby but there are some features in Python and some of its libraries that are really cool. In this post I’m gonna talk about decorators in general and the @patch
decorator in particular and how I’ve used it in some projects to implement feature flagging.
Decorators
Decorators aren’t exclusive to Python, Java has them and there’s a proposal to add them to JS. They don’t exist in Ruby (though that hasn’t stopped Yehuda from implementing them). In Python we can decorate functions or classes, for the purpose of this article I’ll focus just on function decorators.
So what is a function decorator?
It’s a function that decorates another function(duh!). Basically when defining a decorator you can define code to run before and after the decorated function is called, you can use this to change the functions arguments and even its return value.
To use a decorator in Python you just add the decorator prefixed with @ before the method declaration, we can use this, for example, to create an argument logger decorator like so:
Creating an actual decorator is a bit messy in my opinion and I usually have to look it up when I wanna write my own, but there are some pretty useful decorators provided by other libraries which I use all the time.
The @patch decorator
The unittest
modules is a testing framework for writing unit tests that is part of the Python core libraries. It has a bunch of assertion methods, a TestCase
class that all tests should inherit from (similar to minitest), and a pretty powerful set of mocking utilities, amongst which is the @patch
function decorator.
The @patch
decorator is used when you want to mock the import of a file in another file. The way it works is you decorate the test method with the @patch
decorator and in it you specify the path to mock. So in the scope of that test method the object is mocked. You can use the @patch
decorator like this:
In the example the Mainframe
class in the iron_man.py
file is mocked in the test test__connect_to_mainframe_success
. The mocked mainframe class is passed as the first argument of the test method, the mainframe_mock
object is an instance of MagicMock
which is also part of the unittest
module.
You can also just set the value of the object you’re patching by passing it as a second argument to the @patch
decorator which we’ll see in a later example.
Feature flags
Feature flags are ways to toggle functionality and behaviour in an app. Wether you want to have a bunch of code that you want to run in production but in a controlled way or only have features that are only available to some users, features flags are one of the ways you can manage that. When using feature flags I find its important that:
1) It’s easy to check if a feature is turned on or off.
2) You can test two code paths, one with the feature turned on and one with the feature turned off.
3) You can clean up dead code easily once the feature flag has been turned on for good.
Feature flags need to be stored somewhere. They can be stored in a database and be tied to a user or user role in an application or it can be as simple as an environmental variable. For the things I want to show you I’m going to use environment variables because it requires the least amount of code to provide a working example. So our feature flagging bogs down to a features.py
file where each feature is grabbed from the environment and a class that needs to check the value of the flag just imports it from the features
module like this:
Patching feature flags
Now to tie everything together we’re going to use the @patch
decorator to set feature flags on unit tests. Lets take this code example:
Testing it like that doesn’t really work because one test needs the feature flag turned on while the other needs it turned off, so with the help of the @patch
decorator we can do just that.
Now to address the 3 requirements I raised in the beginning with some good old confirmation bias:
1) It’s easy to check if a feature is turned on or off in the code, its one line to import the feature.
2) You can test two code paths, one with the feature turned on and one with the feature turned off by patching the feature in the method you’re testing.
3) You can clean up dead code easily once the feature flag has been turned on for everyone, just search for the feature name on the code, leave the tests with the feature turned off to make sure they fail and remove everything. You can even use the features.py
file as manifest of the features you need to clean in your code base.
I love to write Ruby code and it’s sometimes frustrating to write stuff in Python but in some cases I’ve used some python constructs that don’t exist in Ruby to do some pretty cool stuff. I think its important to dip the toe in as many programming languages as we can to see how problems are solved using the different features each language offers.
Here are links to some of the stuff I mentioned along the article but didn’t want you to click away:
- Js decorator specification
- Python decorator reference
- Ruby decorator implementation by Yehuda
- @patch decorator reference
- Minitest library
- Confirmation bias
Extra stuff:
Have a simple way of doing feature flags in Ruby without using any gems? Got any other cool stuff in Python that you think Ruby can’t do? Just write about your experience or leave a comment.
At Runtime Revolution I’ve learned different languages and frameworks and have always used that knowledge to try and build easy to read and maintain systems.