{ "info": { "author": "Andrew Young", "author_email": "UNKNOWN", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5" ], "description": "===================\nDjango Redis PubSub\n===================\n\n.. image:: https://travis-ci.org/andrewyoung1991/django-redis-pubsub.svg?branch=master\n :target: https://travis-ci.org/andrewyoung1991/django-redis-pubsub\n\n.. image:: https://coveralls.io/repos/github/andrewyoung1991/django-redis-pubsub/badge.svg?branch=master\n :target: https://coveralls.io/github/andrewyoung1991/django-redis-pubsub?branch=master\n\n.. image:: https://codeclimate.com/github/andrewyoung1991/django-redis-pubsub/badges/gpa.svg\n :target: https://codeclimate.com/github/andrewyoung1991/django-redis-pubsub\n :alt: Code Climate\n\n\nasyncronous subscription distrobution for django (with websocket support!!!!).\n\n\nPublishableModel\n================\n\nYou'll first need to create some publishable models.\n\n.. code:: python\n\n # models.py\n\n class Correspondence(PublishableModel):\n PUBLISH_ON_CREATE = False\n PUBLISH_ON_UPDATE = False\n\n participants = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name=\"correspondences\")\n\n def save(self, *args, **kwargs):\n super(Correspondence, self).save(*args, **kwargs)\n # add subscribe all the users to the channel\n channel = self.channel\n for subscriber in self.participants:\n channel.subscribe(subscriber)\n\n\n class Message(PublishableModel):\n PUBLISH_ON_CREATE = True\n PUBLISH_ON_UPDATE = False\n\n correspondence = models.ForeignKey(\"Correspondence\", related_name=\"messages\")\n author = models.ForeignKey(settings.AUTH_USER_MODEL, related_name=\"sent_messages\")\n body = models.TextField()\n\n def save(self, *args, **kwargs):\n if not hasattr(self, \"channel\"):\n self.channel = self.correspondence.channel\n super(Message, self).save(*args, **kwargs)\n\n # views.py\n\n def send_message(request, correspondence, *args, **kwargs):\n message = Message.objects.create(\n correspondence_id=correspondence,\n author=request.user,\n body=request.POST[\"body\"]\n return render_to_response(request, \"messages.html\", {\"message\": message})\n\n # websockets.py\n\n @websocket_pubsub(authenticate=True)\n def read_messages(ws, params, user, manager):\n subscription = user.subscriptions.get(channel__name=\"something:unique\".format(user.username))\n reader = subscription.get_reader(manager=manager)\n\n @reader.callback\n def send_message_alert(channel_name, model):\n alert = json.dumps({\"message\": \"new message from {0}\".format(model.author.get_full_name()))\n ws.send_str(alert)\n return True\n\n listener = yield from reader.listen()\n yield from listener\n\n\nIn the above example, a client who has established a websocket connection to the handler in `websockets.py` will receive alerts as long as the websocket connection remains open. When another client sends a POST request to the send_message view in `views.py` the message will be published and received by the `read_messages.send_message_alert` callback where further processing/serialization can occur.\n\n\nWebsockets\n==========\n\nIf you choose to use `redis_pubsub.contrib.websockets` there are additional packages that you will need to install::\n\n $ pip install aiohttp aiohttp_wsgi\n\nWebsocket handlers belong in module in your application by the name of `websockets.py`. This module should export a `handlerconf`, which is a list of the names of the handlers in the module\n\n.. code:: python\n\n @websocket(\"/\") # this handler will be at http://yourapp.com/\n def myhandler(ws, params, **kwargs):\n ...\n\n handlerconf = [\"myhandler\", ]\n\nWebsocket requests are handled with the excellent `aiohttp` package which takes care of the encoding/decoding, handshake, and cleanup of a websocket session. Handlers for websocket requests are coroutines decorated with either the `redis_pubsub.contrib.websockets.websocket` or `redis_pubsub.contrib.websockets.websocket_pubsub` wrappers. These wrappers handle converting your handler to a coroutine and passing arguments to your handler. A simple handler that echo's a message back to the client would look like this\n\n.. code:: python\n\n @websocket(\"/echo\")\n def echo(ws, params, **kwargs):\n message = yield from ws.receive()\n ws.send_str(message.data)\n\nThe former example shows a websocket handler that waits for a message from a connected client, echo's the message back to the client and closes the connection.\n\n\nWebsocket Authentication\n========================\n\nIf you choose to use authenticated websockets you will need to either install `djangorestframework` and use the `rest_framework.authtoken.models.Token` object as your authentication method or simply use `rest_framework_jwt` to distribute and challenge JTW's provided by your client. to configure authentication with one of these methods (or your own token authentication method) add the module path to the REDIS_PUBSUB config::\n\n REDIS_PUBSUB = {\n \"tokenauth_method\": \"redis_pubsub.auth.authjwt_method\", # defaults to \"redis_pubsub.auth.authtoken_method\"\n }\n\nIf you do decide to roll your own `tokenauth_method`, this method must accept a single argument (the token string) and return either `None` if the token is not valid or an instance of `AUTH_USER_MODEL` if the token is valid.\n\n\nWebsocket Pubsub\n================\n\nYou can access the Pubsub methods provided by `redis_pubsub` in your websocket handlers by decorating your handler with the `redis.pubsub.contrib.websockets.websocket_pubsub` wrapper. This wrapper provides an additional argument `manager` to your handler. The manager can be used to keep track of subscription channels and stop them if necessary\n\n.. code:: python\n\n # websockets.py\n\n @websocket_pubsub(\"/messages\", authenticate=True)\n def message_pusher(ws, params, manager, user, **kwargs):\n subscription = user.subscriptions.get(channel__name=\"messages\")\n reader = subscription.get_reader(manager=manager)\n\n @reader.callback\n def callback(channel_name, message):\n to_client = {\n channel_name: {\n \"author\": message.author.username,\n \"body\": message.body\n }\n }\n ws.send_str(json.dumps())\n return True\n\n listener = yield from reader.listen()\n yield from listener\n\nThis example shows the main purpose of the `redis_pubsub` package, which is to listen for updates on a redis channel and push the publication to a client. Lets break it down line by line\n\n1) retreive the users subscription\n2) create a managed ChannelReader object for this subscription\n3) register a callback to be executed whenever a new publication is received\n4) begin listening for changes\n5) listen until the channel is closed\n\nThe most fruitful method offerd by a SubscriptionManager is `listen_to_all_subscriptions` which takes two arguments, a subscriber and a callback, and publishes subscriptions as they arrive\n\n.. code:: python\n\n # websockets.py\n\n @websocket_pubsub(\"/subscriptions\", authenticate=True)\n def subscriptions(ws, params, manager, user, **kwargs):\n\n def callback(channel_name, message):\n ws.send_str(message.serialize())\n return True\n\n manager.listen_to_all_subscriptions(user, callback)\n\n while True:\n message = yield from ws.receive()\n if message.tp not in (MsgType.error, MsgType.close):\n message = json.loads(message)\n if message[\"action\"] == \"unsubscribe\":\n subscription = user.subscriptions.get(channel__name=message[\"channe\"])\n subscription.active = False\n subscription.save()\n reader = manager.readers[message[\"channel\"]]\n yield from manager.remove(reader)\n elif message[\"action\"] == \"subscribe\":\n channel = Channel.objects.get(name=message[\"channel\"])\n reader = channel.subscribe(user).get_reader(manager=manager)\n reader.callback(callback)\n yield from reader.listen()\n else:\n break\n\nThe callback in this example will keep all subscription channels open and push messages to a client until the websocket has closed. This code provides a simple means of managing users with a multitude of subscriptions. The `while` loop here also handles unsubscribing and subscribing to new channels\n\n.. note::\n\n A callback function should never receive from a websocket or else a RuntimeError will be raised.\n\n\nDeploying\n=========\n\nwhen deploying an application with websockets/aiohttp you will not be able to use the normal django deployment proceedures. Since your django application will be a component of an AioHttp application object, you will have to use Gunicorn as an application server. Using utilities from the `redis_pubsub.contrib.websockets` module you can create a deployment file simply\n\n.. code:: python\n\n # deployment.py\n\n import asyncio\n\n from django.core.wsgi import get_wsgi_application\n \n from aiohttp_wsgi import WSGIHandler\n \n from redis_pubsub.contrib.websockets import setup\n\n\n wsgi_app = get_wsgi_application() # django.setup() is called here\n wsgi_handler = WSGIHandler(wsgi_app)\n \n loop = asyncio.get_event_loop()\n application = setup(loop=loop)\n # any url patterns not matched by the Websocket app go to the django app for handling\n application.router.add_route(\"*\", \"/{path_info:.*}\", wsgi_handler.handle_request)\n\nyou can then start gunicorn by running::\n\n $ gunicorn deployment:application --bind localhost:8080 --worker-class aiohttp.worker.GunicornWebWorker", "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/andrewyoung1991/django-redis-pubsub", "keywords": null, "license": "BSD", "maintainer": null, "maintainer_email": null, "name": "django-redis-pubsub", "package_url": "https://pypi.org/project/django-redis-pubsub/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/django-redis-pubsub/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/andrewyoung1991/django-redis-pubsub" }, "release_url": "https://pypi.org/project/django-redis-pubsub/1.0/", "requires_dist": null, "requires_python": null, "summary": "asyncronous pubsub in django using redis", "version": "1.0" }, "last_serial": 1953808, "releases": { "1.0": [ { "comment_text": "", "digests": { "md5": "3cb71c141fdc7d83df57ac5190911169", "sha256": "e85841d942f9daa6d63188ab719932cc1ab45886602a1a3cfe36c5e51deb927e" }, "downloads": -1, "filename": "django-redis-pubsub-1.0.tar.gz", "has_sig": false, "md5_digest": "3cb71c141fdc7d83df57ac5190911169", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13010, "upload_time": "2016-02-12T20:50:52", "url": "https://files.pythonhosted.org/packages/db/91/f991d755dbe0d3d7bb2a193f0ebe0dfdf208b821a455ff9062aca5e6e941/django-redis-pubsub-1.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "3cb71c141fdc7d83df57ac5190911169", "sha256": "e85841d942f9daa6d63188ab719932cc1ab45886602a1a3cfe36c5e51deb927e" }, "downloads": -1, "filename": "django-redis-pubsub-1.0.tar.gz", "has_sig": false, "md5_digest": "3cb71c141fdc7d83df57ac5190911169", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13010, "upload_time": "2016-02-12T20:50:52", "url": "https://files.pythonhosted.org/packages/db/91/f991d755dbe0d3d7bb2a193f0ebe0dfdf208b821a455ff9062aca5e6e941/django-redis-pubsub-1.0.tar.gz" } ] }