1

Sorry for my english. I want to create a decorator method that can check each step methods and write it db.

This is my method:

class Test:

    @StepStatusManager.logger_steps("GET_LIST") # TypeError: logger_steps() missing 1 required positional argument: 'type'
    def get_mails(self):
       print("GET_MAIL")    

This is my decorator class:

class StepStatusManager:

    def __init__(self):
        self.db = DB()

    def logger_steps(self, type):
        def logger_steps(func):
            @functools.wraps(func)
            def wrapper(*args):
                try:
                    func(*args)
                    self.db.setStatus(type)
                except BaseException as e:
                    print(e)

            return wrapper

        return logger_steps
blasrodri
  • 448
  • 5
  • 16
nesalexy
  • 848
  • 2
  • 9
  • 30

1 Answers1

5

You are trying to call the instance method, logger_steps, directly from the class StepStatusManager, and Python is taking the value "GET_LIST" as the self parameter instead of type. You should create an instance of StepStatusManager and then make the decorator calling the method of the instance instead. It can be as simple as:

manager = StepStatusManager()

class Test:
    @manager.logger_steps("GET_LIST")
    def get_mails(self):
       print("GET_MAIL")

This is now creating an instance of the class and then calling the method on the instance, instead of trying to call the method directly from the class. You can now use manager to decorate as many methods as you want. Also, this would make all decorated methods use the same StepStatusManager, but if you want you can create different instances and use them to decorate different methods; that would allow you to use different self.db for different methods, if you need it.

Another approach could be having the db variable in the class, and make logger_steps a class method instead:

class StepStatusManager:

    db = DB()

    @classmethod
    def logger_steps(cls, type):
        def logger_steps(func):
            @functools.wraps(func)
            def wrapper(*args):
                try:
                    func(*args)
                    cls.db.setStatus(type)
                except BaseException as e:
                    print(e)

            return wrapper

        return logger_steps

class Test:
    @StepStatusManager.logger_steps("GET_LIST")
    def get_mails(self):
       print("GET_MAIL")

Note however that this is less flexible, in that it will not allow you to have methods decorated with different managers, should you ever need to. Also, this is mostly equivalent to have, instead of a class, a StepStatusManager module, where db is a module variable and logger_steps is a module function, and that would probably clearer if you want this functionality:

# StepStatusManager.py

# ...

db = DB()

def logger_steps(type):
    def logger_steps(func):
        @functools.wraps(func)
        def wrapper(*args):
            try:
                func(*args)
                cls.db.setStatus(type)
            except BaseException as e:
                print(e)

        return wrapper

    return logger_steps

# test.py

import StepStatusManager

class Test:
    @StepStatusManager.logger_steps("GET_LIST")
    def get_mails(self):
       print("GET_MAIL")

Again this is maybe more straightforward but less flexible as your first proposed class-based solution.


EDIT:

Just for completeness and comparison, here is yet another version, similar to the one with @classmethod, but using @staticmethod instead (to understand the subtle difference between these two decorators, check one of the many SO questions about it, e.g. What is the difference between @staticmethod and @classmethod? or Meaning of @classmethod and @staticmethod for beginner?):

class StepStatusManager:

    db = DB()

    @staticmethod
    def logger_steps(type):
        def logger_steps(func):
            @functools.wraps(func)
            def wrapper(*args):
                try:
                    func(*args)
                    StepStatusManager.db.setStatus(type)
                except BaseException as e:
                    print(e)

            return wrapper

        return logger_steps

class Test:
    @StepStatusManager.logger_steps("GET_LIST")
    def get_mails(self):
       print("GET_MAIL")

As it frequently happens with @classmethod and @staticmethod, the difference is quite minimal. Their behavior might differ if you are using inheritance, or if you are using a metaclass, or a decorator, or something like that, but otherwise they pretty much the same.

jdehesa
  • 58,456
  • 7
  • 77
  • 121
  • 1
    @jdehesa: I was just wondering if we make `def logger_steps(self, type):` a `@staticmethod`, would the OP's code work? We then don't need to pass an implicit first argument – Sheldore Oct 10 '18 at 11:22
  • 1
    @PM2Ring You are right, thanks. I would have sworn I used that syntax at some point but yes, apparently it is not allowed. – jdehesa Oct 10 '18 at 11:30
  • 1
    @Bazingaa It would work if you make `db` a variable of the class and then use `StepStatusManager.db` instead of `self.db` in the decorator, similarly to the case for `@classmethod` – jdehesa Oct 10 '18 at 11:32
  • 1
    Is it me asking too much of you if you could also provide this alternative using `@staticmethod` in your answer? It would be very helpful for beginners in decorators like me. Your answer would serve a very nice case then for the use of `staticmethod` and `classmethod` – Sheldore Oct 10 '18 at 11:35
  • @Bazingaa Yes, no problem, I added that too. – jdehesa Oct 10 '18 at 11:43