{ "info": { "author": "Leonardo Giordani", "author_email": "giordani.leonardo@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License (GPL)", "License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Topic :: Internet", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Networking" ], "description": "Postage - a Python library for AMQP-based network components\n============================================================\n\n|Build Status| |Version| |PyPi Downloads|\n\nPostage is a Python library which leverages\n`pika `__ and AMQP (through a broker like\n`RabbitMQ `__) to build network-aware software\ncomponents.\n\nThrough **pika** you can add to any Python program the capability of\nsending and receiving messages using AMQP. For example you can listen or\ncommunicate with other programs through a RabbitMQ cluster (The\nreference AMQP broker in this documentation is RabbitMQ).\n\nPostage is a layer built on pika, and aims to simplify the\nimplementation of the messaging part in your Python programs, hiding (as\nmuch as possible) the AMQP details. it provides the following structures\nand concepts:\n\n- **Fingerprint**: an automatic network fingerprint for an application,\n which contains useful data to uniquely identify your program on the\n cluster.\n\n- **Message encoding** implemented in a stand-alone class which can\n easily be replaced by one of your choice. Default encoding is JSON.\n\n- A **message** implementation based on a plain Python dictionary (thus\n usable even without Postage). Messages can be of three types:\n **command**, **status** or **result**, representing the actions of\n asking something (command), communicating something (status) or\n answering a request (result). Command messages can be fire-and-forget\n or RPC. Result messages can further transport a success, an error, or\n a Python exception.\n\n- **Exchanges** can be declared and customized inheriting a dedicated\n class.\n\n- A generic message **producer** class: it simplifies the definition of\n a set of messages an exchange accepts, which helps in defining a\n network API of your component.\n\n- A generic message consumer, or **processor**, that implements a\n powerful handlers mechanism to define which incoming messages a\n component is interested in and how it shall answer.\n\nAbout microthreads\n==================\n\nPostage leverages a microthread library to run network components. The\ncurrent implementation is very simple and largely underused, due to the\nblocking nature of the pika adapter being used. Future plans include a\nreplacement with a more powerful library. This implementation is a good\nstarting point if you want to understand generator-based microthreads\nbut do not expect more. You can read this series of articles\n`here `__\nto begin digging in the matter.\n\nAbout versioning\n================\n\nThis is Postage version 1.1.0.\n\nThis library is versioned with a A.B.C schema ( **A**\\ PI, **B**\\ OOST,\n**C**\\ OMPLAINT ).\n\n- Any change in the COMPLAINT number is a bugfix or even a typo\n correction in the documentation; it is transparent to running systems\n (except that hopefully *that nasty bug* is no more there).\n- Any change in the BOOST number is an API addition. It is transparent\n to running systems, but you should check the changelog to check\n what's new, perhaps *that impossible thing* is now easy as pie.\n- Any change in the API number has to be taken very seriously. Sorry\n but for some reason the API changed, so your running code will no\n more work.\n\nSo update to 1.0.x without hesitation, await the full-of-features 1.1.0\nrelease and beware of the frightening version 2.0.0 that will crash your\nsystems! =)\n\n[The code contained in the *master* branch on GitHub before the PyPI\nrelease was marked with version 3.0.x. Indeed that is the real version\nof the package but since previous versions were not released I wanted to\nbe a good releaser and start from version 1]\n\nLicense\n=======\n\nThis package, Postage, a Python library for AMQP-based network\ncomponents, is licensed under the terms of the GNU General Public\nLicense Version 2 or later (the \"GPL\"). For the GPL 2 please see\nLICENSE-GPL-2.0.\n\nContributing\n============\n\nAny form of contribution is highly welcome, from typos corrections to\ncode patches. Feel free to clone the project and send pull requests.\n\nQuick start\n===========\n\nYou can find the source code for the following examples in the\n``demos/`` directory.\n\nA basic echo server\n-------------------\n\nLet's implement a basic echo server made of two programs. The first sits\ndown and waits for incoming messages with the ``'echo'`` key, the second\nsends one message each time it is run.\n\nBe sure to have a running RabbitMQ system configured with a ``/``\nvirtual host and a ``guest:guest`` user/password.\n\nThe file ``echo_shared.py`` contains the definition of the exchange in\nuse\n\n.. code:: python\n\n from postage import messaging\n\n\n class EchoExchange(messaging.Exchange):\n name = \"echo-exchange\"\n exchange_type = \"direct\"\n passive = False\n durable = True\n auto_delete = False\n\nThe class attributes are the standard paramenters of AMQP exchanges, see\n``exchange_declare()`` in Pika\n`documentation `__.\n\nThe file ``echo_send.py``\\ defines a message producer and uses it to\nsend a message\n\n.. code:: python\n\n from postage import messaging\n import echo_shared\n\n\n class EchoProducer(messaging.GenericProducer):\n eks = [(echo_shared.EchoExchange, 'echo-rk')]\n\n producer = EchoProducer()\n producer.message_echo(\"A test message\")\n\nThe producer has two goals: the first is to **define the standard\nexchange and routing key used to send the messages**, which prevents you\nfrom specifying both each time you send a message. The second goal is to\n**host functions that build messages**; this is an advanced topic, so it\nis discussed later.\n\nIn this simple case the producer does all the work behind the curtain\nand you just need to call ``message_echo()`` providing it as many\nparameters as you want. The producer creates a command message named\n``'echo'``, packs all ``*args`` and ``**kwds`` you pass to the\n``message_echo()`` method inside it, and sends it through the AMQP\nnetwork.\n\nThe file ``echo_receive.py`` defines a message processor that catches\nincoming command messages named ``'echo'`` and prints their payload.\n\n.. code:: python\n\n from postage import microthreads\n from postage import messaging\n import echo_shared\n\n\n class EchoReceiveProcessor(messaging.MessageProcessor):\n @messaging.MessageHandler('command', 'echo')\n def msg_echo(self, content):\n print content['parameters']\n\n eqk = [(echo_shared.EchoExchange, [('echo-queue', 'echo-rk'), ])]\n\n scheduler = microthreads.MicroScheduler()\n scheduler.add_microthread(EchoReceiveProcessor({}, eqk, None, None))\n for i in scheduler.main():\n pass\n\nThe catching method is arbitrarily called ``msg_echo()`` and decorated\nwith ``MessageHandler``, whose parameters are the type of the message\n(``command``, that means we are instructing a component to do something\nfor us), and its name (``echo``, automatically set by calling the\n``message_echo()`` method). The ``msg_echo()`` method must accept one\nparameter, besides ``self``, that is the content of the message. The\ncontent is not the entire message, but a dictionary containing only the\npayload; in this case, for a generic ``command`` message, the payload is\na dictionary containing only the ``parameters`` key, that is\n\nSeems overkill? Indeed, for such a simple application, it is. The\nfollowing examples will hopefully show how those structures heavily\nsimplify complex tasks.\n\nTo run the example just open two shells, execute\n``python echo_receive.py`` in the first one and ``python echo_send.py``\nin the second. If you get a\n``pika.exceptions.ProbableAuthenticationError`` exception please check\nthe configuration of the RabbitMQ server; you need to have a ``/``\nvirtual host and the ``guest`` user shall be active with password\n``guest``.\n\nAn advanced echo server\n-----------------------\n\nLet's add a couple of features to our basic echo server example. First\nof all we want to get information about who is sending the message. This\nis an easy task for Fingerprint objects\n\n.. code:: python\n\n from postage import messaging\n import echo_shared\n\n\n class EchoProducer(messaging.GenericProducer):\n eks = [(echo_shared.EchoExchange, 'echo-rk')]\n\n\n fingerprint = messaging.Fingerprint('echo_send', 'application').as_dict()\n producer = EchoProducer(fingerprint)\n producer.message_echo(\"A single test message\")\n producer.message_echo(\"A fanout test message\", _key='echo-fanout-rk')\n\nAs you can see a Fingerprint just needs the name of the application\n(``echo_send``) and a categorization (``application``), and\nautomatically collect data such as the PID and the host. On receiving\nthe message you can decorate the receiving function with\n``MessageHandlerFullBody`` to access the fingerprint\n\n.. code:: python\n\n @messaging.MessageHandlerFullBody('command', 'echo')\n def msg_echo_fingerprint(self, body):\n print \"Message fingerprint: %s\", body['fingerprint']\n\nThe second thing we are going to add is the ability to send fanout\nmessages. When you connect to an exchange you can do it with a shared\nqueue, i.e. a queue declared with the same name by all the receivers, or\nwith a private queue, that is a unique queue for each receiver. The\nfirst setup leads to a round-robin consumer scenario, with the different\nreceivers picking messages from the same queue in turn. The second\nsetup, on the other hand, makes all the receivers get the same message\nsimultaneously, acting like a fanout delivery.\n\nThe file ``echo_shared.py`` does not change, since the Exchange has the\nsame difinition. In ``echo_receive.py`` we make the greatest number of\nchanges\n\n::\n\n from postage import microthreads\n from postage import messaging\n import echo_shared\n\n\n class EchoReceiveProcessor(messaging.MessageProcessor):\n def __init__(self, fingerprint):\n shared_queue = 'echo-queue'\n private_queue = 'echo-queue-{0}{1}'.format(fingerprint['pid'],\n fingerprint['host'])\n\n eqk = [\n (echo_shared.EchoExchange, [\n (shared_queue, 'echo-rk'),\n (private_queue, 'echo-fanout-rk')\n ]),\n ]\n super(EchoReceiveProcessor, self).__init__(fingerprint,\n eqk, None, None)\n\n @messaging.MessageHandler('command', 'echo')\n def msg_echo(self, content):\n print content['parameters']\n\n @messaging.MessageHandlerFullBody('command', 'echo')\n def msg_echo_fingerprint(self, body):\n print \"Message fingerprint: %s\", body['fingerprint']\n\n\n fingerprint = messaging.Fingerprint('echo_receive', 'controller').as_dict()\n\n scheduler = microthreads.MicroScheduler()\n scheduler.add_microthread(EchoReceiveProcessor(fingerprint))\n for i in scheduler.main():\n pass\n\nAs you can see the ``EchoReceiveProcessor`` redefines the ``__init__()``\nmethod to allow passing just a Fingerprint; as a side-effect, ``eqk`` is\nnow defined inside the method, but its nature does not change. It\nencompasses now two queues for the same exchange; the first queue is\nchared, given that every instance of the reveiver just names it\n``echo-queue``, while the second is private because the name changes\nwith the PID and the host of the current receiver, and those values\ntogether are unique in the cluster.\n\nSo we expect that sending messages with the ``echo`` key will result in\nhitting just one of the receivers at a time, in a round-robin fashion,\nwhile sending messages with the ``echo-fanout`` queue will reach every\nreceiver.\n\nWe defined two different functions to process the incoming ``echo``\nmessage, ``msg_echo()`` and ``msg_echo_fingerprint``; this shows that\nmultiple functions can be set as handler for the same messages. In this\nsimple case the two functions could also be merged in a single one, but\nsometimes it is better to separate the code of different\nfunctionalities, not to mention that the code could also be loaded at\nrun-time, through a plugin system or a live definition.\n\nAn RPC echo server\n------------------\n\nThe third version of the echo server shows how to implement RPC\nmessaging. As before the exchange does not change its signature, so\n``echo_shared.py`` remains the same. When sending the message we must\nspecify the we want to send the RPC form using ``rpc_echo()`` instead of\n``message_echo()``\n\n.. code:: python\n\n from postage import messaging\n import echo_shared\n\n class EchoProducer(messaging.GenericProducer):\n eks = [(echo_shared.EchoExchange, 'echo-rk')]\n\n fingerprint = messaging.Fingerprint('echo_send', 'application').as_dict()\n producer = EchoProducer(fingerprint)\n\n reply = producer.rpc_echo(\"RPC test message\")\n if reply:\n print reply.body['content']['value']\n else:\n print \"RPC failed\"\n\nRemember that RPC calls are blocking, so your program will hang at the\nline ``reply = producer.rpc_echo(\"RPC test message\")``, waiting for the\nserver to answer. Once the reply has been received, it can be tested and\nused as any other message; Postage RPC can return success, error or\nexception replies, and their content changes accordingly.\n\n.. code:: python\n\n from postage import microthreads\n from postage import messaging\n import echo_shared\n\n\n class EchoReceiveProcessor(messaging.MessageProcessor):\n def __init__(self, fingerprint):\n eqk = [\n (echo_shared.EchoExchange, [\n ('echo-queue', 'echo-rk'),\n ]), \n ]\n super(EchoReceiveProcessor, self).__init__(fingerprint, eqk, None, None)\n\n\n @messaging.RpcHandler('command', 'echo')\n def msg_echo(self, content, reply_func):\n print content['parameters']\n reply_func(messaging.MessageResult(\"RPC message received\"))\n \n\n\n fingerprint = messaging.Fingerprint('echo_receive', 'controller').as_dict()\n\n scheduler = microthreads.MicroScheduler()\n scheduler.add_microthread(EchoReceiveProcessor(fingerprint))\n for i in scheduler.main():\n pass\n\nThe receiver does not change severely; you just need to change the\nhandler dadicated to the incoming ``echo`` message. The decorator is now\n``RpcHandler`` and the method must accept a third argument, that is the\nfunction that must be called to answer the incoming message. You have to\npass this function a suitable message, i.e. a ``MessageResult`` if\nsuccessfull, other messages to signal an error or an exception. Please\nnote that after you called the reply function you can continue executing\ncode.\n\nAPI Documentation\n=================\n\nHere you find a description of the messaging part of Postage. Being\nPostage based on AMQP, this help presumes you are familiar with\nstructures defined by this latter (exchanges, queues, bindings, virtual\nhosts, ...) and that you already have a working messaging system (for\nexample a RabbitMQ cluster).\n\nIn the code and in the following text you will find the two terms\n\"application\" and \"component\" used with the same meaning: a Python\nexecutable which communicates with others using AMQP messages through\nPostage. Due to the nature of AMQP you can have components written in\nseveral languages working together: here we assumer both producers and\nconsumers are written using Postage, but remember that you can make\nPostage components work with any other, as far as you stick to its\nrepresentation of messages (more on that later).\n\nEnvironment variables\n---------------------\n\nPostage reads three environment variables, ``POSTAGE_VHOST``,\n``POSTAGE_USER``, and ``POSTAGE_PASSWORD``, which contain the RabbitMQ\nvirtual host in use, the user name and the password. The default values\nfor them are ``/``, ``guest``, ``guest``, i.e. the default values you\ncan find in a bare RabbitMQ installation. Previous versions used\n``POSTAGE_RMQ_USER`` and ``POSTAGE_RMQ_PASSWORD``, which are still\nsupported but deprecated.\n\nUsing the environment variables, especially ``POSTAGE_VHOST``, you can\neasily setup production and development environment and to switch you\njust need to set the variable before executing your Python components\n\n.. code:: sh\n\n POSTAGE_VHOST=development mycomponent.py\n\nYou obviously need to configure RabbitMQ according to your needs,\ndeclaring the virtual hosts you want.\n\nSetting up separate environment enables your components to exchange\nmessages without interfering with the production systems, thus avoiding\nyou to install a separate cluster to test software. The HUP acronym is\nused somewhere in the code to mean Host, User, Password, that is the\ntuple needed to connect to RabbitMQ plus the virtual host.\n\nA last environment variable, ``POSTAGE_DEBUG_MODE``, drives the debug\noutput if set to ``true``. It is intended for Postage debugging use\nonly, since its output is pretty verbose.\n\nFingerprint\n-----------\n\nWhen componentized system become large you need a good way to identify\nyour components, so a simple ``Fingerprint`` object is provided to\nencompass useful values, which are:\n\n- ``name``: the name of the component or executable\n- ``type``: a rough plain categorization of the component\n- ``pid``: the OS pid of the component executable\n- ``host``: the host the component is running on\n- ``user``: the OS user running the component executable\n- ``vhost``: the RabbitMQ virtual host the component is running on\n\nThis object is mainly used to simplify the management of all those\nvalues, and to allow writing compact code. Since Postage messages are\ndictionaries (see below) the object provides a ``as_dict()`` method to\nreturn its dictionary form, along with a ``as_tuple()`` method to\nprovide the tuple form.\n\nYou can use any class to encompass the values you need to identify your\ncomponents: Postage ALWAYS uses the dictionary form of fingerprints, so\nyou need a way to give a meaningful dictionary representation of your\nclass of choice.\n\nObviously to uniquely identify a component on a network you need just\nhost and pid values, but a more complete set of values can greatly\nsimplify management.\n\nFingerprint objects can automatically retrieve all values from the OS,\nneeding only the name and type values; if not passed those are ``None``.\n\n.. code:: python\n\n fingerprint = Fingerprint(name=\"mycomponent\")\n print fingerprint.as_dict()\n\nEncoder\n-------\n\nPostage messages are Python dictionaries serialized in JSON. The\n``JsonEncoder`` object provides the ``encode()`` and ``decode()``\nmethods and the correct type ``application/json``. Encoder class can be\neasly replaced in your components, provided that it sticks to this\ninterface.\n\nMessages\n--------\n\nTo manage the different types of messages, appropriate objects have been\ndefined. The base object is ``Message``: it has a **type**, a **name**\nand a **category**. It can encompass a **fingerprint** and a\n**content**, which are both dictionaries.\n\nThe type of the message is free, even if some have been already defined\nin Postage: **command**, **status**, and **result**. This categorization\nallows the consumers to filter incoming messages according to the action\nthey require.\n\nThe category of the message is not free, and must be one of **message**\nand **rpc** (this nomenclature is somewhat misleading, since RPC are\nmessages just like the standard ones; future plans include a review of\nit). The first type marks fire-and-forget messages, while the second\nsignals RPC ones.\n\nThe dictionary form of the message is the following:\n\n.. code:: python\n\n message = {\n 'type': message_type,\n 'name': message_name,\n 'category': message_category,\n 'version': '2',\n 'fingerprint': {...},\n 'content': {...},\n '_reserved': {...}\n }\n\nThe ``content`` key contains the actual data you put in your message,\nand its structure is free.\n\n**Command** messages send a command to another component. The command\ncan be a fire-and-forget one or an RPC call, according to the message\ncategory; the former is implemented by the ``MessageCommand`` class,\nwhile the latter is implemented by ``RpcCommand``. Both classes need the\nname of the command and an optional dictionary of parameters, which are\nimposed by the actual command. The message fingerprint can be set with\nits ``fingerprint(**kwds)`` method.\n\n.. code:: python\n\n m = messaging.MessageCommand('sum', parameters={a=5, b=6})\n f = Fingerprint(name='mycomponent')\n m.fingerprint(f.as_dict())\n\n**Status** messages bear the status of an application, along with the\napplication fingerprint. The class which implements this type is\n``MessageStatus``. This object needs only a single parameter, which is\nthe status itself. Not that as long as the status is serializable, it\ncan be of any nature.\n\n.. code:: python\n\n m = messaging.MessageStatus('online')\n\n**Result** messages contain the result of an RPC call: three classes\nhave this type, ``MessageResult``, ``MessageResultError``,\n``MessageResultException``. The first is the result of a successful\ncall, the second is the result of an error in a call, while the third\nsignals that an exception was raised by the remote component. This error\nclassification has been inspired by Erlang error management, which I\nfind a good solution. All three classes contain a **value** and a\n**message**, but for errors the value is ``None`` and for exceptions it\nis the name of the Python exception.\n\n.. code:: python\n\n try:\n result = some_operation()\n m = messaging.MessageResult(result)\n except Exception as exc:\n m = messaging.MessageResultException(exc.__class__.__name__, exc.__str__())\n\nExchange\n--------\n\nThe ``Exchange`` class allows to declare exchanges just by customizing\nthe class parameters. It provides a ``parameters`` class property that\ngives a dictionary representation of the exchange itself, as required by\nthe ``exchange_declare()`` method of the AMQP channel.\n\nTo declare your own exchange you just need to inherit ``Exchange``\n\n.. code:: python\n\n from postage import messaging\n class MyExchange(messaging.Exchange):\n name = \"my-custom-exchange\"\n exchange_type = \"topic\"\n passive = False\n durable = True\n auto_delete = False\n\nGenericProducer\n---------------\n\nWhen you use AMQP you are free to use any format for your messages and\nany protocol for sending and receiving data. Postage gives you a\npredefined, though extensible, message format, the ``Message`` object.\nMoreover, through ``GenericProducer``, it gives you a way to easily\ndefine an API, i.e. a set of shortcut functions that create and send\nmessages, through which you can interact with your system.\n\nTo better introduce the simplification implemented by\n``GenericProducer`` let us recap what a component shall do to send a\nmessage using pika and the ``Message`` object.\n\n1. a ``Message`` object has to be declared and filled with the\n information we want to send, according to a given predefined format\n (the message API of our system). The message must contain the correct\n fingerprint and be encoded using the encoder of your choice (choice\n that must be shared by all other components in the system).\n\n2. A connection to the AMQP broker must be established, then all the\n target exchanges must be declared.\n\n3. For each exchange you want to receive the message you shall publish\n it giving the correct routing key for that exchange: the keys you can\n use are part of your messaging API, so you have to \"document\" them\n when you publish the specification for your exchanges.\n\nAs you can see this can quickly lead to a bunch o repeated code, as the\nset of operation you need are often the same or very similar; moreover,\nit needs a source of documentation outside the code, that is, the API\ndoes not document itself (here I mean: there is no way to get a grasp on\nthe set of messages you are defining in your API).\n\nLet us see how ``GenericProducer`` solves these issues. First of all you\nneed to define an exchange:\n\n.. code:: python\n\n class LoggingExchange(messaging.Exchange):\n name = logging-exchange\"\n exchange_type = \"direct\"\n passive = False\n durable = True\n auto_delete = False\n\nThen you need to define a producer, i.e. an object that inherits from\n``GenericProducer``:\n\n.. code:: python\n\n class LoggingProducer(messaging.GenericProducer):\n pass\n\nsince the aim of the producer is that of simplify sending messages to an\nexchange you can here specify a set of exchanges/key couples (EKs) which\nwill be used by default (more on this later).\n\n.. code:: python\n\n class LoggingProducer(messaging.GenericProducer):\n eks = [(LoggingExchange, 'log')]\n\nNow you have to define a function that builds a ``Message`` containing\nthe data you want to send\n\n.. code:: python\n\n class LoggingProducer(messaging.GenericProducer):\n eks = [(LoggingExchange, \"log\")]\n \n def build_message_status_online(self):\n return messaging.MessageStatus('online')\n\nThis allows you to write the following code\n\n.. code:: python\n\n producer = LoggingProducer()\n producer.message_status_online()\n\nwhich will build a ``MessageStatus`` containing the ``'online'`` status\nstring and will send it to the exchange named ``logging-exchange`` with\n``'log'`` as routing key.\n\nMagic methods\n~~~~~~~~~~~~~\n\nAs you can see ``GenericProducer`` automatically defines a\n``message_name()`` method that wraps each of the\n``build_message_name()`` methods you defines. The same happens with RPC\nmessages, where the ``rpc_name()`` method is automatically created to\nwrap ``build_rpc_name()``.\n\n``message_*()`` methods accept two special keyword arguments, namely\n***key\\ **, ***\\ eks**, that change the way the message is sent. The\nbehaviour of the two keywords follows the following algorithm:\n\n1. Calling ``message_name()`` sends the message with the predefined\n ``eks``, i.e. those defined in the producer class. This means that\n the message is sent to each exchange listed in the ``eks`` list of\n the class, with the associated key.\n\n2. Calling ``message_name(_key='rk')`` sends the message to the first\n exchange in ``eks`` with the key ``rk``.\n3. Calling ``message_name(_eks=[(exchange1, rk1), (exchange2, rk2)])``\n uses the specified eks instead of the content of the default ``eks``\n variable; in this case sends the message to ``exchange1`` with\n routing key ``rk1`` and to ``exchange2`` with routing key ``rk2``.\n\nIf you speficy both ``_eks`` and ``_key`` the latter will be ignored.\nThis system allows you to specify a default behaviour when writing the\nproducer and to customize the routing key or even the exchange on the\nfly.\n\nRPC messages accept also ``_timeout`` (seconds), ``_max_retry`` and\n``_queue_only`` to customize the behaviour of the producer when waiting\nfor RPC answers (more on that later).\n\nFingerprint\n~~~~~~~~~~~\n\nWhen a ``GenericProducer`` is instanced a ``Fingerprint`` in its\ndictionary form can be passed as argument and this is included in each\nmessage object the producer sends. If not given, a bare fingerprint is\ncreated inside the object.\n\n.. code:: python\n\n f = Fingerprint(name='mycomponent')\n producer = LoggingProducer(fingerprint=f.as_dict())\n producer.message_status_online()\n\nGeneric messages\n~~~~~~~~~~~~~~~~\n\nYou can use a producer to send generic messages using the ``message()``\nmethod\n\n.. code:: python\n\n p = messaging.GenericProducer()\n p.message(1, \"str\", values={1, 2, 3, \"numbers\"},\n _eks=[(MyExchangeCls, \"a_routing_key\")])\n\nRPC calls\n~~~~~~~~~\n\nRPC calls are blocking calls that leverage a very simple mechanism: the\nlow level AMQP message is given a (usually temporary and private) queue\nthrough its ``reply_to`` property, and this is explicitely used by the\nreceiver to send an answer.\n\nIn Postage an RPC message is defined by a ``build_rpc_name()`` method in\na ``GenericProducer`` and called with ``rpc_name()``; it returns a\nresult message as sent by the component that answered the call and thus\nits type should be one of ``MessageResult``, ``MessageResultError`` or\n``MessageResultException`` for plain Postage.\n\nRPC messages accept the following parameters: ``_timeout`` (the message\ntimeout, defaults to 30 seconds), ``_max_retry`` (the maximum number of\ntimes the message shall be sent again when timing out, default to 4),\nand ``_queue_only`` (the call returns the temporary queue on which the\nanswer message will appear, instead of the message itself).\n\nWhen the maximum number of tries has been reached the call returns a\n``MessageResultException`` with the ``TimeoutError`` exception.\n\nGenericConsumer\n---------------\n\nThe ``GenericConsumer`` class implements a standard AMQP consumer, i.e.\nan object that can connect to exchanges through queues and fetch\nmessages.\n\nA class that inherits from ``GenericConsumer`` shall define an ``eqk``\nclass attribute which is a list of tuples in the form\n``(Exchange, [(Queue, Key), (Queue, Key), ...])``; each tuple means that\nthe given exchange will be subscribed by the listed queues, each of them\nwith the relative routing key. The Queue may be defined as a plain\nstring (the name of the queue) or as a dictionary with the 'name' and\n'flags' keys; the second key will identify a dictionary of flags, such\nas ``{'auto_delete':True}``.\n\n.. code:: python\n\n class MyConsumer(GenericConsumer):\n eqk = (\n PingExchage, [('ping_queue', 'ping_rk')],\n LogExchange, [('log_queue', 'log')]\n )\n\nApart from declaring bindings in the class you can use the\n``queue_bind()`` method that accept an exchange, a queue and a key. This\ncan be useful if you have to declare queues at runtime or if parameters\nsuch as routing key depend on some value you cannot access at\ninstantiation time.\n\nMessageProcessor\n----------------\n\n``MessageProcessor`` objects boost ``GenericConsumer`` to full power =)\nA ``MessageProcessor`` is a ``MicroThread`` with two main attributes:\n``self.consumer`` (a ``GenericConsumer`` or derived class) and a\n``self.fingerprint`` (a ``Fingerprint`` in its dictionary form).\n\nInside a ``MessageProcessor`` you can define a set of methods called\n\"message handlers\" that process incoming messages. The methods can be\nfreely called and have to be decorated with the ``@MessageHandler``\ndecorator; this needs two parameters: the type of the message and the\nname. So defining\n\n.. code:: python\n\n @MessageHandler('command', 'quit')\n def msg_quit(self, content):\n [...]\n\nyou make the method ``msg_quit()`` process each incoming message which\ntype is ``command`` and name is ``quit``. You can define as many message\nhandlers as you want for the same message type/name, but beware that\nthey are all executed in random order. As you can see from the example a\nmessage handler method must accept a parameter which receives the\ncontent of the processed message.\n\nYou can also decorate a method with the ``@RpcHandler`` decorator; in\nthat case the method must accept two parameters, the first being the\ncontent of the received message, the second a reply function. The method\nhas the responsibility of calling it passing a ``MessageResult`` or\nderived object. This mechanism allows the handler to do some cleanup\nafter sending the reply.\n\nMessage handlers can also be defined as classes inside a\n``MessageProcessor`` and have to inherit from ``Handler`` and define a\n``call()`` method which accepts only self; it can then access the\n``self.data`` and ``self.reply_func`` attributes that contain the\nincoming message and the return function. The difference between the\nmethod and class version of the message handlers is that the class\nversion can access the underlying ``MessageProcessor`` through its\n``self.processor`` attribute. This is useful to access the fingerprint\nof the message or any other attribute that is included in the processor.\nA class is then in general richer than a simple method, thus giving more\nfreedom to the programmer.\n\nThe last available decorator is ``MessageHandlerFullBody`` that passes\nto the decorated method or class the full body of the incoming message\ninstead that only the value of the ``content`` key like\n``MessageHandler`` and ``RpcHandler`` do.\n\nDefault handlers\n~~~~~~~~~~~~~~~~\n\n``MessageProcessor`` objects define two default message handlers to\nprocess incoming command ``quit`` and command ``restart``. The first, as\nyou can easily guess from the name, makes the component quit; actually\nit makes the consumer stop consuming messages and the microthread quit,\nso the program executes the code you put after the scheduler loop. If\nyou put no code, the program just exits. The second command makes the\ncomponent restart, i.e. it replaces itself with a new execution of the\nsame program. This makes very easy to update running systems; just\nreplace the code and send a ``restart`` to your components.\n\nMessage filters\n~~~~~~~~~~~~~~~\n\n**New in version 1.1.0** The ``MessageFilter`` class may be used to\ndecorate a message handler and accepts a callable as parameter. The\nprovided callable is called on a copy of each incoming message that\nwould be processed by that handler. Any exception raised by the callable\nresults in the message being discarded without passing through the\nhandler.\n\nYou may use this feature to manage changes in the format of a message,\nand providing a filter that transforms old-style messages into new-style\nones.\n\nGenericApplication\n~~~~~~~~~~~~~~~~~~\n\n**New in version 1.2.0** The ``generic_application.py`` module contains\nthe ``GenericApplication`` class which is a basic unspecialized\ncomponent based on ``messaging.messageProcessor``.\n``GenericApplication`` may be used to build message-driven programs in\nPython that interact through the RabbitMQ system.\n\n``GenericApplication`` is a microthread that may use ``MessageHandler``\nand derived classes to get messages from the RabbitMQ exchanges it\nconnects to. The standard exchange used by this class is\n``generic_application.GenericApplicationExchange``. In the following\nparagraphs the names \"system\" and \"network\" both mean a given\nvirtualhost on a set of clustered RabbitMQ nodes.\n\nA ``GenericApplication`` is identified by a ``name``, an operating\nsystem ``pid`` and a running ``host``. From those values three queues\nare defined inside each instance: ``self.sid``, ``self.hid`` and\n``self.uid``.\n\n- ``self.sid`` is the system-wide queue, which is shared among all\n microthreads with the same ``name``.\n- ``self.hid`` is the host-wide queue, which is shared by all\n microthreads with the same ``name`` and the same ``host``.\n- ``self.uid`` is an unique queue on the whole system. Being linked to\n the OS PID and the running host this queue is owned by a single\n application instance.\n\nThe ``GenericApplication`` class defines several routing keys through\nwhich the above queues are connected to the exchange, namely:\n\n- ``{name}`` is a fanout that delivers messages to every application\n with the given name. For example sending a message with the\n ``monitor`` key will reach all microthreads running with the\n ``monitor`` name.\n- ``{name}/rr`` delivers messages in round robin to every application\n with the given name. Round robin keys leverage the basic AMQP load\n balancing mechanism: the queue is shared among consumers and messages\n are fairly divided among them.\n- ``@{host}`` is a fanout to every application running on the same\n host.\n- ``{name}@{host}`` is a fanout to every application running on the\n same host and with the same name.\n- ``{name}@{host}/rr`` is the round robin version of the previous key.\n It balances message delivering to applications that share name and\n host.\n- ``{pid}@{host}`` delivers a message only the the unique application\n that has the given pid on the given host.\n\nA ``GenericApplication`` may join one or more groups. The list of groups\ncan be specified when instancing the class or dynamically through a\nmessage. In the first case two keys are available to send messages\n\n- ``{name}#{group}`` which is a fanout to every application with the\n same name in the same group.\n- ``{name}#{group}/rr`` which is a round robin to the same set of\n applications.\n\nIf the application joins a group later in its lifecyle, through a\n``join_group`` message, only the fanout key is available. The technical\nreason for this limitation is described in the source code of the\n``msg_join_group()`` message handler.\n\nCredits\n~~~~~~~\n\nFirst of all I want to mention and thank the `Erlang `__\nand `RabbitMQ `__ teams and the maintainer of\n`pika `__, Gavin M. Roy, for their hard\nwork, and for releasing such amazing pieces of software as open source.\n\nMany thanks to `Jeff Knupp `__ for\nhis post `Open Sourcing a Python Project the Right\nWay `__\nand to `Audrey M. Roy `__ for her\n`cookiecutter `__ and\n`cookiecutter-pypackage `__\ntools. All those things make Python packaging a breeze.\n\n.. |Build Status| image:: https://travis-ci.org/lgiordani/postage.png?branch=master\n :target: https://travis-ci.org/lgiordani/postage\n.. |Version| image:: https://badge.fury.io/py/postage.png\n :target: http://badge.fury.io/py/postage\n.. |PyPi Downloads| image:: https://pypip.in/d/postage/badge.png\n :target: https://crate.io/packages/postage?version=latest\n\n\n\n\nHistory\n-------\n\n1.0.0 (2013-12-03)\n++++++++++++++++++\n\n* First release on PyPI.\n\n1.0.1 (2014-06-05)\n++++++++++++++++++\n\n* Queues created through queue_bind() were declared with auto_delete=True, which made the queue disappear as soon as no more consumers were reading from it. This made the consumer lose all messages waiting in the queue. Fixed by removing the auto_delete=True parameter.\n\n1.0.2 (2014-08-05)\n++++++++++++++++++\n\n* Now EQKs may contain queue flags to toggle AMQP parameters such as auto_delete on specific queues\n\n1.1.0 (2014-12-10)\n++++++++++++++++++\n\n* Added filters to alter/manage incoming messages before processing them\n\n1.2.0 (2015-02-27)\n++++++++++++++++++\n\n* Added `generic_application.py` with the `GenericApplication` class\n\n1.2.1 (2015-02-27)\n++++++++++++++++++\n\n* Fixed wrong indentation in `generic_application.py`", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/lgiordani/postage", "keywords": "postage", "license": "GPL v2.0 or newer", "maintainer": null, "maintainer_email": null, "name": "postage", "package_url": "https://pypi.org/project/postage/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/postage/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/lgiordani/postage" }, "release_url": "https://pypi.org/project/postage/1.2.1/", "requires_dist": null, "requires_python": null, "summary": "A Python library for AMQP-based network components", "version": "1.2.1" }, "last_serial": 1441175, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "1ac96c9342b57a8d6cf5ad8b08ee6317", "sha256": "e2ef9e30d506e7637490453eea893e3cf378c840a246085d9c7938d76f1241ef" }, "downloads": -1, "filename": "postage-1.0.0-py27-none-any.whl", "has_sig": false, "md5_digest": "1ac96c9342b57a8d6cf5ad8b08ee6317", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 34799, "upload_time": "2013-12-04T15:20:24", "url": "https://files.pythonhosted.org/packages/d9/80/1011843a2a681277351de84b482efd9da0dc3973ccbc20865c1a6d45511a/postage-1.0.0-py27-none-any.whl" }, { "comment_text": "", "digests": { "md5": "70c8aec65d1a7a56c1e2316ad567901a", "sha256": "b087f867dc67bf288a4c2451694831d5894a25878b5ad642f8f6c006470e603e" }, "downloads": -1, "filename": "postage-1.0.0.tar.gz", "has_sig": false, "md5_digest": "70c8aec65d1a7a56c1e2316ad567901a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44960, "upload_time": "2013-12-04T15:20:11", "url": "https://files.pythonhosted.org/packages/4a/6b/71889ab279f3d4423a69ecdd7678d611a35114b569d1a3ac20136c96c03a/postage-1.0.0.tar.gz" } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "4e4a0614488107f53d03b2f81ebd8770", "sha256": "4310eacd1e7ce8b81da5b4060f79dc5d67565260367bf1455e736111454e29fa" }, "downloads": -1, "filename": "postage-1.0.1.tar.gz", "has_sig": false, "md5_digest": "4e4a0614488107f53d03b2f81ebd8770", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 45322, "upload_time": "2014-06-05T11:19:58", "url": "https://files.pythonhosted.org/packages/83/be/a65d16c6b1408ba0df2c820f07b27a8c4f155e5780805dc11d508958b66d/postage-1.0.1.tar.gz" } ], "1.0.2": [ { "comment_text": "", "digests": { "md5": "57432f72e5bbd8d5d4568630d6f65d4c", "sha256": "528e5262e4fcea2fe2df6f2be04bd838bb3cee362632a37f2158cf55c7939d25" }, "downloads": -1, "filename": "postage-1.0.2.tar.gz", "has_sig": false, "md5_digest": "57432f72e5bbd8d5d4568630d6f65d4c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 45828, "upload_time": "2014-08-05T13:46:18", "url": "https://files.pythonhosted.org/packages/dc/b5/1ac5d3f050c61b0cb21d7d2252a074ec07924668287b6a2cfd054bd49c72/postage-1.0.2.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "7699f2deb7074fbb0c8a08f941da91ee", "sha256": "1c0b2b3bcca50a9807b044183baa83677bbdfeea5611eedfedc3b1147752869a" }, "downloads": -1, "filename": "postage-1.1.0.tar.gz", "has_sig": false, "md5_digest": "7699f2deb7074fbb0c8a08f941da91ee", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 46561, "upload_time": "2014-12-10T14:34:51", "url": "https://files.pythonhosted.org/packages/2c/0a/458f492b2f7a2a0b97c803fbfd92c8f5d55c9a0a1119e73ac8c9faa863ba/postage-1.1.0.tar.gz" } ], "1.2.0": [ { "comment_text": "", "digests": { "md5": "1c56354cd33ff6284b84c693b7ba8a7a", "sha256": "e0ac8fd765f1466d9c217bf37b4d3add4fc2fdfe4402a27e96c7e239605cd917" }, "downloads": -1, "filename": "postage-1.2.0.tar.gz", "has_sig": false, "md5_digest": "1c56354cd33ff6284b84c693b7ba8a7a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51763, "upload_time": "2015-02-27T15:23:53", "url": "https://files.pythonhosted.org/packages/03/5b/c9023999ee35f56b026f26944eb9ba7ded183c3ffed4a0ffb2717ffac6ed/postage-1.2.0.tar.gz" } ], "1.2.1": [ { "comment_text": "", "digests": { "md5": "f67dfa9de8cb162569a9328be4405394", "sha256": "adf419420550c9b81a29c7f8c9b778a61cae8133855948602c7cd675cf6cc414" }, "downloads": -1, "filename": "postage-1.2.1.tar.gz", "has_sig": false, "md5_digest": "f67dfa9de8cb162569a9328be4405394", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51572, "upload_time": "2015-02-27T16:14:44", "url": "https://files.pythonhosted.org/packages/f2/45/2964e3dc2fd109fc63999c621e7ef7192c30ffe18d6eae7cad3c43c40005/postage-1.2.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "f67dfa9de8cb162569a9328be4405394", "sha256": "adf419420550c9b81a29c7f8c9b778a61cae8133855948602c7cd675cf6cc414" }, "downloads": -1, "filename": "postage-1.2.1.tar.gz", "has_sig": false, "md5_digest": "f67dfa9de8cb162569a9328be4405394", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51572, "upload_time": "2015-02-27T16:14:44", "url": "https://files.pythonhosted.org/packages/f2/45/2964e3dc2fd109fc63999c621e7ef7192c30ffe18d6eae7cad3c43c40005/postage-1.2.1.tar.gz" } ] }