Graphtik¶
11.0.0.dev0+v10.5.0-41-g1079c1f
, May 02, 2023
It’s a DAG all the way down!
Computation graphs for Python & Pandas¶
Graphtik is a library to compose, solve, execute & plot graphs of python functions (a.k.a pipelines) that consume and populate named data (a.k.a dependencies), whose names may be nested (such as, pandas dataframe columns), based on whether values for those dependencies exist in the inputs or have been calculated earlier.
In mathematical terms, given:
a partially populated data tree, and
a set of functions operating (consuming/producing) on branches of the data tree,
graphtik collects a subset of functions in a graph that when executed consume & produce as values as possible in the data-tree.
Its primary use case is building flexible algorithms for data science/machine learning projects.
It should be extendable to implement the following:
an IoC dependency resolver (e.g. Java Spring, Google Guice);
an executor of interdependent tasks based on files (e.g. GNU Make);
a custom ETL engine;
a spreadsheet calculation engine.
Graphtik sprang from Graphkit (summer 2019, v1.2.2) to experiment with Python 3.6+ features, but has diverged significantly with enhancements ever since.
Table of Contents
- 1. Operations
- 2. Pipelines
- 3. Plotting and Debugging
- 4. Architecture
- 5. API Reference
- Package: graphtik
- Module: fnop
FnOpFnOp.__abstractmethods__FnOp.__call__()FnOp.__init__()FnOp.__module__FnOp.__repr__()FnOp._abc_implFnOp._fn_needsFnOp._fn_providesFnOp._match_inputs_with_fn_needs()FnOp._prepare_match_inputs_error()FnOp._user_needsFnOp._user_providesFnOp._zip_results_plain()FnOp._zip_results_returns_dict()FnOp._zip_results_with_provides()FnOp.aliasesFnOp.compute()FnOp.depsFnOp.enduredFnOp.fnFnOp.marshalledFnOp.nameFnOp.needsFnOp.node_propsFnOp.parallelFnOp.prepare_plot_args()FnOp.providesFnOp.rescheduledFnOp.returns_dictFnOp.validate_fn_name()FnOp.withset()
NO_RESULTNO_RESULT_BUT_SFX_process_dependencies()as_renames()identity_fn()jsonp_ize_all()operation()prefixed()reparse_operation_data()AutographAutograph.__init__()Autograph.__module__Autograph._apply_renames()Autograph._collect_rest_op_args()Autograph._deduce_provides_from_fn_name()Autograph._from_overrides()Autograph._match_fn_name_pattern()Autograph.domainAutograph.full_path_namesAutograph.out_patternsAutograph.overridesAutograph.renamesAutograph.wrap_func()Autograph.wrap_funcs()Autograph.yield_wrapped_ops()
FnHarvesterPrefkey_is_in_my_project()autographed()camel_2_snake_case()get_autograph_decors()is_regular_class()
- Module: pipeline
PipelinePipeline.__abstractmethods__Pipeline.__call__()Pipeline.__init__()Pipeline.__module__Pipeline.__name__Pipeline.__qualname__Pipeline.__repr__()Pipeline._abc_implPipeline.compile()Pipeline.compute()Pipeline.graphPipeline.namePipeline.needsPipeline.outputsPipeline.predicatePipeline.prepare_plot_args()Pipeline.providesPipeline.withset()
_id_bool()_id_tristate_bool()build_network()compose()nest_any_node()
- Module: modifier
AccessorHCatAcc()JsonpAcc()VCatAcc()_Modifier_Optionals_modifier()_modifier_cstor_matrixacc_contains()acc_delitem()acc_getitem()acc_setitem()dep_renamed()dep_singularized()dep_stripped()dependency()get_accessor()get_jsonp()get_keyword()hcat()implicit()is_implicit()is_optional()is_sfx()is_sfxed()is_token()is_vararg()is_varargish()is_varargs()jsonp_ize()keyword()modifier_withset()modify()optional()sfxed()sfxed_vararg()sfxed_varargs()token()vararg()varargs()vcat()
- Module: planning
NetworkNetwork.needsNetwork.providesNetwork.__abstractmethods__Network.__init__()Network.__module__Network.__repr__()Network._abc_implNetwork._append_operation()Network._apply_graph_predicate()Network._build_execution_steps()Network._cached_plansNetwork._prune_graph()Network.compile()Network.graphNetwork.prepare_plot_args()
_format_cycle()_optionalized()_topo_sort_nodes()_yield_also_chained_docs()_yield_chained_docs()clone_graph_with_stripped_sfxed()collect_requirements()inputs_for_recompute()logroot_doc()unsatisfied_operations()yield_also_chaindocs()yield_also_subdocs()yield_also_superdocs()yield_chaindocs()yield_datanodes()yield_node_names()yield_ops()yield_subdocs()yield_superdocs()
- Module: execution
ExecutionPlanExecutionPlan.netExecutionPlan.needsExecutionPlan.providesExecutionPlan.dagExecutionPlan.stepsExecutionPlan.asked_outsExecutionPlan.commentsExecutionPlan.__abstractmethods__ExecutionPlan.__dict__ExecutionPlan.__module__ExecutionPlan.__repr__()ExecutionPlan._abc_implExecutionPlan._check_if_aborted()ExecutionPlan._execute_sequential_method()ExecutionPlan._execute_thread_pool_barrier_method()ExecutionPlan._handle_task()ExecutionPlan._prepare_tasks()ExecutionPlan.execute()ExecutionPlan.graphExecutionPlan.prepare_plot_args()ExecutionPlan.validate()
OpTaskSolutionSolution.__abstractmethods__Solution.__annotations__Solution.__contains__()Solution.__copy__()Solution.__delitem__()Solution.__getitem__()Solution.__init__()Solution.__module__Solution.__repr__()Solution.__setitem__()Solution._abc_implSolution._initial_inputsSolution._overwrites_cacheSolution._populate_op_layer_with_outputs()Solution._reschedule()Solution.brokenSolution.canceledSolution.check_if_incomplete()Solution.copy()Solution.dagSolution.debugstr()Solution.elapsed_msSolution.executedSolution.graphSolution.is_failed()Solution.is_layeredSolution.layersSolution.operation_executed()Solution.operation_failed()Solution.overwritesSolution.planSolution.prepare_plot_args()Solution.scream_if_incomplete()Solution.solidSolution.update()
_do_task()_isDebugLogging()logtask_context
- Module: plot
PlotterRefStylesStackThemeTheme.arch_urlTheme.broken_colorTheme.canceled_colorTheme.data_bad_html_label_keysTheme.data_templateTheme.edge_defaultsTheme.evicted_colorTheme.failed_colorTheme.fill_colorTheme.kw_dataTheme.kw_data_evictedTheme.kw_data_in_solutionTheme.kw_data_in_solution_nullTheme.kw_data_inpTheme.kw_data_inp_onlyTheme.kw_data_ioTheme.kw_data_labelTheme.kw_data_missingTheme.kw_data_outTheme.kw_data_out_onlyTheme.kw_data_overwrittenTheme.kw_data_prunedTheme.kw_data_sideffectTheme.kw_data_sideffectedTheme.kw_data_to_evictTheme.kw_edgeTheme.kw_edge_aliasTheme.kw_edge_brokenTheme.kw_edge_enduredTheme.kw_edge_head_opTheme.kw_edge_implicitTheme.kw_edge_mapping_keywordTheme.kw_edge_null_resultTheme.kw_edge_optionalTheme.kw_edge_prunedTheme.kw_edge_rescheduledTheme.kw_edge_sideffectTheme.kw_edge_subdocTheme.kw_edge_tail_opTheme.kw_graphTheme.kw_graph_plottable_typeTheme.kw_graph_plottable_type_unknownTheme.kw_legendTheme.kw_opTheme.kw_op_canceledTheme.kw_op_enduredTheme.kw_op_executedTheme.kw_op_failedTheme.kw_op_labelTheme.kw_op_label2Theme.kw_op_marshalledTheme.kw_op_parallelTheme.kw_op_prune_commentTheme.kw_op_prunedTheme.kw_op_rescheduledTheme.kw_op_returns_dictTheme.kw_stepTheme.kw_step_badgeTheme.node_defaultsTheme.null_colorTheme.op_bad_html_label_keysTheme.op_badge_stylesTheme.op_templateTheme.overwrite_colorTheme.pruned_colorTheme.resched_thicknessTheme.show_chaindocsTheme.show_stepsTheme.sideffect_colorTheme.steps_colorTheme.subdoc_colorTheme.theme_attributes()Theme.truncate_argsTheme.vector_colorTheme.withset()
USER_STYLE_PREFFIXactive_plotter_plugged()as_identifier()default_jupyter_renderget_active_plotter()get_node_name()graphviz_html_string()is_empty_array()is_empty_frame()is_nx_node_dependent()legend()make_data_value_tooltip()make_fn_tooltip()make_op_prune_comment()make_op_tooltip()make_overwrite_tooltip()make_template()quote_html_tooltips()quote_node_id()remerge()save_plot_file_by_sha1()set_active_plotter()supported_plot_formats()
- Module: config
abort_run()debug_enabled()evictions_skipped()execution_pool_plugged()get_execution_pool()is_abort()is_debug()is_endure_operations()is_layered_solution()is_marshal_tasks()is_parallel_tasks()is_reschedule_operations()is_skip_evictions()operations_endured()operations_reschedullled()reset_abort()set_debug()set_endure_operations()set_execution_pool()set_layered_solution()set_marshal_tasks()set_parallel_tasks()set_reschedule_operations()set_skip_evictions()solution_layered()tasks_in_parallel()tasks_marshalled()
- Module: base
AbortedExceptionIncompleteExecutionErrorOperationPlotArgsPlotArgs.clone_or_merge_graph()PlotArgs.clusteredPlotArgs.clustersPlotArgs.dotPlotArgs.dot_itemPlotArgs.filenamePlotArgs.graphPlotArgs.inputsPlotArgs.jupyter_renderPlotArgs.kw_render_pydotPlotArgs.namePlotArgs.nx_attrsPlotArgs.nx_itemPlotArgs.outputsPlotArgs.plottablePlotArgs.plotterPlotArgs.solutionPlotArgs.stepsPlotArgs.themePlotArgs.with_defaults()
PlottableRenArgsTokenasdict()aslist()asset()astuple()first_solid()func_name()func_source()func_sourcelines()
- Module: jetsam
- Module: jsonpointer
- Package: sphinxext
- 6. Changes
- TODOs
- Changelog
- v11.0.0.dev0 (?? Apr 2023, @ankostis): AUTOGRAPHED
- v10.5.0 (25 Apr 2023, @ankostis): REVIVE project, Bump DEPS
- v10.4.0 (9 Oct 2020, @ankostis): CWD, callbacks non-marshalled, preserve concat index-names
- v10.3.0 (21 Sep 2020, @ankostis): CONCAT pandas, Hierarchical overwrites, implicit(), post-cb
- v10.2.1 (18 Sep 2020, @ankostis): plot sol bugfix
- v10.2.0 (16 Sep 2020, @ankostis): RECOMPUTE, pre-callback, drop op_xxx, ops-eq-op.name, drop NULL_OP
- v10.1.0 (5 Aug 2020, @ankostis): rename return-dict outs; step number badges
- v10.0.0 (19 Jul 2020, @ankostis): Implicits; modify(); auto-name pipelines; plot data as overspilled
- v9.3.0 (8 Jul 2020, @ankostis): Sphinx AutoDocumenter; fix plot sfx-nodes
- v9.1.0 (4 Jul 2020, @ankostis): Bugfix, panda-polite, privatize modifier fields
- v9.0.0 (30 Jun 2020, @ankostis): JSONP; net, evictions & sfxed fixes; conveyor fn; rename modules
- v8.4.0 (15 May 2020, @ankostis): subclass-able Op, plot edges from south–>north of nodes
- v8.3.1 (14 May 2020, @ankostis): plot edges from south–>north of nodes
- v8.3.0 (12 May 2020, @ankostis): mapped–>keyword, drop sol-finalize
- v8.2.0 (11 May 2020, @ankostis): custom Solutions, Task-context
- v8.1.0 (11 May 2020, @ankostis): drop last plan, Rename/Nest, Netop–>Pipeline, purify modules
- v8.0.2 (7 May 2020, @ankostis): re-MODULE; sideffect –> sfx; all DIACRITIC Modifiers; invert “merge” meaning
- v7.1.2 (6 May 2020, @ankostis): minor reschedule fixes and refactoring
- v7.1.0 (4 May 2020, @ankostis): Cancelable sideffects, theme-ize & expand everything
- v7.0.0 (28 Apr 2020, @ankostis): In-solution sideffects, unified OpBuilder, plot badges
- v6.2.0 (19 Apr 2020, @ankostis): plotting fixes & more styles, net find util methods
- v6.1.0 (14 Apr 2020, @ankostis): config plugs & fix styles
- v6.0.0 (13 Apr 2020, @ankostis): New Plotting Device…
- v5.7.1 (7 Apr 2020, @ankostis): Plot job, fix RTD deps
- v5.7.0 (6 Apr 2020, @ankostis): FIX +SphinxExt in Wheel
- v5.2.2 (03 Mar 2020, @ankostis): stuck in PARALLEL, fix Impossible Outs, plot quoting, legend node
- v5.2.1 (28 Feb 2020, @ankostis): fix plan cache on skip-evictions, PY3.8 TCs, docs
- v5.2.0 (27 Feb 2020, @ankostis): Map needs inputs –> args, SPELLCHECK
- v5.1.0 (22 Jan 2020, @ankostis): accept named-tuples/objects provides
- v5.0.0 (31 Dec 2019, @ankostis): Method–>Parallel, all configs now per op flags; Screaming Solutions on fails/partials
- v4.4.1 (22 Dec 2019, @ankostis): bugfix debug print
- v4.4.0 (21 Dec 2019, @ankostis): RESCHEDULE for PARTIAL Outputs, on a per op basis
- v4.3.0 (16 Dec 2019, @ankostis): Aliases
- v4.2.0 (16 Dec 2019, @ankostis): ENDURED Execution
- v4.1.0 (13 Dec 2019, @ankostis): ChainMap Solution for Rewrites, stable TOPOLOGICAL sort
- v4.0.1 (12 Dec 2019, @ankostis): bugfix
- v4.0.0 (11 Dec 2019, @ankostis): NESTED merge, revert v3.x Unvarying, immutable OPs, “color” nodes
- v3.1.0 (6 Dec 2019, @ankostis): cooler
prune() - v3.0.0 (2 Dec 2019, @ankostis): UNVARYING NetOperations, narrowed, API refact
- v2.3.0 (24 Nov 2019, @ankostis): Zoomable SVGs & more op jobs
- v2.2.0 (20 Nov 2019, @ankostis): enhance OPERATIONS & restruct their modules
- v2.1.1 (12 Nov 2019, @ankostis): global configs
- v2.1.0 (20 Oct 2019, @ankostis): DROP BW-compatible, Restruct modules/API, Plan perfect evictions
- v2.0.0b1 (15 Oct 2019, @ankostis): Rebranded as Graphtik for Python 3.6+
- v1.3.0 (Oct 2019, @ankostis): NEVER RELEASED: new DAG solver, better plotting & “sideffect”
- v1.2.4 (Mar 7, 2018)
- 1.2.2 (Mar 7, 2018, @huyng): Fixed versioning
- 1.2.1 (Feb 23, 2018, @huyng): Fixed multi-threading bug and faster compute through caching of find_necessary_steps
- 1.2.0 (Feb 13, 2018, @huyng)
- 1.1.0 (Nov 9, 2017, @huyng)
- 1.0.4 (Nov 3, 2017, @huyng): Networkx 2.0 compatibility
- 1.0.3 (Jan 31, 2017, @huyng): Make plotting dependencies optional
- 1.0.2 (Sep 29, 2016, @pumpikano): Merge pull request yahoo#5 from yahoo/remove-packaging-dep
- 1.0.1 (Aug 24, 2016)
- 1.0 (Aug 2, 2016, @robwhess)
- Index
Features¶
Deterministic pre-decided execution plan (unless partial-outputs or endured operations defined, see below).
Can assemble existing functions without modifications into pipelines.
dependency resolution can bypass calculation cycles based on data given and asked.
Support functions with partial outputs; keep working even if certain endured operations fail.
Facilitate trivial conveyor operations and alias on provides.
Support cycles, by annotating repeated updates of dependency values as tokens or sideffected (e.g. to add columns into
pandas.DataFrames).Hierarchical dependencies may access data values deep in solution with json pointer path expressions.
Hierarchical dependencies annotated as implicit imply which subdoc dependency the function reads or writes in the parent-doc.
Early eviction of intermediate results from solution, to optimize memory footprint.
Solution tracks all intermediate overwritten values for the same dependency.
Elaborate Graphviz plotting with configurable plot themes.
Integration with Sphinx sites with the new
graphtikdirective.Authored with debugging in mind.
Parallel execution (but underdeveloped & DEPRECATED).
Anti-features¶
It’s not meant to follow complex conditional logic based on dependency values (though it does support that to a limited degree).
It’s not an orchestrator for long-running tasks, nor a calendar scheduler - Apache Airflow, Dagster or Luigi may help for that.
It’s not really a parallelizing optimizer, neither a map-reduce framework - look additionally at Dask, IpyParallel, Celery, Hive, Pig, Hadoop, etc.
It’s not a stream/batch processor, like Spark, Storm, Fink, Kinesis, because it pertains function-call semantics, calling only once each function to process data-items.
Differences with schedula¶
schedula is a powerful library written roughly for the same purpose, and works differently along these lines (ie features below refer to schedula):
terminology (<graphtik> := <schedula>):
pipeline := dispatcher
plan := workflow
solution := solution
Dijkstra planning runs while calling operations:
Powerful & flexible (ie all operations are dynamic, domains are possible, etc).
Supports weights.
Cannot pre-calculate & cache execution plans (slow).
Calculated values are stored inside a graph (mimicking the structure of the functions):
graph visualizations absolutely needed to inspect & debug its solutions.
- graphs imply complex pre/post processing & traversal algos
(vs constructing/traversing data-trees).
Reactive plotted diagrams, web-server runs behind the scenes.
Operation graphs are stackable:
plotted nested-graphs support drill-down.
graphtik emulates that with data/operation names (operation nesting), but always a unified graph is solved at once, bc it is impossible to dress nesting-funcs as a python-funcs and pre-solve plan (schedula does not pre-solve plan, Dijkstra runs all the time). See TODO about plotting such nested graphs.
Schedula does not calculate all possible values (ie no overwrites).
Schedula computes precedence based on weights and lexicographical order of function name.
Re-inserting operation does not overrides its current function - must remove it first.
graphtik precedence based insertion order during composition.
Virtual start and end data-nodes needed for Dijkstra to solve the dag.
No domains (execute-time conditionals deciding whether a function must run).
Probably Re-computations is more straightforward in graphtik.
TODO: more differences with schedula exist.
Quick start¶
Here’s how to install:
pip install graphtik
OR with dependencies for plotting support (and you need to install Graphviz program separately with your OS tools):
pip install graphtik[plot]
Let’s build a graphtik computation pipeline that produces the following
x3 outputs out of x2 inputs (α and β):
>>> from graphtik import compose, operation
>>> from operator import mul, sub
>>> @operation(name="abs qubed",
... needs=["α-α×β"],
... provides=["|α-α×β|³"])
... def abs_qubed(a):
... return abs(a) ** 3
Hint
Notice that graphtik has not problem working in unicode chars for dependency names.
Compose the abspow function along with mul & sub built-ins
into a computation graph:
>>> graphop = compose("graphop",
... operation(mul, needs=["α", "β"], provides=["α×β"]),
... operation(sub, needs=["α", "α×β"], provides=["α-α×β"]),
... abs_qubed,
... )
>>> graphop
Pipeline('graphop', needs=['α', 'β', 'α×β', 'α-α×β'],
provides=['α×β', 'α-α×β', '|α-α×β|³'],
x3 ops: mul, sub, abs qubed)
You may plot the function graph in a file like this (if in jupyter, no need to specify the file, see Jupyter notebooks):
>>> graphop.plot('graphop.svg') # doctest: +SKIP
As you can see, any function can be used as an operation in Graphtik, even ones imported from system modules.
Run the graph-operation and request all of the outputs:
>>> sol = graphop(**{'α': 2, 'β': 5})
>>> sol
{'α': 2, 'β': 5, 'α×β': 10, 'α-α×β': -8, '|α-α×β|³': 512}
Solutions are plottable as well:
>>> solution.plot('solution.svg') # doctest: +SKIP
Run the graph-operation and request a subset of the outputs:
>>> solution = graphop.compute({'α': 2, 'β': 5}, outputs=["α-α×β"])
>>> solution
{'α-α×β': -8}
… where the (interactive) legend is this:
>>> from graphtik.plot import legend
>>> l = legend()
legend¶