0

I have a few methods that require a certain condition to be applied to function properly. To avoid code repetition I've created a decorator to check if this condition is applied and only then execute the method. Here is the code:

class ClientHandler:
    def __init__(self):
        self.__is_dead = False
        self.c_ident = c_ident

    def __s_operation(func):
        @functools.wraps(func)
        def check(*args):
            self = args[0]
            if not self.__is_dead:
                func(*args)
            else:
                logger.error(
                    "couldn't proceed - client {} is dead!".format(
                        self.c_ident
                    )
                )
        return check

    @__s_operation
    def __receive_m(self, size) -> bytes:
        ...

    @__s_operation
    def close_c(self, message: str) -> None:
        ...

I was following this answer to be able to use class attributes and call class methods from my decorator, but I ran into 2 errors:

  1. 'ClientHandler' object is not callable on the func(*args) line, when I'm trying to call a class method from a decorator.
  2. function '__s_operation' lacks a positional argument, when I'm trying to add my decorator @__s_operation over a class method.

I don't understand what is the problem and how to solve it, because in the other answer on stack overflow everything seems working. Also, I'm not sure if I will be able to reach my private attributes and private methods from this decorator. I would be grateful for your help. Thank you.

2 Answers 2

3

@Yzkodot's solution is good.

But I miss a @staticmethod in his example - to make explicitly clear that no self in the first argument position is needed.

import functools
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class ClientHandler:
    # Make this a staticmethod so it doesn't expect self
    @staticmethod
    def _s_operation(func):
        @functools.wraps(func)
        def wrapper(self, *args, **kwargs):
            if not self._is_dead:
                return func(self, *args, **kwargs)
            else:
                logger.error(f"Couldn't proceed - client {self.c_ident} is dead!")
                return None
        return wrapper

    def __init__(self, c_ident: str):
        self._is_dead = False
        self.c_ident = c_ident

    @_s_operation
    def receive_message(self, size: int) -> bytes:
        logger.info(f"{self.c_ident} received {size} bytes.")
        return b"x" * size

    @_s_operation
    def close_connection(self, message: str) -> None:
        logger.info(f"{self.c_ident} closed with message: {message}")
        self._is_dead = True

Use it as:

client = ClientHandler("alpha")
client.receive_message(10)      ## => Will work
client.close_connection("done") ## => Will close
client.receive_message(5)       ## => Blocked by the decorator

Output:

INFO:__main__:alpha received 10 bytes.
INFO:__main__:alpha closed with message: done
ERROR:__main__:Couldn't proceed - client alpha is dead!

Sign up to request clarification or add additional context in comments.

1 Comment

yeah I was thinking about that as well!
2

I think your problem lies on calling @functools.wraps(func) over the check() function. This one is already supposed to be the wrapper.

I slightly modified your code as following (only for testing) and it seems to work:

class ClientHandler:
    def __init__(self):
        self.__is_dead = False

    def __s_operation(func):
        def check(*args):
            print("The decorator code has worked!")

            self = args[0]
            if not self.__is_dead:
                func(*args)
            else:
                logging.error(
                    "couldn't proceed - client {} is dead!".format(
                        self.c_ident
                    )
                )
        return check

    @__s_operation
    def receive_m(self) -> bytes:
        print("message received")



if __name__ == "__main__":
    ch = ClientHandler()
    ch.receive_m()

Over the terminal I get correctly both print statements.

1 Comment

I thought I could use functools.wraps to not lose information about original function. As explained here: stackoverflow.com/a/309000/19329071

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.