June
June

Reputation: 543

Monkey patching a python class method from a library

I'm importing a library someone else made and I'm wanting to change the way a particular class method works from that library so I've copied the class method to my own file and wanting to replace it at runtime. This seems to work just fine for functions but seems to fall apart for class methods.

a.library.package.library_file.py

class LibraryClass(ParentClass):
    @classmethod
    def get_cost(cls, time):
        return int(time * cls.hourly)

I'm wanting to replace it with this

class LibraryClass(ParentClass):
    @classmethod
    def get_cost(cls, time):
        return 1234

I've tried to just do a normal replace which works just fine for regular functions

import a.library.package.library_file

...

a.library.package.library_file.LibraryClass.get_cost = get_cost

But it doesn't seem to work right at all, the method is called with the wrong arguments at the wrong time and results in a crash. After some research on Google, StackOverflow, and Python I began trying to use the mock classes.

from unittest.mock import patch

@patch.object('a.library.package.library_file.LibraryClass', 'get_cost')
def get_cost(cls, time):
    return 1234

The good news is it doesn't crash, bad news is it doesn't do anything, the old code is still there and it's like my code doesn't exist.

I've tried all kinds of other ways to do this such as

import a.library.package.library_file

@patch.object(a.library.package.library_file.LibraryClass, 'get_cost')
...

or

from a.library.package.library_file import LibraryClass

@patch.object(LibraryClass, 'get_cost')
...

but every time the method is never touched. It's like my code doesn't exist and the old code is used.

Upvotes: 11

Views: 6994

Answers (2)

June
June

Reputation: 543

It turns out the solution was simple as I thought it might be. It took more than a day of digging and there's just no help anywhere online but I found an obscure random post from china on page 2 of Google which finally answered my question. Link Here

This is the resulting code that works

from types import MethodType
from a.library.package.library_file.py import LibraryClass

def get_cost(cls, time):
    return 1234


LibraryClass.get_cost = MethodType(get_cost, LibraryClass)

It's the same as replacing a function only you need to wrap it in "MethodType" because the code your swapping out has "classmethod" ties to the class and so the new code needs to be tied the same in order to work.

Upvotes: 20

Mathieu Rollet
Mathieu Rollet

Reputation: 2364

Can you just import the original class and create your own class that inherits from the original class ? Then you redefine the method that you want to override and just use your custom class instead of the original class:

from a_library import LibraryClass

class YourClass(LibraryClass):
    @classmethod
    def get_cost(cls, time):
        # redefine the method here
        return 1234

cost = YourClass.get_cost(time)

Then just use YourClass instead of the library class. It will behave exactly like the library class, but with your method instead of the original method.

Upvotes: 1

Related Questions