Можно ли в 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
Вернуться на верх