Search code examples
pythontwistedintrospectionbuildbot

In the context of a buildbot master.cfg file, how do I instantiate a class based on a string using native modules


In stock python, I can do the following to instantiate a class based on a string containing its name:

#!/usr/bin/env python2.7
import sys
class Foo(object): pass
cls = getattr(sys.modules[__name__], 'Foo')
instance = cls()
print repr(instance)

Which outputs the following:

ahammond@af6119›~⁑ ./test.py
<__main__.Foo object at 0x1095b0a10>

I'd like to do something similar inside a buildbot master.cfg file, however the following (simplified)

class BaseBuild(object): pass

class BuildStashToSrpmsToRpmsToDepot(BaseBuild):

    def init(name, build_type):
        self.name = name

    def setup():
        pass  # actually does stuff here, but...

for build_name, build_options in config['builds'].iteritems():
    cls = getattr(sys.modules[__name__], build_options['build_type'])
    build = cls(name=build_name, **build_options)
    build.setup()

Produces

2015-03-11 18:39:24-0700 [-] error while parsing config file:
    Traceback (most recent call last):
      File "/opt/buildbot_virtualenv/lib/python2.7/site-packages/twisted/internet/defer.py", line 577, in _runCallbacks
        current.result = callback(current.result, *args, **kw)
      File "/opt/buildbot_virtualenv/lib/python2.7/site-    packages/twisted/internet/defer.py", line 1155, in gotResult
        _inlineCallbacks(r, g, deferred)
      File "/opt/buildbot_virtualenv/lib/python2.7/site-packages/twisted/internet/defer.py", line 1099, in _inlineCallbacks
        result = g.send(result)
      File "/opt/buildbot_git/master/buildbot/master.py", line 189, in startService
        self.configFileName)
    --- <exception caught here> ---
      File "/opt/buildbot_git/master/buildbot/config.py", line 156, in loadConfig
        exec f in localDict
      File "/home/buildbot/master.cfg", line 208, in <module>
        cls = getattr(sys.modules[__name__], build_options['build_type'])
    exceptions.AttributeError: 'module' object has no attribute 'BuildStashToSrpmsToRpmsToDepot'

2015-03-11 18:39:24-0700 [-] Configuration Errors:
2015-03-11 18:39:24-0700 [-]   error while parsing config file: 'module' object has no attribute 'BuildStashToSrpmsToRpmsToDepot' (traceback in logfile)

Phrased another way, I guess what I'm really asking is what is the temporary module used while loading a new master.cfg and how can I reference it?

I'm currently using a dictionary mapping of { 'class name': class_object } but I'd prefer something a little more native.


Solution

  • Ok, so the problem is here:

    cls = getattr(sys.modules[__name__], build_options['build_type'])
    

    This does not work because exec makes it so that __name__ has the value "__builtin__". However, you can use globals() to get the current globals:

    cls = globals()[build_options['build_type']]
    

    For instance, if I add the following code into a brand new master.cfg file (the one automatically created by buildbot create-master master, renamed from master.cfg.sample):

    # Load the configuration from somewhere.
    import json
    config = json.load(open("./config.json"))
    
    class BaseBuild(object):
        pass
    
    class Something(BaseBuild):
    
        def __init__(self, name, build_type):
            self.name = name
    
        def setup(self):
            print self.name, "something setup called"
    
    class SomethingElse(BaseBuild):
    
        def __init__(self, name, build_type):
            self.name = name
    
        def setup(self):
            print self.name, "something else setup called"
    
    for build_name, build_options in config['builds'].iteritems():
        cls = globals()[build_options['build_type']]
        build = cls(name=build_name, **build_options)
        build.setup()
    

    And I create the following config.json file in the same directory as master.cfg:

    {
        "builds": {
            "one": {
                "build_type": "Something"
            },
            "two": {
                "build_type": "SomethingElse"
            }
        }
    }
    

    Then when I run buildbot start master, I'll get these lines in the log:

    2015-03-13 12:11:05-0400 [-] two something else setup called
    2015-03-13 12:11:05-0400 [-] one something setup called