Last active
August 29, 2015 14:05
-
-
Save SavinaRoja/6b9e65970dc0db522d59 to your computer and use it in GitHub Desktop.
Some experiments with decorators that smooth API style changes transition
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Theoretical exercises in patterns to make API style changes smoother | |
from functools import wraps | |
def deprecated_in_favor_of(new_name): | |
def wrap(f): | |
@wraps | |
def wrapped_f(*args, **kwargs): | |
if not wrapped_f._called: | |
print(f.__name__ + ' deprecated in favor of ' + new_name) | |
try: | |
f(*args, **kwargs) | |
finally: | |
wrapped_f._called = True | |
wrapped_f._called = False | |
return wrapped_f | |
return wrap | |
def replace_method_if_overridden(*methods): | |
def wrap(f): | |
def check_methods(self, *args, **kwargs): | |
""" | |
Subroutine for inspecting methods for override flag. | |
""" | |
all_methods = [self.__getattribute__(m) for m in methods] | |
for method in all_methods: | |
#Look for overridden flag value | |
if not hasattr(method, '_overridden'): | |
return method | |
return f | |
@wraps | |
def wrapped_f(*args, **kwargs): | |
if wrapped_f._func is None: | |
wrapped_f._func = check_methods(*args, **kwargs) | |
wrapped_f._func(*args, **kwargs) | |
wrapped_f._func = None | |
return wrapped_f | |
return wrap | |
def override_flag(f): | |
f._overridden = False # Value unimportant, only presence as attribute | |
return f | |
class Spam(object): | |
deprecation = deprecated_in_favor_of | |
replace = replace_method_if_overridden | |
def __init__(self): | |
pass | |
#This represents a simple alias-over change | |
@deprecation('do_foo') | |
def doFoo(self): | |
#I suppose I could technically build this alias functionality into the | |
#decorator. | |
self.do_foo() | |
def do_foo(self): | |
print('foo') | |
#In cases where users are expected to override methods called during run | |
#We would like a simple pattern to allow changes to either method to impact | |
#the way the interface works. Simply aliasing would not allow this. | |
@override_flag | |
def whileWaiting(self): | |
""" | |
The old API users expect to be able to change behavior using this method | |
""" | |
print('whileWaiting') | |
@override_flag | |
def while_waiting(self): | |
""" | |
The new API users expect to be able to change behavior using this method | |
""" | |
print('while_waiting') | |
@replace('while_waiting', 'whileWaiting') | |
def _while_waiting(self): | |
""" | |
This is the meta method for waiting action, the behavior of which may | |
be defined in either `while_waiting` or `whileWaiting`. | |
""" | |
print('unreplaced') | |
#The iterating function which calls whileWaiting/while_waiting | |
#This remains static; decorator handles the dynamic check | |
def waiting_iterator(self): | |
for _ in range(5): | |
self._while_waiting() | |
#Supposing that the old API had a class named OldSpam, we can alias it | |
OldSpam = Spam | |
#Let's test the use of both Old and New API sub-classing | |
class MyOldSpam(OldSpam): | |
def __init__(self): | |
super(MyOldSpam, self).__init__() | |
def whileWaiting(self): # No override decorator | |
print('I am old school') | |
class MyNewSpam(Spam): | |
def __init__(self): | |
super(MyNewSpam, self).__init__() | |
def while_waiting(self): # Again, no override decorator | |
print('Riding the new wave') | |
if __name__ == '__main__': | |
old = MyOldSpam() | |
new = MyNewSpam() | |
old.waiting_iterator() | |
new.waiting_iterator() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment