# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['channels_rabbitmq']

package_data = \
{'': ['*']}

install_requires = \
['carehare>=1.0,<2.0', 'channels>=3.0,<5', 'msgpack>=1.0,<2.0']

setup_kwargs = {
    'name': 'channels-rabbitmq',
    'version': '4.0.1',
    'description': 'RabbitMQ-based ASGI channel layer implementation',
    'long_description': 'channels_rabbitmq\n=================\n\nA Django Channels channel layer that uses RabbitMQ as its backing store.\n\nDoes not support `Worker and Background Tasks\n<https://channels.readthedocs.io/en/stable/topics/worker.html>`_.\n(See `Rationale\n<https://github.com/CJWorkbench/channels_rabbitmq/pull/11#issuecomment-499185070>`_\nand use ``await get_channel_layer().current_connection`` to send to job queues.)\n\nWorks with Python 3.8 or 3.9.\n\nInstallation\n------------\n\n``pip install channels_rabbitmq``\n\nUsage\n-----\n\nThen set up the channel layer in your Django settings file like so::\n\n    CHANNEL_LAYERS = {\n        "default": {\n            "BACKEND": "channels_rabbitmq.core.RabbitmqChannelLayer",\n            "CONFIG": {\n                "host": "amqp://guest:guest@127.0.0.1/asgi",\n                # "ssl_context": ... (optional)\n            },\n        },\n    }\n\nPossible options for ``CONFIG`` are listed below.\n\n``host``\n~~~~~~~~\n\nURL of the server to connect to, adhering to `RabbitMQ spec\n<https://www.rabbitmq.com/uri-spec.html>`_. To connect to a RabbitMQ cluster,\nuse a DNS server to resolve a hostname to multiple IP addresses.\nchannels_rabbitmq will automatically reconnect if at least one of them is\nreachable in case of a disconnection.\n\n``expiry``\n~~~~~~~~~~\n\nMinimum number of seconds a message should wait in a RabbitMQ queue, before it\nmay be silently dropped.\n\nDefaults to ``60``. You generally shouldn\'t need to change this, but you may\nwant to turn it down if you have peaky traffic you wish to drop, or up if you\nhave peaky traffic you want to backlog until you get to it.\n\n``local_capacity``\n~~~~~~~~~~~~~~~~~~\n\nNumber of incoming messages queued in memory. Defaults to ``100``. (A message\nsent to a group with two channels counts as one message.) When ``local_capacity``\nmessages are queued, the message backlog will grow on RabbitMQ.\n\n(This controls the ``prefetch_count`` on the RabbitMQ queue.)\n\n``local_expiry``\n~~~~~~~~~~~~~~~~\n\nMinimum number of seconds a message received from RabbitMQ must be held in\nmemory waiting for ``receive()``, before it may be dropped. Defaults to\n``expiry``.\n\nA warning will be logged when a message expires locally. The warning can\nindicate that a channel has more messages than it can handle; or that\nmessages are being sent to a channel that does not exist. (Perhaps a missing\nchannel was implied by ``group_add()``, and a matching ``group_discard()``\nwas never called.)\n\nIf ``local_expiry < expiry``, then you can end up ignoring (and logging)\nmessages locally while they still exist in the RabbitMQ queue. These messages\nwill be acked, so RabbitMQ will behave as though they were delivered.\n\n``remote_capacity``\n~~~~~~~~~~~~~~~~~~~\n\nNumber of messages stored on RabbitMQ for each client. Defaults to ``100``.\n(A message sent to a group with three channels on two distinct clients counts\nas two messages.) When ``remote_capacity`` messages are queued in RabbitMQ,\nthe channel will refuse new messages. Calls from any client to ``send()`` or\n``group_send()`` to the at-capacity client will raise ``ChannelFull``.\n\n``ssl_context``\n~~~~~~~~~~~~~~~\n\nAn `SSL context\n<https://docs.python.org/3/library/ssl.html#ssl-contexts>`_. Changes the\ndefault ``host`` port to 5671 (instead of 5672).\n\nFor instance, to connect to an TLS RabbitMQ service that will verify your\nclient::\n\n    import ssl\n    ssl_context = ssl.create_default_context(\n        cafile=str(Path(__file__).parent.parent / \'ssl\' / \'server.cert\'),\n    )\n    ssl_context.load_cert_chain(\n        certfile=str(Path(__file__).parent.parent / \'ssl\' / \'client.certchain\'),\n        keyfile=str(Path(__file__).parent.parent / \'ssl\' / \'client.key\'),\n    )\n    CHANNEL_LAYERS[\'default\'][\'CONFIG\'][\'ssl_context\'] = ssl_context\n\nBy default, there is no SSL context; all messages (and passwords) are\nare transmitted in cleartext.\n\n``groups_exchange``\n~~~~~~~~~~~~~~~~~~~\n\nGlobal direct exchange name used by channels to exchange group messages.\nDefaults to ``"groups"``. See also `Design decisions`_.\n\nAccessing Carehare\n------------------\n\nWe use `carehare`_ for its thorough handling of errors.\n\nDjango Channels\' specification does not account for "connecting" and\n"disconnecting". This layer does its best by constantly reconnecting, forever.\n\nCall ``await get_channel_layer().current_connection`` to access an open Carehare\nconnection. This lets you use job queues without "Worker and Background Tasks".\nLike this::\n\n    # raise asyncio.CancelledError on failure\n    connection = await get_channel_layer().carehare_connection\n\n    # raise carehare.ConnectionClosed or carehare.ChannelClosed on error\n    await connection.publish(b"task", routing_key="job_queue")\n\n(The Carehare documentation explains how to build workers.)\n\nA note on errors: a "connected" connection isn\'t guaranteed to *stay* connected\nthroughout every publish. It was merely connected *at some point in the past*.\nWhen a disconnect occurs, all pending operations on that connection will raise\n``carehare.ConnectionClosed``. This channel layer will log the error, and\n``get_channel_layer().carehare_connection`` will point to a new Future. (This\nerror+reconnect is *guaranteed to happen* in production.)\n\nPublish messages from Celery\n----------------------------\n\nMany `Celery\n<https://docs.celeryproject.org/en/stable/getting-started/introduction.html>`_\nusers want to send messages to websockets users.\n\nThis is doable, though not intuitive. Don\'t use any Django Channels code:\nChannels layers depend on long-running connections, and Celery bans those.\nSame goes for carehare: don\'t use it from Celery.\n\nInstead, from a Celery-worker ``@task`` you can send messages to your\nDjango-Channels consumers using Celery\'s RabbitMQ connection::\n\n    from typing import Any, Dict\n\n    import msgpack\n\n    def publish_message_to_group(message: Dict[str, Any], group: str) -> None:\n        with current_app.producer_pool.acquire(block=True) as producer:\n            producer.publish(\n                msgpack.packb({\n                  "__asgi_group__": group,\n                  **message,\n                }),\n                exchange="groups",  # groups_exchange\n                content_encoding="binary",\n                routing_key=group,\n                retry=False,  # Channel Layer at-most once semantics\n            )\n\nTo call it, from a Celery-worker ``@task``...::\n\n    publish_message_to_group({ "type": "chat.message", "text": "hi" }, "a-group")\n\n... and a Django-Channels consumer like this will receive it::\n\n    class WebsocketConnectionConsumer(AsyncWebsocketConsumer):\n        async def connect(self):\n            await self.channel_layer.group_add("a-group", self.channel_name)\n\n        async def disconnect(self):\n            await self.channel_layer.group_discard("a-group", self.channel_name)\n\n        async def chat_message(self, event):\n            assert event["text"] == "hi"\n\nAlternatively, write your workers asynchronously, directly in `carehare`_.\nIt\'s more lightweight and faster than Celery, and the error handling is\nsimpler.\n\nDesign decisions\n----------------\n\nTo scale enormously, this layer only creates one RabbitMQ queue per instance.\nThat means one web server gets one RabbitMQ queue, no matter how many\nwebsocket connections are open. For each message being sent, the client-side\nlayer determines the RabbitMQ queue name and uses it as the routing key.\n\nGroups are implemented using a single, global RabbitMQ direct exchange called\n"groups" by default. To send a message to a group, the layer sends the message\nto the "groups" exchange with the group name as the routing key. The client\nbinds and unbinds during ``group_add()`` and ``group_remove()`` to ensure\nmessages for any of its groups will reach it. See also the `groups_exchange`_\noption.\n\nRabbitMQ queues are ``exclusive``: when a client disconnects (through close or\ncrash), RabbitMQ will delete the queue and unbind the groups.\n\nOnce a connection has been created, it pollutes the event loop so that\n``async_to_sync()`` will destroy the connection if it was created within\n``async_to_sync()``. Each connection starts a background async loop that pulls\nmessages from RabbitMQ and routes them to receiver queues; each ``receive()``\nqueries receiver queues. Empty queues with no connections are deleted.\n\nDeviations from the Channel Layer Specification\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe `Channel Layer Specification\n<https://channels.readthedocs.io/en/latest/channel_layer_spec.html>`_ bends to\nRedis-related restrictions. RabbitMQ cannot emulate Redis. Here are the\ndifferences:\n\n* **No ``flush`` extension**: To flush all state, simply disconnect all clients.\n  (RabbitMQ won\'t allow one client to delete another client\'s data structures.)\n* **No ``group_expiry`` option**: The `group_expiry option\n  <https://channels.readthedocs.io/en/latest/channel_layer_spec.html#persistence>`_\n  recovers when a ``group_add()`` has no matching ``group_discard()``. But the\n  "group membership expiry" logic has a fatal flaw: it disconnects legitimate\n  members. ``channels_rabbitmq`` addresses each root problem instead:\n\n  * Web-server crash: RabbitMQ wipes all state related to a web server when\n    the web server disconnects. There\'s no problem here for ``group_expiry``\n    to solve.\n  * Programming errors: You may err and call ``group_add()`` without\n    eventually calling ``group_discard()``. Redis can\'t detect this\n    programming error (because it can\'t detect web-server crashes). RabbitMQ\n    can. The ``local_expiry`` option keeps your site running after you\n    erroneously miss a ``group_discard()``. The channel layer warns when\n    discarding expired messages. Monitor your server logs to detect your\n    errors.\n* **No "normal channels"**: `Normal channels\n  <https://channels.readthedocs.io/en/latest/channel_layer_spec.html#channels>`_\n  are job queues. In most projects, "normal channel" readers are worker\n  processes, ideally divorced from Websockets and Django.\n\n  If you want an async, RabbitMQ-based job queue, investigate `carehare\n  <https://github.com/CJWorkbench/carehare>`_.\n\n  If you\'re using Celery with the same RabbitMQ server, you can `publish\n  messages from Celery`_, too.\n\nDependencies\n------------\n\nYou\'ll need Python 3.8+ and a RabbitMQ server.\n\nIf you have Docker, here\'s how to start a development server::\n\n    ssl/prepare-certs.sh  # Create SSL certificates used in tests\n    docker run --rm -it \\\n         -p 5671:5671 \\\n         -p 5672:5672 \\\n         -p 15672:15672 \\\n         -v "/$(pwd)"/ssl:/ssl \\\n         -e RABBITMQ_SSL_CACERTFILE=/ssl/ca.cert \\\n         -e RABBITMQ_SSL_CERTFILE=/ssl/server.cert \\\n         -e RABBITMQ_SSL_KEYFILE=/ssl/server.key \\\n         -e RABBITMQ_SSL_VERIFY=verify_peer \\\n         -e RABBITMQ_SSL_FAIL_IF_NO_PEER_CERT=true \\\n         rabbitmq:3.7.8-management-alpine\n\nYou can access the RabbitMQ management interface at http://localhost:15672.\n\nContributing\n------------\n\nTo add features and fix bugs\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nFirst, start a development RabbitMQ server::\n\n    ssl/prepare-certs.sh  # Create SSL certificates used in tests\n    docker run --rm -it \\\n         -p 5671:5671 \\\n         -p 5672:5672 \\\n         -p 15672:15672 \\\n         -v "/$(pwd)"/ssl:/ssl \\\n         -e RABBITMQ_SSL_CACERTFILE=/ssl/ca.cert \\\n         -e RABBITMQ_SSL_CERTFILE=/ssl/server.cert \\\n         -e RABBITMQ_SSL_KEYFILE=/ssl/server.key \\\n         -e RABBITMQ_SSL_VERIFY=verify_peer \\\n         -e RABBITMQ_SSL_FAIL_IF_NO_PEER_CERT=true \\\n         rabbitmq:3.8.11-management-alpine\n\nNow take on the development cycle:\n\n#. ``tox`` # to ensure tests pass.\n#. Write new tests in ``tests/`` and make sure they fail.\n#. Write new code in ``channels_rabbitmq/`` to make the tests pass.\n#. Submit a pull request.\n\nTo deploy\n~~~~~~~~~\n\nUse `semver <https://semver.org/>`_.\n\n#. ``git push`` and make sure Travis tests all pass.\n#. ``git tag vX.X.X``\n#. ``git push --tags``\n\nTravisCI will push to PyPi.\n',
    'author': 'Adam Hooper',
    'author_email': 'adam@adamhooper.com',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'None',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.8,<4.0',
}


setup(**setup_kwargs)
