Как применить одну и ту же цепочку декораторов к нескольким функциям
@extend_schema(
methods=['GET'],
responses={(200, STYLES_MIME_TYPE): OpenApiTypes.BINARY})
@extend_schema(
methods=['PUT'],
request={STYLES_MIME_TYPE: OpenApiTypes.BINARY},
responses={(204, 'application/json'): OpenApiResponse(
response={'type': 'array', 'items': {'type': 'integer', 'format': 'int32'}},
examples=[OpenApiExample(
'Returned style IDs example',
status_codes=['204'],
value=[101, 102, 103])])})
@api_view(['GET', 'PUT'])
@permission_classes([IsAuthenticated|ReadOnly])
@renderer_classes([StylesRenderer, StylesJSONRenderer])
@parser_classes([StylesParser])
def styles(request: Request, pid: int) -> Response:
"""
Get or save styles for a project.
GET - protobuf binary response
POST - returnd IDs for saved styles
"""
try:
project = Project.objects.get(pk=pid)
return _handle_styles(request, project)
except Project.DoesNotExist:
raise Http404()
@extend_schema(
methods=['GET'],
responses={(200, STYLES_MIME_TYPE): OpenApiTypes.BINARY})
@extend_schema(
methods=['PUT'],
request={STYLES_MIME_TYPE: OpenApiTypes.BINARY},
responses={(204, 'application/json'): OpenApiResponse(
response={'type': 'array', 'items': {'type': 'integer', 'format': 'int32'}},
examples=[OpenApiExample(
'Returned style IDs example',
status_codes=['204'],
value=[101, 102, 103])])})
@api_view(['GET', 'PUT'])
@permission_classes([IsAuthenticated|ReadOnly])
@renderer_classes([StylesRenderer, StylesJSONRenderer])
@parser_classes([StylesParser])
def styles_xref(request: Request, xref: uuid.UUID) -> Response:
"""
Get or save styles for a project.
GET - protobuf binary response
POST - returnd IDs for saved styles
"""
try:
project = Project.objects.get(xref=xref)
return _handle_styles(request, project)
except Project.DoesNotExist:
raise Http404()
Это Django, и очевидно, что я хочу использовать одни и те же декораторы для этих двух представлений. Единственная разница в том, что один ищет объект по int ID, а другой по UUID xref полю. Как я могу сохранить это DRY?
Модифицированный пример из GeeksForGeeks
Декораторы, по сути, являются обычными функциями. Таким образом, вы можете использовать метод partial для предоставления им набора аргументов без фактического вызова метода.
from functools import partial
def decorator(like):
print("Inside decorator")
def inner(func):
print(like)
func()
return inner
premade_decorator = partial(decorator, like = "geeksforgeeks")
@premade_decorator()
def my_func():
print("Inside actual function")
Теперь мы знаем, как предварительно назначить параметры декоратору, мы можем попробовать соединить их в цепочку:
def chain_decorator(dec_list):
def inner(func):
for dec in dec_list:
func = partial(dec(), func)
return func
return inner
@chain_decorator([premade_decorator1, premade_decorator2])
def my_func():
print("Inside actual function")
return 1
print(my_func())
Вы можете определить новый декоратор, который возвращает предварительно декорированную функцию с нужной вам цепочкой. Например, мы можем сначала определить три пользовательских декоратора:
# A decorator factory which returns a new decorator.
def decorator_factory(message):
def decorator(function):
# Wraps the decorated function.
def wrapper(*args, **kwargs):
# Example behavior:
# - Prints a message before calling the decorated function.
print(message)
# Calls the decorated function.
return function(*args, **kwargs)
return wrapper
return decorator
# Defines three new decorators.
decorator_1 = decorator_factory("Ham")
decorator_2 = decorator_factory("Spam")
decorator_3 = decorator_factory("Eggs")
В настоящее время способ вызова этих декораторов напоминает следующий, который быстро становится повторяющимся для множества функций:
@decorator_1
@decorator_2
@decorator_3
def f():
pass # Do something.
@decorator_1
@decorator_2
@decorator_3
def g():
pass # Do something.
@decorator_1
@decorator_2
@decorator_3
def h():
pass # Do something.
Однако, вы можете украсить функцию-обертку в теле декоратора:
def decorator_chain(function):
@decorator_1
@decorator_2
@decorator_3
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
Что упрощает определения функций до:
@decorator_chain
def f():
pass # Do something.
@decorator_chain
def g():
pass # Do something.
@decorator_chain
def h():
pass # Do something.
В приведенном вами примере это может выглядеть примерно так:
def decorator_chain(function):
@extend_schema(
methods = ['GET'],
responses = {(200, STYLES_MIME_TYPE): OpenApiTypes.BINARY}
)
@extend_schema(
methods = ['PUT'],
request = {STYLES_MIME_TYPE: OpenApiTypes.BINARY},
responses = {
(204, 'application/json'): OpenApiResponse(
response = {'type': 'array', 'items': {'type': 'integer', 'format': 'int32'}},
examples = [
OpenApiExample(
'Returned style IDs example',
status_codes = ['204'],
value = [101, 102, 103]
)
]
)
}
)
@api_view(['GET', 'PUT'])
@permission_classes([IsAuthenticated | ReadOnly])
@renderer_classes([StylesRenderer, StylesJSONRenderer])
@parser_classes([StylesParser])
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
@decorator_chain
def styles(request: Request, pid: int) -> Response:
"""
Get or save styles for a project.
GET - protobuf binary response
POST - returnd IDs for saved styles
"""
try:
project = Project.objects.get(pk=pid)
return _handle_styles(request, project)
except Project.DoesNotExist:
raise Http404()
@decorator_chain
def styles_xref(request: Request, xref: uuid.UUID) -> Response:
"""
Get or save styles for a project.
GET - protobuf binary response
POST - returnd IDs for saved styles
"""
try:
project = Project.objects.get(xref=xref)
return _handle_styles(request, project)
except Project.DoesNotExist:
raise Http404()
Использование фабрики декораторов может даже позволить вам быстро создавать различные варианты заданной цепочки декораторов.