I have a decorator that is supposed to use a parameter that's passed in from the commandline e.g
@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"]
any suggestions on this?
CodePudding user response:
You can make a "meta-decorator", something like:
from functool import wraps
def metadeco(function):
@wraps(function)
def func(*args, **kwargs):
name = kwargs['name']
return deco(name)(function)(*args, **kwargs)
return func
and then work with that meta-decorator:
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']
# …
CodePudding user response:
You don't have access to the command-line value when the @deco
decorator is applied, no. But you can delay applying that decorator until you do have access.
Do so by creating your own decorator. A decorator is simply a function that applied when Python parses the @decorator
and def functionname
lines, right after Python created the function object; the return value of the decorator takes the place of the decorated function. What you need to make sure, then, is that your decorator returns a different function that can apply the deco
decorator when the command is being executed.
Here is such a decorator:
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
Then use that decorator on your command handler:
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"]
What happens here? When Python handles the @apply_deco_from_name
and def handle(...)
lines, it sees this as a complete function statement inside the class. It'll create a handle
function object, then passes that to the decorator, so it calls apply_deco_from_name(handle)
. The decorator defined above return wrapper
instead.
And when Django then executes the command handler, it will do so by calling that replacement with wrapper(command, [other arguments], name="command-line-value-for-name", [other options])
. At that point the code creates a new decorated version of the handler with decorated = deco("command-line-value-for-name")(f)
just like Python would have done had you used @deco("command-line-value-for-name")
in your command class. deco("command-line-value-for-name")
returns a decorator function, and deco("command-line-value-for-name")(f)
returns a wrapper, and you can call that wrapper at the end.
CodePudding user response:
Your decorator doesn't really need to be a decorator. Since you are using classes, you can make use of the mixin pattern:
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