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

packages = \
['carehare']

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

install_requires = \
['pamqp>=3.0,<4.0']

setup_kwargs = {
    'name': 'carehare',
    'version': '0.0.10',
    'description': 'asyncio RabbitMQ client',
    'long_description': 'carehare\n========\n\nAsyncio RabbitMQ client that handles all the edge cases.\n\nInstallation\n------------\n\n``pip install carehare``\n\nUsage\n-----\n\nConsumer (``async for``)::\n\n    try:\n        async with carehare.connect("amqps://guest:guest@localhost/") as connection:\n            try:\n                await connection.queue_declare("my-queue", exclusive=True)\n                async with connection.acking_consumer("my-queue") as consumer:\n                    async for message in consumer:\n                        print(repr(message))\n                        # ... if we raise an exception here, we won\'t ack.\n                        #\n                        # ... if we `break` from this loop, we won\'t ack.\n                        #\n                        # Call `consumer.close()` before (or instead of) `break`\n                        # to abort iteration.\n            except carehare.ChannelClosedByServer:\n                logger.info("RabbitMQ told this one consumer to go away")\n            except carehare.ConnectionClosed:\n                # Either RabbitMQ is telling us an error (and the outer context\n                # manager will throw it), or we called connection.close()\n                # ourselves (so we want to close).\n                pass\n    except carehare.ConnectionClosedByServer:\n        # str(error) will give the RabbitMQ error message\n        logger.error("RabbitMQ closed our connection")\n    except carehare.ConnectionClosedByHeartbeatMonitor:\n        logger.error("RabbitMQ went away")\n\nConsumer (``next_delivery``)::\n\n    try:\n        async with carehare.connect("amqps://guest:guest@localhost/") as connection:\n            try:\n                await connection.queue_declare("my-queue", exclusive=True)\n                async with connection.acking_consumer("my-queue") as consumer:\n                    while True:\n                        message, delivery_tag = await consumer.next_delivery()\n                        # You must ack() (with no await). If RabbitMQ doesn\'t\n                        # receive this ack, it may deliver the same message to\n                        # another client.\n                        consumer.ack(delivery_tag)\n                        if message.startswith(b"okay, go away now"):\n                            break\n            except carehare.ChannelClosedByServer:\n                logger.info("RabbitMQ told this one consumer to go away")\n            except carehare.ConnectionClosed:\n                # Either RabbitMQ is telling us an error (and the outer context\n                # manager will throw it), or we called connection.close()\n                # ourselves (so we want to close).\n                pass\n    except carehare.ConnectionClosedByServer:\n        # str(error) will give the RabbitMQ error message\n        logger.error("RabbitMQ closed our connection")\n    except carehare.ConnectionClosedByHeartbeatMonitor:\n        logger.error("RabbitMQ went away")\n\nPublisher::\n\n    try:\n        async with carehare.connect("amqps://guest:guest@localhost/") as connection:\n            try:\n                await connection.publish(b"Hello, world!", routing_key="my-queue")\n            except carehare.ServerSentNack:\n                logger.warn("Failed to publish message")\n            except carehare.ChannelClosedByServer:\n                # str(err) will give the RabbitMQ error message -- for instance,\n                # "404 NOT_FOUND" if the exchange does not exist\n                logger.error("Problem with the exchange")\n    except carehare.ConnectionClosedByServer:\n        # str(error) will give the RabbitMQ error message\n        logger.error("RabbitMQ closed our connection")\n    except carehare.ConnectionClosedByHeartbeatMonitor:\n        logger.error("RabbitMQ went away")\n\nDesign decisions\n----------------\n\n``carehare`` is designed to turn RabbitMQ\'s asynchronous error system into\n_understandable_ Python exceptions.\n\nChannels\n~~~~~~~~\n\nCarehare doesn\'t let you control RabbitMQ Channels. They aren\'t Pythonic. (In\nRabbitMQ, an exception on a channel closes the channel -- and cancels all its\npending operations.)\n\nInstead, carehare uses channels to handle errors. For instance, Queue.Declare\ncosts three operations: Channel.Open, Queue.Declare, Channel.Close. Since the\noperation has its own channel, it won\'t interfere with other operations if it\ncauses an exception.\n\nThere\'s a speed-up for publishing: we lazily open a Channel per *exchange*.\nError codes like "not found", "access refused" and "not implemented" will make\ncarehare raise an exception on all pending publishes on the same exchange. Don\'t\nworry: a normal "Nack" ("message wasn\'t delivered") will only make your single\nmessage fail.\n\nExceptions\n~~~~~~~~~~\n\n"Exceptions" are hardly exceptional: as a programmer, they are your job. These\nones are designed to help you solve them.\n\nConnection methods return ``asyncio.Future`` objects. You must await each one\nand handle its errors.\n\nEven though you\'re using Python async context managers, exceptions can\'t\nhappen *everywhere*. Carehare will only raise when you ``await`` a response\nfrom RabbitMQ. \n\nIn particular, ``consumer.ack()`` will never raise! You must call it from the\nmain event loop, but you won\'t await it.\n\nTo code safely, catch these exceptions religiously:\n\n* ``carehare.ChannelClosedByServer``: RabbitMQ did not like the command you\n  just ran. Read the exception message for details. After you receive this\n  message, you may continue using the RabbitMQ connection.\n* ``carehare.ConnectionClosed``: When the connection shuts down, every pending\n  ``Future`` will raise this. Only the actual ``Connection`` context manager\n  will raise the underlying exception: a ``carehare.ConnectionClosedByServer``\n  with a descriptive error message.\n\nCarehare won\'t raise ``asyncio.Cancelled``.\n\nBack-pressure\n~~~~~~~~~~~~~\n\nThe core logic is synchronous. It\'s simpler to reason about. The downside:\nneither RabbitMQ nor users will wait for buffers to empty before sending more\ndata.\n\nUse application-level logic to make sure you don\'t run out of memory:\n\n* Consuming? Don\'t worry. Use ``prefetch_count`` to limit the number of messages\n  RabbitMQ sends. Always ack: carehare won\'t permit ``no-ack``.\n* Publishing? Carehare forces "publisher confirms", so each publish returns a\n  ``Future``. Your application is responsible for not calling publish() too many\n  times simultaneously. Use an ``asyncio.Semaphore`` or reason about your\n  specific use (for instance, "my server will host max 100 clients, and each\n  client can only publish one message at a time").\n\nComparison to other async RabbitMQ clients\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nOther clients tend to try and achieve "RabbitMQ in Python". They fail when it\ncomes to exceptions:\n\n* ``aiormq``: If your code generates an error during consume, aiormq (4.1.1)\n  will catch it and ignore it -- stalling your program.\n* ``aioamqp``: If your connection produces an unexpected error, aioamqp will\n  catch it and ignore it -- stalling your program. Also, the latest release was\n  in 2019.\n\nThis author believes it\'s too confusing to model RabbitMQ\'s API in Python.\nInstead, carehare models your *intent* in Python.\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    test-server/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)"/test-server:/test-server \\\n         -e RABBITMQ_SSL_CACERTFILE=/test-server/ca.cert \\\n         -e RABBITMQ_SSL_CERTFILE=/test-server/server.cert \\\n         -e RABBITMQ_SSL_KEYFILE=/test-server/server.key \\\n         -e RABBITMQ_SSL_VERIFY=verify_peer \\\n         -e RABBITMQ_SSL_FAIL_IF_NO_PEER_CERT=true \\\n         -e RABBITMQ_CONFIG_FILE=/test-server/rabbitmq \\\n         rabbitmq:3.8.11-management-alpine\n\nDuring testing, see 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 (see above).\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 ``carehare/`` 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)
