/*! \page page_python_blocks Python Blocks

How to create blocks in Python

\section pyblocks_streaming Streaming Data Blocks

We create blocks in Python very much like we would in C++, just with
more Python. Figure out which type of block you want to create:

\li general block (gr.basic_block)
\li synchronous block (gr.sync_block)
\li decimator (gr.sync_decimator)
\li interpolator (gr.sync_interpolator)

The block class inherits from one of these base classes, and then in
defining the parent class, we set the I/O signature. However, unlike
in C++ where we use the gr::io_signature class, here we can just
create a Python list of the I/O data sizes using numpy data types:

\li numpy.int8
\li numpy.int16
\li numpy.int32
\li numpy.float32
\li numpy.float64

Like a normal C++ version of the block, we then create and initialize
any variables in the constructor, define any setters and getters, and
create the work function. The prototype for the work function is quite
simple:

\code
def work(self, input_items, output_items)
\endcode

The input_items and output_items are lists of lists. The input_items
contains a vector of input samples for every input stream, and the
output_items is a vector for each output stream where we can place
items. Then length of output_items[0] is equivalent to the
noutput_items concept we are so familiar with from the C++ blocks.

Following is an example Python block that adds two input streams
together. This block is used in the qa_block_gateway.py test code.

\code
class add_2_f32_1_f32(gr.sync_block):
    def __init__(self):
        gr.sync_block.__init__(
            self,
            name = "add 2 f32",
            in_sig = [numpy.float32, numpy.float32],
            out_sig = [numpy.float32],
        )

    def work(self, input_items, output_items):
        output_items[0][:] = input_items[0] + input_items[1]
        return len(output_items[0])
\endcode

The block defines two input floating point streams by setting in_sig
to "[numpy.float32, numpy.float32]" and a single output float stream
in out_sig of "[numpy.float32]."

The work function then just adds the two input streams together. The
streams are input_items[0] and input_items[1]. The block still returns
the concept of noutput_items like we use in C++, only we get it here
by getting len(output_items[0]). Because this is a sync_block, we also
know that the size of the input_items for both streams is the same as
the size of the output_items vector.



\section pyblocks_tags Using Stream Tags

Python blocks have access to the stream tag system like their C++
counterparts. The interface is almost identical except they behave
just a bit more like we would expect in Python.

To add tags to the data stream, we use the add_item_tag function:

\code
def work(self, input_items, output_items):
    ....
    add_item_tag(which_output, abs_offset,
                 key, value, srcid)
    ....
\endcode

The abs_offset is an integer of the sample that the tag is attached
to, and key and value are both PMTs to set the key:value pair of the
tag information, and the srcid is an optional PMT to define the source
of the block that generate the tag.

We then can get tags using either the get_tags_in_range or
get_tags_in_window. Again, like their C++ counter parts, the
get_tags_in_range uses the absolute item offset numbering (using
nitems_read) while the get_tags_in_window uses relative offsets within
the current window of items available to the work function. The main
difference from the C++ function is that instead of having the first
argument be a vector where the tags are stored, the Python version
just returns a list of tags. We would use it like this:

\code
def work(self, input_items, output_items):
    ....
    tags = get_tags_in_window(which_input, rel_start, rel_end)
    ....
\endcode



\section pyblocks_msgs Using Message Passing

Again, like their C++ counterparts, Python blocks can use the
asynchronous message passing interface. We define output message
handlers using:

\code
self.message_port_register_out(pmt.intern("<port name>"))
\endcode

We can then post messages to this using the message_port_pub function:

\code
self.message_port_pub(pmt.intern("<port name>"), <pmt message>)
\endcode

We then register input messages and handlers in similar ways:

\code
self.message_port_register_in(pmt.intern("<port name>"))
self.set_msg_handler(pmt.intern("<port name>"), <msg handler function>)
\endcode

Putting this together below is a very simple example:

\code
class msg_block(gr.basic_block):
    def __init__(self):
        gr.basic_block.__init__(
            self,
            name="msg_block",
            in_sig=None,
            out_sig=None)

        self.message_port_register_out(pmt.intern('msg_out'))
        self.message_port_register_in(pmt.intern('msg_in'))
        self.set_msg_handler(pmt.intern('msg_in'), self.handle_msg)

    def handle_msg(self, msg):
        self.message_port_pub(pmt.intern('msg_out'),
                              pmt.intern('message received!'))
\endcode

*/