Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to dynamically build a python class based on data saved in the database?

The class name, properties and property calculation formula are saved in the database. How to build a python class dynamically based on these data?

Please take a look at the following example, where the className and properties are retrieved from the database:

className = 'Person'
properties = {
    'name': None,
    'weight': None,     # unitOfMeasure: kg     
    'height': None,     # unitOfMeasure: m
    'BMI': 'weight/(height*height)'   # body mass index
}

cls_dict = {}
init_code = "def __init__(self, "
init_args = []
for prop, value in properties.items():
    if value is not None:
        init_code += prop + ", "
        init_args.append(prop)
    cls_dict[prop] = value
init_code = init_code[:-2] + "):\n"
for prop in properties:
    if properties[prop] is None:
        init_code += "    self." + prop + " = " + prop + "\n"
    else:
        init_code += "    self." + prop + " = eval(properties['" + prop + "'])\n"
cls_dict['__init__'] = init_code
globals()[className] = type(className, (object,), cls_dict)

Bob = Person('Bob', 77, 1.7)

print(Bob.BMI)

The error message when running the above code is:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

TypeError: 'str' object is not callable

Because the class names, attributes and calculation rules saved in the database are unknown, Python classes and methods need to be dynamically constructed. Of course, classes and attributes may also be other categories of data, such as:

className = 'Product'
properties = {
    'name': None,
    'length': None,
    'height': None,
    'width': None,
    'volume': 'length*height*width'
}

How can I modify my code to make it work? Or is there any other better way to achieve this function?

>Solution :

If you insist, you could maybe do something like

import dataclasses


def expr_to_func(expr, ctor_property_names):
    # Not bulletproof.
    for prop in ctor_property_names:
        expr = expr.replace(prop, f"self.{prop}")
    return eval(f"lambda self: {expr}")


def make_class(class_name, properties):
    ctor_property_names = [k for k, v in properties.items() if v is None]
    # Create base dataclass and add properties on it
    klass = dataclasses.make_dataclass(class_name, ctor_property_names)
    for prop, expr in properties.items():
        if not expr:
            continue
        setattr(klass, prop, property(expr_to_func(expr, ctor_property_names)))
    return klass


def main():
    className = "Person"

    properties = {
        "name": None,
        "weight": None,
        "height": None,
        "BMI": "weight/(height*height)",
    }

    klass = make_class(className, properties)

    globals()[className] = klass  # i'd advise against this

    Bob = Person(name="Bob", weight=77, height=1.7)
    print(Bob.BMI)


if __name__ == "__main__":
    main()

but this is verging on pretty deep magic already.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading