meshed.makers#
Makers
This module contains tools to make meshed objects in different ways.
Main entry points:
code_to_dag: turn a function whose body isout = func(args...)lines (or such a function’s source string) into aDAG.code_to_fnodes: the same parsing, but returning the tuple ofFuncNodeobjects instead of assembling aDAG.src_to_func_node_factory: the lower-level step yieldingFuncNodefactories (partials that still lack theirfunc).mk_fnodes_from_fn_factories: inject functions into those factories to getFuncNodeobjects.
Let’s start with an example where we have some code representing a user story:
>>> def user_story():
... wfs = call(src_to_wf, data_src)
... chks_iter = map(chunker, wfs)
... chks = chain(chks_iter)
... fvs = map(featurizer, chks)
... model_outputs = map(model, fvs)
If the code is compliant (has only function calls and assignments of their result),
we can extract FuncNode factories from these lines (uses AST behind the scenes).
>>> from meshed.makers import src_to_func_node_factory
>>> fnodes_factories = list(src_to_func_node_factory(user_story))
Each factory is a curried version of FuncNode, set up to be able to make a DAG
equivalent to the user story, once we provide the necessary functions (call,
map, and chain).
>>> from functools import partial
>>> assert all(
... isinstance(x, partial) and issubclass(x.func, FuncNode) for x in fnodes_factories
... )
See that the FuncNode factories are all set up with
name (id),
out (output variable name),
bind (names of the variables where the function will source it’s arguments), and
func_label (which can be used when displaying the DAG, or as a key to the function
to use).
>>> assert [x.keywords for x in fnodes_factories] == [
... {'name': 'call',
... 'out': 'wfs',
... 'bind': {0: 'src_to_wf', 1: 'data_src'},
... 'func_label': 'call'},
... {'name': 'map',
... 'out': 'chks_iter',
... 'bind': {0: 'chunker', 1: 'wfs'},
... 'func_label': 'map'},
... {'name': 'chain',
... 'out': 'chks',
... 'bind': {0: 'chks_iter'},
... 'func_label': 'chain'},
... {'name': 'map_04',
... 'out': 'fvs',
... 'bind': {0: 'featurizer', 1: 'chks'},
... 'func_label': 'map'},
... {'name': 'map_05',
... 'out': 'model_outputs',
... 'bind': {0: 'model', 1: 'fvs'},
... 'func_label': 'map'}
... ]
What can we do with that?
Well, provide the functions, so the DAG can actually compute.
You can do it yourself, or get a little help with mk_fnodes_from_fn_factories.
>>> from meshed.dag import DAG
>>> from meshed.makers import mk_fnodes_from_fn_factories
>>> fnodes = list(mk_fnodes_from_fn_factories(fnodes_factories))
>>> dag = DAG(fnodes)
>>> print(dag.synopsis_string())
src_to_wf,data_src -> call -> wfs
chunker,wfs -> map -> chks_iter
chks_iter -> chain -> chks
featurizer,chks -> map_04 -> fvs
model,fvs -> map_05 -> model_outputs
Wait! But we didn’t actually provide the functions we wanted to use!
What happened?!?
What happened is that mk_fnodes_from_fn_factories just made some for us.
It used the convenient meshed.util.mk_place_holder_func which makes a function
(that happens to actually compute something and be picklable).
>>> from inspect import signature
>>> str(signature(dag))
'(src_to_wf, data_src, chunker, featurizer, model)'
We can actually call the dag and get something meaningful:
>>> dag(1, 2, 3, 4, 5)
'map(model=5, fvs=map(featurizer=4, chks=chain(chks_iter=map(chunker=3, wfs=call(src_to_wf=1, data_src=2)))))'
If you don’t want mk_fnodes_from_fn_factories to do that (because you are in
prod and need to make sure as much as possible is explicitly as expected, you can
simply use a different factory_to_func argument. The default one is:
>>> from meshed.makers import dlft_factory_to_func
which you can also reuse to make your own.
See below how we provide a name_to_func_map to specify how func_label values should
map to actual functions, and set use_place_holder_fallback=False to make
sure that we don’t ever fallback on a placeholder function as we did above.
>>> def _call(x, y):
... # would use operator.methodcaller('__call__') but doesn't have a __name__
... return x + y
>>> def _map(x, y):
... return [x, y]
>>> def _chain(iterable):
... return sum(iterable)
>>>
>>> factory_to_func = partial(
... dlft_factory_to_func,
... name_to_func_map={'map': _map, 'chain': _chain, 'call': _call},
... use_place_holder_fallback=False
... )
>>>
>>> fnodes = list(mk_fnodes_from_fn_factories(fnodes_factories, factory_to_func))
>>> dag = DAG(fnodes)
On the surface, we get the same dag as we had before – at least from the point of view of the dag signature, names, and relationships between these names:
>>> print(dag.synopsis_string())
src_to_wf,data_src -> call -> wfs
chunker,wfs -> map -> chks_iter
chks_iter -> chain -> chks
featurizer,chks -> map_04 -> fvs
model,fvs -> map_05 -> model_outputs
>>> str(signature(dag))
'(src_to_wf, data_src, chunker, featurizer, model)'
But see below that the dag is now using the functions we specified:
>>> # dag(src_to_wf=1, data_src=2, chunker=3, featurizer=4, model=5)
>>> # will trigger this:
>>> # src_to_wf=1, data_src=2 -> call -> wfs == 1 + 2 == 3
>>> # chunker=3 , wfs=3 -> map -> chks_iter == [3, 3]
>>> # chks_iter=6 -> chain -> chks == 3 + 3 == 6
>>> # featurizer=4, chks=6 -> map_04 -> fvs == [4, 6]
>>> # model=5, fvs=[4, 6] -> map_05 -> model_outputs == [5, [4, 6]]
>>> dag(1, 2, 3, 4, 5)
[5, [4, 6]]
Functions
|
Map every attribute name of |
|
Build a |
|
Make a |
|
Parse |
|
Will produce a json-serializable dictionary from a dag. |
|
Get a function for the given factory, looking its |
|
Serialize a |
|
Make some components (kwargs) based on the |
Tell whether the class of |
|
|
|
|
Will produce a dag from a json-serializable dictionary. |
|
Rebuild a |
|
Chain |
|
Make func nodes from func node factories and a specification of how to make the nodes from these. |
|
Make |
|
Curry |
|
Split an assignment statement into its |
Parse source code and generate tuples of information about it. |
|
|
Turn one body statement into a |
|
Parse source code and generate tuples of information about it. |
|
Extract FuncNode kwargs (name, out, and bind) from ast (target,value) pairs |
|
Parse |
|
Like |
Make a |
|
|
|
|
Converts an iterable of func call triples to an iterable of |
Classes
Mapping view of |
- meshed.makers.attr_dict(obj)[source]#
Map every attribute name of
objnot starting with an underscore to its value.
- meshed.makers.code_to_digraph(src)[source]#
Make a
graphviz.Digraphof theDAGthatcode_to_dag(src)builds.
- meshed.makers.dag_to_jdict(dag, *, func_to_jdict=None)[source]#
Will produce a json-serializable dictionary from a dag.
- meshed.makers.dlft_factory_to_func(factory, name_to_func_map=None, use_place_holder_fallback=True)[source]#
Get a function for the given factory, looking its
func_labelup inname_to_func_map.If the label is missing from the map, a placeholder function (see
meshed.util.mk_place_holder_func) is made unlessuse_place_holder_fallbackisFalse, in which caseKeyErroris raised.
- class meshed.makers.dlft_factory_to_func_mapping[source]#
Bases:
MappingMapping view of
dlft_factory_to_func:m[factory]isdlft_factory_to_func(factory).Only
__getitem__is defined, so a subclass must add__iter__and__len__before it can be instantiated.
- meshed.makers.extract_tokens(string, pos=0, endpos=9223372036854775807)#
Return a list of all non-overlapping matches of pattern in string.
- meshed.makers.fnode_to_jdict(fnode, *, func_to_jdict=None)[source]#
Serialize a
FuncNodeto a dict of itsname,func_label,bindandout.The function itself is included (under
func) only whenfunc_to_jdictis given to serialize it.
- meshed.makers.func_nodes_to_named_funcs(func_nodes)[source]#
Make some components (kwargs) based on the
.outand.funcof theFuncNodeobjects.Example use: To get from
DAGtoSlabs.>>> from meshed import DAG, FuncNode >>> dag = DAG([ ... FuncNode(lambda x: x + 1, out='a'), ... FuncNode(lambda a: a + 2, out='b',), ... FuncNode(lambda a, b: a * b, out='c'), ... ]) >>> dag(x=10) 143 >>> named_funcs = func_nodes_to_named_funcs(dag.func_nodes) >>> isinstance(named_funcs, dict) True >>> list(named_funcs) ['a', 'b', 'c'] >>> callable(named_funcs['a']) True >>> assert dag.find_func_node('a').func(3) == named_funcs['a'](3) ==4
The inverse of this function is
named_funcs_to_func_nodes.>>> func_nodes = list(named_funcs_to_func_nodes(named_funcs)) >>> dag2 = DAG(func_nodes) >>> assert dag2(x=3) == dag(x=3) == 24
- meshed.makers.is_from_ast_module(o)[source]#
Tell whether the class of
oreports_astas its module.Note that on Python 3.12 the
astnode classes reportast, not_ast, so this returnsFalsefor them.
- meshed.makers.jdict_to_dag(jdict, *, jdict_to_func=None)[source]#
Will produce a dag from a json-serializable dictionary.
- meshed.makers.jdict_to_fnode(jdict, *, jdict_to_func=None)[source]#
Rebuild a
FuncNodefrom a dict made byfnode_to_jdict.jdict_to_funcis required to turnjdict["func"]back into a callable; without it,NotImplementedErroris raised.
- meshed.makers.lined_dag(funcs)[source]#
Chain
funcsinto aDAGwhere each function’s output feeds the first parameter of the next.Edges are added with
DAG.add_edges, which raisesValueErrorif a function’s first parameter already carries the name of another function infuncs.
- meshed.makers.mk_fnodes_from_fn_factories(fnodes_factories, factory_to_func=<function dlft_factory_to_func>)[source]#
Make func nodes from func node factories and a specification of how to make the nodes from these.
- Parameters:
fnodes_factories (
Iterable[Callable[...,FuncNode]]) – An iterable of FuncNodeFactoryfactory_to_func (
Callable[[Callable[...,FuncNode]],Callable]) – A function that will give you a function given a FuncNodeFactory input (where it will draw the information it needs to know what kind of function to make).
- Return type:
- Returns:
- meshed.makers.named_funcs_to_func_nodes(named_funcs)[source]#
Make
FuncNodeobjects from keyword arguments, using the key as the.outof theFuncNodeand the value as the.funcof theFuncNode.Example use: To get from
SlabstoDAG.>>> from meshed import DAG >>> func_nodes = list(named_funcs_to_func_nodes(dict( ... a=lambda x: x + 1, ... b=lambda a: a + 2, ... c=lambda a, b: a * b) ... )) >>> dag = DAG(func_nodes) >>> dag(x=3) 24
The inverse of this function is
func_nodes_to_named_funcs.>>> named_funcs = func_nodes_to_named_funcs(dag.func_nodes) >>> dag2 = DAG(named_funcs_to_func_nodes(named_funcs)) >>> assert dag2(x=3) == dag(x=3) == 24
- meshed.makers.node_kwargs_to_func_node_factory(node_kwargs)[source]#
Curry
FuncNodewithnode_kwargs(name,out,bind, …), leavingfuncto be supplied.
- meshed.makers.parse_assignment(body, info=None)[source]#
Split an assignment statement into its
(target, call)ast nodes.Raises
ValueErrorifbodyis not an (annotated) assignment, andAssertionErrorif it has several targets or its value is not a call. Theinfoargument is ignored (it is recomputed frombody).- Return type:
- meshed.makers.parse_assignment_steps(src)#
Parse source code and generate tuples of information about it.
- Parameters:
src – The source string or a python object whose code string can be extracted.
- Returns:
And generator of “target_values”
>>> from meshed.makers import parse_steps >>> def foo(): ... x = func1(a, b=2) ... y = func2(x, c=3) >>> target_values = list(parse_steps(foo))
Let’s look at the first target_value to see what it contains:
>>> name, call = target_values[0] # a 2-tuple >>> assert isinstance(name, ast.Name) # the first element is a ast Name object >>> sorted(vars(name)) ['col_offset', 'ctx', 'end_col_offset', 'end_lineno', 'id', 'lineno'] >>> name.id 'x' >>> assert isinstance(call, ast.Call) # the first element is a ast Call object >>> sorted(vars(call)) ['args', 'col_offset', 'end_col_offset', 'end_lineno', 'func', 'keywords', 'lineno'] >>> call.args[0].id 'a' >>> call.keywords[0].arg 'b' >>> call.keywords[0].value.value 2
Basically, these ast objects contain all we need to know about the (parsed) source.
- meshed.makers.parse_body(body, *, body_index=None)[source]#
Turn one body statement into a
(target, call)pair of ast nodes, orNone.Assignments go through
parse_assignment; a bare call gets the dummy target_{body_index};returnstatements and string constants (docstrings) giveNone(skipped); anything else raisesValueError.
- meshed.makers.parse_steps(src)[source]#
Parse source code and generate tuples of information about it.
- Parameters:
src – The source string or a python object whose code string can be extracted.
- Returns:
And generator of “target_values”
>>> from meshed.makers import parse_steps >>> def foo(): ... x = func1(a, b=2) ... y = func2(x, c=3) >>> target_values = list(parse_steps(foo))
Let’s look at the first target_value to see what it contains:
>>> name, call = target_values[0] # a 2-tuple >>> assert isinstance(name, ast.Name) # the first element is a ast Name object >>> sorted(vars(name)) ['col_offset', 'ctx', 'end_col_offset', 'end_lineno', 'id', 'lineno'] >>> name.id 'x' >>> assert isinstance(call, ast.Call) # the first element is a ast Call object >>> sorted(vars(call)) ['args', 'col_offset', 'end_col_offset', 'end_lineno', 'func', 'keywords', 'lineno'] >>> call.args[0].id 'a' >>> call.keywords[0].arg 'b' >>> call.keywords[0].value.value 2
Basically, these ast objects contain all we need to know about the (parsed) source.
- meshed.makers.parsed_to_node_kwargs(target_value)[source]#
Extract FuncNode kwargs (name, out, and bind) from ast (target,value) pairs
- Parameters:
target_value – A (target, value) pair
- Return type:
- Returns:
A
{name:..., out:..., bind:...}dict (meant to be used to curry FuncNode
Where can you make make target_values? With the
parse_assignment_stepsfunction.>>> from meshed.makers import parse_assignment_steps >>> def foo(): ... x = func1(a, b=2) ... y = func2(x, func1, c=3, d=x) >>> for target_value in parse_assignment_steps(foo): ... for d in parsed_to_node_kwargs(target_value): ... print(d) {'name': 'func1', 'out': 'x', 'bind': {0: 'a', 'b': 2}} {'name': 'func2', 'out': 'y', 'bind': {0: 'x', 1: 'func1', 'c': 3, 'd': 'x'}}
- meshed.makers.robust_ast_parse(src)[source]#
Parse
srcwithast.parse, retrying with the common leading indent stripped onIndentationError.
- meshed.makers.signed_itemgetter(*keys)[source]#
Like
operator.itemgetter, except has a signature, which we needed
- meshed.makers.simple_code_to_digraph(src)#
Make a
graphviz.Digraphof theDAGthatcode_to_dag(src)builds.
- meshed.makers.triples_to_fnodes(triples)[source]#
Converts an iterable of func call triples to an iterable of
FuncNodeobjects. (Which in turn can be converted to aDAG.)Note how the python identifiers are extracted (on the basis of “an unbroken sequence of alphanumerical (and underscore) characters”, ignoring all other characters).
>>> from meshed import DAG >>> dag = DAG( ... triples_to_fnodes( ... [ ... ('alpha bravo', 'charlie', 'delta echo'), ... (' foxtrot &^$#', 'golf', ' alpha, echo'), ... ]) ... ) >>> print(dag.synopsis_string()) delta,echo -> charlie -> alpha__bravo alpha__bravo -> alpha__0 -> alpha alpha__bravo -> bravo__1 -> bravo alpha,echo -> golf -> foxtrot