from collections import namedtuple Param = namedtuple("Param", "name default doc") ReqParam = namedtuple("ReqParam", "name doc") Getter = namedtuple("Getter", "name initial doc") PROPERTY_TEMPLATE = """\ @property def {name}(self): "{doc}" if not hasattr(self, "_{name}"): self._{name} = {initial} return self._{name}""" def dataclass(typename, doc, params, properties, verbose=False): "Return a new class around the params and properties." namespace = {} # handle class docstring if not doc: doc = "" docstring = doc.splitlines() if not docstring: docstring = ['""'] elif len(docstring) > 1: docstring.insert(0, '"""') docstring.append('"""') docstring.extend(line for line in doc.splitlines) else: docstring = ['"{}"'.format(docstring[0])] # handle __init__ and params params_constant = [] __init__ = [] if params: params_constant = ["PARAMS = ("] doc = [] assignment = [] for param in params: params_constant.append(" {}{},".format( param.__class__.__name__, param.__getnewargs__(), )) namespace[param.__class__.__name__] = param.__class__ default = "" if hasattr(param, "default"): default = param.default if hasattr(default, "__name__"): namespace[default.__name__] = default default = default.__name__ default = "={}".format(default) __init__.append(" {}{},".format(param.name, default)) if param.doc: doc.append(" {} - {}".format(param.name, param.doc)) assignment.append("self.{} = {}".format(param.name, param.name)) __init__.append("):") params_constant.append(" )") if doc: __init__.append('"""') __init__.append("Parameters:") __init__.extend(doc) __init__.append("") __init__.append('"""') __init__.append("") __init__.extend(assignment) __init__ = [" "+line for line in __init__] __init__.insert(0, "def __init__(self,") # handle properties properties_constant = [] props = [] for prop in properties: properties_constant.append(" {}{},".format( prop.__class__.__name__, prop.__getnewargs__(), )) namespace[prop.__class__.__name__] = prop.__class__ initial = prop.initial if hasattr(initial, "__name__"): namespace[initial.__name__] = initial initial = initial.__name__ prop = PROPERTY_TEMPLATE.format( name=prop.name, initial=initial, doc=prop.doc, ) props.extend(prop.splitlines()) props.append("") if properties_constant: properties_constant.insert(0, "PROPERTIES = (") properties_constant.append(" )") # put it all together template = ["class {}(object):".format(typename)] template.extend(" "+line for line in docstring) template.extend(" "+line for line in params_constant) template.extend(" "+line for line in properties_constant) template.append("") template.extend(" "+line for line in __init__) template.append("") template.extend(" "+line for line in props) template.append("") template = "\n".join(template) if verbose: print(template) try: exec(template, namespace) #except SyntaxError, e: except SyntaxError as e: raise SyntaxError(e.msg + ':\n' + template) return namespace[typename] def data(cls): "A class decorator for the dataclass function." return dataclass(cls.__name__, cls.__doc__, cls.PARAMS, cls.PROPERTIES)