.. _flexible_edge_behaviors_between_nodes: Flexible Edge Behaviors Between Nodes ===================================== Whereas each node in the graph is defined by an asynchronous generator function, the edge between two nodes is represented by a queue object that feeds data into the destination node and collects data from the source nodes (see :ref:`tasks_and_queues`). By default, this queue object spits out data items to the associated destination node one at a time, as it becomes available from any of the source nodes, and as efficieintly as coordinated by the async event loop. In your application, you may want to override this default queue for custom edge behaviors for one or more edges in the graph. When a node is created with :func:`~async_graph_data_flow.AsyncGraph.add_node`, custom edge behaviors can be implemented by passing in a custom queue object to the ``queue`` parameter. This queue object must be an instance of :class:`asyncio.Queue` or a subclass thereof. (The default queue object is simply an instance of :class:`asyncio.Queue`.) A custom queue object can result in a variety of edge behaviors between nodes: * A queue pre-loaded with data items: At the outset of the graph execution, if you would like certain nodes (including non-starting nodes of the graph) to receive specific data items immediately, you can pass in a queue object pre-loaded with these data items for these nodes. Implementationally, create an :class:`asyncio.Queue` instance and call ``await queue.put(item)`` as desired to pre-load the queue with data items, then pass this queue object to the ``queue`` parameter of the destination node at :func:`~async_graph_data_flow.AsyncGraph.add_node`. * Do something with the data after it is received from a source node and before it is fed to the destination node: If you would like to transform the data or perform any other operation on it, your custom queue likely comes from a subclass of :class:`asyncio.Queue` where you override the ``get`` and/or ``put`` methods, becase these methods control what happens to the data item after it has arrived at the queue and before it is fed to the destination node. In the following examples, we focus on the second use case, where flexible edge behaviors are achieved by a subclass of :class:`asyncio.Queue`. Batching -------- Instead of the edge queue receiving and feeding data items one at a time, you may want to batch the data items before feeding them to the destination node. To do so, let's define the following: * A ``BatchQueue`` class that subclasses :class:`asyncio.Queue` for batching data items. * A sentinel class ``EndOfData`` to signal the end of data. Perhaps the most common use case for batching is to group data items into batches of a fixed size: .. mermaid:: flowchart LR start1[ ] -.- queue1((" ")) subgraph data_source queue1 --> node1["  "] end style start1 fill-opacity:0, stroke-opacity:0; queue2((custom queue
to batch data)) node2["async gen
function"] subgraph batched_inputs queue2 --> |"three inputs:
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10]"| node2 end style queue2 fill:#ffccff, stroke:#030303, stroke-width:2px; node1 --> |yields
1, 2, 3, ..., 10| queue2 node2 -.-> STOP[ ] style STOP fill-opacity:0, stroke-opacity:0; .. literalinclude:: ../../examples/batching_by_batch_size.py :language: python :emphasize-lines: 6, 10, 39, 51 Using the same ``BatchQueue`` and ``EndOfData`` defined above, it's also possible to have the effect of waiting for all data items from the source nodes before feeding them to the destination node, by setting the batch size to ``float('inf')`` (infinity, for no batch size limit). Beyond batch size, batching can be controlled by other criteria using your own custom queue class, such as special data items or markers (in a way, the ``EndOfData`` marker above is an example), time intervals, or other conditions. Combining Data from Multiple Source Nodes ------------------------------------------ By default, a source node must yield data that matches the function signature of the destination node. To work around this constraint, a custom queue object at the edge between the source and destination nodes can be used. One use case is to combine data from multiple source nodes before feeding it to the destination node. For example: .. mermaid:: flowchart LR start1[ ] -.- queue1((" ")) subgraph threes queue1 --> node1["  "] end style start1 fill-opacity:0, stroke-opacity:0; start2[ ] -.- queue2((" ")) subgraph fours queue2 --> node2["  "] end style start2 fill-opacity:0, stroke-opacity:0; start3[ ] -.- queue3((" ")) subgraph fives queue3 --> node3["  "] end style start3 fill-opacity:0, stroke-opacity:0; queue4((custom queue
to combine data)) node4["async gen
function"] subgraph final_node queue4 --> |"three inputs
to the func:
(3, 4, 5)
(3, 4, 5)
(3, 4, 5)"| node4 end style queue4 fill:#ffccff, stroke:#030303, stroke-width:2px; node1 --> |yields
3, 3, 3| queue4 node2 --> |yields
4, 4, 4, 4| queue4 node3 --> |yields
5, 5, 5, 5, 5| queue4 node4 -.-> STOP[ ] style STOP fill-opacity:0, stroke-opacity:0; .. literalinclude:: ../../examples/combine_data_from_multiple_source_nodes.py :language: python :emphasize-lines: 7, 44, 51, 58, 72