Generate Random Trees

TL;DR

Nutree can generate random tree structures from a structure definition.

Warning

This feature is experimental and may change in future versions.

Nutree can generate random tree structures from a structure definition. This can be used to create hierarchical data for test, demo, or benchmarking of nutree itself.

The result can also be used as a source for creating fixtures for other targets in a following next step.
See Wundebaum demo and the fixture generator for an example.

The structure is defined as Python dictionary that describes the parent-child relationships to be created. This definition is then passed to tree.Tree.build_random_tree():

structure_def = {
    ...
    # Relations define the possible parent / child relationships between
    # node types and optionally override the default properties.
    "relations": {
        "__root__": {  # System root, i.e. we define the top nodes here
            "TYPE_1": {
                # How many instances to create:
                ":count": 10,
                # Attribute names and values for every instance:
                "ATTR_1": "This is a top node",
                "ATTR_2": True,
                "ATTR_3": 42,
            },
        },
        "TYPE_1": {  # Potential child nodes of TYPE_1
            "TYPE_2": {
                # How many instances to create:
                ":count": 3,
                # Attribute names and values for every instance:
                "title": "This is a child node of TYPE_1",
            },
        },
    },
}

tree = Tree.build_random_tree(structure_def)

Example:

structure_def = {
    # Name of the generated tree (optional)
    "name": "fmea",
    # Types define the default properties of the gernated nodes
    "types": {
        # '*' Defines default properties for all node types (optional)
        "*": {
            ":factory": DictWrapper,  # Default node class (optional)
        },
        # Specific default properties for each node type
        "function": {"icon": "gear"},
        "failure": {"icon": "exclamation"},
        "cause": {"icon": "tools"},
        "effect": {"icon": "lightning"},
    },
    # Relations define the possible parent / child relationships between
    # node types and optionally override the default properties.
    "relations": {
        "__root__": {
            "function": {
                ":count": 3,
                "title": TextRandomizer(("{idx}: Provide $(Noun:plural)",)),
                "details": BlindTextRandomizer(dialect="ipsum"),
                "expanded": True,
            },
        },
        "function": {
            "failure": {
                ":count": RangeRandomizer(1, 3),
                "title": TextRandomizer("$(Noun:plural) not provided"),
            },
        },
        "failure": {
            "cause": {
                ":count": RangeRandomizer(1, 3),
                "title": TextRandomizer("$(Noun:plural) not provided"),
            },
            "effect": {
                ":count": RangeRandomizer(1, 3),
                "title": TextRandomizer("$(Noun:plural) not provided"),
            },
        },
    },
}

tree = TypedTree.build_random_tree(structure_def)

assert isinstance(tree, TypedTree)
assert tree.calc_height() == 3

tree.print()

May produce:

TypedTree<'fmea'>
├── function → DictWrapper<{'icon': 'gear', 'title': '1: Provide Seniors', 'details': 'Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.', 'expanded': True}>
│   ├── failure → DictWrapper<{'icon': 'exclamation', 'title': 'Streets not provided'}>
│   │   ├── cause → DictWrapper<{'icon': 'tools', 'title': 'Decisions not provided'}>
│   │   ├── effect → DictWrapper<{'icon': 'lightning', 'title': 'Spaces not provided'}>
│   │   ╰── effect → DictWrapper<{'icon': 'lightning', 'title': 'Kings not provided'}>
│   ╰── failure → DictWrapper<{'icon': 'exclamation', 'title': 'Entertainments not provided'}>
│       ├── cause → DictWrapper<{'icon': 'tools', 'title': 'Programs not provided'}>
│       ├── effect → DictWrapper<{'icon': 'lightning', 'title': 'Dirts not provided'}>
│       ╰── effect → DictWrapper<{'icon': 'lightning', 'title': 'Dimensions not provided'}>
├── function → DictWrapper<{'icon': 'gear', 'title': '2: Provide Shots', 'details': 'Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum.', 'expanded': True}>
│   ├── failure → DictWrapper<{'icon': 'exclamation', 'title': 'Trainers not provided'}>
│   │   ├── cause → DictWrapper<{'icon': 'tools', 'title': 'Girlfriends not provided'}>
│   │   ├── cause → DictWrapper<{'icon': 'tools', 'title': 'Noses not provided'}>
│   │   ├── effect → DictWrapper<{'icon': 'lightning', 'title': 'Closets not provided'}>
│   │   ╰── effect → DictWrapper<{'icon': 'lightning', 'title': 'Potentials not provided'}>
│   ╰── failure → DictWrapper<{'icon': 'exclamation', 'title': 'Punches not provided'}>
│       ├── cause → DictWrapper<{'icon': 'tools', 'title': 'Inevitables not provided'}>
│       ├── cause → DictWrapper<{'icon': 'tools', 'title': 'Fronts not provided'}>
│       ╰── effect → DictWrapper<{'icon': 'lightning', 'title': 'Worths not provided'}>
╰── function → DictWrapper<{'icon': 'gear', 'title': '3: Provide Shots', 'details': 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.', 'expanded': True}>
    ╰── failure → DictWrapper<{'icon': 'exclamation', 'title': 'Recovers not provided'}>
        ├── cause → DictWrapper<{'icon': 'tools', 'title': 'Viruses not provided'}>
        ├── effect → DictWrapper<{'icon': 'lightning', 'title': 'Dirts not provided'}>
        ╰── effect → DictWrapper<{'icon': 'lightning', 'title': 'Readings not provided'}>

A few things to note

  • The generated tree contains DictWrapper instances as node.data value.

  • Every node.data contains items from the structure definition except for the ones starting with a colon, for example ":count".
    The node items are merged with the default properties defined in the types section.

  • Randomizers are used to generate random data for each instance. They derive from the Randomizer base class.

  • The TextRandomizer and BlindTextRandomizer classes are used to generate random text using the Fabulist library.

  • tree.Tree.build_random_tree() creates instances of Tree, while typed_tree.TypedTree.build_random_tree() creates instances of TypedTree.

  • The generated tree contains instances of the DictWrapper class by default, but can be overridden for each node type by adding a ":factory": CLASS entry.

Note

The random text generator is based on the Fabulist library and can use any of its providers to generate random data.
Make sure to install the fabulist package to use the text randomizers TextRandomizer and BlindTextRandomizer. Either install fabulist separately or install nutree with extras: pip install "nutree[random]" or pip install "nutree[all]".