Package: graffiti
# graffiti - a library for declarative computation## Installation`pip install graffiti`*or*1. `git clone https://github.com/SegFaultAX/graffiti`1. `cd graffiti/`1. To install: `python setup.py install`1. To get development dependencies: `pip install -r requirements.txt`1. To run tests: `nosetests`1. To audit source: `python setup.py audit`Check out my [blog post](http://mkbernard.com/blog/2014/06/graffiti-a-python-library-for-declarative-computation/)for more background on the "why" of this project. Get in touch if you have anycomments or questions!Inspired by Prismatic's Graph library (https://github.com/prismatic/plumbing)## Overview```pythonfrom graffiti import Graphstats_descriptor = {"n": lambda xs: len(xs),"m": lambda xs, n: sum(xs) / n,"m2": lambda xs, n: sum(x ** 2 for x in xs) / n,"v": lambda m, m2: m2 - m ** 2}graph = Graph(stats_descriptor)graph({ "xs": [1, 2, 3, 4, 5] })#=> {'xs': [1, 2, 3, 4, 5], 'n': 5, 'm': 3, 'm2': 11, 'v': 2}# if pydot is installedgraph.visualize()```## TutorialConsider the following perfectly reasonable Python code:```pythondef stats(xs):n = len(xs)m = sum(xs) / nm2 = sum(x ** 2 for x in xs) / nv = m2 - m ** 2return {"xs": xs,"n": n,"m": m,"m2": m2,"v": v,}```The first computation is based on our input (xs), and each additionalcomputation builds up more values based on things we've computed already. It'sconvenient to visualize this computation as a graph with the user inputs at theroot and values we're interested in at the leaves.graffiti allows you to structure your computation in exactly that fashion:```pythonstats_graph = {"n": lambda xs: len(xs),"m": lambda xs, n: sum(xs) / n,"m2": lambda xs, n: sum(x ** 2 for x in xs) / n,"v": lambda m, m2: m2 - m ** 2}graph = Graph(stats_graph)graph({ "xs": range(100) })```graffiti finds the relationships between the nodes in your graph and determinesthe optimal execution order. Leaves in the graph are computed lazily which meanswe can choose to only evaluate the ones we're interested in:```pythongraph({ "xs": range(100) }, _keys={"m", "n"})```In this case, graffiti will only evaluate what's needed to compute m and n, butnot the rest of the graph. You can also build nested graphs with dependenciesacross nesting levels:```pythonstats_graph = {"n": lambda xs: len(xs),"m": lambda xs, n: sum(xs) / n,"m2": lambda xs, n: sum(x ** 2 for x in xs) / n,"v": lambda m, m2: m2 - m ** 2,"order": {"sorted": lambda xs: sorted(xs),"reversed": lambda xs: sorted(xs, reverse=True)},}graph = Graph(stats_graph)graph({ "xs": range(100) })graph({ "xs": range(10) }, _keys={"order"})```Again, none of the unecessary nodes will be computed. Nodes in the graph canalso contain optional arguments:```pythongraph = Graph({ "mul": lambda n, p=10: n * p })graph({ "n": 10 }) # mul == 100graph({ "n": 10, "p": 20 }) # mul == 200```If the optional key is provided as an input to the graph evaluator, it willoverride the default value of the node. This makes it easy to play withdifferent values or settings as the computation flows through your pipeline.Finally, graphs are resumable. Since a graph object takes a dict as input andreturns a dict as output, you can replay a previously generated dict as theinput to the next thereby reusing all previously computed values:```pythonstats_graph = {"n": lambda xs: len(xs),"m": lambda xs, n: sum(xs) / n,"m2": lambda xs, n: sum(x ** 2 for x in xs) / n,"v": lambda m, m2: m2 - m ** 2,}graph = Graph(stats_graph)v1 = graph({ "xs": [1,2,3] }, _keys={"n"}) # just nv2 = graph(v1, _keys={"m"}) # v1 + just mv3 = graph(v2) # the rest of the graph```This allows you to defer the evaluation of expensive keys until the momentthey're actually needed without duplicating previous computations.graffiti also supports drawing the transitive graph of dependencies:```pythonstats_graph = {"n": lambda xs: len(xs),"m": lambda xs, n: sum(xs) / n,"m2": lambda xs, n: sum(x ** 2 for x in xs) / n,"v": lambda m, m2: m2 - m ** 2,"order": {"sorted": lambda xs: sorted(xs),"reversed": lambda xs: sorted(xs, reverse=True)},}graph = Graph(stats_graph)graph.visualize()```![](https://raw.githubusercontent.com/SegFaultAX/graffiti/master/example_graph.png)Using graffiti allows you to structure your computation and the complexinterdependencies therein naturally and efficiently. Most importantly, yourcomputational pipeline is just data. That means it's easy to inspect, easy toreason about, and easy to build tooling around.This project is still under active development. Contact me on Twitter@SegFaultAX if you have any questions, comments, or bug reports.## LicenseCopyright 2014 Michael-Keith BernardAvailabe under the MIT License. See LICENSE for full details.