Можно ли в Django передать декоратору аргументы командной строки?
У меня есть декоратор, который должен использовать параметр, передаваемый из командной строки, например
@deco(name)
def handle(self, *_args, **options):
name = options["name"]
def deco(name):
// The name should come from commandline
pass
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"--name",
type=str,
required=True,
)
@deco(//How can I pass the name here?)
def handle(self, *_args, **options):
name = options["name"]
есть предложения по этому поводу?
Вы можете сделать "мета-декоратор", что-то вроде:
from functool import wraps
def metadeco(function):
@wraps(function)
def func(*args, **kwargs):
name = kwargs['name']
return deco(name)(function)(*args, **kwargs)
return func
и затем работать с этим мета-декоратором:
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"--name",
type=str,
required=True,
)
@metadeco
def handle(self, *_args, **options):
name = options['name']
# …
У вас нет доступа к значению командной строки, когда применяется декоратор @deco
, нет. Но вы можете отложить применение этого декоратора до тех пор, пока у вас не будет доступа.
Сделайте это, создав свой собственный декоратор. Декоратор - это просто функция, которая применяется, когда Python разбирает строки @decorator
и def functionname
, сразу после того, как Python создал объект функции; возвращаемое значение декоратора занимает место декорированной функции. Поэтому вам нужно убедиться, что ваш декоратор возвращает другую функцию, которая может применить декоратор deco
во время выполнения команды .
Вот такой декоратор:
from functools import wraps
def apply_deco_from_name(f):
@wraps(f)
def wrapper(self, *args, **options):
# this code is called instead of the decorated method
# and *now* we have access to the options mapping.
name = options["name"] # or use options.pop("name") to remove it
decorated = deco(name)(f) # the same thing as @deco(name) for the function
return decorated(self, *args, **options)
return wrapper
Затем используйте этот декоратор в обработчике команд:
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"--name",
type=str,
required=True,
)
@apply_deco_from_name
def handle(self, *_args, **options):
name = options["name"]
Что здесь происходит? Когда Python обрабатывает строки @apply_deco_from_name
и def handle(...)
, он воспринимает это как полный оператор функции внутри класса. Он создаст объект функции handle
, затем передаст его декоратору, чтобы тот вызвал apply_deco_from_name(handle)
. Определенный выше декоратор возвращает wrapper
вместо .
Когда Django затем выполнит обработчик команды, он сделает это, вызвав эту замену с помощью wrapper(command, [other arguments], name="command-line-value-for-name", [other options])
. В этот момент код создает новую декорированную версию обработчика с помощью decorated = deco("command-line-value-for-name")(f)
так же, как это сделал бы Python, если бы вы использовали @deco("command-line-value-for-name")
в своем классе команд. deco("command-line-value-for-name")
возвращает функцию-декоратор, а deco("command-line-value-for-name")(f)
возвращает обертку, и вы можете вызвать эту обертку в конце.
Ваш декоратор на самом деле не обязательно должен быть декоратором. Поскольку вы используете классы, вы можете воспользоваться паттерном mixin:
class YourMixin:
def handle(self, name):
# Code that was previously in deco
class Command(YourMixin, BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"--name",
type=str,
required=True,
)
def handle(self, *_args, **options):
# Code before calling YourMixin.handle
name = options["name"]
super().handle(name)
# Code after calling YourMixin.handle