This guide introduces the core concepts of dags and shows you how to get started.
Core Concept¶
dags works by analyzing function signatures to build a dependency graph. If a function
has a parameter named x, and another function is named x, then the first function
depends on the second.
def x():
return 1def y(x):
return x + 1y depends on x because its parameter is named x. Visually, we can represent this as:
Basic Usage¶
Creating a Combined Function¶
We start by defining individual functions that represent parts of a simple tax model. In a real project, each function might live in its own module — what matters is that dags can discover dependencies from parameter names alone.
import dags
def income():
return 50000
def tax_rate():
return 0.3
def tax(income, tax_rate):
return income * tax_rate
def net_income(income, tax):
return income - tax
all_functions = {
"income": income,
"tax_rate": tax_rate,
"tax": tax,
"net_income": net_income,
}As before, we can visualize the dependencies:
We have collected all four functions into a single dictionary. This is the typical starting point: in a real economic model you might have dozens of functions computing income, taxes, transfers, utility, etc.
Now we use concatenate_functions to combine them into one function that we can call. We pass:
functions— the dictionary of all available functionstargets— which outputs we actually want (here:net_incomeandtax)return_type="dict"— return results as a dictionary (the default is a tuple)
combined_no_inputs = dags.concatenate_functions(
functions=all_functions,
targets=["net_income", "tax"],
return_type="dict",
)Since every input (income, tax_rate) is already provided by a function in the dictionary, the combined function needs no arguments at all:
combined_no_inputs(){'net_income': 35000.0, 'tax': 15000.0}Providing External Inputs¶
In the example above, income and tax_rate were hard-wired as functions that always return the same value. That is rarely useful in practice — typically these values come from data or vary across scenarios.
A more realistic setup is to include only the functions that actually compute something, and leave the raw inputs to be provided when calling the combined function:
tax_and_net_income = {"tax": tax, "net_income": net_income}When we combine just these two functions, dags notices that income and tax_rate are not produced by any function in the dictionary. It turns them into required arguments of the combined function.
combined_with_inputs = dags.concatenate_functions(
functions=tax_and_net_income,
targets=["net_income", "tax"],
return_type="dict",
)Calling without arguments raises an InvalidFunctionArgumentsError, telling you exactly which inputs are missing:
combined_with_inputs()---------------------------------------------------------------------------
InvalidFunctionArgumentsError Traceback (most recent call last)
Cell In[8], line 1
----> 1 combined_with_inputs()
File ~/checkouts/readthedocs.org/user_builds/dags/checkouts/latest/src/dags/output.py:102, in dict_output.<locals>.decorator_dict_output.<locals>.wrapper_dict_output(*args, **kwargs)
100 @functools.wraps(func)
101 def wrapper_dict_output(*args: P.args, **kwargs: P.kwargs) -> dict[str, T]:
--> 102 raw = func(*args, **kwargs)
103 return dict(zip(keys, raw, strict=True))
File ~/checkouts/readthedocs.org/user_builds/dags/checkouts/latest/src/dags/signature.py:138, in with_signature.<locals>.decorator_with_signature.<locals>.wrapper_with_signature(*args, **kwargs)
134 _fail_if_duplicated_arguments(present_args, present_kwargs, funcname)
135 _fail_if_invalid_keyword_arguments(
136 present_kwargs, valid_kwargs, funcname
137 )
--> 138 _fail_if_missing_arguments(
139 present_args, present_kwargs, valid_kwargs, funcname
140 )
141 return func(*args, **kwargs)
File ~/checkouts/readthedocs.org/user_builds/dags/checkouts/latest/src/dags/signature.py:219, in _fail_if_missing_arguments(present_args, present_kwargs, required_args, funcname)
217 missing_str = ", ".join(sorted(missing))
218 msg = f"{funcname} is missing required argument{s}: {missing_str}"
--> 219 raise InvalidFunctionArgumentsError(msg)
InvalidFunctionArgumentsError: The concatenated function is missing required arguments: income, tax_rateWe provide the missing inputs as keyword arguments:
combined_with_inputs(income=50000, tax_rate=0.3){'net_income': 35000.0, 'tax': 15000.0}Return Types¶
By default, concatenate_functions returns results as a tuple (in the order of targets). We used return_type="dict" above to get a labeled dictionary instead, which is usually more convenient:
combined_tuple = dags.concatenate_functions(
functions=all_functions,
targets=["net_income", "tax"],
)
combined_tuple()(35000.0, 15000.0)Inspecting the DAG¶
Under the hood, dags builds a dependency graph to figure out which functions to run and in what order. You can inspect this graph directly using create_dag, which returns a networkx DiGraph:
dag = dags.create_dag(
functions=all_functions,
targets=["net_income"],
)list(dag.nodes())['income', 'tax_rate', 'tax', 'net_income']list(dag.edges())[('income', 'tax'),
('income', 'net_income'),
('tax_rate', 'tax'),
('tax', 'net_income')]Finding Dependencies¶
get_ancestors is useful for understanding which functions (and inputs) contribute to a given target. This can help you debug unexpected results or understand a large model’s structure:
dags.get_ancestors(
functions=all_functions,
targets=["net_income"],
){'income', 'tax', 'tax_rate'}Inspecting Functions¶
dags provides utility functions for working with function signatures. These are handy when you need to programmatically inspect or modify functions.
Getting Function Arguments¶
get_free_arguments returns the parameter names of a function (excluding those which have been partialed already and those with defaults):
def my_func(a, b, c=1):
return a + b + c
dags.get_free_arguments(my_func)['a', 'b', 'c']Getting Type Annotations¶
get_annotations returns type annotations:
def my_func(a: int, b: float) -> float:
return a + b
dags.get_annotations(my_func){'return': float, 'a': int, 'b': float}Renaming Arguments¶
When integrating functions from different sources — say, a tax module that uses gross_income and a benefits module that uses total_income for the same quantity — you need to align parameter names so dags can connect them. rename_arguments creates a wrapper with renamed parameters:
def original(x, y):
return x + y
renamed = dags.rename_arguments(original, mapper={"x": "a", "y": "b"})
dags.get_free_arguments(renamed)['a', 'b']Next Steps¶
Learn about common usage patterns from real projects
Explore tree structures for nested data