Как передать обратные вызовы и их аргументы из упакованной функции в декоратор с Python 3.x?
Я пишу универсальную оболочку вокруг REST API. У меня есть несколько функций, подобных приведенной ниже, которые отвечают за получение пользователя с его адреса электронной почты. Интерес представляет то, как обрабатывается ответ на основе списка ожидаемых кодов состояния (помимо HTTP 200 ) и обратных вызовов, связанных с каждым ожидаемым кодом состояния:
import requests def get_user_from_email(email): response = requests.get('http://example.com/api/v1/users/email:%s' % email) # define callbacks def return_as_json(response): print('Found user with email [%s].' % email) return response.json() def user_with_email_does_not_exist(response): print('Could not find any user with email [%s]. Returning `None`.' % email), return None expected_status_codes_and_callbacks = < requests.codes.ok: return_as_json, # HTTP 200 == success 404: user_with_email_does_not_exist, >if response.status_code in expected_status_codes_and_callbacks: callback = expected_status_codes_and_callbacks[response.status_code] return callback(response) else: response.raise_for_status() john_doe = get_user_from_email('john.doe@company.com') print(john_doe is not None) # True unregistered_user = get_user_from_email('unregistered.user@company.com') print(unregistered_user is None) # True
Приведенный выше код работает хорошо, поэтому я хочу реорганизовать и обобщить часть обработки ответов. Я хотел бы получить следующий код:
@process_response() def get_user_from_email(email): # define callbacks def return_as_json(response): print('Found user with email [%s].' % email) return response.json() def user_with_email_does_not_exist(response): print('Could not find any user with email [%s]. Returning `None`.' % email), return None return requests.get('https://example.com/api/v1/users/email:%s' % email)
С process_response декоратором, определенным как:
import functools def process_response(extra_response_codes_and_callbacks=None): def actual_decorator(f): @functools.wraps(f) def wrapper(*args, **kwargs): response = f(*args, **kwargs) if response.status_code in expected_status_codes_and_callbacks: action_to_perform = expected_status_codes_and_callbacks[response.status_code] return action_to_perform(response) else: response.raise_for_status() # raise exception on unexpected status code return wrapper return actual_decorator
Моя проблема в том, что декоратор жалуется на отсутствие доступа к return_as_json и user_with_email_does_not_exist , потому что эти обратные вызовы определены внутри обернутой функции.
Если я решу переместить обратные вызовы за пределы упакованной функции, например на том же уровне, что и сам декоратор, то обратные вызовы не будут иметь доступа к переменным ответа и электронной почты внутри упакованной функции.
# does not work either, as response and email are not visible from the callbacks def return_as_json(response): print('Found user with email [%s].' % email) return response.json() def user_with_email_does_not_exist(response): print('Could not find any user with email [%s]. Returning `None`.' % email), return None @process_response() def get_user_from_email(email): return requests.get('https://example.com/api/v1/users/email:%s' % email)
Какой правильный подход здесь? Я нахожу синтаксис декоратора очень чистым, но не могу понять, как передать ему необходимые части (либо сами обратные вызовы, либо их входные аргументы, такие как response и email ).