-
-
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. |
Yes, Python is a dynamically typed language, but PyCharm can usually help refactor anyway.
@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!
This is both genius and evil. We do this with thread locals (in Django, but I imagine it would work for Pyramid.) Since every request must run on the same thread from start to finish, setting the request in a thread local with a request middleware, and clearing it on response or error middleware suffices. Then it's available anywhere it needs to be. It's still better to pass it as a function argument, but it's not always practical, and the puritan approach taken by Django that it's ALWAYS best to pass it through function arguments is sometimes very obviously the wrong thing to do. It's almost impossible to come up with a simple rule that can be applied in all scenarios, which is why a puritan mindset is overly simplistic and limited. Which is just as true in life as it is in programming.