from feature_change import change
def log_diff(**kwargs):
# This will be called if the results of
# `new_function` and `current_function` differ
def log_call(**kwargs):
# This will be called on every call to `current_function`
def new_function():
# This is a new function we want to check if
# it *really* behaves like the current
@change(new=new_function, on_diff=log_diff, on_call=log_call)
def current_function():
# The current function. Just decorate it with `change` and everything
# will work! No need to change the code!
Table of Contents
A Python decorator that helps you run two different versions of a function at the same time and track differences without breaking the current behavior.
It is based on Auth0's feature-change for Node.js. Original article
-
Install with pip or your favorite package manager
pip install feature-change
a. If you can't or don't want to add a new dependency, feel free to just copy the current code from
change.py
. It is pretty straightforward. -
Define the logging functions you want to be called. They will receive two keyword arguments
current
andnew
, with the current and the new result.def log_diff(current, new): print(f"The current value is {current} but the new one is {new})
-
Decorate your current function with
@change
, passing the new implementation that you want to test and any logging function you have defined. Currently you can define logging on two occasions ―on_diff
will be called if the results are different andon_call
will always be called@change(new=new_func, on_diff=log_diff) def current_function(): ...
The functions used for logging will receive two keyword arguments ― current
and new
. They will contain the return of both the current and the new functions.
change
can call custom functions on two situations:
-
on_call
― everytime the old function is called -
on_diff
― when there's a difference between the return of the current function and the new one.
from random import random
from feature_change import change
def log_call(current, new):
print("Call detected!")
def log_diff(current, new):
print(f"Difference detected. Current = {current}; new = {new}")
def new_sum(a, b):
"""
This function will be wrong on 50% of the calls
"""
if random() > 0.5:
return 0
return a + b
@change(new=new_sum, on_call=log_call, on_diff=log_diff)
def current_sum(a, b):
return a + b
for _ in range(10):
current_sum(1, 41)
This code will return something like
Call detected!
Difference detected. Current = 42; new = 0
Call detected!
Call detected!
Call detected!
Difference detected. Current = 42; new = 0
Call detected!
Call detected!
Call detected!
Call detected!
Difference detected. Current = 42; new = 0
Call detected!
Call detected!
Call detected!
Difference detected. Current = 42; new = 0
Call detected!
Call detected!
Call detected!
Call detected!
Call detected!
Difference detected. Current = 42; new = 0
Call detected!
Difference detected. Current = 42; new = 0
Call detected!
Difference detected. Current = 42; new = 0
Call detected!
Call detected!
Keep in mind that the result may be different in your machine because the new function fails randomly.
-
Both functions will be called ― be aware of side effects like database writes. If both functions write to a database, they will both be executed, leading to possible inconsistencies.
-
Be aware of slow logs ― the callables you define for
on_diff
andon_call
should not be very expensive. You could, by example, just count how many timeson_diff
was called using some fast db like redis. -
Equality is simple ― currently the results are checked using simply the
==
operator. Keep that in mind if your results are complex objects whose equality is not well defined. An optional argument for a custom equality checker will be add on the future.
Auth0's feature-change
is based on GitHub's scientist.
scientist
itself has a lot more features than this lib and it
also has implementations on different languages