I am having trouble keeping up with pcbnew python API changes.
Some time ago the footprint flip method pcbnew.MODULE.Flip changed signature, but I was able to find a solution. The solution goes like this. If I find that I am running new python API, I remap current method to a new name and then I bind a mocked method to the old name. It’s a hack, but it works so my plugins are up till now 5.1.x and 5.99 compatible
if pcbnew.MODULE.Flip.__doc__ == "Flip(MODULE self, wxPoint aCentre, bool aFlipLeftRight)":
# remap the current method to new name
pcbnew.MODULE.Flip_new = pcbnew.MODULE.Flip
# create new method with the same signature as in V5.1.x versions
def old_flip(self, aCentre):
""" monkeypatched method to have the same signature as in V5.1.x versions """
pcbnew.MODULE.Flip_new(self, aCentre, False)
from types import MethodType
pcbnew.MODULE.Flip = MethodType(old_flip, pcbnew.MODULE)
But now I have a problem as pcbnew.MODULE.GetPath changed return type. Previously it returned a string, but now it returns a pcbnew.KIID object. This object has AsString method, which should work for me. My thinking is that sooner or later I’ll have to update my plugins for this, so I might as well do it now. I can go through the code and swap every call of pcbnew.MODULE.GetPath() with pcbnew.MODULE.GetPath().AsString() so the plugin should work with 5.99 branch.
But in order for same code to also work in 5.1.x branch I’d have to monkeypatch pcbnew so that pcbnew.MODULE.GetPath would return an object (does not matter what type) which would also have AsString method, where invoking this method would actually invoke original pcbnew.MODULE.GetPath. How do I do this?
Invoking magic spirits @HiGreg, @qu1ck to help me with the issue
Does the KIID have a __str__ method? If so, then calling that (or str(OBJECT)) on either KIID or string should result in a string.
Alternatively, create a new function kiid_or_string_tostring() that checks the type (be sure to be compatible with both Python2 and Python3, as windows is still on Python2 due to a wxPython issue.
Edit: clarified __str__ and fixed removal of underscores.
KIID has a __str__method, but I don’t seem to get anywhere with it. Output from pcbnew python console
import pcbnew
board = pcbnew.GetBoard()
mod = board.FindModuleByReference('U101')
path = mod.GetPath()
path.__str__()
"<pcbnew.KIID_PATH; proxy of <Swig Object of type 'KIID_PATH *' at 0x000000000bb8e2d0> >"
str(path)
"<pcbnew.KIID_PATH; proxy of <Swig Object of type 'KIID_PATH *' at 0x000000000bb8e2d0> >"
path.AsString()
u'/00000000-0000-0000-0000-00005e4b26bc'
Anyhow I don’t know if my intentions got through correctly.
With 5.99 I’ve got pcbnew.MODULE.GetPath().AsString() and I’m fine with it.
But I’d also like to run the same code on 5.1.x. In order to do this, I’d like to monkeypatch the pcbnew.MODULE.GetPath() method to return an object, which would have AsString() method. Then I would also be able to use the same code in 5.1.x. So when V6 comes out, I’d just get rid of the monkeypatched code.
The spirits said: thou shall not monkeypatch public api library!
Really, don’t touch global pcbnew namespace, that’s a sure way to make your plugin incompatible with others as well as potentially breaking the API itself.
Just do as HiGreg said: instead of forcing new api to look like old api try using old one and if that fails use new one. In some cases you can check for existence of method before hand but try/catch although ugly works every time.
Regarding KIID looks like __str__ is autogenerated and probably should be overriden in swig to call AsString() but that should be done in kicad not with monkeypatching.
The Pythonic way is try/except. From this question on Stack overflow: The official Python Documentation mentions EAFP: Easier to ask for forgiveness than permission and Rob Knight notes that catching errors rather than avoiding them, can result in cleaner, easier to read code.
Thanks for the etiquette lesson. I’ll try to avoid monkeypatching public api library. So for the pcbnew.MODULE.GetPath() in 5.99 I’ll raise an issue and see if the overload the SWIG so that it returns a string.
My only reasoning for this is that if I want to support both branches (5.1.x and 5.99) I’ll have shitload of if hasattr or try/except blocks on multiple places within the code. Currently this is really no-go for me. So monkeypatching seemed as an obvious DRY solution. I’ll think about how will I proceed, but I might drop the support for 5.99.
For anybody stumbling on it, this is doable as shown:
if not hasattr(pcbnew, 'KIID'):
# define new class which has AsString method
class UuidPath():
def __init__(self, parent):
self.parent = parent
def AsString(self):
return pcbnew.MODULE.GetPath_new(self.parent)
# bind it to pcbnew
pcbnew.MODULE.UuidPath = UuidPath
# remap the GetPath method as the original name will be overwritten
pcbnew.MODULE.GetPath_new = pcbnew.MODULE.GetPath
def old_path(self):
a = pcbnew.MODULE.UuidPath(self)
return a
pcbnew.MODULE.GetPath = old_path
But as qu1ck pointed out this is modifying python API (overriding pcbnew.MODULE.GetPath method).
Either I’ll have to find a way to patch the python API only when runing my code and there restoring the API back or I’ll have to find another solution. Otherwise I am just waiting for trouble to come looking for me.
You know when you start down the wrong path and you just keep going and going and you don’t even realize there is a better way nor do you think is there a better way. This is/was one of those times.
Thanks for pulling out from a labyrinth that I’ve built myself.