-
-
Save ttsiodras/88f0afc712b4bfc3fe16 to your computer and use it in GitHub Desktop.
# | |
# I inherited a large code base, where hundreds of code paths end up | |
# calling "common_function_called_in_gazillion_places". | |
# | |
# And the need arose for this function to access the HTTP request's | |
# headers... | |
# | |
# What to do? Refactor all the places leading up to here? In a dynamically | |
# typed language, with no compiler to tell us the places to refactor? | |
# | |
# NO - let's hack the universe instead. | |
def get_the_request(): | |
""" | |
Look up the call stack to see if one of our callers has "self.request" | |
(i.e. the Pyramid request) and if so, return it | |
""" | |
for f in inspect.stack(): | |
if 'self' in f[0].f_locals: | |
self = f[0].f_locals['self'] | |
if hasattr(self, 'request'): | |
return self.request | |
else: | |
return None | |
def common_function_called_in_gazillion_places( variables, action ): | |
... | |
# Get the request from our callers, and extract the IP from it | |
request = get_the_request() | |
if request is not None: | |
ip_addr = request.remote_addr | |
if 'X-Forwarded-For' in request.headers: | |
ip_addr = request.headers['X-Forwarded-For'] | |
if ip_addr not in ['127.0.0.1', 'localhost'] and \ | |
isinstance(ip_addr, (str,unicode)): | |
event_data['context'] = {'ip': ip_addr} | |
.... | |
# TADA!!! | |
# | |
# OK, now I can burn in programmer Hell - for all eternity. |
@ttsiodras you could probably implement this method as the author has, but add some logging to it, then attempt a find/replace, then periodically check your logs. You should be able to find any calls you missed and replace them over time while retaining some form of backwards compatibility.
@ttsiodras I had to do something similar in 2009 for a trac extension, for the same exact reason, so definitely absolutely brilliant, ;) . https://gist.github.com/seanjensengrey/84beab9d6f907c0dc433
Frame hacks are what make Python, Python. See http://farmdev.com/src/secrets/framehack/
Another option would be to put in a fixme into function_called_everywhere
that logs all the invocation points.
def fixme(msg):
print >>sys.stderr,"fixme:" + sys._getframe().f_code.co_filename + ":" + \
sys._getframe().f_back.f_code.co_name + ":" + \
str(sys._getframe().f_back.f_lineno) + ":" + msg
Effectively what you have done is enable dynamic scope for a lexically scoped language. One could also think of dynamic scope as a form of execution context or lightweight dependency injection.
@bertjwregeer Thanks - good to know!
Yes, Python is a dynamically typed language, but PyCharm can usually help refactor anyway.