Jaymon / captain

command line python scripts for humans
MIT License
13 stars 1 forks source link

"obj must be an instance or subtype of type" with custom Type classes #9

Closed Jaymon closed 9 years ago

Jaymon commented 9 years ago

With a setup like this:

class Directory(str):
    def __new__(cls, d):
        return super(Directory, cls).__new__(cls, d)

@arg('--dir', '-d', dest='indir', type=Directory)
def main(indir):
    pass

calling on the command line would fail with a TypeError saying "obj must be an instance or subtype of type"

This was a head scratcher because I couldn't duplicate it just in another script or when I called it from the main method, so this:

@arg('--dir', '-d', dest='indir')
def main(indir):
    indir = Directory(indir)

worked just fine. I even inspected the argparse code and couldn't see any problems. Turns out:

So then I started to add some print statements around the problem line of code, and sure enough, what I thought was impossible was in fact possible.

print PluginAAlias print self.class print isinstance(self, PluginAAlias)

Gave me:

class pluginAAlias.PluginAAlias class pluginAAlias.PluginAAlias False

Huh??!!! I then expanded the print statements to print the id() of the classes, and sure enough the id’s were different – so that was why isinstance was failing, and super() was raising the TypeError.

With a little more instrumenting, I found that our code for loading the plugin modules can get run repeatedly. And this was the final piece of the puzzle. Our plugin-loading code was not using import, but imp.load_module(). The docs for imp.load_module() tell us that repeated calls with the same module reference will act like a reload.

So then I was able to trace the problem to Captain's parsing loading the module multiple times, so I added a sentinal so imp.load_source is only called once and now everything works as expected.

I've added this issue so I have a historical record of what was done.