Интроспекция в Python
При создании своей history к Django возник вопрос: “А как же получать юзера при сохранении очередной ревизии?”. Первым делом это было решено при помощи грязных хаков - с помощью функции-хелпера во view к объекту формы добавлялись юзер, айпишник, а потом в форме руками приходилось передавать дополнительные параметры в save(), которые хватаются декоратором, ну а он уже выполняет всю работу. Очень некрасиво, много лишних телодвижений, и можно что-нибудь забыть. Хотелось бы переделать это дело на сигналы… Но в сигналах нет информации о том, кто же это выполняет действие - а значит, надо каким-то образом их получить. Интроспекция - вот выход из ситуации!
Внимательный осмотр бранча full-history (который работает на сигналах) из репозитория Django ни к чему не привёл - у них хоть и есть user = models.ForeignKey(User, default="1"), но он нигде не используется и не заполняется. Потом был осмотрен проект django-modelhistory, который фактически является улучшенным и дополненным бранчем full-history, и там был найден код, который, используя интроспекцию, возвращал юзера. Выглядит этот код как Smart Fortwo, попросту говоря - ужасно. Вот он:
def snoop_the_call_chain():
"""
Currently, a hackish (and a bit naive) way of walking up the call chain
to determine the user (if any) that initiated the change in the system.
"""
from django.contrib.auth.models import User
cur = inspect.currentframe()
desiredFrame = None
desiredFrameCount = 0
f = None
try:
ancestors = inspect.getouterframes(cur)
count = 0
for frame in ancestors:
if frame[3] == "save":
desiredFrameCount = count
count = count+1
# Ensure that we have a callee for the save methods
# which we're auditing.
if debug_mode: print "Desired Frame: (%d)\n" % (desiredFrameCount+1)
if desiredFrameCount >= len(ancestors):
if debug_mode: print "No callee in call chain for save method. Bailing ..."
return None
desiredFrame = ancestors[desiredFrameCount+1][0]
if inspect.isframe(desiredFrame):
for name,value in inspect.getmembers(desiredFrame):
if name == "f_locals":
dictionary = dict(value)
count = 1
if debug_mode: print "=========== Begin Frame Objects ==============="
for key,val in dictionary.items():
if debug_mode: print " (%d) %s: %s" % (count,key,val)
count = count + 1
if debug_mode: print "============ End Frame Objects ================\n"
if 'request' in dictionary.keys() and\
dictionary['request'].user:
if debug_mode: print "*** Found Calling User ***"
return dictionary['request'].user
finally:
del cur
Гммм… Не самый очевидный код, плюс в нём есть несколько багов. При помощи мануала по Python и дополнительного мозга код был понят. А потом написан заново, адекватный и рабочий:
def obtain_request():
"""
True hackish way of walking up the call chain to get the HttpRequest object
that initiated the change in the system.
"""
ancestors = inspect.getouterframes(inspect.currentframe())
for frame_record in ancestors:
frame = frame_record[0]
if 'request' in frame.f_locals:
req = frame.f_locals['request']
if isinstance(req, HttpRequest):
return req
Работает отлично. Какие есть замечания, предложения, пожелания, может вопросы?