recipe_grid.recipe
: Recipe Data Model¶
The recipe_grid.recipe
module defines the Directed Acyclic Graph
(DAG) data structure used to describe recipes.
Overview¶
Recipes are defined as a series of trees, principally consisting of
Ingredient
nodes and Step
nodes.
An Ingredient
node contains a name and an optional quantity.
A Step
node contains a description of the step and a series of
input nodes (children).
A simple recipe might only consist of a single tree of Ingredient
and Step
nodes. For example, here’s how a simple pasta meal might
be represented.
![digraph foo {
subgraph {
node [shape=ellipse]
onion [label=<Onion<br/>(Ingredient)>]
tomatoes [label=<Chopped Tomatoes<br/>(Ingredient)>]
pasta [label=<Pasta<br/>(Ingredient)>]
}
subgraph {
node [shape=rectangle]
chop [label=<Chop<br/>(Step)>]
fry [label=<Fry<br/>(Step)>]
boildown [label=<Boil down<br/>(Step)>]
boil [label=<Boil<br/>(Step)>]
mix [label=<Mix<br/>(Step)>]
}
chop -> onion;
fry -> chop;
boildown -> fry;
boildown -> tomatoes;
boil -> pasta;
mix -> boildown;
mix -> boil;
}](_images/graphviz-f90fa211730acb239d920c7be13f45872440432a.png)
Sometimes a sub tree within a recipe might be identified as its own sub recipe
and given an emphasising border and title in the rendered recipe table. For
example, in the recipe above we might make the pasta sauce a sub recipe. To
represent this, a SubRecipe
node type is used which always has exactly one
child and one or more named outputs. In this example, there is only one output
(‘Pasta sauce’). We’ll return to the case with multiple named outputs later.
![digraph foo {
subgraph {
node [shape=ellipse]
onion [label=<Onion<br/>(Ingredient)>]
tomatoes [label=<Chopped Tomatoes<br/>(Ingredient)>]
pasta [label=<Pasta<br/>(Ingredient)>]
}
subgraph {
node [shape=rectangle]
chop [label=<Chop<br/>(Step)>]
fry [label=<Fry<br/>(Step)>]
boildown [label=<Boil down<br/>(Step)>]
boil [label=<Boil<br/>(Step)>]
mix [label=<Mix<br/>(Step)>]
}
subgraph {
node [shape=Mrecord]
sauce [label="{<fs>Pasta sauce|<fo>(SubRecipe)}"]
}
chop -> onion;
fry -> chop;
boildown -> fry;
boildown -> tomatoes;
sauce:fo -> boildown;
boil -> pasta;
mix -> sauce:fs:n;
mix -> boil;
}](_images/graphviz-42e2a893c108cdb715c8c1b7df1ffb6ebf82f8d5.png)
In other cases, an ingredient or whole sub recipe may be split into parts and
used separately. In this case, a Reference
node may be used to
refer to a SubRecipe
which must be the root of a previous tree in the
recipe. For example, in the recipe below, a Sub Recipe for grated cheese is
referenced in two places, with 75% being mixed into the main recipe and 25%
being used to top it:
![digraph foo {
subgraph {
node [shape=ellipse]
cheese [label=<Cheese<br/>(Ingredient)>]
white_sauce [label=<White sauce<br/>(Ingredient)>]
pasta [label=<Pasta<br/>(Ingredient)>]
}
subgraph {
node [shape=rectangle]
grate [label=<Grate<br/>(Step)>]
mix [label=<Mix<br/>(Step)>]
top [label=<Top<br/>(Step)>]
}
subgraph {
node [shape=diamond]
refm [label=<75%<br/>(Reference)>]
reft [label=<25%<br/>(Reference)>]
}
subgraph {
node [shape=Mrecord]
grated_cheese [label="{<fc>Grated cheese|<fo>(SubRecipe)}"]
}
grate -> cheese;
grated_cheese:fo -> grate;
mix -> refm;
mix -> white_sauce;
mix -> pasta;
top -> mix;
top -> reft;
subgraph {
edge [constraint=false,style=dashed];
refm:sw -> grated_cheese:fc:n
reft:sw -> grated_cheese:fc:n
}
}](_images/graphviz-6e6d59a98bf11c0933b22921e6a96157bbec3f28.png)
A final case is where a SubRecipe
has more than one output. This might occur
when, for example, when vegetables are boiled and both the vegetables and the
water are reserved and used in a later step. Each output may then be referenced
separately by Reference
nodes. SubRecipe
nodes with more than
one output must be the root of a recipe tree.
![digraph foo {
subgraph {
node [shape=ellipse]
vegetables [label=<Vegetables<br/>(Ingredient)>]
gravy_granules [label=<Gravy granules<br/>(Ingredient)>]
}
subgraph {
node [shape=rectangle]
boil [label=<Boil, reserving water<br/>(Step)>]
make [label=<Make gravy<br/>(Step)>]
pour [label=<Pour over<br/>(Step)>]
}
subgraph {
node [shape=diamond]
refv [label=<(Reference)>]
refw [label=<(Reference)>]
}
subgraph {
node [shape=Mrecord]
boiled_veg [label="{{<fv>Boiled Vegetables|<fw>Water}|<fo>(SubRecipe)}"]
}
boil -> vegetables;
boiled_veg -> boil;
make -> gravy_granules;
make -> refw;
pour -> make;
pour -> refv;
subgraph {
edge [constraint=false,style=dashed];
refv:sw -> boiled_veg:fv:n
refw:sw -> boiled_veg:fw:n
}
}](_images/graphviz-b88e751c26dffd45e54e77652caf5abe5c4e08cf.png)
Data structures¶
A Recipe is defined at its root by a Recipe
instance. In a simple
document there will be a single Recipe
instance which contains the
entire DAG for that recipe. For more complex documents where the recipe is
presented in several sections, each section of the recipe will be represented
by its own Recipe
with back-references to prior Recipe
s in Recipe.follows
.
- class recipe_grid.recipe.Recipe(recipe_trees: Tuple[RecipeTreeNode, ...], follows: Recipe | None = None)¶
A recipe, defined in terms of a series of recipe trees. Later trees may reference the outputs of earlier trees resulting in a Directed Acyclic Graph (DAG) structure describing a recipe.
- recipe_trees: Tuple[RecipeTreeNode, ...]¶
The recipe tree roots for this
Recipe
.
DAG Structure¶
Recipe trees are in turn defined using subclasses of
RecipeTreeNode
:
- class recipe_grid.recipe.RecipeTreeNode¶
- iter_children() Iterable[RecipeTreeNode] ¶
Iterate over the children of this node.
- substitute(old: RecipeTreeNode, new: RecipeTreeNode) RecipeTreeNode ¶
Return a copy of this recipe tree with the node
old
replaced withnew
. (The old tree will remain intact).
- class recipe_grid.recipe.Ingredient(description: ScaledValueString, quantity: Quantity | None = None)¶
A leaf node in a tree describing an ingredient to be used.
- description: ScaledValueString¶
A description of the ingredient.
- class recipe_grid.recipe.Step(description: ScaledValueString, inputs: Tuple[RecipeTreeNode, ...])¶
A node in a tree where the node represents a step in a recipe (e.g. ‘mix’) and the children represent the inputs to that step. Children may be other
Step
instances,Ingredient
instances orReference
instances referring to outputs of otherSubRecipe
s.- description: ScaledValueString¶
A description of the step to be carried out.
- inputs: Tuple[RecipeTreeNode, ...]¶
The inputs to (i.e. children) of this step.
- class recipe_grid.recipe.SubRecipe(sub_tree: RecipeTreeNode, output_names: Tuple[ScaledValueString, ...], show_output_names: bool = True)¶
A sub recipe is a node representing a logical division in a recipe with some semantic significance. For example, a pie recipe may divide the recipe into two sub recipes: one for the filling and another for the pastry.
- sub_tree: RecipeTreeNode¶
The steps describing this sub-recipe.
- output_names: Tuple[ScaledValueString, ...]¶
One or more names given to the outputs of this sub recipe.
In the simple case there will be exactly one named output. In our pie recipe example, the sub recipe for the filling might have a single output named “Filling” and the pastry sub recipe might have one named “Pastry”.
For sub recipes which produce multiple outputs, names for these must be enumerated. For example, a sub recipe describing boiling some vegetables where both the vegetables and water will be used, two output names (e.g. “Boiled Vegetables” and “Vegetable Water” might be given).
Note
Sub recipes with a single output name may appear anywhere within a recipe tree. Where a SubRecipe is not at the root of a recipe it will typically be rendered inline but inset and labelled with the output name.
Sub recipes with more than one named output may only be used as the root of a recipe tree.
- show_output_names: bool = True¶
Specifies whether the output name(s) for this subrecipe should be rendered. For example when a sub-recipe consists of a single ingredient (e.g. ‘300g spam’) with a single output (e.g. ‘spam’), adding extra labelling would just be a distraction and so this setting should be False.
- class recipe_grid.recipe.Reference(sub_recipe: SubRecipe, output_index: int = 0, amount: Quantity | Proportion = Proportion(value=1.0, percentage=False, remainder_wording=None, preposition=''))¶
A reference to a named output of a
SubRecipe
.- output_index: int = 0¶
The
SubRecipe
and output index being referenced. Only sub recipes which form the root of a recipe tree may be referenced.
- amount: Quantity | Proportion = Proportion(value=1.0, percentage=False, remainder_wording=None, preposition='')¶
The amount of the referenced output to use. Default: all of it.
Quantities and Proportions¶
Absolute quantities (for ingredients and references) and relative proportions (for references only) are defined as follows:
- class recipe_grid.recipe.Quantity(value: int | float | Fraction, unit: str | None = None, value_unit_spacing: str = '', preposition: str = '')¶
An absolute quantity.
Suggested rendering:
q.value + (q.value_unit_spacing + q.unit if q.unit is not None else "") + q.preposition
- class recipe_grid.recipe.Proportion(value: int | float | Fraction | None = None, percentage: bool | None = None, remainder_wording: str | None = None, preposition: str = '')¶
A relative proportion.
Suggested rendering:
( (q.value * 100 if q.percentage else q.value) if q.value is None else q.remainder_wording ) + q.preposition
Exceptions¶
The DAG structure invariants are enforced by checks in their constructors and
any non-conforming input will produce a RecipeInvariantError
subclass.
- exception recipe_grid.recipe.RecipeInvariantError¶
Base class for exceptions thrown when an invariant of the
Recipe
data structure is violated.
- exception recipe_grid.recipe.MultiOutputSubRecipeUsedAsNonRootNodeError¶
Thrown when a
SubRecipe
with more than one named output is added as non-root node in a recipe tree.
- exception recipe_grid.recipe.OutputIndexError¶
Thrown when a
Reference
refers to output which does not exist in the referencedSubRecipe
.