Need python class decorator of parent to run when class is inherited
Question: I'm working with an example from py.test(using xdist) that uses the unittest
library.

I have a class:

    
    
    def on_platforms(platforms):
        def decorator(base_class):
            module = sys.modules[base_class.__module__].__dict__
            for i, platform in enumerate(platforms):
                d = dict(base_class.__dict__)
                d['desired_capabilities'] = platform
                name = "%s_%s" % (base_class.__name__, i + 1)
            module[name] = new.classobj(name, (base_class,), d)
    return decorator
    
    @on_platforms(browsers)
    class BaseTest:
        def setUp(self):
            pass
    

This contains common code that I need to run in all the test classes I will
create. In the files that contain actual tests to run I have:

    
    
    from BaseTest.py import BaseTest
    class FirstTest(BaseTest):
        def test_to_run(self):
            pass
    

Then I have a single main file which will import all the test classes and run
them all when the main.py file is executed.

main.py: import unittest from FirstTest.py import FirstTest

    
    
    if __name__ == "__main__":
        unittest.main()
    

Finally, I attempt to execute with:

    
    
    py.test main.py -n3 --boxed
    

But what happens is it executes main.py succesfully but no tests are run. Why
aren't the test being run? Thanks.


Answer: I think you want to use a metaclass, rather than a decorator to create the
extra versions of your `BaseClass`. The metaclass will be called to create any
subclasses that are created by later code (while the decorator will not). This
means you'll get copies of the subclasses automatically.

    
    
    import sys
    
    browsers=['x','y','z']
    
    class Meta(type):
        def __new__(meta, name, bases, dct):
            cls = type.__new__(meta, name, bases, dct)
            mod_dct = sys.modules[cls.__module__].__dict__
            if 'desired_capabilities' not in dct:
                for i, platform in enumerate(cls.platforms):
                    sub_name = "%s_%s" % (name, i + 1)
                    sub_cls = meta(sub_name, (cls,), {'desired_capabilities': platform})
                    mod_dct[sub_name] = sub_cls
            return cls
    
    class BaseTest(metaclass=Meta):  # metaclass code will create BaseTest_1, BaseTest_2, ...
        platforms = browsers
        def setUp(self):
            pass
    
    class SubTest(BaseTest):  # metaclass code will create SubTest_1, SubTest_2, ...
        pass
    

This uses Python 3 syntax for the metaclass. If you're using Python 2, inherit
from `object` and use the `__metaclass__` class variable instead:

    
    
    class BaseTest(object):
        __metaclass__ = Meta
        # ...
    

The `platforms` class attribute will be inherited by subclasses of `BaseTest`
(though they can override it with their own list) and the metaclass will
create the extra subclasses for them. The `if 'desired_capabilities' not in
dct` test is needed to make sure the platform-specific subclasses the
metaclass creates for you do not recursively get their own subclasses.

