My Problem:
I've created a series of nodes that each have a set of attribute objects associated with them. The attribute objects are initialized with descriptions and names for each attribute. I'd like these attributes, and their descriptions, to show up in my sphinx documentation without having to maintain the name/description in two places, once in the doc string of the class and once in the initialization of the attribute.
To illustrate the problem, consider the following code:
class Foo(object):
"""My doc string
"""
@classmethod
def default_attributes(cls):
return {'foo':'description of foo attribute',
'bar':'description of bar attribute'}
@classmethod
def attributes_string(cls):
attributes = cls.default_attributes()
result = '\nDefault Attributes:\n'
for key, value in attributes.iteritems():
result += '%s: %s\n' % (key, value)
return result
print Foo.__doc__
I'd like the result of Foo.attributes_string to show up in the doc string of Foo so that I get this:
My doc string
Default Attributes:
foo: description of foo attribute
bar: description of bar attribute
My Solution Attempts:
First I thought "Hey that's easy! I'll just setup a class decorator!":
def my_decorator(cls):
doc = getattr(cls, '__doc__', '')
doc += cls.attributes_string()
cls.__doc__ = doc
return cls
@my_decorator
class Foo(object):
"""My doc string
"""
This failed miserably with the following error:
AttributeError: attribute '__doc__' of 'type' objects is not writable
So then I thought "Well then I'll just use a metaclass to set __doc__ before the class is created!". When I went to implement this I immediately ran into a problem: How do you call a classmethod for a class that hasn't been created yet?
I circumvented that problem with a very hacky workaround that makes me cringe: I create the class twice, once without modifying it so I can call it's classmethod, and then again to create the class with the proper __doc__:
class Meta(type):
def __new__(meta_cls, name, bases, cls_dict):
tmpcls = super(Meta, meta_cls).__new__(meta_cls, name, bases, cls_dict)
doc = cls_dict.get('__doc__', '')
doc += tmpcls.attributes_string()
cls_dict['__doc__'] = doc
return super(Meta, meta_cls).__new__(meta_cls, name, bases, cls_dict)
class Foo(object):
"""My doc string
"""
__metaclass__ = Meta
This totally works and gives me the result I was looking for:
My doc string
Default Attributes:
foo: description of foo attribute
bar: description of bar attribute
However, isn't it terribly inefficient to create the class twice? Does it matter? Is there a better way? Is what I'm trying to do really dumb?