1

In pymodbus library in server.sync, SocketServer.BaseRequestHandler is used, and defines as follow:

class ModbusBaseRequestHandler(socketserver.BaseRequestHandler):
""" Implements the modbus server protocol
This uses the socketserver.BaseRequestHandler to implement
the client handler.
"""
running = False
framer = None

def setup(self):
    """ Callback for when a client connects
    """
    _logger.debug("Client Connected [%s:%s]" % self.client_address)
    self.running = True
    self.framer = self.server.framer(self.server.decoder, client=None)
    self.server.threads.append(self)

def finish(self):
    """ Callback for when a client disconnects
    """
    _logger.debug("Client Disconnected [%s:%s]" % self.client_address)
    self.server.threads.remove(self)

def execute(self, request):
    """ The callback to call with the resulting message
    :param request: The decoded request message
    """
    try:
        context = self.server.context[request.unit_id]
        response = request.execute(context)
    except NoSuchSlaveException as ex:
        _logger.debug("requested slave does not exist: %s" % request.unit_id )
        if self.server.ignore_missing_slaves:
            return  # the client will simply timeout waiting for a response
        response = request.doException(merror.GatewayNoResponse)
    except Exception as ex:
        _logger.debug("Datastore unable to fulfill request: %s; %s", ex, traceback.format_exc() )
        response = request.doException(merror.SlaveFailure)
    response.transaction_id = request.transaction_id
    response.unit_id = request.unit_id
    self.send(response)

# ----------------------------------------------------------------------- #
# Base class implementations
# ----------------------------------------------------------------------- #
def handle(self):
    """ Callback when we receive any data
    """
    raise NotImplementedException("Method not implemented by derived class")

def send(self, message):
    """ Send a request (string) to the network
    :param message: The unencoded modbus response
    """

raise NotImplementedException("Method not implemented by derived class")

setup() is called when a client is connected to the server, and finish() is called when a client is disconnected. I want to manipulate these methods (setup() and finish()) in another class in another file which use the library (pymodbus) and add some code to setup and finish functions. I do not intend to modify the library, since it may cause strange behavior in specific situation.

---Edited ---- To clarify, I want setup function in ModbusBaseRequestHandler class to work as before and remain untouched, but add sth else to it, but this modification should be done in my code not in the library.

  • Can you just subclass this class, and use your subclass in place of the base class? Or do you need to monkeypatch it on the fly for some reason? (And if so, why?) – abarnert Jun 18 '18 at 06:11
  • To clarify, I want setup function in ModbusBaseRequestHandler class to work as before and remain untouched, but add sth else to it, but this modification should be done in my code not in the library. –  Jun 18 '18 at 08:05

2 Answers2

0

The simplest, and usually best, thing to do is to not manipulate the methods of ModbusBaseRequestHandler, but instead inherit from it and override those methods in your subclass, then just use the subclass wherever you would have used the base class:

class SoupedUpModbusBaseRequestHandler(ModbusBaseRequestHandler):
    def setup(self):
        # do different stuff
        # call super().setup() if you want
        # or call socketserver.BaseRequestHandler.setup() to skip over it
        # or call neither

Notice that a class statement is just a normal statement, and can go anywhere any other statement can, even in the middle of a method. So, even if you need to dynamically create the subclass because you won't know what you want setup to do until runtime, that's not a problem.


If you actually need to monkeypatch the class, that isn't very hard—although it is easy to screw things up if you aren't careful.

def setup(self):
    # do different stuff
ModbusBaseRequestHandler.setup = setup

If you want to be able to call the normal implementation, you have to stash it somewhere:

_setup = ModbusBaseRequestHandler.setup
def setup(self):
    # do different stuff
    # call _setup whenever you want
ModbusBaseRequestHandler.setup = setup

If you want to make sure you copy over the name, docstring, etc., you can use `wraps:

@functools.wraps(ModbusBaseRequestHandler.setup)
def setup(self):
    # do different stuff
ModbusBaseRequestHandler.setup = setup

Again, you can do this anywhere in your code, even in the middle of a method.


If you need to monkeypatch one instance of ModbusBaseRequestHandler while leaving any other instances untouched, you can even do that. You just have to manually bind the method:

def setup(self):
    # do different stuff
myModbusBaseRequestHandler.setup = setup.__get__(myModbusBaseRequestHandler)

If you want to call the original method, or wraps it, or do this in the middle of some other method, etc., it's otherwise basically the same as the last version.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Thank you @abamert. I followed your advice, but the problem is when I applied the methods you suggested, the setup() function in ModbusBaseRequestHandler is ignored and the setup function I defines runs. I do not want it. I need both sides run, I mean setup function in ModbusBaseRequestHandler runs and do what I add in my code as well. –  Jun 19 '18 at 00:59
  • @MehdiBm Did you see the comments about things like `# call super().setup() if you want`? That's exactly how you get the base class code to run. You have to do that explicitly. The reason Python does it this way is that it allows you to choose whether you want to call the base method at the start of the method, or at the end, or in the middle, or not at all. – abarnert Jun 19 '18 at 01:09
  • When I use the first method in which I made a class and inside it setup() function and call super().setup() and then made an instance of class includes _setup()_, I got the error _TypeError: __init__() takes exactly 4 arguments (1 given)_. –  Jun 19 '18 at 02:59
0

It can be done by Interceptor

from functools import wraps 

def iterceptor(func):
    print('this is executed at function definition time (def my_func)')

@wraps(func)
def wrapper(*args, **kwargs):
    print('this is executed before function call')
    result = func(*args, **kwargs)
    print('this is executed after function call')
    return result

return wrapper


@iterceptor
def my_func(n):
   print('this is my_func')
   print('n =', n)


my_func(4)

more explanation can be found here