Skip to main content

Tree manipulation module

Project description

gardener - simple tree manipulation module

Basic usage

from gardener import Node, register_hook

@register_hook("child")
def child(node):
    return Node.another_child(
        values=[value * 2 for value in node.values]
    )


tree = Node.root(
    value=56,
    some_property=Node.child(
        values=[10, 20, 80]
    )
)


print(tree.pretty())
"""
{
  "key": "root",
  "props": {
    "value": 56,
    "some_property": {
      "key": "another_child",
      "props": {
        "values": [
          20,
          40,
          160
        ]
      }
    }
  }
}
"""

Installation

python -m pip install gardener

Getting started

Gardener is useful for tree manipulation (building and transforming complex trees)

To start working with it, you need to understand a few concepts:

hook is a function called on node creation. It can create a brand new node or change current one.
node is an object with a key - string identifier and props - an arbitrary dictionary

Building a tree

To build a tree, you can use Node.name[.name2.name3...](**props):

from gardener import Node


root = Node.root() # props={}, key="root"
oak = Node.tree.oak(age=23) # props={"age": 23}, key="tree:oak"
long_name = Node.very.long.node.key() # props={}, key="very:long:node:key"

Arbitrary name length makes it possible to create namespaces:

from gardener import Node

tree = Node.tree

tree.oak(age=23) # props={"age": 23}, key="tree:oak"
tree.pine(age=2) # props={"age": 2}, key="tree:pine"

Hooks

To transform your tree, you can define any number of hooks.
Hooks are called on node creation, in order they were defined - hooks defined earlier would be called earlier.

A hook is just a function getting node as argument and returning a transformed node.
To define hook, use register_hook(key):

from gardener import Node, register_hook

@register_hook("car")
def make_any_car_faster(node):
    node.engine.horsepower = node.engine.horsepower * 5
    return node

@register_hook("engine")
def engine_tweak(node):
    node.horsepower -= 1
    return node

@register_hook("car")
def no_red_cars(node):
    if node.color == "red":
        node.color = "black"
    return node

@register_hook("car")
def make_supercar(node):
    if node.engine.horsepower > 500:
        return Node.supercar(**node.props)
    return node

parking_lot = Node.lot(
    cars=[
        Node.car(
            engine=Node.engine(horsepower=100),
            color="red"
        ),
        Node.car(
            engine=Node.engine(horsepower=200),
            color="white"
        ),
        Node.car(
            engine=Node.engine(horsepower=205),
            color="red"
        ),
    ]
)
# notice how car.engine.horsepower is (x - 1) * 5 because engine is created before the car
print(parking_lot.pretty())
"""
{
  "key": "lot",
  "props": {
    "cars": [
      {
        "key": "car",
        "props": {
          "engine": {
            "key": "engine",
            "props": {
              "horsepower": 495
            }
          },
          "color": "black"
        }
      },
      {
        "key": "supercar",
        "props": {
          "engine": {
            "key": "engine",
            "props": {
              "horsepower": 995
            }
          },
          "color": "white"
        }
      },
      {
        "key": "supercar",
        "props": {
          "engine": {
            "key": "engine",
            "props": {
              "horsepower": 1020
            }
          },
          "color": "black"
        }
      }
    ]
  }
}
"""

Important notes:

  • if your hook changes existing node (but key remains the same) - use node.copy() or make changes in place.
  • if you change the key of the node, make a new node.

First is needed to avoid infinite recursion (otherwise your hook would be called again and again on the same node).
Second is needed to ensure hooks are called on the new node.

Pretty printing

Gardener uses built-in json module to make a pretty-printed tree:

from gardener import Node

print(Node.tree.oak(age=23).pretty())
"""
{
  "key": "tree:oak",
  "props": {
    "age": 23
  }
}
"""

If your values are JSON serializable, this would work out-of-the-box.
Otherwise you will need to extend GardenerJSON class:

from gardener import Node, GardenerJSON

class MyGardenerJSON(GardenerJSON):
    def default(self, obj):
        if obj is Ellipsis:
            return "..." # return any JSON serializable object
        return super().default(obj)

# Ellipsis is not JSON serializable
print(Node.tree.oak(cool_object=Ellipsis).pretty(cls=MyGardenerJSON))
"""
{
  "key": "tree:oak",
  "props": {
    "cool_object": "..."
  }
}
"""

In fact, Node.pretty accepts any keyword argument of json.dumps

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gardener-1.0.3.tar.gz (4.1 kB view details)

Uploaded Source

Built Distribution

gardener-1.0.3-py3.9.egg (5.4 kB view details)

Uploaded Source

File details

Details for the file gardener-1.0.3.tar.gz.

File metadata

  • Download URL: gardener-1.0.3.tar.gz
  • Upload date:
  • Size: 4.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.7.1 importlib_metadata/4.9.0 pkginfo/1.8.2 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.0

File hashes

Hashes for gardener-1.0.3.tar.gz
Algorithm Hash digest
SHA256 726a9387cdd1865fa5e148921a6f1b1ad9d82c2447e30a94226bf6d5be891aaf
MD5 8c34a4e9df7b3ec0914d7e6e1c468e93
BLAKE2b-256 0d9e82c63a3e95d6c1b1bde947f8b45e1c8c7a9b35d1ad504785875f479de78d

See more details on using hashes here.

File details

Details for the file gardener-1.0.3-py3.9.egg.

File metadata

  • Download URL: gardener-1.0.3-py3.9.egg
  • Upload date:
  • Size: 5.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.7.1 importlib_metadata/4.9.0 pkginfo/1.8.2 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.0

File hashes

Hashes for gardener-1.0.3-py3.9.egg
Algorithm Hash digest
SHA256 0e42894741b88ec2d3fa05b3ebbe00a7c7381d41be25669644eac12d5ce60da5
MD5 00bace75090e754f51c2c01e6945b0d8
BLAKE2b-256 9731a891ecabcdd4605ce9326eecce3d4e0fdf4b7818a003b50775d16433b04f

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page