Skip to content

Instantly share code, notes, and snippets.

@ttsiodras
Last active August 21, 2017 09:48
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ttsiodras/88f0afc712b4bfc3fe16 to your computer and use it in GitHub Desktop.
Save ttsiodras/88f0afc712b4bfc3fe16 to your computer and use it in GitHub Desktop.
I just did something that depending on your viewpoint on coding, is either an insult to God and humanity, or absolutely brilliant.
#
# 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.
@explorigin
Copy link

This is both awesome and exactly why Python cannot get rid of the GIL.

@explorigin
Copy link

Correction (since github won't let me edit) It's awesome that you can do this, but how hard would it be to Search/Replace calls?

@toddrjen
Copy link

"is either an insult to God and humanity, or absolutely brilliant". Is "both" an acceptable answer?

@ttsiodras
Copy link
Author

@toddrjen: Yes, it most certainly is :-)

@ttsiodras
Copy link
Author

@explorigin:

It can't be done with search and replace - e.g.

def memberInPyramidServiceClass(self, ...):  # Here, "self.request" makes sense
    bar = ....
    SomeClass.somethingElse(bar)

...

@staticmethod
def somethingElse(bar):  # "self.request" doesn't make sense here - search and replace => BOOM
    common_function_called_in_gazillion_places(...)

The hack works in all the cases where eventually the call chain reaches a Pyramid service (a context where "self.request" exists).

@digitalresistor
Copy link

Pyramid does make self.request available as a threadlocal as well, which can be reached by using a single function:

http://docs.pylonsproject.org/projects/pyramid//en/latest/api/threadlocal.html#pyramid.threadlocal.get_current_request

@humbhenri
Copy link

if request is not None: can be replaced by if request: ?

@jhrmnn
Copy link

jhrmnn commented Jul 24, 2015

@humbhenri not if you want to treat separately the case bool(self.request) == False and self.request is not None.

@eloff
Copy link

eloff commented Jul 24, 2015

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.

@keturn
Copy link

keturn commented Jul 24, 2015

Yes, Python is a dynamically typed language, but PyCharm can usually help refactor anyway.

@cache-rules
Copy link

@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.

@seanjensengrey
Copy link

@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.

@ttsiodras
Copy link
Author

@bertjwregeer Thanks - good to know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment